Skip to content

Commit 67a1ccb

Browse files
tbcd: move block header cache from items to size (#360)
* back this up * Backup cache purge * Joshua comments * Purge cache in batches instead of taking the mutex all the time * One more Joshua comment * nominally working of block cache minus eviction * Remove unused Purge * Add LRU mechanics * Add tests * Fix insert of element * Test misses and purges as well * Limit cache to 1gb by default and rig up prometheus * Derp disable promPollVerbose * Fix stats races (thanks joshua) and add stats to header cache as well * Test header cache as well and make checks more readable * added extra basic test for headercache * fix naming, form joshua * Fixup of New in antonio code --------- Co-authored-by: AL-CT <[email protected]>
1 parent a93a0b2 commit 67a1ccb

File tree

11 files changed

+648
-79
lines changed

11 files changed

+648
-79
lines changed

cmd/tbcd/tbcd.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2024 Hemi Labs, Inc.
1+
// Copyright (c) 2024-2025 Hemi Labs, Inc.
22
// Use of this source code is governed by the MIT License,
33
// which can be found in the LICENSE file.
44

@@ -25,7 +25,8 @@ const (
2525
defaultLogLevel = daemonName + "=INFO;tbc=INFO;level=INFO"
2626
defaultNetwork = "testnet3" // XXX make this mainnet
2727
defaultHome = "~/." + daemonName
28-
bhsDefault = int(1e6) // enough for mainnet
28+
bDefaultSize = "1gb" // ~640 blocks on mainnet
29+
bhsDefaultSize = "128mb" // enough for mainnet
2930
)
3031

3132
var (
@@ -46,16 +47,16 @@ var (
4647
Help: "enable auto utxo and tx indexes",
4748
Print: config.PrintAll,
4849
},
49-
"TBC_BLOCK_CACHE": config.Config{
50-
Value: &cfg.BlockCache,
51-
DefaultValue: 250,
52-
Help: "number of cached blocks",
50+
"TBC_BLOCK_CACHE_SIZE": config.Config{
51+
Value: &cfg.BlockCacheSize,
52+
DefaultValue: bDefaultSize,
53+
Help: "size of block cache",
5354
Print: config.PrintAll,
5455
},
55-
"TBC_BLOCKHEADER_CACHE": config.Config{
56-
Value: &cfg.BlockheaderCache,
57-
DefaultValue: bhsDefault,
58-
Help: "number of cached blockheaders",
56+
"TBC_BLOCKHEADER_CACHE_SIZE": config.Config{
57+
Value: &cfg.BlockheaderCacheSize,
58+
DefaultValue: bhsDefaultSize,
59+
Help: "size of blockheader cache",
5960
Print: config.PrintAll,
6061
},
6162
"TBC_BLOCK_SANITY": config.Config{

database/tbcd/database.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2024 Hemi Labs, Inc.
1+
// Copyright (c) 2024-2025 Hemi Labs, Inc.
22
// Use of this source code is governed by the MIT License,
33
// which can be found in the LICENSE file.
44

@@ -91,6 +91,7 @@ type Database interface {
9191
BlockHeaderBest(ctx context.Context) (*BlockHeader, error) // return canonical
9292
BlockHeaderByHash(ctx context.Context, hash *chainhash.Hash) (*BlockHeader, error)
9393
BlockHeaderGenesisInsert(ctx context.Context, wbh *wire.BlockHeader, height uint64, diff *big.Int) error
94+
BlockHeaderCacheStats() CacheStats
9495

9596
// Block headers
9697
BlockHeadersByHeight(ctx context.Context, height uint64) ([]BlockHeader, error)
@@ -103,6 +104,7 @@ type Database interface {
103104
BlockInsert(ctx context.Context, b *btcutil.Block) (int64, error)
104105
// BlocksInsert(ctx context.Context, bs []*btcutil.Block) (int64, error)
105106
BlockByHash(ctx context.Context, hash *chainhash.Hash) (*btcutil.Block, error)
107+
BlockCacheStats() CacheStats
106108

107109
// Transactions
108110
BlockUtxoUpdate(ctx context.Context, direction int, utxos map[Outpoint]CacheOutput) error
@@ -413,3 +415,12 @@ func TxIdBlockHashFromTxKey(txKey TxKey) (*chainhash.Hash, *chainhash.Hash, erro
413415
}
414416
return txId, blockHash, nil
415417
}
418+
419+
// Cache
420+
type CacheStats struct {
421+
Hits int
422+
Misses int
423+
Purges int
424+
Size int
425+
Items int
426+
}

database/tbcd/level/blockcache.go

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) 2025 Hemi Labs, Inc.
2+
// Use of this source code is governed by the MIT License,
3+
// which can be found in the LICENSE file.
4+
5+
package level
6+
7+
import (
8+
"container/list"
9+
"fmt"
10+
"sync"
11+
12+
"github.com/btcsuite/btcd/btcutil"
13+
"github.com/btcsuite/btcd/chaincfg/chainhash"
14+
15+
"github.com/hemilabs/heminetwork/database/tbcd"
16+
)
17+
18+
var blockSize = 1677721 // ~1.6MB rough size of a mainnet block as of Jan 2025
19+
20+
type blockElement struct {
21+
element *list.Element
22+
block []byte
23+
}
24+
25+
type lowIQLRU struct {
26+
mtx sync.Mutex
27+
28+
size int // this is the approximate max size
29+
30+
m map[chainhash.Hash]blockElement
31+
totalSize int
32+
33+
// lru list, when used move to back of the list
34+
l *list.List
35+
36+
// stats
37+
c tbcd.CacheStats
38+
}
39+
40+
func (l *lowIQLRU) Put(v *btcutil.Block) {
41+
l.mtx.Lock()
42+
defer l.mtx.Unlock()
43+
44+
hash := v.Hash()
45+
if _, ok := l.m[*hash]; ok {
46+
return
47+
}
48+
49+
block, err := v.Bytes()
50+
if err != nil {
51+
// data corruption, panic
52+
panic(err)
53+
}
54+
55+
// evict first element in list
56+
if l.totalSize+len(block) > l.size {
57+
// LET THEM EAT PANIC
58+
re := l.l.Front()
59+
rha := l.l.Remove(re)
60+
rh := rha.(*chainhash.Hash)
61+
l.totalSize -= len(l.m[*rh].block)
62+
delete(l.m, *rh)
63+
l.c.Purges++
64+
}
65+
66+
// block lookup and lru append
67+
l.m[*hash] = blockElement{element: l.l.PushBack(hash), block: block}
68+
l.totalSize += len(block)
69+
70+
l.c.Size = l.totalSize
71+
}
72+
73+
func (l *lowIQLRU) Get(k *chainhash.Hash) (*btcutil.Block, bool) {
74+
l.mtx.Lock()
75+
defer l.mtx.Unlock()
76+
77+
be, ok := l.m[*k]
78+
if !ok {
79+
l.c.Misses++
80+
return nil, false
81+
}
82+
b, err := btcutil.NewBlockFromBytes(be.block)
83+
if err != nil {
84+
// panic for diagnostics at this time
85+
panic(err)
86+
}
87+
88+
// update access
89+
l.l.MoveToBack(be.element)
90+
91+
l.c.Hits++
92+
93+
return b, true
94+
}
95+
96+
func (l *lowIQLRU) Stats() tbcd.CacheStats {
97+
l.mtx.Lock()
98+
defer l.mtx.Unlock()
99+
l.c.Items = len(l.m)
100+
return l.c
101+
}
102+
103+
func lowIQLRUSizeNew(size int) (*lowIQLRU, error) {
104+
if size <= 0 {
105+
return nil, fmt.Errorf("invalid size: %v", size)
106+
}
107+
// approximate number of blocks
108+
count := size / blockSize
109+
if count <= 0 {
110+
return nil, fmt.Errorf("invalid count: %v", count)
111+
}
112+
return &lowIQLRU{
113+
size: size,
114+
m: make(map[chainhash.Hash]blockElement, count),
115+
l: list.New(),
116+
}, nil
117+
}

0 commit comments

Comments
 (0)