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  }