Skip to content

fix(node): refuse a reorg over empty undo data and tell the operator at startup - #38

Open
MeABzZz wants to merge 1 commit into
BTCDecoded:mainfrom
MeABzZz:followups/undo-startup-check
Open

MeABzZz wants to merge 1 commit into
BTCDecoded:mainfrom
MeABzZz:followups/undo-startup-check

Conversation

@MeABzZz

@MeABzZz MeABzZz commented Oct 21, 2026

Copy link
Copy Markdown

Problem

Before the parallel-IBD undo fix (now on blvm-consensus main as 59357b7, the change BTCDecoded/blvm-consensus#22 proposed), parallel IBD stored an empty BlockUndoLog for every block it connected. Upgrading the binary doesn't repair data that's already on disk. On such a node, a reorg over those blocks:

  • "disconnects" them by restoring nothing
  • connects the fork
  • leaves the UTXO set wrong (old coins still there)

and logs nothing about it.

A correct undo log is never empty: the coinbase always creates at least one output, and every created output is recorded (apply.rs). So "missing or empty" pins down exactly this damage.

Fix (Malmi lens: tell the operator, no silent failure)

New node::undo_check module:

  • Startup: check_recent_undo_at_startup checks the undo logs of the last 100 blocks (coinbase-maturity depth, never genesis). It only reads undo logs, not blocks.

    • If any are missing or empty, it logs one error! with:
      • how many and which heights
      • that reorgs over them will be refused
      • what to do: stop the node, move the data directory aside, resync from scratch (blvm has no -reindex yet)
    • If all are fine, it logs an info! line.
  • Reorg: before reorganize_chain_with_witnesses, try_activate_heavier_fork checks every block it would disconnect (everything above the common ancestor). If any has missing or empty undo data, it refuses with an error that names:

    • the reorg depth
    • the height and display-order hash of the bad block
    • the same recovery advice

    This happens before the UTXO set is touched, so the node stays on its chain with its UTXO set intact.

Wired in start_components and reorg_executor.rs. Everything is production-gated, like the undo storage it reads.

Tests

tests/fork_reorg_regtest.rs (end to end through SyncCoordinator::process_block):

  • The existing fork test was split into build_fork() / import_fork(); its behaviour is unchanged.
  • New heavier_fork_over_empty_undo_logs_is_refused: mine main chain 1..=12, overwrite undo for heights 5..=12 with an empty log (the old-IBD state), then import a heavier fork that diverges at 5. The tip must stay on main and the stored UTXO set must be unchanged.
    • main: FAILS. The node reorgs onto the fork over empty undo (left: [31,109,…] fork tip vs right: [67,183,…] main tip).
    • branch: passes.

tests/undo_startup_check.rs (6 tests, all pass on the branch). This file doesn't compile on main, because the check doesn't exist there.

  • healthy node: no heights reported
  • all-empty node: the last 100 heights reported (120..=21)
  • mixed node: only the bad heights are reported, missing and empty both count, genesis is never checked
  • the operator message contains what happened, the consequence, and the resync instructions
  • disconnect checks pass on good undo and refuse on empty undo (Refusing reorg of depth 3 … height 28 … resync) and on missing undo

Also green: fork_reorg_equal_work, fork_reorg_events, cargo test --lib reorg, cargo clippy --lib --test undo_startup_check, cargo fmt.

Reviewer notes

  • A refused reorg leaves the node on a lighter chain until the operator resyncs. That's deliberate: being loudly stuck beats silently corrupting the UTXO set. It logs the error each time it retries.
  • verifychain level 4 already reports missing undo, but it doesn't flag empty logs. I left it alone.

Merge order

…at startup

Parallel IBD before the undo fix stored empty BlockUndoLogs. A reorg
over those blocks restored nothing and left the UTXO set wrong, with no
message. Check the last 100 blocks at startup and log what happened and
how to recover; refuse a reorg whose disconnected blocks have missing
or empty undo data.
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.

1 participant