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