-
Notifications
You must be signed in to change notification settings - Fork 0
/
nonce_settings.go
163 lines (143 loc) · 5.48 KB
/
nonce_settings.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package blockchain
import (
"context"
"fmt"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
)
// Used for when running tests on a live test network, so tests can share nonces and run in parallel on the same network
var (
altGlobalNonceManager = sync.Map{}
)
// useGlobalNonceManager for when running tests on a non-simulated network
func useGlobalNonceManager(chainId *big.Int) *NonceSettings {
if _, ok := altGlobalNonceManager.Load(chainId.Uint64()); !ok {
altGlobalNonceManager.Store(chainId.Uint64(), newNonceSettings())
settings, _ := altGlobalNonceManager.Load(chainId.Uint64())
go settings.(*NonceSettings).watchInstantTransactions()
altGlobalNonceManager.Range(func(key, value interface{}) bool {
chainID := key.(uint64)
settings := value.(*NonceSettings)
if settings != nil {
fmt.Printf("Using a new Global Nonce Manager for chain %d\n%v", chainID, settings.Nonces)
}
return true
})
}
settings, _ := altGlobalNonceManager.Load(chainId.Uint64())
return settings.(*NonceSettings)
}
// convenience function
func newNonceSettings() *NonceSettings {
return &NonceSettings{
NonceMu: &sync.Mutex{},
Nonces: make(map[string]uint64),
instantTransactions: make(map[string]map[uint64]chan struct{}),
instantNonces: make(map[string]uint64),
registerChan: make(chan instantTxRegistration),
sentChan: make(chan string),
}
}
// NonceSettings is a convenient wrapper for holding nonce state
type NonceSettings struct {
NonceMu *sync.Mutex
Nonces map[string]uint64
// used to properly meter out instant txs on L2s
instantTransactions map[string]map[uint64]chan struct{}
instantNonces map[string]uint64
instantNoncesMu sync.Mutex
registerChan chan instantTxRegistration
sentChan chan string
}
// watchInstantTransactions should only be called when minConfirmations for the chain is 0, generally an L2 chain.
// This helps meter out transactions to L2 chains, so that nonces only send in order. For most (if not all) L2 chains,
// the mempool is small or non-existent, meaning we can't send nonces out of order, otherwise the tx is instantly
// rejected.
func (ns *NonceSettings) watchInstantTransactions() {
ns.instantTransactions = make(map[string]map[uint64]chan struct{})
checkInterval := time.NewTicker(time.Millisecond * 50)
defer checkInterval.Stop()
for {
select {
case toRegister := <-ns.registerChan:
if _, ok := ns.instantTransactions[toRegister.fromAddr]; !ok {
ns.instantTransactions[toRegister.fromAddr] = make(map[uint64]chan struct{})
}
ns.instantTransactions[toRegister.fromAddr][toRegister.nonce] = toRegister.releaseChan
case sentAddr := <-ns.sentChan:
ns.instantNoncesMu.Lock()
ns.instantNonces[sentAddr]++
ns.instantNoncesMu.Unlock()
case <-checkInterval.C:
for addr, releaseChannels := range ns.instantTransactions {
ns.instantNoncesMu.Lock()
nonceToSend := ns.instantNonces[addr]
ns.instantNoncesMu.Unlock()
if txChannel, ok := releaseChannels[nonceToSend]; ok {
close(txChannel)
delete(releaseChannels, nonceToSend)
}
}
}
}
}
// registerInstantTransaction helps meter out txs for L2 chains. Register, then wait to receive from the returned channel
// to know when your Tx can send. See watchInstantTransactions for a deeper explanation.
func (ns *NonceSettings) registerInstantTransaction(fromAddr string, nonce uint64) <-chan struct{} {
releaseChan := make(chan struct{})
ns.registerChan <- instantTxRegistration{
fromAddr: fromAddr,
nonce: nonce,
releaseChan: releaseChan,
}
return releaseChan
}
// sentInstantTransaction shows that you have sent this instant transaction, unlocking the next L2 transaction to run.
// See watchInstantTransactions for a deeper explanation.
func (ns *NonceSettings) sentInstantTransaction(fromAddr string) {
ns.sentChan <- fromAddr
}
// GetNonce keep tracking of nonces per address, add last nonce for addr if the map is empty
func (e *EthereumClient) GetNonce(ctx context.Context, addr common.Address) (uint64, error) {
e.NonceSettings.NonceMu.Lock()
defer e.NonceSettings.NonceMu.Unlock()
// See current state of the nonce manager, handy for debugging
// fmt.Println("-------Nonce Manager Current State-----------------")
// for address, nonce := range e.NonceSettings.Nonces {
// fmt.Printf("%s: %d\n", address, nonce)
// }
// fmt.Println("---------------------------------------------------")
if _, ok := e.NonceSettings.Nonces[addr.Hex()]; !ok {
pendingNonce, err := e.Client.PendingNonceAt(ctx, addr)
if err != nil {
return 0, err
}
e.NonceSettings.Nonces[addr.Hex()] = pendingNonce
e.NonceSettings.instantNoncesMu.Lock()
e.NonceSettings.instantNonces[addr.Hex()] = pendingNonce
e.NonceSettings.instantNoncesMu.Unlock()
return pendingNonce, nil
}
e.NonceSettings.Nonces[addr.Hex()]++
return e.NonceSettings.Nonces[addr.Hex()], nil
}
// PeekPendingNonce returns the current pending nonce for the address. Does not change any nonce settings state
func (e *EthereumClient) PeekPendingNonce(addr common.Address) (uint64, error) {
e.NonceSettings.NonceMu.Lock()
defer e.NonceSettings.NonceMu.Unlock()
if _, ok := e.NonceSettings.Nonces[addr.Hex()]; !ok {
pendingNonce, err := e.Client.PendingNonceAt(context.Background(), addr)
if err != nil {
return 0, err
}
e.NonceSettings.Nonces[addr.Hex()] = pendingNonce
}
return e.NonceSettings.Nonces[addr.Hex()], nil
}
type instantTxRegistration struct {
fromAddr string
nonce uint64
releaseChan chan struct{}
}