Skip to content

Commit 1b978b7

Browse files
committed
Expire local nonce after a certain timeout
We want to be able to recover from a situation when mempool crashes. To achieve it, we cache the last local nonce value only for 5 seconds.
1 parent c294bce commit 1b978b7

2 files changed

Lines changed: 49 additions & 12 deletions

File tree

pkg/chain/ethereum/ethutil/nonce.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ package ethutil
22

33
import (
44
"context"
5+
"time"
56

67
"github.com/ethereum/go-ethereum/accounts/abi/bind"
78
"github.com/ethereum/go-ethereum/common"
89
)
910

11+
// The time for which the nonce value cached locally is valid. The local copy
12+
// is invalidated after the certain duration to let the nonce recover in case
13+
// the mempool crashed before propagating the last transaction sent.
14+
const localNonceTrustDuration = 5 * time.Second
15+
1016
// NonceManager tracks the nonce for the account and allows to update it after
1117
// each successfully submitted transaction. Tracking the nonce locall is
1218
// required when transactions are submitted from multiple goroutines or when
@@ -23,9 +29,10 @@ import (
2329
// 4. Call IncrementNonce(),
2430
// 5. Release transaction lock.
2531
type NonceManager struct {
26-
account common.Address
27-
transactor bind.ContractTransactor
28-
localNonce uint64
32+
account common.Address
33+
transactor bind.ContractTransactor
34+
localNonce uint64
35+
expirationDate time.Time
2936
}
3037

3138
// NewNonceManager creates NonceManager instance for the provided account using
@@ -45,7 +52,9 @@ func NewNonceManager(
4552

4653
// CurrentNonce returns the nonce value that should be used for the next
4754
// transaction. The nonce is evaluated as the higher value from the local
48-
// nonce and pending nonce fetched from the Ethereum client.
55+
// nonce and pending nonce fetched from the Ethereum client. The local nonce
56+
// is cached for the specific duration. If the local nonce expired, the pending
57+
// nonce returned from the chain is used.
4958
//
5059
// CurrentNonce is NOT safe for concurrent use. It is up to the code using this
5160
// function to provide the required synchronization, optionally including
@@ -59,17 +68,32 @@ func (nm *NonceManager) CurrentNonce() (uint64, error) {
5968
return 0, err
6069
}
6170

71+
now := time.Now()
72+
6273
if pendingNonce < nm.localNonce {
63-
logger.Infof(
64-
"local nonce [%v] is higher than pending [%v]; using the local one",
65-
nm.localNonce,
66-
pendingNonce,
67-
)
74+
if now.Before(nm.expirationDate) {
75+
logger.Infof(
76+
"local nonce [%v] is higher than pending [%v]; using the local one",
77+
nm.localNonce,
78+
pendingNonce,
79+
)
80+
} else {
81+
logger.Infof(
82+
"local nonce [%v] is higher than pending [%v] but local "+
83+
"nonce expired; updating local nonce",
84+
nm.localNonce,
85+
pendingNonce,
86+
)
87+
88+
nm.localNonce = pendingNonce
89+
}
6890
}
6991

92+
nm.expirationDate = now.Add(localNonceTrustDuration)
93+
7094
if pendingNonce > nm.localNonce {
7195
logger.Infof(
72-
"local nonce [%v] is lower than pending [%v]; updating",
96+
"local nonce [%v] is lower than pending [%v]; updating local nonce",
7397
nm.localNonce,
7498
pendingNonce,
7599
)

pkg/chain/ethereum/ethutil/nonce_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"math/big"
66
"testing"
7+
"time"
78

89
"github.com/ethereum/go-ethereum"
910
"github.com/ethereum/go-ethereum/common"
@@ -14,35 +15,47 @@ func TestResolveAndIncrement(t *testing.T) {
1415
tests := map[string]struct {
1516
pendingNonce uint64
1617
localNonce uint64
18+
expirationDate time.Time
1719
expectedNonce uint64
1820
expectedNextNonce uint64
1921
}{
2022
"pending and local the same": {
2123
pendingNonce: 10,
2224
localNonce: 10,
25+
expirationDate: time.Now().Add(time.Second),
2326
expectedNonce: 10,
2427
expectedNextNonce: 11,
2528
},
2629
"pending nonce higher": {
2730
pendingNonce: 121,
2831
localNonce: 120,
32+
expirationDate: time.Now().Add(time.Second),
2933
expectedNonce: 121,
3034
expectedNextNonce: 122,
3135
},
3236
"pending nonce lower": {
3337
pendingNonce: 110,
3438
localNonce: 111,
39+
expirationDate: time.Now().Add(time.Second),
3540
expectedNonce: 111,
3641
expectedNextNonce: 112,
3742
},
43+
"pending nonce lower and local one expired": {
44+
pendingNonce: 110,
45+
localNonce: 111,
46+
expirationDate: time.Now().Add(-1 * time.Second),
47+
expectedNonce: 110,
48+
expectedNextNonce: 111,
49+
},
3850
}
3951

4052
for testName, test := range tests {
4153
t.Run(testName, func(t *testing.T) {
4254
transactor := &mockTransactor{test.pendingNonce}
4355
manager := &NonceManager{
44-
transactor: transactor,
45-
localNonce: test.localNonce,
56+
transactor: transactor,
57+
localNonce: test.localNonce,
58+
expirationDate: test.expirationDate,
4659
}
4760

4861
nonce, err := manager.CurrentNonce()

0 commit comments

Comments
 (0)