github.com/status-im/status-go@v1.1.0/services/ext/requests.go (about)

     1  package ext
     2  
     3  import (
     4  	"fmt"
     5  	"hash/fnv"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/status-im/status-go/eth-node/types"
    10  )
    11  
    12  const (
    13  	// DefaultRequestsDelay will be used in RequestsRegistry if no other was provided.
    14  	DefaultRequestsDelay = 3 * time.Second
    15  )
    16  
    17  type requestMeta struct {
    18  	timestamp time.Time
    19  	lastUID   types.Hash
    20  }
    21  
    22  // NewRequestsRegistry creates instance of the RequestsRegistry and returns pointer to it.
    23  func NewRequestsRegistry(delay time.Duration) *RequestsRegistry {
    24  	r := &RequestsRegistry{
    25  		delay: delay,
    26  	}
    27  	r.Clear()
    28  	return r
    29  }
    30  
    31  // RequestsRegistry keeps map for all requests with timestamp when they were made.
    32  type RequestsRegistry struct {
    33  	mu           sync.Mutex
    34  	delay        time.Duration
    35  	uidToTopics  map[types.Hash]types.Hash
    36  	byTopicsHash map[types.Hash]requestMeta
    37  }
    38  
    39  // Register request with given topics. If request with same topics was made in less then configured delay then error
    40  // will be returned.
    41  func (r *RequestsRegistry) Register(uid types.Hash, topics []types.TopicType) error {
    42  	r.mu.Lock()
    43  	defer r.mu.Unlock()
    44  	topicsHash := topicsToHash(topics)
    45  	if meta, exist := r.byTopicsHash[topicsHash]; exist {
    46  		if time.Since(meta.timestamp) < r.delay {
    47  			return fmt.Errorf("another request with the same topics was sent less than %s ago. Please wait for a bit longer, or set `force` to true in request parameters", r.delay)
    48  		}
    49  	}
    50  	newMeta := requestMeta{
    51  		timestamp: time.Now(),
    52  		lastUID:   uid,
    53  	}
    54  	r.uidToTopics[uid] = topicsHash
    55  	r.byTopicsHash[topicsHash] = newMeta
    56  	return nil
    57  }
    58  
    59  // Has returns true if given uid is stored in registry.
    60  func (r *RequestsRegistry) Has(uid types.Hash) bool {
    61  	r.mu.Lock()
    62  	defer r.mu.Unlock()
    63  	_, exist := r.uidToTopics[uid]
    64  	return exist
    65  }
    66  
    67  // Unregister removes request with given UID from registry.
    68  func (r *RequestsRegistry) Unregister(uid types.Hash) {
    69  	r.mu.Lock()
    70  	defer r.mu.Unlock()
    71  	topicsHash, exist := r.uidToTopics[uid]
    72  	if !exist {
    73  		return
    74  	}
    75  	delete(r.uidToTopics, uid)
    76  	meta := r.byTopicsHash[topicsHash]
    77  	// remove topicsHash only if we are trying to unregister last request with this topic.
    78  	if meta.lastUID == uid {
    79  		delete(r.byTopicsHash, topicsHash)
    80  	}
    81  }
    82  
    83  // Clear recreates all structures used for caching requests.
    84  func (r *RequestsRegistry) Clear() {
    85  	r.mu.Lock()
    86  	defer r.mu.Unlock()
    87  	r.uidToTopics = map[types.Hash]types.Hash{}
    88  	r.byTopicsHash = map[types.Hash]requestMeta{}
    89  }
    90  
    91  // topicsToHash returns non-cryptographic hash of the topics.
    92  func topicsToHash(topics []types.TopicType) types.Hash {
    93  	hash := fnv.New32()
    94  	for i := range topics {
    95  		_, _ = hash.Write(topics[i][:]) // never returns error per documentation
    96  	}
    97  	return types.BytesToHash(hash.Sum(nil))
    98  }