Skip to content

add block number to ReadBlockByHash#3703

Merged
cody-littley merged 3 commits into
mainfrom
cjl/store-block-number
Jul 9, 2026
Merged

add block number to ReadBlockByHash#3703
cody-littley merged 3 commits into
mainfrom
cjl/store-block-number

Conversation

@cody-littley

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

Add an additional return value to ReadBlockByHash() that provides the block's number.

Testing performed to validate your change

unit tests

@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
This is a breaking BlockDB API change for any caller of ReadBlockByHash, and littblock’s on-disk block value layout changes—stores written with the old bare-proto format may not decode until migrated or rewritten.

Overview
ReadBlockByHash now returns utils.Option[BlockWithNumber] instead of utils.Option[*Block], via a new BlockWithNumber type that pairs the block with its GlobalBlockNumber. ReadBlockByNumber is unchanged.

In littblock, block values are reframed as [version][8-byte global number][proto block] so hash-secondary lookups (which only carry the hash key) can still recover the number; QC values get a similar one-byte version prefix. memblock tracks number beside each hash entry to match the API.

Contract tests assert hash lookups return the correct Number; codec tests cover versioned encode/decode and rejection of unknown versions.

Reviewed by Cursor Bugbot for commit f146305. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJul 9, 2026, 1:25 PM

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.76923% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.39%. Comparing base (ccfbb21) to head (f146305).
⚠️ Report is 21 commits behind head on main.

Files with missing lines Patch % Lines
sei-db/ledger_db/block/littblock/codec.go 80.00% 3 Missing and 3 partials ⚠️
sei-db/ledger_db/block/littblock/litt_block_db.go 73.33% 3 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3703      +/-   ##
==========================================
- Coverage   59.30%   58.39%   -0.91%     
==========================================
  Files        2273     2186      -87     
  Lines      188364   178779    -9585     
==========================================
- Hits       111705   104401    -7304     
+ Misses      66596    65113    -1483     
+ Partials    10063     9265     -798     
Flag Coverage Δ
sei-chain-pr 87.63% <80.76%> (?)
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...b/ledger_db/block/littblock/litt_block_iterator.go 64.44% <100.00%> (ø)
sei-db/ledger_db/block/memblock/mem_block_db.go 98.03% <100.00%> (ø)
sei-tendermint/autobahn/types/block.go 82.91% <ø> (-0.02%) ⬇️
sei-db/ledger_db/block/littblock/litt_block_db.go 69.09% <73.33%> (-0.29%) ⬇️
sei-db/ledger_db/block/littblock/codec.go 87.50% <80.00%> (-12.50%) ⬇️

... and 89 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-tested change that adds a GlobalBlockNumber return value to ReadBlockByHash by embedding the number in a newly version-framed on-disk value; the signature change is propagated to both implementations and all (test-only) callers. No blockers, but a doc typo and a documented backward-incompatibility with pre-existing DBs are worth noting.

Findings: 0 blocking | 3 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Backward compatibility: the new version-prefixed decoders reject any pre-existing unversioned LittDB values, and recoverCursors calls decodeQC at startup (litt_block_db.go:119), so reopening a DB written by the previous code would fail to recover. The author documents this as intentional ("this store is not yet in production, so no prior format is supported") in littblock/codec.go, so this is an accepted design decision rather than a blocker — but if any dev/test stores exist they must be wiped on upgrade. (Codex flagged this as a blocker; downgraded here given the explicit documented non-production status.)
  • Cursor's second-opinion review produced no output (empty file).
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Doc typo: this sentence is missing its opening word. It should read "If not found, the block number returned is undefined." (currently "not found, the block number returned is undefined.").

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward API extension with proper versioned framing and thorough tests; the two nits are inline.

Extended reasoning...

Overview

This PR extends BlockDB.ReadBlockByHash to also return the block's GlobalBlockNumber. On the durable littblock side, block values are reframed as [version:1][number:8][proto] so a hash-alias read (which only carries the hash key) can recover the number; QC values also get a 1-byte version prefix. The in-memory memblock mirrors this via a new hashEntry struct. Interface docs, callers, and tests are updated in lock-step.

Security risks

None material. This touches only the block DB serialization layer — no auth, crypto, permissions, or user-facing input handling. The new decode paths validate length and reject unknown version bytes with explicit errors rather than partial values, and short-buffer rejection is covered by tests.

Level of scrutiny

Low. The change is mechanical and self-contained: an API extension plus a versioned framing bump for on-disk values. The littblock store is explicitly documented as pre-production ("no prior format is supported"), so the on-disk format change carries no migration risk. Tests cover round-trip with the embedded number, unknown-version rejection, garbage rejection, and hash-lookup correctness across both implementations and gapped writes.

Other factors

The two findings surfaced by the bug hunter are both pure documentation nits (a misleading version-constant comment and a missing If in a godoc sentence) with no runtime impact — worth fixing but not blocking. The QC-before-block invariant, watermark semantics, and iterator snapshot semantics are preserved. Shared test suite assertBlocksReadable now asserts hash-lookup returns the correct number for both impls, giving strong contract coverage.

Comment thread sei-db/ledger_db/block/littblock/codec.go
Comment thread sei-tendermint/autobahn/types/block_db.go Outdated
// 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need qcSerializationVersion to return a block number?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't. I just added a 1 byte serialization version to the data schema since it's good practice to encode serialization versions (and it was originally missing). This way, if we ever decide to alter the serialization schema for a QC in the future, we will be able to do so with little headache.

// 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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we didn't have to encode block number into the block because it was the key. Would this hurt the performance? I guess not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is in the primary key, but it's not in the secondary key. LittDB keeps no map from secondary keys to primary keys, and so if we want this data when we get by block hash, it needs to go into the value data.

Technically yes, this does add some overhead. But in practical terms, this overhead is negligible. We're adding 8 bytes onto a 2 megabyte block (this is the size of the blocks Greg asked me to simulate in my benchmark).

@cody-littley cody-littley requested a review from Kbhat1 July 7, 2026 15:31

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A focused, well-tested change that adds the block's GlobalBlockNumber to ReadBlockByHash; the interface and both implementations are updated consistently. The only point worth confirming is that the change alters the on-disk value format for both blocks and QCs (adding a version prefix), which would reject any pre-existing littblock data on upgrade — likely a non-issue since the store appears to be pre-production, but worth verifying.

Findings: 0 blocking | 4 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • On-disk format compatibility (raised by Codex): decodeBlock/decodeQC now require a leading version byte, and the block value additionally embeds an 8-byte number, so the previous raw-protobuf values become undecodable after upgrade. This affects both blocks AND QCs, so recoverCursors (which decodes the newest persisted QC in NewBlockDB) would fail to open any store containing legacy values. I could not find littblock.NewBlockDB wired into any production path (only tests and blocksim), so this is most likely pre-production with no persisted data to migrate — please confirm; if any node has persisted littblock data, a migration or legacy decoder is required before merge.
  • Cursor's second-opinion review file (cursor-review.md) was empty — that pass produced no output. REVIEW_GUIDELINES.md was also empty, so no repo-specific standards were applied.
  • The new GlobalBlockNumber return value of ReadBlockByHash is not yet consumed by any production caller (grep finds no non-test/non-impl callers); this is a forward-looking API addition, which is fine but worth noting.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

if len(value) < blockValuePrefixLen {
return 0, nil, fmt.Errorf("block value too short: %d bytes", len(value))
}
if value[0] != blockSerializationVersion {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] This introduces a versioned on-disk value format ([version][number][proto] for blocks, and [version][proto] for QCs at line 122). Any block/QC value persisted by a prior build was raw protobuf with no version byte, so this check will reject it on upgrade — and since recoverCursors in NewBlockDB decodes the newest persisted QC, an existing store would fail to open. This looks safe only because littblock.NewBlockDB doesn't appear to be wired into any production path yet. Please confirm there is no persisted littblock data in the field; otherwise a migration or a legacy (unversioned) decode fallback is needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no production use yet

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, well-tested API extension; prior doc nits are fixed.

Extended reasoning...

Overview

This PR adds a GlobalBlockNumber return value to BlockDB.ReadBlockByHash so callers can recover a block's number from a hash lookup. To support this on the durable littblock backend (where the by-hash secondary key does not map to the by-number primary key), the block value's on-disk format changes: it is now framed as [version:1][number:8 big-endian][proto(Block)]. QC values pick up a matching 1-byte version prefix as a schema-evolution hook (not functionally required for this PR). All three implementations (interface, litt, mem), their tests, and the block iterator's decode call site are updated in lockstep.

Security risks

None material. The storage format change is a breaking bytes-on-disk change, but the author confirmed on the thread that this store is not yet in production, so there is no migration concern. Decode does bounds- and version-check before reading, so malformed input surfaces an error rather than a panic or partial value.

Level of scrutiny

Low. This is a self-contained API/serialization tweak in a pre-production ledger store, with unit tests covering the round-trip, version rejection, and the memblock/littblock parity. The interface signature is threaded correctly to every caller (verified via grep — only 4 files reference ReadBlockByHash, all included in the PR).

Other factors

Both nits from my prior review — the missing If in the interface godoc, and the misleading "must never share a version number" comment on the two version constants — were addressed in the latest commit. Reviewer questions from wen-coding were answered by the author (QC version byte is defensive, per-block overhead is 8 bytes on a 2 MB block). No outstanding concerns.

Comment thread sei-db/ledger_db/block/littblock/codec.go
Comment thread sei-db/ledger_db/block/littblock/codec_test.go Outdated
Comment thread sei-db/ledger_db/block/littblock/litt_block_db.go Outdated
@cody-littley cody-littley enabled auto-merge July 9, 2026 13:23

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small, well-tested change that adds the block number to ReadBlockByHash via a new BlockWithNumber return type and a version-framed durable value format. The code is correct and contained; the only open question is whether the new value framing breaks any already-persisted littblock databases.

Findings: 0 blocking | 4 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Confirm no persisted littblock data must be migrated. This PR reframes the durable LittDB value for BOTH blocks and QCs ([version:1][number:8][proto] / [version:1][proto]) with no legacy decode path — old raw-proto values would now fail with "unsupported serialization version". This looks safe because littblock is only wired into blocksim + tests today (ReadBlockByHash/NewBlockDB have no live-node consumers), so v1 is being established cleanly. But if any devnet/testnet has already persisted littblock blocks/QCs in the old unversioned format, reads will break on upgrade. Confirm this, or add an unversioned-fallback decode. (This is Codex's High finding, re-scoped to a non-blocker given the greenfield status.)
  • The block number is now stored redundantly in both the LittDB key (blockKey(n)) and the value (~8 extra bytes/block). This is documented and justified by the shared value between the primary and hash-alias keys, so it's acceptable — noting it only for the record.
  • Second-opinion passes: Codex produced the format-compatibility finding (addressed above); Cursor's review file (cursor-review.md) was empty — that pass produced no output.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

if len(value) < blockValuePrefixLen {
return 0, nil, fmt.Errorf("block value too short: %d bytes", len(value))
}
if value[0] != blockSerializationVersion {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] This rejects any value whose first byte isn't the current version, including the old unversioned raw-proto format written by the base code. Since littblock is currently only used by blocksim and tests (no live-node consumers of ReadBlockByHash/NewBlockDB), establishing v1 cleanly is reasonable. But please confirm no environment has already persisted littblock blocks/QCs in the old format — otherwise reads will fail post-upgrade and you'd need an unversioned-fallback decode path here (and in decodeQC).

@cody-littley cody-littley added this pull request to the merge queue Jul 9, 2026
Merged via the queue into main with commit e7c5eca Jul 9, 2026
69 checks passed
@cody-littley cody-littley deleted the cjl/store-block-number branch July 9, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants