Skip to content

feat(validator): link validated transactions to signed blocks - #2531

Open
sergerad wants to merge 5 commits into
nextfrom
sergerad-validator-tx-block-links
Open

feat(validator): link validated transactions to signed blocks#2531
sergerad wants to merge 5 commits into
nextfrom
sergerad-validator-tx-block-links

Conversation

@sergerad

@sergerad sergerad commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

Part 1 of a 4-PR stack formalizing the validator admin API (#2455).

Private-transaction records are written at validation time, before the transaction is part of any block, so the schema had no way to answer "which block committed this transaction" — which #2455 requires for block-range filtering and pagination.

  • Migration 002 adds a block_transactions link table keyed on (block_num, block_tx_index). Foreign keys enforce that a link references a stored block header and a transaction validated by this validator, and a UNIQUE constraint keeps a transaction in at most one block. In-flight and never-committed transactions simply have no link row.
  • SignBlock now persists each transaction's in-block position in the same database transaction as the signed header (insert_signed_block). Replacement is explicit: the handler passes is_replacement, and the replaced block is deleted first — its links go with it via ON DELETE CASCADE, so transactions dropped by the replacement revert to uncommitted.
  • Adds the committed-order listing query: strict keyset pagination over (block_num, block_tx_index), where the row limit is honored exactly and a caller resumes one position past the last row returned. It is not yet served — the next PR in the stack wires it to the paginated listing endpoint.

Legacy rows stay unlinked: a backfill would need per-block transaction ids from the filesystem BlockStore, which migrations cannot reach today; with the upcoming network resets the legacy set should be small or empty.

Stack: #2531#2532#2533#2517

Changelog

[[entry]]
scope       = "validator"
impact      = "migration"
description = "Validator database migration links validated transactions to the signed block that includes them."

@sergerad sergerad changed the title sergerad validator tx block links feat(validator): link validated transactions to signed blocks Aug 31, 2026
@sergerad
sergerad force-pushed the sergerad-validator-tx-block-links branch from 47820e4 to 88c1db6 Compare August 31, 2026 02:13
@sergerad
sergerad marked this pull request as ready for review August 31, 2026 02:18
@sergerad
sergerad requested review from Mirko-von-Leipzig and kkovaacs and removed request for Mirko-von-Leipzig August 31, 2026 02:18
@sergerad
sergerad force-pushed the sergerad-validator-tx-block-links branch from 88c1db6 to cbf6c7b Compare August 31, 2026 02:50
Comment thread bin/validator/src/db/mod.rs Outdated
Comment thread bin/validator/src/db/mod.rs Outdated
Comment thread bin/validator/src/db/mod.rs Outdated
Comment thread bin/validator/src/db/mod.rs Outdated
Comment on lines +613 to +615
// A page size of one row still yields whole blocks: each page overshoots to the end of the
// block that crosses the limit. A sweep resumes by advancing `block_from` one block past
// the last block a page returned.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah.. this just feels wrong. If a caller asks for 10 items, then send <= 10 items? Or don't allow them to specify at all if you're not going to honor it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Replaced with keyset pagination over (block_num, block_tx_index).

Comment thread bin/validator/src/db/queries/list_validated_transactions/records.sql Outdated
Comment thread bin/validator/src/db/queries/list_validated_transactions/mod.rs Outdated
Comment thread bin/validator/src/db/queries/link_block_transactions/mod.rs Outdated
Comment thread bin/validator/src/db/migrations/002_transaction_block_links.sql
Comment thread bin/validator/src/db/migrations/002_transaction_block_links.sql Outdated
Records are written at validation time, before the transaction is in any
block, so the schema had no block linkage. Add nullable block_num and
block_tx_index columns to validated_transactions plus a partial index,
persist each transaction's in-block position in the same database
transaction as the signed header, and clear stale links when a header is
replaced at the same height. Add the committed-order listing query the
administration API will page with in a follow-up.
@sergerad
sergerad force-pushed the sergerad-validator-tx-block-links branch from cbf6c7b to 1fbc55c Compare September 2, 2026 01:05
Replace the nullable block_num/block_tx_index columns on
validated_transactions with a block_transactions link table. Foreign
keys enforce that links reference a stored header and a validated
transaction, and deleting a block cascades to its links, so a
replacement cannot leave stale links behind.

Make block replacement explicit: sign_block passes is_replacement and
the writer deletes the replaced block before inserting the new header
and its links, instead of REPLACE INTO plus manual link clearing.

Simplify the listing to strict keyset pagination over
(block_num, block_tx_index): the row limit is honored exactly, and the
include_records mode is dropped until an endpoint needs it.

Also make the migration comments self-contained and inline the test
parameter helpers.
@sergerad
sergerad force-pushed the sergerad-validator-tx-block-links branch from 1fbc55c to 6e1547c Compare September 2, 2026 01:19
Comment thread bin/validator/src/db/mod.rs Outdated
Comment thread bin/validator/src/db/queries/link_block_transactions/mod.rs Outdated
Pull the is_replacement branch out of the database writer: sign_block
deletes the replaced block itself and insert_signed_block only inserts,
so the writer method has one job. Assert that linking a transaction
inserts exactly one row.
Splitting the replacement deletion out of insert_signed_block left the
delete and the insert in separate database transactions. Restore
atomicity without reintroducing the boolean: replace_signed_block
deletes the replaced block and persists its successor in one database
transaction, and sign_block branches between it and insert_signed_block.
Every handler repeated map_err(|err| Status::<code>(format!(...,
err.as_report()))). Add a StatusResultExt extension trait offering
or_internal(context) and or_invalid_argument(context) over any
ErrorReport error, and convert the sign_block and
submit_proven_transaction call sites. Persistence errors now name the
arm that failed (insert vs replace), and the semaphore errors carry
their source chain.
/// Maps a `Result`'s error into a `tonic::Status` carrying `context` and the error's full source
/// chain, collapsing the `map_err(|err| Status::<code>(err.as_report_context(...)))` boilerplate
/// every handler repeats.
trait StatusResultExt<T> {

@sergerad sergerad Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@Mirko-von-Leipzig I don't think this is duplicating #[derive(GrpcError)] because the use case here is mapping at the call site, rather than at the error type. I don't think we want Validator to be redacting internal error contents either.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants