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 }