@@ -10,9 +10,10 @@ import (
1010 "github.com/0glabs/0g-storage-client/common/blockchain"
1111 "github.com/ethereum/go-ethereum/accounts/abi/bind"
1212 "github.com/ethereum/go-ethereum/common"
13- "github.com/ethereum/go-ethereum/core/types"
1413 "github.com/ethereum/go-ethereum/crypto"
1514 "github.com/openweb3/web3go"
15+ "github.com/openweb3/web3go/types"
16+ "github.com/pkg/errors"
1617 "github.com/sirupsen/logrus"
1718)
1819
@@ -26,11 +27,13 @@ type TxRetryOption struct {
2627 Timeout time.Duration
2728 MaxNonGasRetries int
2829 MaxGasPrice * big.Int
30+ Step int64
2931}
3032
3133var SpecifiedBlockError = "Specified block header does not exist"
32- var DefaultTimeout = 30 * time .Second
33- var DefaultMaxNonGasRetries = 10
34+ var DefaultTimeout = 15 * time .Minute
35+ var DefaultMaxNonGasRetries = 20
36+ var DefaultStep = int64 (15 )
3437
3538func IsRetriableSubmitLogEntryError (msg string ) bool {
3639 return strings .Contains (msg , SpecifiedBlockError ) || strings .Contains (msg , "mempool" ) || strings .Contains (msg , "timeout" )
@@ -52,6 +55,22 @@ func NewFlowContract(flowAddress common.Address, clientWithSigner *web3go.Client
5255 return & FlowContract {contract , flow , clientWithSigner }, nil
5356}
5457
58+ func (f * FlowContract ) GetNonce (ctx context.Context ) (* big.Int , error ) {
59+ sm , err := f .clientWithSigner .GetSignerManager ()
60+ if err != nil {
61+ return nil , err
62+ }
63+
64+ addr := sm .List ()[0 ].Address ()
65+
66+ nonce , err := f .clientWithSigner .Eth .TransactionCount (addr , nil )
67+ if err != nil {
68+ return nil , err
69+ }
70+
71+ return nonce , nil
72+ }
73+
5574func (f * FlowContract ) GetGasPrice () (* big.Int , error ) {
5675 gasPrice , err := f .clientWithSigner .Eth .GasPrice ()
5776 if err != nil {
@@ -110,68 +129,161 @@ func TransactWithGasAdjustment(
110129 opts * bind.TransactOpts ,
111130 retryOpts * TxRetryOption ,
112131 params ... interface {},
113- ) (* types.Transaction , error ) {
132+ ) (* types.Receipt , error ) {
114133 // Set timeout and max non-gas retries from retryOpts if provided.
115134 if retryOpts == nil {
116135 retryOpts = & TxRetryOption {
117- Timeout : DefaultTimeout ,
118136 MaxNonGasRetries : DefaultMaxNonGasRetries ,
119137 }
120138 }
121139
140+ if retryOpts .MaxNonGasRetries == 0 {
141+ retryOpts .MaxNonGasRetries = DefaultMaxNonGasRetries
142+ }
143+
144+ if retryOpts .Step == 0 {
145+ retryOpts .Step = DefaultStep
146+ }
147+
148+ if t , ok := opts .Context .Deadline (); ok {
149+ retryOpts .Timeout = time .Until (t )
150+ }
151+
152+ logrus .WithField ("timeout" , retryOpts .Timeout ).WithField ("maxNonGasRetries" , retryOpts .MaxNonGasRetries ).Debug ("Set retry options" )
153+
154+ if opts .Nonce == nil {
155+ // Get the current nonce if not set.
156+ nonce , err := contract .GetNonce (opts .Context )
157+ if err != nil {
158+ return nil , err
159+ }
160+ // add one to the nonce
161+ opts .Nonce = nonce
162+ }
163+
164+ logrus .WithField ("nonce" , opts .Nonce ).Info ("Set nonce" )
165+
122166 if opts .GasPrice == nil {
123167 // Get the current gas price if not set.
124168 gasPrice , err := contract .GetGasPrice ()
125169 if err != nil {
126- return nil , fmt . Errorf ( "failed to get gas price: %w" , err )
170+ return nil , errors . WithMessage ( err , "failed to get gas price" )
127171 }
128172 opts .GasPrice = gasPrice
129173 logrus .WithField ("gasPrice" , opts .GasPrice ).Debug ("Receive current gas price from chain node" )
130174 }
131175
132176 logrus .WithField ("gasPrice" , opts .GasPrice ).Info ("Set gas price" )
133177
134- nRetries := 0
135- for {
136- // Create a fresh context per iteration.
137- ctx , cancel := context .WithTimeout (context .Background (), retryOpts .Timeout )
138- opts .Context = ctx
139- tx , err := contract .FlowTransactor .contract .Transact (opts , method , params ... )
140- cancel () // cancel this iteration's context
141- if err == nil {
142- return tx , nil
143- }
178+ receiptCh := make (chan * types.Receipt , 1 )
179+ errCh := make (chan error , 1 )
180+ failCh := make (chan error , 1 )
144181
145- errStr := strings .ToLower (err .Error ())
182+ var ctx context.Context
183+ var cancel context.CancelFunc
184+ if retryOpts .Timeout > 0 {
185+ ctx , cancel = context .WithTimeout (context .Background (), retryOpts .Timeout )
186+ } else {
187+ ctx , cancel = context .WithCancel (context .Background ())
188+ }
146189
147- if ! IsRetriableSubmitLogEntryError (errStr ) {
148- return nil , fmt .Errorf ("failed to send transaction: %w" , err )
190+ // calculate number of gas retry by dividing max gas price by current gas price and the ration
191+ nGasRetry := 0
192+ if retryOpts .MaxGasPrice != nil {
193+ gasPrice := opts .GasPrice
194+ for gasPrice .Cmp (retryOpts .MaxGasPrice ) <= 0 {
195+ gasPrice = new (big.Int ).Mul (gasPrice , big .NewInt (retryOpts .Step ))
196+ gasPrice .Div (gasPrice , big .NewInt (10 ))
197+ nGasRetry ++
149198 }
199+ }
150200
151- if strings .Contains (errStr , "mempool" ) || strings .Contains (errStr , "timeout" ) {
152- if retryOpts .MaxGasPrice == nil {
153- return nil , fmt .Errorf ("mempool full and no max gas price is set, failed to send transaction: %w" , err )
154- } else {
155- newGasPrice := new (big.Int ).Mul (opts .GasPrice , big .NewInt (11 ))
156- newGasPrice .Div (newGasPrice , big .NewInt (10 ))
157- if newGasPrice .Cmp (retryOpts .MaxGasPrice ) > 0 {
158- opts .GasPrice = new (big.Int ).Set (retryOpts .MaxGasPrice )
159- } else {
160- opts .GasPrice = newGasPrice
201+ go func () {
202+ nRetries := 0
203+ for {
204+ select {
205+ case <- ctx .Done ():
206+ // main or another goroutine canceled the context
207+ logrus .Info ("Context canceled; stopping outer loop" )
208+ return
209+ default :
210+ }
211+ tx , err := contract .FlowTransactor .contract .Transact (opts , method , params ... )
212+
213+ var receipt * types.Receipt
214+ if err == nil {
215+ // Wait for successful execution
216+ go func () {
217+ receipt , err = contract .WaitForReceipt (ctx , tx .Hash (), true , blockchain.RetryOption {NRetries : retryOpts .MaxNonGasRetries })
218+ if err == nil {
219+ receiptCh <- receipt
220+ return
221+ }
222+ errCh <- err
223+ }()
224+ // even if the receipt is received, this loop will continue until the context is canceled
225+ time .Sleep (30 * time .Second )
226+ err = fmt .Errorf ("timeout" )
227+ }
228+
229+ logrus .WithError (err ).Error ("Failed to send transaction" )
230+
231+ errStr := strings .ToLower (err .Error ())
232+
233+ if ! IsRetriableSubmitLogEntryError (errStr ) {
234+ if strings .Contains (errStr , "invalid nonce" ) {
235+ return
161236 }
162- logrus .WithError (err ).Infof ("Increasing gas price to %v due to mempool/timeout error" , opts .GasPrice )
237+ failCh <- errors .WithMessage (err , "failed to send transaction" )
238+ return
163239 }
164- } else {
165- nRetries ++
166- if nRetries >= retryOpts .MaxNonGasRetries {
167- return nil , fmt .Errorf ("failed to send transaction after %d retries: %w" , nRetries , err )
240+
241+ // If the error is due to mempool full or timeout, retry with a higher gas price
242+ if strings .Contains (errStr , "mempool" ) || strings .Contains (errStr , "timeout" ) {
243+ if retryOpts .MaxGasPrice == nil {
244+ failCh <- errors .WithMessage (err , "mempool full and no max gas price is set, failed to send transaction" )
245+ return
246+ } else if opts .GasPrice .Cmp (retryOpts .MaxGasPrice ) >= 0 {
247+ return
248+ } else {
249+ newGasPrice := new (big.Int ).Mul (opts .GasPrice , big .NewInt (retryOpts .Step ))
250+ newGasPrice .Div (newGasPrice , big .NewInt (10 ))
251+ if newGasPrice .Cmp (retryOpts .MaxGasPrice ) > 0 {
252+ opts .GasPrice = new (big.Int ).Set (retryOpts .MaxGasPrice )
253+ } else {
254+ opts .GasPrice = newGasPrice
255+ }
256+ logrus .WithError (err ).Infof ("Increasing gas price to %v due to mempool/timeout error" , opts .GasPrice )
257+ }
258+ } else {
259+ nRetries ++
260+ if nRetries >= retryOpts .MaxNonGasRetries {
261+ failCh <- errors .WithMessage (err , "failed to send transaction" )
262+ return
263+ }
264+ logrus .WithError (err ).Infof ("Retrying with same gas price %v, attempt %d" , opts .GasPrice , nRetries )
168265 }
169- logrus .WithError (err ).Infof ("Retrying with same gas price %v, attempt %d" , opts .GasPrice , nRetries )
170266 }
267+ }()
171268
172- time .Sleep (10 * time .Second )
269+ nErr := 0
270+ for {
271+ select {
272+ case receipt := <- receiptCh :
273+ cancel ()
274+ return receipt , nil
275+ case err := <- errCh :
276+ nErr ++
277+ if nErr >= nGasRetry {
278+ failCh <- errors .WithMessage (err , "All gas price retries failed" )
279+ cancel ()
280+ return nil , err
281+ }
282+ case err := <- failCh :
283+ cancel ()
284+ return nil , err
285+ }
173286 }
174-
175287}
176288
177289func (submission Submission ) Fee (pricePerSector * big.Int ) * big.Int {
0 commit comments