From 904e834cdf00a22b7839d42b48432468a64e9faf Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 6 Jul 2026 10:36:08 -0500 Subject: [PATCH 1/3] add block number to ReadBlockByHash --- sei-db/ledger_db/block/block_db_test.go | 10 +-- sei-db/ledger_db/block/littblock/codec.go | 70 ++++++++++++++++--- .../ledger_db/block/littblock/codec_test.go | 35 ++++++++-- .../block/littblock/litt_block_db.go | 19 ++--- .../block/littblock/litt_block_iterator.go | 2 +- .../ledger_db/block/memblock/mem_block_db.go | 22 ++++-- sei-tendermint/autobahn/types/block_db.go | 5 +- 7 files changed, 124 insertions(+), 39 deletions(-) diff --git a/sei-db/ledger_db/block/block_db_test.go b/sei-db/ledger_db/block/block_db_test.go index 3b905cd4f5..3bbb11f228 100644 --- a/sei-db/ledger_db/block/block_db_test.go +++ b/sei-db/ledger_db/block/block_db_test.go @@ -98,7 +98,7 @@ func testEmptyDB(t *testing.T, build builder) { require.NoError(t, err) require.False(t, blk.IsPresent()) - byHash, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) + byHash, _, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) require.NoError(t, err) require.False(t, byHash.IsPresent()) @@ -135,7 +135,7 @@ func testReadRoundTrip(t *testing.T, build builder) { require.NoError(t, err) require.False(t, missNum.IsPresent()) - missHash, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) + missHash, _, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) require.NoError(t, err) require.False(t, missHash.IsPresent()) } @@ -630,11 +630,12 @@ func testWriteBlockGaps(t *testing.T, build builder) { require.True(t, ok, "block %d should exist", n) require.Equal(t, blocks[n].Header().Hash(), got.Header().Hash()) - byHash, err := db.ReadBlockByHash(blocks[n].Header().Hash()) + byHash, hashNum, err := db.ReadBlockByHash(blocks[n].Header().Hash()) require.NoError(t, err) got, ok = byHash.Get() require.True(t, ok, "block %d should be found by hash", n) require.Equal(t, blocks[n].Header().Hash(), got.Header().Hash()) + require.Equal(t, n, hashNum, "block %d hash lookup should return its number", n) } // Numbers in the gaps were never written and must miss. @@ -831,11 +832,12 @@ func assertBlocksReadable(t *testing.T, db types.BlockDB, batches []batch) { require.True(t, ok, "block %d should exist", n) require.Equal(t, blk.Header().Hash(), got.Header().Hash()) - byHash, err := db.ReadBlockByHash(blk.Header().Hash()) + byHash, hashNum, err := db.ReadBlockByHash(blk.Header().Hash()) require.NoError(t, err) got, ok = byHash.Get() require.True(t, ok, "block by hash should exist") require.Equal(t, blk.Header().Hash(), got.Header().Hash()) + require.Equal(t, n, hashNum, "block %d hash lookup should return its number", n) } } } diff --git a/sei-db/ledger_db/block/littblock/codec.go b/sei-db/ledger_db/block/littblock/codec.go index 7a75715f04..2e5c6d8183 100644 --- a/sei-db/ledger_db/block/littblock/codec.go +++ b/sei-db/ledger_db/block/littblock/codec.go @@ -2,6 +2,7 @@ package littblock import ( "encoding/binary" + "fmt" "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types" ) @@ -63,22 +64,71 @@ func decodeNumberKey(key []byte) types.GlobalBlockNumber { return decodeKey(key[1:]) } -// encodeBlock marshals a block to the bytes stored as its table value. -func encodeBlock(blk *types.Block) []byte { - return types.BlockConv.Marshal(blk) +// blockSerializationVersion and qcSerializationVersion are the current versions +// of the block and QC value framing written to LittDB. Every stored value is +// prefixed with its kind's version byte so the on-disk format can evolve +// independently per kind. The two version spaces are deliberately separate — a +// block and a QC must never share a version number — so they are free to +// diverge. Decode rejects any other version outright: this store is not yet in +// production, so no prior format is supported. +const blockSerializationVersion byte = 1 +const qcSerializationVersion byte = 1 + +// blockValuePrefixLen is the fixed header preceding a block's proto bytes: one +// version byte followed by the 8-byte big-endian GlobalBlockNumber. +const blockValuePrefixLen = 1 + 8 + +// encodeBlock marshals a block to the bytes stored as its table value. The value +// is framed as [version:1][GlobalBlockNumber:8 big-endian][proto(Block)]. The +// number is embedded so a by-hash lookup — which reaches this same shared value +// through a secondary key that carries only the hash — can still recover it. +func encodeBlock(n types.GlobalBlockNumber, blk *types.Block) []byte { + proto := types.BlockConv.Marshal(blk) + value := make([]byte, 0, blockValuePrefixLen+len(proto)) + value = append(value, blockSerializationVersion) + value = binary.BigEndian.AppendUint64(value, uint64(n)) + value = append(value, proto...) + return value } -// decodeBlock unmarshals a block from its stored table value. -func decodeBlock(value []byte) (*types.Block, error) { - return types.BlockConv.Unmarshal(value) +// decodeBlock unmarshals a block and its embedded GlobalBlockNumber from the +// value produced by encodeBlock. +func decodeBlock(value []byte) (types.GlobalBlockNumber, *types.Block, error) { + if len(value) < blockValuePrefixLen { + return 0, nil, fmt.Errorf("block value too short: %d bytes", len(value)) + } + if value[0] != blockSerializationVersion { + return 0, nil, fmt.Errorf("unsupported block serialization version %d", value[0]) + } + n := types.GlobalBlockNumber(binary.BigEndian.Uint64(value[1:blockValuePrefixLen])) + blk, err := types.BlockConv.Unmarshal(value[blockValuePrefixLen:]) + if err != nil { + return 0, nil, fmt.Errorf("failed to unmarshal block: %w", err) + } + return n, blk, nil } -// encodeQC marshals a FullCommitQC to the bytes stored as its table value. +// encodeQC marshals a FullCommitQC to the bytes stored as its table value, +// framed as [version:1][proto(FullCommitQC)]. func encodeQC(qc *types.FullCommitQC) []byte { - return types.FullCommitQCConv.Marshal(qc) + proto := types.FullCommitQCConv.Marshal(qc) + value := make([]byte, 0, 1+len(proto)) + value = append(value, qcSerializationVersion) + value = append(value, proto...) + return value } -// decodeQC unmarshals a FullCommitQC from its stored table value. +// decodeQC unmarshals a FullCommitQC from the value produced by encodeQC. func decodeQC(value []byte) (*types.FullCommitQC, error) { - return types.FullCommitQCConv.Unmarshal(value) + if len(value) < 1 { + return nil, fmt.Errorf("qc value too short: %d bytes", len(value)) + } + if value[0] != qcSerializationVersion { + return nil, fmt.Errorf("unsupported qc serialization version %d", value[0]) + } + qc, err := types.FullCommitQCConv.Unmarshal(value[1:]) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal qc: %w", err) + } + return qc, nil } diff --git a/sei-db/ledger_db/block/littblock/codec_test.go b/sei-db/ledger_db/block/littblock/codec_test.go index 4213425283..e9143f058d 100644 --- a/sei-db/ledger_db/block/littblock/codec_test.go +++ b/sei-db/ledger_db/block/littblock/codec_test.go @@ -66,15 +66,19 @@ func TestPrefixedKeys(t *testing.T) { func TestBlockRoundTrip(t *testing.T) { rng := utils.TestRngFromSeed(1) - for range 16 { + for i := range 16 { + n := types.GlobalBlockNumber(i * 7) blk := types.GenBlock(rng) - value := encodeBlock(blk) - decoded, err := decodeBlock(value) + value := encodeBlock(n, blk) + require.Equal(t, blockSerializationVersion, value[0], "value must be version-prefixed") + gotN, decoded, err := decodeBlock(value) require.NoError(t, err) + // The embedded block number must round-trip. + require.Equal(t, n, gotN) // Header hash uniquely identifies a block; equal hash => same block. require.Equal(t, blk.Header().Hash(), decoded.Header().Hash()) - // Re-encoding the decoded block must reproduce the same bytes. - require.Equal(t, value, encodeBlock(decoded)) + // Re-encoding the decoded block (with the same number) must reproduce the same bytes. + require.Equal(t, value, encodeBlock(gotN, decoded)) } } @@ -83,6 +87,7 @@ func TestQCRoundTrip(t *testing.T) { for range 16 { qc := types.GenFullCommitQC(rng) value := encodeQC(qc) + require.Equal(t, qcSerializationVersion, value[0], "value must be version-prefixed") decoded, err := decodeQC(value) require.NoError(t, err) // Re-encoding the decoded QC must reproduce the same bytes. @@ -91,10 +96,26 @@ func TestQCRoundTrip(t *testing.T) { } func TestDecodeRejectsGarbage(t *testing.T) { - // Invalid protobuf wire bytes must surface an error rather than a partial value. + // Invalid bytes must surface an error rather than a partial value. garbage := []byte{0xff, 0xff, 0xff, 0xff} - _, blockErr := decodeBlock(garbage) + _, _, blockErr := decodeBlock(garbage) require.Error(t, blockErr) _, qcErr := decodeQC(garbage) require.Error(t, qcErr) } + +func TestDecodeRejectsUnknownVersion(t *testing.T) { + rng := utils.TestRngFromSeed(3) + + // A block value whose version byte is not the current one must be rejected, + // even though the rest of the value is well-formed. + blockValue := encodeBlock(1, types.GenBlock(rng)) + blockValue[0] = blockSerializationVersion + 1 + _, _, blockErr := decodeBlock(blockValue) + require.Error(t, blockErr) + + qcValue := encodeQC(types.GenFullCommitQC(rng)) + qcValue[0] = qcSerializationVersion + 1 + _, qcErr := decodeQC(qcValue) + require.Error(t, qcErr) +} diff --git a/sei-db/ledger_db/block/littblock/litt_block_db.go b/sei-db/ledger_db/block/littblock/litt_block_db.go index 97e0585e09..f67fe2cfba 100644 --- a/sei-db/ledger_db/block/littblock/litt_block_db.go +++ b/sei-db/ledger_db/block/littblock/litt_block_db.go @@ -144,7 +144,7 @@ func (s *blockDB) WriteBlock(n types.GlobalBlockNumber, blk *types.Block) error n, s.lastQCNext, types.ErrBlockMissingQC) } - value := encodeBlock(blk) + value := encodeBlock(n, blk) hash := blk.Header().Hash() hashAlias := &litttypes.SecondaryKey{ Key: blockHashKey(hash), @@ -255,28 +255,29 @@ func (s *blockDB) QCs(reverse bool) (types.QCIterator, error) { func (s *blockDB) ReadBlockByNumber( n types.GlobalBlockNumber, ) (utils.Option[*types.Block], error) { - return getBlock(s.table, blockKey(n)) + blk, _, err := getBlock(s.table, blockKey(n)) + return blk, err } func (s *blockDB) ReadBlockByHash( hash types.BlockHeaderHash, -) (utils.Option[*types.Block], error) { +) (utils.Option[*types.Block], types.GlobalBlockNumber, error) { return getBlock(s.table, blockHashKey(hash)) } -func getBlock(table littdb.Table, key []byte) (utils.Option[*types.Block], error) { +func getBlock(table littdb.Table, key []byte) (utils.Option[*types.Block], types.GlobalBlockNumber, error) { value, exists, err := table.Get(key) if err != nil { - return utils.None[*types.Block](), fmt.Errorf("failed to read block: %w", err) + return utils.None[*types.Block](), 0, fmt.Errorf("failed to read block: %w", err) } if !exists { - return utils.None[*types.Block](), nil + return utils.None[*types.Block](), 0, nil } - blk, err := decodeBlock(value) + n, blk, err := decodeBlock(value) if err != nil { - return utils.None[*types.Block](), fmt.Errorf("failed to unmarshal block: %w", err) + return utils.None[*types.Block](), 0, fmt.Errorf("failed to unmarshal block: %w", err) } - return utils.Some(blk), nil + return utils.Some(blk), n, nil } func (s *blockDB) ReadQCByBlockNumber( diff --git a/sei-db/ledger_db/block/littblock/litt_block_iterator.go b/sei-db/ledger_db/block/littblock/litt_block_iterator.go index 72460ee8b3..62bf6d5b94 100644 --- a/sei-db/ledger_db/block/littblock/litt_block_iterator.go +++ b/sei-db/ledger_db/block/littblock/litt_block_iterator.go @@ -44,7 +44,7 @@ func (b *blockIterator) Block() (*types.Block, error) { if err != nil { return nil, fmt.Errorf("failed to read block value: %w", err) } - blk, err := decodeBlock(value) + _, blk, err := decodeBlock(value) if err != nil { return nil, fmt.Errorf("failed to unmarshal block: %w", err) } diff --git a/sei-db/ledger_db/block/memblock/mem_block_db.go b/sei-db/ledger_db/block/memblock/mem_block_db.go index ff631ef7a7..0e129c2508 100644 --- a/sei-db/ledger_db/block/memblock/mem_block_db.go +++ b/sei-db/ledger_db/block/memblock/mem_block_db.go @@ -19,13 +19,21 @@ type qcEntry struct { upper types.GlobalBlockNumber } +// hashEntry pairs a block with its GlobalBlockNumber so ReadBlockByHash can +// return the number, mirroring the littblock implementation which embeds it in +// the stored value. +type hashEntry struct { + blk *types.Block + n types.GlobalBlockNumber +} + // blockDB is an in-memory types.BlockDB. It holds blocks and QCs by pointer (no // marshaling) and is intended as a test/benchmark fixture, not a durable // implementation. type blockDB struct { mu sync.RWMutex byNumber map[types.GlobalBlockNumber]*types.Block - byHash map[types.BlockHeaderHash]*types.Block + byHash map[types.BlockHeaderHash]hashEntry qcsByLower map[types.GlobalBlockNumber]qcEntry // Write-order cursors (see types.BlockDB contract). @@ -39,7 +47,7 @@ type blockDB struct { func NewBlockDB() types.BlockDB { return &blockDB{ byNumber: make(map[types.GlobalBlockNumber]*types.Block), - byHash: make(map[types.BlockHeaderHash]*types.Block), + byHash: make(map[types.BlockHeaderHash]hashEntry), qcsByLower: make(map[types.GlobalBlockNumber]qcEntry), } } @@ -58,7 +66,7 @@ func (s *blockDB) WriteBlock(n types.GlobalBlockNumber, blk *types.Block) error n, s.lastQCNext, types.ErrBlockMissingQC) } s.byNumber[n] = blk - s.byHash[blk.Header().Hash()] = blk + s.byHash[blk.Header().Hash()] = hashEntry{blk: blk, n: n} s.lastBlockNumber = n s.hasBlocks = true return nil @@ -193,13 +201,13 @@ func (s *blockDB) ReadBlockByNumber( func (s *blockDB) ReadBlockByHash( hash types.BlockHeaderHash, -) (utils.Option[*types.Block], error) { +) (utils.Option[*types.Block], types.GlobalBlockNumber, error) { s.mu.RLock() defer s.mu.RUnlock() - if blk, ok := s.byHash[hash]; ok { - return utils.Some(blk), nil + if e, ok := s.byHash[hash]; ok { + return utils.Some(e.blk), e.n, nil } - return utils.None[*types.Block](), nil + return utils.None[*types.Block](), 0, nil } func (s *blockDB) ReadQCByBlockNumber( diff --git a/sei-tendermint/autobahn/types/block_db.go b/sei-tendermint/autobahn/types/block_db.go index c5baafcd3d..c58fa41433 100644 --- a/sei-tendermint/autobahn/types/block_db.go +++ b/sei-tendermint/autobahn/types/block_db.go @@ -205,11 +205,14 @@ type BlockDB interface { // given value. The hash is the same value as block.Header().Hash() // for the block that was passed to WriteBlock. // + // This method returns the block number for the block if it is found. + // not found, the block number returned is undefined. + // // Returns utils.None if no such block has been written. A pruned // block MAY also return None, but — as with ReadBlockByNumber — // reclamation is asynchronous, so a pruned block may remain readable // until it is actually reclaimed. Non-blocking. - ReadBlockByHash(hash BlockHeaderHash) (utils.Option[*Block], error) + ReadBlockByHash(hash BlockHeaderHash) (utils.Option[*Block], GlobalBlockNumber, error) // ReadQCByBlockNumber returns the FullCommitQC whose // GlobalRange().First ≤ n < GlobalRange().Next — i.e. the QC that From 175dccbb4f04ccf491633797cbaaf8793472a554 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 7 Jul 2026 10:41:14 -0500 Subject: [PATCH 2/3] made suggested changes --- sei-db/ledger_db/block/littblock/codec.go | 10 +++------- sei-tendermint/autobahn/types/block_db.go | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/sei-db/ledger_db/block/littblock/codec.go b/sei-db/ledger_db/block/littblock/codec.go index 2e5c6d8183..c39bf69276 100644 --- a/sei-db/ledger_db/block/littblock/codec.go +++ b/sei-db/ledger_db/block/littblock/codec.go @@ -64,14 +64,10 @@ func decodeNumberKey(key []byte) types.GlobalBlockNumber { return decodeKey(key[1:]) } -// blockSerializationVersion and qcSerializationVersion are the current versions -// of the block and QC value framing written to LittDB. Every stored value is -// prefixed with its kind's version byte so the on-disk format can evolve -// independently per kind. The two version spaces are deliberately separate — a -// block and a QC must never share a version number — so they are free to -// diverge. Decode rejects any other version outright: this store is not yet in -// production, so no prior format is supported. +// Serialization version for blocks. const blockSerializationVersion byte = 1 + +// Serialization version for QCs. const qcSerializationVersion byte = 1 // blockValuePrefixLen is the fixed header preceding a block's proto bytes: one diff --git a/sei-tendermint/autobahn/types/block_db.go b/sei-tendermint/autobahn/types/block_db.go index c58fa41433..81e4aebd5f 100644 --- a/sei-tendermint/autobahn/types/block_db.go +++ b/sei-tendermint/autobahn/types/block_db.go @@ -206,7 +206,7 @@ type BlockDB interface { // for the block that was passed to WriteBlock. // // This method returns the block number for the block if it is found. - // not found, the block number returned is undefined. + // If not found, the block number returned is undefined. // // Returns utils.None if no such block has been written. A pruned // block MAY also return None, but — as with ReadBlockByNumber — From f146305ab96b598d7384f252df1554906b47eb4f Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 9 Jul 2026 08:23:00 -0500 Subject: [PATCH 3/3] made suggested changes --- sei-db/ledger_db/block/block_db_test.go | 20 ++++++------- .../ledger_db/block/littblock/codec_test.go | 2 +- .../block/littblock/litt_block_db.go | 28 ++++++++++--------- .../ledger_db/block/memblock/mem_block_db.go | 6 ++-- sei-tendermint/autobahn/types/block.go | 8 ++++++ sei-tendermint/autobahn/types/block_db.go | 10 +++---- 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/sei-db/ledger_db/block/block_db_test.go b/sei-db/ledger_db/block/block_db_test.go index 3bbb11f228..266befeb4e 100644 --- a/sei-db/ledger_db/block/block_db_test.go +++ b/sei-db/ledger_db/block/block_db_test.go @@ -98,7 +98,7 @@ func testEmptyDB(t *testing.T, build builder) { require.NoError(t, err) require.False(t, blk.IsPresent()) - byHash, _, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) + byHash, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) require.NoError(t, err) require.False(t, byHash.IsPresent()) @@ -135,7 +135,7 @@ func testReadRoundTrip(t *testing.T, build builder) { require.NoError(t, err) require.False(t, missNum.IsPresent()) - missHash, _, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) + missHash, err := db.ReadBlockByHash(types.GenBlockHeaderHash(utils.TestRngFromSeed(1))) require.NoError(t, err) require.False(t, missHash.IsPresent()) } @@ -630,12 +630,12 @@ func testWriteBlockGaps(t *testing.T, build builder) { require.True(t, ok, "block %d should exist", n) require.Equal(t, blocks[n].Header().Hash(), got.Header().Hash()) - byHash, hashNum, err := db.ReadBlockByHash(blocks[n].Header().Hash()) + byHash, err := db.ReadBlockByHash(blocks[n].Header().Hash()) require.NoError(t, err) - got, ok = byHash.Get() + bwn, ok := byHash.Get() require.True(t, ok, "block %d should be found by hash", n) - require.Equal(t, blocks[n].Header().Hash(), got.Header().Hash()) - require.Equal(t, n, hashNum, "block %d hash lookup should return its number", n) + require.Equal(t, blocks[n].Header().Hash(), bwn.Block.Header().Hash()) + require.Equal(t, n, bwn.Number, "block %d hash lookup should return its number", n) } // Numbers in the gaps were never written and must miss. @@ -832,12 +832,12 @@ func assertBlocksReadable(t *testing.T, db types.BlockDB, batches []batch) { require.True(t, ok, "block %d should exist", n) require.Equal(t, blk.Header().Hash(), got.Header().Hash()) - byHash, hashNum, err := db.ReadBlockByHash(blk.Header().Hash()) + byHash, err := db.ReadBlockByHash(blk.Header().Hash()) require.NoError(t, err) - got, ok = byHash.Get() + bwn, ok := byHash.Get() require.True(t, ok, "block by hash should exist") - require.Equal(t, blk.Header().Hash(), got.Header().Hash()) - require.Equal(t, n, hashNum, "block %d hash lookup should return its number", n) + require.Equal(t, blk.Header().Hash(), bwn.Block.Header().Hash()) + require.Equal(t, n, bwn.Number, "block %d hash lookup should return its number", n) } } } diff --git a/sei-db/ledger_db/block/littblock/codec_test.go b/sei-db/ledger_db/block/littblock/codec_test.go index e9143f058d..3c5aff9eca 100644 --- a/sei-db/ledger_db/block/littblock/codec_test.go +++ b/sei-db/ledger_db/block/littblock/codec_test.go @@ -67,7 +67,7 @@ func TestPrefixedKeys(t *testing.T) { func TestBlockRoundTrip(t *testing.T) { rng := utils.TestRngFromSeed(1) for i := range 16 { - n := types.GlobalBlockNumber(i * 7) + n := types.GlobalBlockNumber(i) blk := types.GenBlock(rng) value := encodeBlock(n, blk) require.Equal(t, blockSerializationVersion, value[0], "value must be version-prefixed") diff --git a/sei-db/ledger_db/block/littblock/litt_block_db.go b/sei-db/ledger_db/block/littblock/litt_block_db.go index f67fe2cfba..9396a981b9 100644 --- a/sei-db/ledger_db/block/littblock/litt_block_db.go +++ b/sei-db/ledger_db/block/littblock/litt_block_db.go @@ -252,32 +252,34 @@ func (s *blockDB) QCs(reverse bool) (types.QCIterator, error) { return &qcIterator{it: it}, nil } -func (s *blockDB) ReadBlockByNumber( - n types.GlobalBlockNumber, -) (utils.Option[*types.Block], error) { - blk, _, err := getBlock(s.table, blockKey(n)) - return blk, err +func (s *blockDB) ReadBlockByNumber(n types.GlobalBlockNumber) (utils.Option[*types.Block], error) { + result, err := getBlock(s.table, blockKey(n)) + if err != nil { + return utils.None[*types.Block](), err + } + if bwn, ok := result.Get(); ok { + return utils.Some(bwn.Block), nil + } + return utils.None[*types.Block](), nil } -func (s *blockDB) ReadBlockByHash( - hash types.BlockHeaderHash, -) (utils.Option[*types.Block], types.GlobalBlockNumber, error) { +func (s *blockDB) ReadBlockByHash(hash types.BlockHeaderHash) (utils.Option[types.BlockWithNumber], error) { return getBlock(s.table, blockHashKey(hash)) } -func getBlock(table littdb.Table, key []byte) (utils.Option[*types.Block], types.GlobalBlockNumber, error) { +func getBlock(table littdb.Table, key []byte) (utils.Option[types.BlockWithNumber], error) { value, exists, err := table.Get(key) if err != nil { - return utils.None[*types.Block](), 0, fmt.Errorf("failed to read block: %w", err) + return utils.None[types.BlockWithNumber](), fmt.Errorf("failed to read block: %w", err) } if !exists { - return utils.None[*types.Block](), 0, nil + return utils.None[types.BlockWithNumber](), nil } n, blk, err := decodeBlock(value) if err != nil { - return utils.None[*types.Block](), 0, fmt.Errorf("failed to unmarshal block: %w", err) + return utils.None[types.BlockWithNumber](), fmt.Errorf("failed to unmarshal block: %w", err) } - return utils.Some(blk), n, nil + return utils.Some(types.BlockWithNumber{Block: blk, Number: n}), nil } func (s *blockDB) ReadQCByBlockNumber( diff --git a/sei-db/ledger_db/block/memblock/mem_block_db.go b/sei-db/ledger_db/block/memblock/mem_block_db.go index 0e129c2508..d1481c2f89 100644 --- a/sei-db/ledger_db/block/memblock/mem_block_db.go +++ b/sei-db/ledger_db/block/memblock/mem_block_db.go @@ -201,13 +201,13 @@ func (s *blockDB) ReadBlockByNumber( func (s *blockDB) ReadBlockByHash( hash types.BlockHeaderHash, -) (utils.Option[*types.Block], types.GlobalBlockNumber, error) { +) (utils.Option[types.BlockWithNumber], error) { s.mu.RLock() defer s.mu.RUnlock() if e, ok := s.byHash[hash]; ok { - return utils.Some(e.blk), e.n, nil + return utils.Some(types.BlockWithNumber{Block: e.blk, Number: e.n}), nil } - return utils.None[*types.Block](), 0, nil + return utils.None[types.BlockWithNumber](), nil } func (s *blockDB) ReadQCByBlockNumber( diff --git a/sei-tendermint/autobahn/types/block.go b/sei-tendermint/autobahn/types/block.go index 7b3e220e30..b4fcbd23f0 100644 --- a/sei-tendermint/autobahn/types/block.go +++ b/sei-tendermint/autobahn/types/block.go @@ -28,6 +28,14 @@ type BlockNumber uint64 // GlobalBlockNumber is the number of a block in the global chain. type GlobalBlockNumber uint64 +// BlockWithNumber pairs a block with its GlobalBlockNumber. It is used as the +// payload of the utils.Option returned by ReadBlockByHash so that the block +// number is only present when the block itself is present. +type BlockWithNumber struct { + Block *Block + Number GlobalBlockNumber +} + // BlockHeaderHash is the hash of a BlockHeader. type BlockHeaderHash hashable.Hash[*pb.BlockHeader] diff --git a/sei-tendermint/autobahn/types/block_db.go b/sei-tendermint/autobahn/types/block_db.go index 81e4aebd5f..5210c3f8c5 100644 --- a/sei-tendermint/autobahn/types/block_db.go +++ b/sei-tendermint/autobahn/types/block_db.go @@ -202,17 +202,15 @@ type BlockDB interface { ReadBlockByNumber(n GlobalBlockNumber) (utils.Option[*Block], error) // ReadBlockByHash returns the block whose header hashes to the - // given value. The hash is the same value as block.Header().Hash() - // for the block that was passed to WriteBlock. - // - // This method returns the block number for the block if it is found. - // If not found, the block number returned is undefined. + // given value, paired with its GlobalBlockNumber. The hash is the + // same value as block.Header().Hash() for the block that was passed + // to WriteBlock. // // Returns utils.None if no such block has been written. A pruned // block MAY also return None, but — as with ReadBlockByNumber — // reclamation is asynchronous, so a pruned block may remain readable // until it is actually reclaimed. Non-blocking. - ReadBlockByHash(hash BlockHeaderHash) (utils.Option[*Block], GlobalBlockNumber, error) + ReadBlockByHash(hash BlockHeaderHash) (utils.Option[BlockWithNumber], error) // ReadQCByBlockNumber returns the FullCommitQC whose // GlobalRange().First ≤ n < GlobalRange().Next — i.e. the QC that