@@ -2,6 +2,7 @@ package chainio
22
33import (
44 "context"
5+ "encoding/hex"
56 "fmt"
67 "math/big"
78 "time"
@@ -80,9 +81,19 @@ func NewAvsWriterFromConfig(baseConfig *config.BaseConfig, ecdsaConfig *config.E
8081 }, nil
8182}
8283
83- // Sends AggregatedResponse and waits for the receipt for three blocks, if not received
84- // it will try again bumping the last tx gas price based on `CalculateGasPriceBump`
85- // This process happens indefinitely until the transaction is included.
84+ // SendAggregatedResponse continuously sends a RespondToTask transaction until it is included in the blockchain.
85+ // This function:
86+ // 1. Simulates the transaction to calculate the nonce and initial gas price without broadcasting it.
87+ // 2. Repeatedly attempts to send the transaction, bumping the gas price after `timeToWaitBeforeBump` has passed.
88+ // 3. Monitors for the receipt of previously sent transactions or checks the state to confirm if the response
89+ // has already been processed (e.g., by another transaction).
90+ // 4. Validates that the aggregator and batcher have sufficient balance to cover transaction costs before sending.
91+ //
92+ // Returns:
93+ // - A transaction receipt if the transaction is successfully included in the blockchain.
94+ // - If no receipt is found, but the batch state indicates the response has already been processed, it exits
95+ // without an error (returning `nil, nil`).
96+ // - An error if the process encounters a fatal issue (e.g., permanent failure in verifying balances or state).
8697func (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 ) {
8798 txOpts := * w .Signer .GetTxOpts ()
8899 txOpts .NoSend = true // simulate the transaction
@@ -108,40 +119,75 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe
108119 waitForTxConfig .NumRetries = waitForTxConfigNumRetries
109120 waitForTxConfig .MaxElapsedTime = timeToWaitBeforeBump
110121
122+ var sentTxs []* types.Transaction
123+
124+ batchMerkleRootHashString := hex .EncodeToString (batchMerkleRoot [:])
125+
111126 respondToTaskV2Func := func () (* types.Receipt , error ) {
112127 gasPrice , err := utils .GetGasPriceRetryable (w .Client , w .ClientFallback )
113128 if err != nil {
114129 return nil , err
115130 }
116-
117- bumpedGasPrice := utils .CalculateGasPriceBumpBasedOnRetry (gasPrice , gasBumpPercentage , gasBumpIncrementalPercentage , i )
118- // new bumped gas price must be higher than the last one (this should hardly ever happen though)
119- if bumpedGasPrice .Cmp (txOpts .GasPrice ) > 0 {
120- txOpts .GasPrice = bumpedGasPrice
131+ previousTxGasPrice := txOpts .GasPrice
132+ // in order to avoid replacement transaction underpriced
133+ // the bumped gas price has to be at least 10% higher than the previous one.
134+ minimumGasPriceBump := utils .CalculateGasPriceBumpBasedOnRetry (previousTxGasPrice , 10 , 0 , 0 )
135+ suggestedBumpedGasPrice := utils .CalculateGasPriceBumpBasedOnRetry (
136+ gasPrice ,
137+ gasBumpPercentage ,
138+ gasBumpIncrementalPercentage ,
139+ i ,
140+ )
141+ // check the new gas price is sufficiently bumped.
142+ // if the suggested bump does not meet the minimum threshold, use a fallback calculation to slightly increment the previous gas price.
143+ if suggestedBumpedGasPrice .Cmp (minimumGasPriceBump ) > 0 {
144+ txOpts .GasPrice = suggestedBumpedGasPrice
121145 } else {
122- // bump the last tx gas price a little by `gasBumpIncrementalPercentage` to replace it.
123- txOpts .GasPrice = utils .CalculateGasPriceBumpBasedOnRetry (txOpts .GasPrice , gasBumpIncrementalPercentage , 0 , 0 )
146+ txOpts .GasPrice = minimumGasPriceBump
124147 }
125148
126149 if i > 0 {
150+ w .logger .Infof ("Trying to get old sent transaction receipt before sending a new transaction" , "merkle root" , batchMerkleRootHashString )
151+ for _ , tx := range sentTxs {
152+ receipt , _ := w .Client .TransactionReceipt (context .Background (), tx .Hash ())
153+ if receipt == nil {
154+ receipt , _ = w .ClientFallback .TransactionReceipt (context .Background (), tx .Hash ())
155+ if receipt != nil {
156+ w .checkIfAggregatorHadToPaidForBatcher (tx , batchIdentifierHash )
157+ return receipt , nil
158+ }
159+ }
160+ }
161+ w .logger .Infof ("Receipts for old transactions not found, will check if the batch state has been responded" , "merkle root" , batchMerkleRootHashString )
162+ batchState , _ := w .BatchesStateRetryable (& bind.CallOpts {}, batchIdentifierHash )
163+ if batchState .Responded {
164+ w .logger .Infof ("Batch state has been already responded" , "merkle root" , batchMerkleRootHashString )
165+ return nil , nil
166+ }
167+ w .logger .Infof ("Batch state has not been responded yet, will send a new tx" , "merkle root" , batchMerkleRootHashString )
168+
127169 onGasPriceBumped (txOpts .GasPrice )
128170 }
129171
130172 // We compare both Aggregator funds and Batcher balance in Aligned against respondToTaskFeeLimit
131173 // Both are required to have some balance, more details inside the function
132174 err = w .checkAggAndBatcherHaveEnoughBalance (simTx , txOpts , batchIdentifierHash , senderAddress )
133175 if err != nil {
176+ w .logger .Errorf ("Permanent error when checking aggregator and batcher balances, err %v" , err , "merkle root" , batchMerkleRootHashString )
134177 return nil , retry.PermanentError {Inner : err }
135178 }
136179
137- w .logger .Infof ("Sending RespondToTask transaction with a gas price of %v" , txOpts .GasPrice )
138-
180+ w .logger .Infof ("Sending RespondToTask transaction with a gas price of %v" , txOpts .GasPrice , "merkle root" , batchMerkleRootHashString )
139181 realTx , err := w .RespondToTaskV2Retryable (& txOpts , batchMerkleRoot , senderAddress , nonSignerStakesAndSignature )
140182 if err != nil {
183+ w .logger .Errorf ("Respond to task transaction err, %v" , err , "merkle root" , batchMerkleRootHashString )
141184 return nil , err
142185 }
186+ sentTxs = append (sentTxs , realTx )
143187
188+ w .logger .Infof ("Transaction sent, waiting for receipt" , "merkle root" , batchMerkleRootHashString )
144189 receipt , err := utils .WaitForTransactionReceiptRetryable (w .Client , w .ClientFallback , realTx .Hash (), waitForTxConfig )
190+
145191 if receipt != nil {
146192 w .checkIfAggregatorHadToPaidForBatcher (realTx , batchIdentifierHash )
147193 return receipt , nil
@@ -151,13 +197,16 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe
151197 // we increment the i here to add an incremental percentage to increase the odds of being included in the next blocks
152198 i ++
153199
154- w .logger .Infof ("RespondToTask receipt waiting timeout has passed, will try again..." )
200+ w .logger .Infof ("RespondToTask receipt waiting timeout has passed, will try again..." , "merkle_root" , batchMerkleRootHashString )
155201 if err != nil {
156202 return nil , err
157203 }
158204 return nil , fmt .Errorf ("transaction failed" )
159205 }
160206
207+ // This just retries the bump of a fee in case of a timeout
208+ // The wait is done before on WaitForTransactionReceiptRetryable, and all the functions are retriable,
209+ // so this retry doesn't need to wait more time
161210 return retry .RetryWithData (respondToTaskV2Func , respondToTaskV2Config )
162211}
163212
@@ -186,7 +235,9 @@ func (w *AvsWriter) checkIfAggregatorHadToPaidForBatcher(tx *types.Transaction,
186235func (w * AvsWriter ) checkAggAndBatcherHaveEnoughBalance (tx * types.Transaction , txOpts bind.TransactOpts , batchIdentifierHash [32 ]byte , senderAddress [20 ]byte ) error {
187236 w .logger .Info ("Checking if aggregator and batcher have enough balance for the transaction" )
188237 aggregatorAddress := txOpts .From
189- txCost := new (big.Int ).Mul (new (big.Int ).SetUint64 (tx .Gas ()), txOpts .GasPrice )
238+ txGasAsBigInt := new (big.Int ).SetUint64 (tx .Gas ())
239+ txGasPrice := txOpts .GasPrice
240+ txCost := new (big.Int ).Mul (txGasAsBigInt , txGasPrice )
190241 w .logger .Info ("Transaction cost" , "cost" , txCost )
191242
192243 batchState , err := w .BatchesStateRetryable (& bind.CallOpts {}, batchIdentifierHash )
0 commit comments