Skip to content

Validator chain invariants#1774

Open
sergerad wants to merge 4 commits intonextfrom
sergerad-validator-chain
Open

Validator chain invariants#1774
sergerad wants to merge 4 commits intonextfrom
sergerad-validator-chain

Conversation

@sergerad
Copy link
Collaborator

@sergerad sergerad commented Mar 11, 2026

Context

Closes #1773.

In #1764 we are adding a validator subcommand to construct and sign the genesis block. In this PR, we are building on top of that and storing the genesis block as the initial chain tip in the validator DB.

The validator is updated to maintain a chain tip as it continues to sign blocks. It uses this state to validate chain continuity (block number sequence, commitment references, etc).

Changes

  • Added a block_headers table to the validator database.
  • Bootstrap now initializes the validator database with the genesis block header as the chain tip (new --data-directory argument).
  • validate_block now enforces chain continuity: sequential block numbers and matching previous block commitments.
  • validate_block allows for the current chain tip in block_headers table to be overwritten to allow for idempotence.
  • Made the db module public so the bootstrap command can call save_chain_tip and load directly.

@sergerad sergerad added the no changelog This PR does not require an entry in the `CHANGELOG.md` file label Mar 11, 2026
@sergerad sergerad requested review from Mirko-von-Leipzig, bobbinth and mmagician and removed request for bobbinth March 11, 2026 00:47
@sergerad sergerad marked this pull request as ready for review March 11, 2026 00:48
Copy link
Collaborator

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

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

Minor nits, but I think we need to allow for a single block fork.

@sergerad sergerad changed the title Validator chain continuity check Validator chain invariants Mar 12, 2026
Base automatically changed from sergerad-validator-bootstrap to next March 12, 2026 06:16
@sergerad sergerad force-pushed the sergerad-validator-chain branch from 8586d1d to e2dee35 Compare March 12, 2026 06:47
CREATE INDEX idx_validated_transactions_block_num ON validated_transactions(block_num);

CREATE TABLE block_headers (
block_num INTEGER PRIMARY KEY,
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we also store block_commitment can we avoid pulling in the entire header each check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That depends on whether we want to do any additional checks on top of the block number + commitment ones I have now.

@bobbinth may have had some ideas on that point - whether there is more to compare with a new / proposed chain tip (not signed) against the previous signed chain tip beyond just block number and commitment continuity.

Copy link
Collaborator

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

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

Thank you!

@sergerad sergerad requested a review from drahnr March 18, 2026 07:53
Comment on lines 101 to +103
&data_directory,
&accounts_directory,
&data_directory,
Copy link
Contributor

Choose a reason for hiding this comment

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

We're passing the same data_directory twice - I assume this means that we can't distinguish the two directories in bundled mode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea no need to distinguish. TBH I don't think we ever needed bundled bootstrap subcommand at all. And we are working on removing bundled command altogether in #1786.

Comment on lines 39 to +45
genesis_block_directory: PathBuf,
/// Directory to write the account secret files (.mac) to.
#[arg(long, value_name = "DIR")]
accounts_directory: PathBuf,
/// Directory in which to store the validator's database.
#[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")]
data_directory: PathBuf,
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be for a separate discussion/PR, but I find the argument names a little misleading: it's not immediately clear whether these are input directories or output directories, for example:

  • genesis_block_directory is an output location, but
  • genesis_config_file is an input location (here, path, not a directory, but the same argument applies)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wouldn't hurt to specify _output_ for the directories I guess

Comment on lines +42 to +44
/// 3. The block is either: a. The valid next block in the chain (sequential block number, matching
/// previous block commitment), or b. A replacement block at the same height as the current chain
/// tip, validated against the previous block header.
Copy link
Contributor

Choose a reason for hiding this comment

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

what are the situations where we would run into scenario b) ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The order of operations for block N+1 is roughly

  1. [node] block is built
  2. [validator] block is signed
    1. block is signed
    2. block is now in validator database as latest
    3. signed block is returned to [node]
  3. [node] block is stored (and is now considered committed)

So there are several steps between (2ii) and (3) where things can go wrong e.g. node or validator crash, or network IO issue, or node database issue, etc, where the validator would have the signed block N+1 but the node is still on N.

The validator can only consider N+1 finalized once the node requests N+2. The alternative would be a two-phase ack node submit -> validator sign -> node ack. But that's more complicated than this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog This PR does not require an entry in the `CHANGELOG.md` file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validator ensures canonical chain

3 participants