block-sdk/block/utils/decoder.go
David Terpay bfdd58411a
feat: Cache Tx Decoder (#528)
* init

* nit

* nits

* nit

* more nits

* go version bump

* image bump

* nit
2024-06-20 17:03:22 -04:00

122 lines
2.8 KiB
Go

package utils
import (
"fmt"
"sync"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// DefaultMaxSize is the default maximum size of the cache.
var DefaultMaxSize uint64 = 500
// CacheTxDecoder wraps the sdk.TxDecoder and caches the decoded transactions with
// an LRU'esque cache. Each transaction is cached using the transaction's hash
// as the key. The cache is purged when the number of transactions in the cache
// exceeds the maximum size. The oldest transactions are removed first.
type CacheTxDecoder struct {
mut sync.Mutex
decoder sdk.TxDecoder
cache map[string]sdk.Tx
window []string
insertIndex int
oldestIndex int
maxSize uint64
}
// NewDefaultCacheTxDecoder returns a new CacheTxDecoder.
func NewDefaultCacheTxDecoder(
decoder sdk.TxDecoder,
) (*CacheTxDecoder, error) {
if decoder == nil {
return nil, fmt.Errorf("decoder cannot be nil")
}
return &CacheTxDecoder{
decoder: decoder,
cache: make(map[string]sdk.Tx),
window: make([]string, DefaultMaxSize),
insertIndex: 0,
oldestIndex: 0,
maxSize: DefaultMaxSize,
}, nil
}
// NewCacheTxDecoder returns a new CacheTxDecoder with the given cache interval.
func NewCacheTxDecoder(
decoder sdk.TxDecoder,
maxSize uint64,
) (*CacheTxDecoder, error) {
if decoder == nil {
return nil, fmt.Errorf("decoder cannot be nil")
}
return &CacheTxDecoder{
decoder: decoder,
cache: make(map[string]sdk.Tx),
window: make([]string, maxSize),
insertIndex: 0,
oldestIndex: 0,
maxSize: maxSize,
}, nil
}
// Decode decodes the transaction bytes into a sdk.Tx. It caches the decoded
// transaction using the transaction's hash as the key.
func (ctd *CacheTxDecoder) TxDecoder() sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, error) {
ctd.mut.Lock()
defer ctd.mut.Unlock()
hash := TxHash(txBytes)
if tx, ok := ctd.cache[hash]; ok {
return tx, nil
}
tx, err := ctd.decoder(txBytes)
if err != nil {
return nil, err
}
// Purge the cache if necessary
if uint64(len(ctd.cache)) >= ctd.maxSize {
// Purge the oldest transaction
entry := ctd.window[ctd.oldestIndex]
delete(ctd.cache, entry)
// Increment the oldest index
ctd.oldestIndex++
ctd.oldestIndex %= int(ctd.maxSize)
}
// Update the cache and window
ctd.cache[hash] = tx
ctd.window[ctd.insertIndex] = hash
// Increment the insert index
ctd.insertIndex++
ctd.insertIndex %= int(ctd.maxSize)
return tx, nil
}
}
// Len returns the number of transactions in the cache.
func (ctd *CacheTxDecoder) Len() int {
ctd.mut.Lock()
defer ctd.mut.Unlock()
return len(ctd.cache)
}
// Contains returns true if the cache contains the transaction with the given hash.
func (ctd *CacheTxDecoder) Contains(txBytes []byte) bool {
ctd.mut.Lock()
defer ctd.mut.Unlock()
hash := TxHash(txBytes)
_, ok := ctd.cache[hash]
return ok
}