From 5fac4b02e3160469632f9def2d3ac249a39df1cc Mon Sep 17 00:00:00 2001 From: Marco Peereboom Date: Thu, 9 Jan 2025 12:38:58 +0000 Subject: [PATCH] Add tests --- database/tbcd/level/blockcache.go | 46 ++++++++++++++++------ database/tbcd/level/cache_test.go | 65 +++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 database/tbcd/level/cache_test.go diff --git a/database/tbcd/level/blockcache.go b/database/tbcd/level/blockcache.go index b59dabab3..9b858c97c 100644 --- a/database/tbcd/level/blockcache.go +++ b/database/tbcd/level/blockcache.go @@ -13,23 +13,32 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" ) -const blockSize = 1677721 // ~1.6MB rough size of a mainnet block as of Jan 2025 +var blockSize = 1677721 // ~1.6MB rough size of a mainnet block as of Jan 2025 -type timeBlock struct { +type blockElement struct { element *list.Element block []byte } +type CacheStats struct { + Hits int + Misses int + Purges int +} + type lowIQLRU struct { mtx sync.RWMutex size int // this is the approximate max size - m map[chainhash.Hash]timeBlock + m map[chainhash.Hash]blockElement totalSize int - // lru list + // lru list, when used move to back of the list l *list.List + + // stats + c CacheStats } func (l *lowIQLRU) Put(v *btcutil.Block) { @@ -43,18 +52,21 @@ func (l *lowIQLRU) Put(v *btcutil.Block) { block, err := v.Bytes() if err != nil { + // data corruption, panic panic(err) - // XXX don't cache but panic for now for diagnostic } // evict first element in list - if l.totalSize+len(block) >= l.size { + if l.totalSize+len(block) > l.size { // LET THEM EAT PANIC re := l.l.Front() rha := l.l.Remove(re) - rh := rha.(chainhash.Hash) - l.totalSize -= len(l.m[rh].block) - delete(l.m, rh) + // fmt.Printf("rha %v\n", spew.Sdump(rha)) + // fmt.Printf("==== re %T rha %T\n", re, rha) + rh := rha.(*list.Element).Value.(*chainhash.Hash) + l.totalSize -= len(l.m[*rh].block) + delete(l.m, *rh) + l.c.Purges++ } // lru list @@ -62,7 +74,7 @@ func (l *lowIQLRU) Put(v *btcutil.Block) { l.l.PushBack(element) // block lookup - l.m[*hash] = timeBlock{element: element, block: block} + l.m[*hash] = blockElement{element: element, block: block} l.totalSize += len(block) } @@ -72,19 +84,29 @@ func (l *lowIQLRU) Get(k *chainhash.Hash) (*btcutil.Block, bool) { be, ok := l.m[*k] if !ok { + l.c.Misses++ return nil, false } b, err := btcutil.NewBlockFromBytes(be.block) if err != nil { - panic(err) // XXX delete from cache and return nil, false but panic for diagnostics at this time + // panic for diagnostics at this time + panic(err) } // update access l.l.MoveToBack(be.element) + l.c.Hits++ + return b, true } +func (l *lowIQLRU) Stats() CacheStats { + l.mtx.RLock() + defer l.mtx.RUnlock() + return l.c +} + func lowIQLRUNewSize(size int) (*lowIQLRU, error) { if size <= 0 { return nil, fmt.Errorf("invalid size: %v", size) @@ -96,7 +118,7 @@ func lowIQLRUNewSize(size int) (*lowIQLRU, error) { } return &lowIQLRU{ size: size, - m: make(map[chainhash.Hash]timeBlock, count), + m: make(map[chainhash.Hash]blockElement, count), l: list.New(), }, nil } diff --git a/database/tbcd/level/cache_test.go b/database/tbcd/level/cache_test.go new file mode 100644 index 000000000..b625d015b --- /dev/null +++ b/database/tbcd/level/cache_test.go @@ -0,0 +1,65 @@ +package level + +import ( + "testing" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +func newBlock(prevHash *chainhash.Hash, nonce uint32) (chainhash.Hash, *btcutil.Block) { + bh := wire.NewBlockHeader(0, prevHash, &chainhash.Hash{}, 0, uint32(nonce)) + b := wire.NewMsgBlock(bh) + return bh.BlockHash(), btcutil.NewBlock(b) +} + +func TestLRUCache(t *testing.T) { + maxCache := 10 + blockSize = 81 // we'll use empty blocks + l, err := lowIQLRUNewSize(blockSize * maxCache) + if err != nil { + t.Fatal(err) + } + + prevHash := chainhash.Hash{} // genesis + blocks := make([]chainhash.Hash, 0, maxCache*2) + for i := 0; i < maxCache; i++ { + h, b := newBlock(&prevHash, uint32(i)) + t.Logf("%v: %v", i, h) + blocks = append(blocks, h) + l.Put(b) + prevHash = h + } + + // verify stats are 0 + s := l.Stats() + if s.Hits != 0 && s.Misses != 0 && s.Purges != 0 { + t.Fatal(spew.Sdump(s)) + } + + // retrieve all blocks + for k := range blocks { + if _, ok := l.Get(&blocks[k]); !ok { + t.Fatalf("block not found: %v", blocks[k]) + } + } + + // verify hits are maxBlocks + s = l.Stats() + if s.Hits != 10 && s.Misses != 0 && s.Purges != 0 { + t.Fatal(spew.Sdump(s)) + } + + // purge oldest cache entries + for i := maxCache; i < maxCache*2; i++ { + h, b := newBlock(&prevHash, uint32(i)) + t.Logf("%v: %v", i, h) + blocks = append(blocks, h) + l.Put(b) + prevHash = h + } + + t.Logf("%v", spew.Sdump(l.Stats())) +}