@@ -2,6 +2,7 @@ package chainio
22
33import (
44 "context"
5+ "encoding/hex"
56 "fmt"
67 "math/big"
78 "time"
@@ -75,9 +76,19 @@ func NewAvsWriterFromConfig(baseConfig *config.BaseConfig, ecdsaConfig *config.E
7576 }, nil
7677}
7778
78- // Sends AggregatedResponse and waits for the receipt for three blocks, if not received
79- // it will try again bumping the last tx gas price based on `CalculateGasPriceBump`
80- // This process happens indefinitely until the transaction is included.
79+ // SendAggregatedResponse continuously sends a RespondToTask transaction until it is included in the blockchain.
80+ // This function:
81+ // 1. Simulates the transaction to calculate the nonce and initial gas price without broadcasting it.
82+ // 2. Repeatedly attempts to send the transaction, bumping the gas price after `timeToWaitBeforeBump` has passed.
83+ // 3. Monitors for the receipt of previously sent transactions or checks the state to confirm if the response
84+ // has already been processed (e.g., by another transaction).
85+ // 4. Validates that the aggregator and batcher have sufficient balance to cover transaction costs before sending.
86+ //
87+ // Returns:
88+ // - A transaction receipt if the transaction is successfully included in the blockchain.
89+ // - If no receipt is found, but the batch state indicates the response has already been processed, it exits
90+ // without an error (returning `nil, nil`).
91+ // - An error if the process encounters a fatal issue (e.g., permanent failure in verifying balances or state).
8192func (w * AvsWriter ) SendAggregatedResponse (batchIdentifierHash [32 ]byte , batchMerkleRoot [32 ]byte , senderAddress [20 ]byte , nonSignerStakesAndSignature servicemanager.IBLSSignatureCheckerNonSignerStakesAndSignature , gasBumpPercentage uint , gasBumpIncrementalPercentage uint , timeToWaitBeforeBump time.Duration , onGasPriceBumped func (* big.Int )) (* types.Receipt , error ) {
8293 txOpts := * w .Signer .GetTxOpts ()
8394 txOpts .NoSend = true // simulate the transaction
@@ -93,39 +104,73 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe
93104 txOpts .NoSend = false
94105 i := 0
95106
107+ var sentTxs []* types.Transaction
108+
109+ batchMerkleRootHashString := hex .EncodeToString (batchMerkleRoot [:])
110+
96111 respondToTaskV2Func := func () (* types.Receipt , error ) {
97112 gasPrice , err := utils .GetGasPriceRetryable (w .Client , w .ClientFallback )
98113 if err != nil {
99114 return nil , err
100115 }
101-
102- bumpedGasPrice := utils .CalculateGasPriceBumpBasedOnRetry (gasPrice , gasBumpPercentage , gasBumpIncrementalPercentage , i )
103- // new bumped gas price must be higher than the last one (this should hardly ever happen though)
104- if bumpedGasPrice .Cmp (txOpts .GasPrice ) > 0 {
105- txOpts .GasPrice = bumpedGasPrice
116+ previousTxGasPrice := txOpts .GasPrice
117+ // in order to avoid replacement transaction underpriced
118+ // the bumped gas price has to be at least 10% higher than the previous one.
119+ minimumGasPriceBump := utils .CalculateGasPriceBumpBasedOnRetry (previousTxGasPrice , 10 , 0 , 0 )
120+ suggestedBumpedGasPrice := utils .CalculateGasPriceBumpBasedOnRetry (
121+ gasPrice ,
122+ gasBumpPercentage ,
123+ gasBumpIncrementalPercentage ,
124+ i ,
125+ )
126+ // check the new gas price is sufficiently bumped.
127+ // if the suggested bump does not meet the minimum threshold, use a fallback calculation to slightly increment the previous gas price.
128+ if suggestedBumpedGasPrice .Cmp (minimumGasPriceBump ) > 0 {
129+ txOpts .GasPrice = suggestedBumpedGasPrice
106130 } else {
107- // bump the last tx gas price a little by `gasBumpIncrementalPercentage` to replace it.
108- txOpts .GasPrice = utils .CalculateGasPriceBumpBasedOnRetry (txOpts .GasPrice , gasBumpIncrementalPercentage , 0 , 0 )
131+ txOpts .GasPrice = minimumGasPriceBump
109132 }
110133
111134 if i > 0 {
135+ w .logger .Infof ("Trying to get old sent transaction receipt before sending a new transaction" , "merkle root" , batchMerkleRootHashString )
136+ for _ , tx := range sentTxs {
137+ receipt , _ := w .Client .TransactionReceipt (context .Background (), tx .Hash ())
138+ if receipt == nil {
139+ receipt , _ = w .ClientFallback .TransactionReceipt (context .Background (), tx .Hash ())
140+ if receipt != nil {
141+ w .checkIfAggregatorHadToPaidForBatcher (tx , batchIdentifierHash )
142+ return receipt , nil
143+ }
144+ }
145+ }
146+ w .logger .Infof ("Receipts for old transactions not found, will check if the batch state has been responded" , "merkle root" , batchMerkleRootHashString )
147+ batchState , _ := w .BatchesStateRetryable (& bind.CallOpts {}, batchIdentifierHash )
148+ if batchState .Responded {
149+ w .logger .Infof ("Batch state has been already responded" , "merkle root" , batchMerkleRootHashString )
150+ return nil , nil
151+ }
152+ w .logger .Infof ("Batch state has not been responded yet, will send a new tx" , "merkle root" , batchMerkleRootHashString )
153+
112154 onGasPriceBumped (txOpts .GasPrice )
113155 }
114156
115157 // We compare both Aggregator funds and Batcher balance in Aligned against respondToTaskFeeLimit
116158 // Both are required to have some balance, more details inside the function
117159 err = w .checkAggAndBatcherHaveEnoughBalance (simTx , txOpts , batchIdentifierHash , senderAddress )
118160 if err != nil {
161+ w .logger .Errorf ("Permanent error when checking aggregator and batcher balances, err %v" , err , "merkle root" , batchMerkleRootHashString )
119162 return nil , retry.PermanentError {Inner : err }
120163 }
121164
122- w .logger .Infof ("Sending RespondToTask transaction with a gas price of %v" , txOpts .GasPrice )
123-
165+ w .logger .Infof ("Sending RespondToTask transaction with a gas price of %v" , txOpts .GasPrice , "merkle root" , batchMerkleRootHashString )
124166 realTx , err := w .RespondToTaskV2Retryable (& txOpts , batchMerkleRoot , senderAddress , nonSignerStakesAndSignature )
125167 if err != nil {
168+ w .logger .Errorf ("Respond to task transaction err, %v" , err , "merkle root" , batchMerkleRootHashString )
126169 return nil , err
127170 }
171+ sentTxs = append (sentTxs , realTx )
128172
173+ w .logger .Infof ("Transaction sent, waiting for receipt" , "merkle root" , batchMerkleRootHashString )
129174 receipt , err := utils .WaitForTransactionReceiptRetryable (w .Client , w .ClientFallback , realTx .Hash (), timeToWaitBeforeBump )
130175 if receipt != nil {
131176 w .checkIfAggregatorHadToPaidForBatcher (realTx , batchIdentifierHash )
@@ -136,14 +181,18 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe
136181 // we increment the i here to add an incremental percentage to increase the odds of being included in the next blocks
137182 i ++
138183
139- w .logger .Infof ("RespondToTask receipt waiting timeout has passed, will try again..." )
184+ w .logger .Infof ("RespondToTask receipt waiting timeout has passed, will try again..." , "merkle_root" , batchMerkleRootHashString )
140185 if err != nil {
141186 return nil , err
142187 }
143188 return nil , fmt .Errorf ("transaction failed" )
144189 }
145190
146- return retry .RetryWithData (respondToTaskV2Func , retry .MinDelay , retry .RetryFactor , 0 , retry .MaxInterval , 0 )
191+ // This just retries the bump of a fee in case of a timeout
192+ // The wait is done before on WaitForTransactionReceiptRetryable, and all the functions are retriable,
193+ // so this retry doesn't need to wait more time
194+ maxInterval := time .Millisecond * 500
195+ return retry .RetryWithData (respondToTaskV2Func , retry .MinDelay , retry .RetryFactor , 0 , maxInterval , 0 )
147196}
148197
149198// Calculates the transaction cost from the receipt and compares it with the batcher respondToTaskFeeLimit
@@ -171,7 +220,9 @@ func (w *AvsWriter) checkIfAggregatorHadToPaidForBatcher(tx *types.Transaction,
171220func (w * AvsWriter ) checkAggAndBatcherHaveEnoughBalance (tx * types.Transaction , txOpts bind.TransactOpts , batchIdentifierHash [32 ]byte , senderAddress [20 ]byte ) error {
172221 w .logger .Info ("Checking if aggregator and batcher have enough balance for the transaction" )
173222 aggregatorAddress := txOpts .From
174- txCost := new (big.Int ).Mul (new (big.Int ).SetUint64 (tx .Gas ()), txOpts .GasPrice )
223+ txGasAsBigInt := new (big.Int ).SetUint64 (tx .Gas ())
224+ txGasPrice := txOpts .GasPrice
225+ txCost := new (big.Int ).Mul (txGasAsBigInt , txGasPrice )
175226 w .logger .Info ("Transaction cost" , "cost" , txCost )
176227
177228 batchState , err := w .BatchesStateRetryable (& bind.CallOpts {}, batchIdentifierHash )
@@ -183,8 +234,8 @@ func (w *AvsWriter) checkAggAndBatcherHaveEnoughBalance(tx *types.Transaction, t
183234 respondToTaskFeeLimit := batchState .RespondToTaskFeeLimit
184235 w .logger .Info ("Checking balance against Batch RespondToTaskFeeLimit" , "RespondToTaskFeeLimit" , respondToTaskFeeLimit )
185236 // Note: we compare both Aggregator funds and Batcher balance in Aligned against respondToTaskFeeLimit
186- // Batcher will pay up to respondToTaskFeeLimit, for this he needs that amount of funds in Aligned
187- // Aggregator will pay any extra cost, for this he needs at least respondToTaskFeeLimit in his balance
237+ // Batcher will pay up to respondToTaskFeeLimit, for this he needs that amount of funds in Aligned
238+ // Aggregator will pay any extra cost, for this he needs at least respondToTaskFeeLimit in his balance
188239 return w .compareBalances (respondToTaskFeeLimit , aggregatorAddress , senderAddress )
189240}
190241
0 commit comments