status-go/services/ext/requests.go

99 lines
2.6 KiB
Go

package ext
import (
"fmt"
"hash/fnv"
"sync"
"time"
"github.com/status-im/status-go/eth-node/types"
)
const (
// DefaultRequestsDelay will be used in RequestsRegistry if no other was provided.
DefaultRequestsDelay = 3 * time.Second
)
type requestMeta struct {
timestamp time.Time
lastUID types.Hash
}
// NewRequestsRegistry creates instance of the RequestsRegistry and returns pointer to it.
func NewRequestsRegistry(delay time.Duration) *RequestsRegistry {
r := &RequestsRegistry{
delay: delay,
}
r.Clear()
return r
}
// RequestsRegistry keeps map for all requests with timestamp when they were made.
type RequestsRegistry struct {
mu sync.Mutex
delay time.Duration
uidToTopics map[types.Hash]types.Hash
byTopicsHash map[types.Hash]requestMeta
}
// Register request with given topics. If request with same topics was made in less then configured delay then error
// will be returned.
func (r *RequestsRegistry) Register(uid types.Hash, topics []types.TopicType) error {
r.mu.Lock()
defer r.mu.Unlock()
topicsHash := topicsToHash(topics)
if meta, exist := r.byTopicsHash[topicsHash]; exist {
if time.Since(meta.timestamp) < r.delay {
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)
}
}
newMeta := requestMeta{
timestamp: time.Now(),
lastUID: uid,
}
r.uidToTopics[uid] = topicsHash
r.byTopicsHash[topicsHash] = newMeta
return nil
}
// Has returns true if given uid is stored in registry.
func (r *RequestsRegistry) Has(uid types.Hash) bool {
r.mu.Lock()
defer r.mu.Unlock()
_, exist := r.uidToTopics[uid]
return exist
}
// Unregister removes request with given UID from registry.
func (r *RequestsRegistry) Unregister(uid types.Hash) {
r.mu.Lock()
defer r.mu.Unlock()
topicsHash, exist := r.uidToTopics[uid]
if !exist {
return
}
delete(r.uidToTopics, uid)
meta := r.byTopicsHash[topicsHash]
// remove topicsHash only if we are trying to unregister last request with this topic.
if meta.lastUID == uid {
delete(r.byTopicsHash, topicsHash)
}
}
// Clear recreates all structures used for caching requests.
func (r *RequestsRegistry) Clear() {
r.mu.Lock()
defer r.mu.Unlock()
r.uidToTopics = map[types.Hash]types.Hash{}
r.byTopicsHash = map[types.Hash]requestMeta{}
}
// topicsToHash returns non-cryptographic hash of the topics.
func topicsToHash(topics []types.TopicType) types.Hash {
hash := fnv.New32()
for i := range topics {
_, _ = hash.Write(topics[i][:]) // never returns error per documentation
}
return types.BytesToHash(hash.Sum(nil))
}