github.com/status-im/status-go@v1.1.0/services/wallet/balance/nonce_range.go (about)

     1  package balance
     2  
     3  import (
     4  	"math/big"
     5  	"reflect"
     6  	"sort"
     7  	"sync"
     8  
     9  	"github.com/ethereum/go-ethereum/common"
    10  )
    11  
    12  type nonceRange struct {
    13  	nonce int64
    14  	max   *big.Int
    15  	min   *big.Int
    16  }
    17  
    18  type sortedNonceRangesCacheType addressChainMap[[]nonceRange] // address->chainID->[]nonceRange
    19  
    20  type nonceRangeCache[T cacheIface[int64, nonceRange]] struct {
    21  	nonceRanges  addressChainMap[T]
    22  	sortedRanges sortedNonceRangesCacheType
    23  	rw           sync.RWMutex
    24  }
    25  
    26  func newNonceRangeCache[T cacheIface[int64, nonceRange]]() *nonceRangeCache[T] {
    27  	return &nonceRangeCache[T]{
    28  		nonceRanges:  make(addressChainMap[T]),
    29  		sortedRanges: make(sortedNonceRangesCacheType),
    30  	}
    31  }
    32  
    33  func (b *nonceRangeCache[T]) updateNonceRange(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64) {
    34  	b.rw.Lock()
    35  	defer b.rw.Unlock()
    36  
    37  	_, exists := b.nonceRanges[account]
    38  	if !exists {
    39  		b.nonceRanges[account] = make(map[uint64]T)
    40  	}
    41  	_, exists = b.nonceRanges[account][chainID]
    42  	if !exists {
    43  		b.nonceRanges[account][chainID] = reflect.New(reflect.TypeOf(b.nonceRanges[account][chainID]).Elem()).Interface().(T)
    44  		b.nonceRanges[account][chainID].init()
    45  	}
    46  
    47  	nr := b.nonceRanges[account][chainID].get(*nonce)
    48  	if nr == reflect.Zero(reflect.TypeOf(nr)).Interface() {
    49  		nr = nonceRange{
    50  			max:   big.NewInt(0).Set(blockNumber),
    51  			min:   big.NewInt(0).Set(blockNumber),
    52  			nonce: *nonce,
    53  		}
    54  	} else {
    55  		if nr.max.Cmp(blockNumber) == -1 {
    56  			nr.max.Set(blockNumber)
    57  		}
    58  
    59  		if nr.min.Cmp(blockNumber) == 1 {
    60  			nr.min.Set(blockNumber)
    61  		}
    62  	}
    63  
    64  	b.nonceRanges[account][chainID].set(*nonce, nr)
    65  	b.sortRanges(account, chainID)
    66  }
    67  
    68  func (b *nonceRangeCache[_]) findNonceInRange(account common.Address, chainID uint64, block *big.Int) *int64 {
    69  	b.rw.RLock()
    70  	defer b.rw.RUnlock()
    71  
    72  	for k := range b.sortedRanges[account][chainID] {
    73  		nr := b.sortedRanges[account][chainID][k]
    74  		cmpMin := nr.min.Cmp(block)
    75  		if cmpMin == 1 {
    76  			return nil
    77  		} else if cmpMin == 0 {
    78  			return &nr.nonce
    79  		} else {
    80  			cmpMax := nr.max.Cmp(block)
    81  			if cmpMax >= 0 {
    82  				return &nr.nonce
    83  			}
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func (b *nonceRangeCache[T]) sortRanges(account common.Address, chainID uint64) {
    91  	// DO NOT LOCK HERE - this function is called from a locked function
    92  
    93  	keys := b.nonceRanges[account][chainID].keys()
    94  
    95  	sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
    96  
    97  	ranges := []nonceRange{}
    98  	for _, k := range keys {
    99  		r := b.nonceRanges[account][chainID].get(k)
   100  		ranges = append(ranges, r)
   101  	}
   102  
   103  	_, exists := b.sortedRanges[account]
   104  	if !exists {
   105  		b.sortedRanges[account] = make(map[uint64][]nonceRange)
   106  	}
   107  
   108  	b.sortedRanges[account][chainID] = ranges
   109  }
   110  
   111  func (b *nonceRangeCache[T]) clear() {
   112  	b.rw.Lock()
   113  	defer b.rw.Unlock()
   114  
   115  	b.nonceRanges = make(addressChainMap[T])
   116  	b.sortedRanges = make(sortedNonceRangesCacheType)
   117  }
   118  
   119  func (b *nonceRangeCache[T]) size(account common.Address, chainID uint64) int {
   120  	b.rw.RLock()
   121  	defer b.rw.RUnlock()
   122  
   123  	_, exists := b.nonceRanges[account]
   124  	if !exists {
   125  		return 0
   126  	}
   127  
   128  	_, exists = b.nonceRanges[account][chainID]
   129  	if !exists {
   130  		return 0
   131  	}
   132  
   133  	return b.nonceRanges[account][chainID].len()
   134  }