Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9a8ba84
Add v4 keystone height hash index
marcopeereboom Jun 10, 2025
93c50e9
decode all keystones
marcopeereboom Jun 10, 2025
6725cbd
Decode the various databases
marcopeereboom Jun 10, 2025
33b0add
encode height to keystone hash
marcopeereboom Jun 10, 2025
cf9ff92
Use abbreviated hash
marcopeereboom Jun 10, 2025
7b14ab6
correct spot
marcopeereboom Jun 10, 2025
19f441c
64 entire bits
marcopeereboom Jun 10, 2025
9f8e579
test this shit real quick
marcopeereboom Jun 10, 2025
33d1de3
remove test code and put results in database
marcopeereboom Jun 10, 2025
93b7a33
Let's start putting this together
marcopeereboom Jun 10, 2025
d0b1651
Show antonio
marcopeereboom Jun 11, 2025
bf14da2
remove fixed XXX
marcopeereboom Jun 11, 2025
f6c6d6f
return height as well with a shortcut
marcopeereboom Jun 11, 2025
efb37e6
add height hash index encoding test
AL-CT Jun 11, 2025
953d45a
fix block missing height error and linter
AL-CT Jun 11, 2025
f73790a
Do we want this antonio?
marcopeereboom Jun 11, 2025
2b36211
oops remove panic
marcopeereboom Jun 11, 2025
73c165d
Here is a better version antonio
marcopeereboom Jun 11, 2025
9a9b734
oops, fix error
marcopeereboom Jun 11, 2025
1d09dc4
minor test fixes for db upgrade
AL-CT Jun 11, 2025
3ba098e
add keystone by height and tests
AL-CT Jun 11, 2025
bcb0fc2
Don't use CloneBytes
marcopeereboom Jun 12, 2025
430fa1b
Cleanup
marcopeereboom Jun 12, 2025
211f1fb
verify height and use found height
marcopeereboom Jun 12, 2025
b236647
Attempt at depth
marcopeereboom Jun 12, 2025
1a2f959
fix height seeking on keystone by hash
AL-CT Jun 12, 2025
8cd2b60
off by one fix and tests
AL-CT Jun 12, 2025
def77d6
add keystonesbyheight to hemictl
AL-CT Jun 12, 2025
ac36984
add keystonesbyheight rpc call
AL-CT Jun 12, 2025
c467c79
try to please codeql for all the unnecessary reasons
marcopeereboom Jun 13, 2025
eaa20aa
Just stop being clever to save 4 bytes per keystone
marcopeereboom Jun 13, 2025
adc9c70
fix upgrade and add testing
AL-CT Jun 13, 2025
e3bff87
Revert "fix upgrade and add testing"
AL-CT Jun 16, 2025
5acc2e6
fix v4 upgrade
AL-CT Jun 16, 2025
6acc0fb
add v4 upgrade test and new v3 test database
AL-CT Jun 16, 2025
668434f
Cleanup a little
marcopeereboom Jun 17, 2025
5cf28d2
fix keystones by height
AL-CT Jun 17, 2025
825ac2c
add extra test
AL-CT Jun 19, 2025
5e9552b
forgot one
AL-CT Jun 19, 2025
4e9684f
fix comments and suggestions
AL-CT Jun 19, 2025
b7ce764
cleanup
AL-CT Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Just stop being clever to save 4 bytes per keystone
  • Loading branch information
marcopeereboom authored and AL-CT committed Jun 24, 2025
commit eaa20aaea25023d6823fa9f65233a1e2cc75825f
2 changes: 1 addition & 1 deletion api/tbcapi/tbcapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ type BlockKeystoneByL2KeystoneAbrevHashResponse struct {
}

type KeystonesByHeightRequest struct {
Height uint64 `json:"height"`
Height uint32 `json:"height"`
Depth int `json:"depth"`
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/hemictl/hemictl.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ func tbcdb(pctx context.Context, flags []string) error {
return errors.New("height: must be set")
}

h, err := strconv.ParseUint(height, 10, 64)
h, err := strconv.ParseUint(height, 10, 32)
if err != nil {
return fmt.Errorf("parse height: %w", err)
}
Expand All @@ -777,7 +777,7 @@ func tbcdb(pctx context.Context, flags []string) error {
return fmt.Errorf("parse depth: %w", err)
}

kssList, err := s.KeystonesByHeight(ctx, h, int(d))
kssList, err := s.KeystonesByHeight(ctx, uint32(h), int(d))
if err != nil {
return fmt.Errorf("retrieve keystones: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions database/tbcd/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ type Database interface {
BlockKeystoneUpdate(ctx context.Context, direction int, keystones map[chainhash.Hash]Keystone, keystoneIndexHash chainhash.Hash) error
BlockKeystoneByL2KeystoneAbrevHash(ctx context.Context, abrevhash chainhash.Hash) (*Keystone, error)
BlockHeaderByKeystoneIndex(ctx context.Context) (*BlockHeader, error)
KeystonesByHeight(ctx context.Context, height uint64, depth int) ([]Keystone, error)
KeystonesByHeight(ctx context.Context, height uint32, depth int) ([]Keystone, error)
}

type Keystone struct {
BlockHash chainhash.Hash // Block that contains abbreviated keystone
BlockHeight uint64 // Block height
BlockHeight uint32 // Block height
AbbreviatedKeystone [hemi.L2KeystoneAbrevSize]byte // Abbreviated keystone
}

Expand Down
61 changes: 14 additions & 47 deletions database/tbcd/level/level.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const (

heighthashSize = 8 + 1 + chainhash.HashSize
blockheaderSize = 120
keystoneSize = chainhash.HashSize + hemi.L2KeystoneAbrevSize
keystoneSize = 4 + chainhash.HashSize + hemi.L2KeystoneAbrevSize
keystoneHeightHashSize = 1 + 4 + chainhash.HashSize // h uint32(height) block_hash
)

Expand Down Expand Up @@ -462,33 +462,6 @@ func (l *ldb) BlockKeystoneByL2KeystoneAbrevHash(ctx context.Context, abrevhash
return nil, fmt.Errorf("l2 keystone: %w", err)
}
ks := decodeKeystone(eks)

// Recreate height from index.
i := kssDB.NewIterator(keystoneHeightHashRange(abrevhash), nil)
defer func() {
i.Release()
}()

// seek for index with expected hash but increasing height
nextKey := encodeKeystoneHeightHash(0, abrevhash)
for j := 1; i.Seek(nextKey[:]); j++ {
// check if retrieved key indeed has the expected hash
if bytes.Equal(i.Key(), nextKey[:]) {
break
}
nextKey = encodeKeystoneHeightHash(uint32(j), abrevhash)
}

// check if we reached the end and didn't find a matching key
if !bytes.Equal(i.Key(), nextKey[:]) {
return nil, database.NotFoundError(fmt.Sprintf("height not found: %v", ks.BlockHash))
}
if i.Error() != nil {
return nil, fmt.Errorf("range: %w", i.Error())
}
height, _ := decodeKeystoneHeightHash(i.Key())
ks.BlockHeight = uint64(height)

return &ks, nil
}

Expand Down Expand Up @@ -1967,8 +1940,11 @@ func (l *ldb) BlockTxUpdate(ctx context.Context, direction int, txs map[tbcd.TxK
// [blockhash,abbreviated keystone] or [32+76] bytes. The abbreviated keystone
// hash is the leveldb table key.
func encodeKeystone(ks tbcd.Keystone) (eks [keystoneSize]byte) {
copy(eks[0:32], ks.BlockHash[:])
copy(eks[32:], ks.AbbreviatedKeystone[:])
var h [4]byte
binary.BigEndian.PutUint32(h[:], ks.BlockHeight)
copy(eks[0:4], h[:])
copy(eks[4:4+32], ks.BlockHash[:])
copy(eks[36:], ks.AbbreviatedKeystone[:])
return
}

Expand All @@ -1979,13 +1955,14 @@ func encodeKeystoneToSlice(ks tbcd.Keystone) []byte {

// decodeKeystone reverse the process of encodeKeystone.
func decodeKeystone(eks []byte) (ks tbcd.Keystone) {
bh, err := chainhash.NewHash(eks[0:32])
ks.BlockHeight = binary.BigEndian.Uint32(eks[0:4])
bh, err := chainhash.NewHash(eks[4 : 4+32])
if err != nil {
panic(err) // Can't happen
}
ks.BlockHash = *bh
// copy the values to prevent slicing reentrancy problems.
copy(ks.AbbreviatedKeystone[:], eks[32:])
copy(ks.AbbreviatedKeystone[:], eks[36:])
return ks
}

Expand Down Expand Up @@ -2019,12 +1996,6 @@ func decodeKeystoneHeightHash(v []byte) (height uint32, hash chainhash.Hash) {
return
}

func keystoneHeightHashRange(hash chainhash.Hash) *util.Range {
limit := encodeKeystoneHeightHashSlice(0, hash)
limit[0]++
return &util.Range{Start: encodeKeystoneHeightHashSlice(0, hash), Limit: limit}
}

func keystoneHeightRange(height uint32, depth int) *util.Range {
// Casting is a bit awkward here but I am not sure if we can make this
// look better somehow.
Expand All @@ -2040,16 +2011,13 @@ func keystoneHeightRange(height uint32, depth int) *util.Range {
}
}

func (l *ldb) KeystonesByHeight(ctx context.Context, height uint64, depth int) ([]tbcd.Keystone, error) {
func (l *ldb) KeystonesByHeight(ctx context.Context, height uint32, depth int) ([]tbcd.Keystone, error) {
log.Tracef("KeystonesByHeight")
defer log.Tracef("KeystonesByHeight exit")

if depth == 0 {
return nil, errors.New("depth must not be 0")
}
if height > math.MaxUint32 {
return nil, errors.New("overflow for codeql")
}
if int64(height)+int64(depth) > math.MaxUint32 {
return nil, errors.New("the overflow that matters")
}
Expand All @@ -2058,19 +2026,18 @@ func (l *ldb) KeystonesByHeight(ctx context.Context, height uint64, depth int) (
}

kssDB := l.pool[level.KeystonesDB]
i := kssDB.NewIterator(keystoneHeightRange(uint32(height), depth), nil)
i := kssDB.NewIterator(keystoneHeightRange(height, depth), nil)
defer func() { i.Release() }()

kssList := make([]tbcd.Keystone, 0, 16)
for i.Next() {
h, hash := decodeKeystoneHeightHash(i.Key())
_, hash := decodeKeystoneHeightHash(i.Key())
eks, err := kssDB.Get(hash[:], nil)
if err != nil {
// mismatch between heighthash and hash indexes
panic(fmt.Sprintf("data corruption: %v", err))
}
deks := decodeKeystone(eks)
deks.BlockHeight = uint64(h)
kssList = append(kssList, deks)
}
if len(kssList) == 0 {
Expand Down Expand Up @@ -2109,15 +2076,15 @@ func (l *ldb) BlockKeystoneUpdate(ctx context.Context, direction int, keystones
// previously found block.
if ks.BlockHash.IsEqual(&v.BlockHash) {
kssBatch.Delete(k[:])
kssBatch.Delete(encodeKeystoneHeightHashSlice(uint32(v.BlockHeight), k))
kssBatch.Delete(encodeKeystoneHeightHashSlice(v.BlockHeight, k))
}
}
case 1:
has, _ := kssTx.Has(k[:], nil)
if !has {
// Only store unknown keystones and indexes
kssBatch.Put(k[:], encodeKeystoneToSlice(v))
kssBatch.Put(encodeKeystoneHeightHashSlice(uint32(v.BlockHeight), k), nil)
kssBatch.Put(encodeKeystoneHeightHashSlice(v.BlockHeight, k), nil)
}
}

Expand Down
10 changes: 5 additions & 5 deletions database/tbcd/level/level_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,14 @@ func TestMD(t *testing.T) {
}
}

func makeKssMap(from uint64, kssList []hemi.L2Keystone, blockHashSeed string) map[chainhash.Hash]tbcd.Keystone {
func makeKssMap(from uint32, kssList []hemi.L2Keystone, blockHashSeed string) map[chainhash.Hash]tbcd.Keystone {
kssMap := make(map[chainhash.Hash]tbcd.Keystone)
for i, l2Keystone := range kssList {
abrvKs := hemi.L2KeystoneAbbreviate(l2Keystone).Serialize()
kssMap[*hemi.L2KeystoneAbbreviate(l2Keystone).Hash()] = tbcd.Keystone{
BlockHash: chainhash.Hash(testutil.FillBytes(blockHashSeed, 32)),
AbbreviatedKeystone: abrvKs,
BlockHeight: from + uint64(i),
BlockHeight: from + uint32(i),
}
}
return kssMap
Expand Down Expand Up @@ -224,7 +224,7 @@ func TestHeightHashIndexing(t *testing.T) {
kssMap[*hemi.L2KeystoneAbbreviate(ks).Hash()] = tbcd.Keystone{
BlockHash: chainhash.Hash(testutil.FillBytes("blockhash", 32)),
AbbreviatedKeystone: abrvKs,
BlockHeight: uint64(i + 1),
BlockHeight: uint32(i + 1),
}
l2Block += 25
}
Expand Down Expand Up @@ -267,7 +267,7 @@ func TestHeightHashIndexing(t *testing.T) {

// check each height
for n := range blockNum {
kssList, err := db.KeystonesByHeight(ctx, uint64(n+1), 1)
kssList, err := db.KeystonesByHeight(ctx, uint32(n+1), 1)
if err != nil {
t.Fatal(err)
}
Expand All @@ -277,7 +277,7 @@ func TestHeightHashIndexing(t *testing.T) {
}

for _, k := range kssList {
if k.BlockHeight != uint64(n+1) {
if k.BlockHeight != uint32(n+1) {
t.Fatalf("keystone height mismatch %v, expected %v", k.BlockHeight, n+1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion service/tbc/crawler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,7 @@ func processKeystones(block *btcutil.Block, direction int, kssCache map[chainhas
abvKss := aPoPTx.L2Keystone.Serialize()
kssCache[*aPoPTx.L2Keystone.Hash()] = tbcd.Keystone{
BlockHash: blockHash,
BlockHeight: blockHeight,
BlockHeight: uint32(blockHeight),
AbbreviatedKeystone: abvKss,
}
}
Expand Down
2 changes: 1 addition & 1 deletion service/tbc/tbc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1736,7 +1736,7 @@ func (s *Server) BlockByHash(ctx context.Context, hash chainhash.Hash) (*btcutil
}

// KeystonesByHeight returns any keystones found at a given height and up to a depth.
func (s *Server) KeystonesByHeight(ctx context.Context, height uint64, depth int) ([]tbcd.Keystone, error) {
func (s *Server) KeystonesByHeight(ctx context.Context, height uint32, depth int) ([]tbcd.Keystone, error) {
log.Tracef("KeystonesByHeight")
defer log.Tracef("KeystonesByHeight exit")

Expand Down
18 changes: 9 additions & 9 deletions service/tbc/tbcfork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1717,7 +1717,7 @@ func TestKeystoneIndexNoFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err := s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err := s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -1765,7 +1765,7 @@ func TestKeystoneIndexNoFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand All @@ -1789,7 +1789,7 @@ func TestKeystoneIndexNoFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b3.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b3.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -1858,7 +1858,7 @@ func TestKeystoneIndexNoFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -2391,7 +2391,7 @@ func TestKeystoneIndexFork(t *testing.T) {
t.Fatalf("wrong blockhash for stored keystone: %v", kss1Hash)
}
// check if keystone stored using heighthash index
hk, err := s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err := s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -2440,7 +2440,7 @@ func TestKeystoneIndexFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand All @@ -2464,7 +2464,7 @@ func TestKeystoneIndexFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b3.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b3.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -2576,7 +2576,7 @@ func TestKeystoneIndexFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -2664,7 +2664,7 @@ func TestKeystoneIndexFork(t *testing.T) {
}

// check if keystone stored using heighthash index
hk, err = s.db.KeystonesByHeight(ctx, uint64(b2.Height()), 1)
hk, err = s.db.KeystonesByHeight(ctx, uint32(b2.Height()), 1)
if err != nil {
t.Fatal(err)
}
Expand Down