status-im/status-go

View on GitHub
services/ext/requests.go

Summary

Maintainability
A
0 mins
Test Coverage
B
84%
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))
}