Skip to content

Fix crash in --validate_blocks for open state blocks - #5099

Open
clemahieu wants to merge 1 commit into
nanocurrency:developfrom
clemahieu:fix/validate-blocks-open-block-prev-balance
Open

clemahieu wants to merge 1 commit into
nanocurrency:developfrom
clemahieu:fix/validate-blocks-open-block-prev-balance

Conversation

@clemahieu

Copy link
Copy Markdown
Contributor

Summary

nano_node --validate_blocks crashes with terminate called after throwing an instance of 'std::bad_optional_access' (SIGABRT) the first time it encounters a state-type open block, on any ledger, whenever pruning is disabled (the common case — pruning is opt-in).

Root cause

In check_account()'s "Validate block details set in the sideband" branch:

auto prev_balance = node->ledger.any.block_balance (transaction, block->previous ());
if (!node->ledger.pruning || prev_balance)
{
	if (block->balance () < prev_balance.value ())
	...

prev_balance is a std::optional<nano::amount>. The guard !node->ledger.pruning || prev_balance is only a real check when pruning is enabled — when it's disabled, !node->ledger.pruning is true, so the guard passes regardless of whether prev_balance actually has a value, and the .value() calls below run unconditionally.

For any account's open block, previous is the zero hash by definition. block_balance(transaction, zero_hash) correctly returns an empty optional — there is no block at hash zero to look up. So this crashes deterministically on the first state-type open block processed, on essentially every real-world ledger (pruning is opt-in and uncommon).

Confirmed via gdb + a RelWithDebInfo build with a temporary diagnostic print at the crash site, against a real synced ledger:

account=nano_1111111111111111111111111111111111111111111111111117353trpda
previous=0000000000000000000000000000000000000000000000000000000000000000
height=0
type=6 (state)

(Not a special/reserved account — its public key is just numerically close to zero, hence the address encodes as mostly 1s.)

Fix

Default prev_balance to zero when block->previous() is the zero hash (an open block), mirroring the pattern already used a few lines above for the epoch-link signature check in the same function, which already handles this correctly via nano::amount prev_balance (0) + value_or (0). Only fall back to the pruned-block-exists check when pruning is actually enabled; report a clear error via the existing print_error_message mechanism instead of crashing for the (otherwise unreachable, and likely indicative of a real problem) case of a missing previous balance with pruning disabled.

Testing

  • Reproduced the crash reliably against a real, live-synced ledger (not synthetic data).
  • Confirmed deterministic, not a threading race (--threads=1 reproduces identically).
  • Confirmed unchanged/still-present on V27.0, V28.2 (current stable), and develop prior to this fix.
  • Applied the fix, rebuilt, and re-ran --validate_blocks against the exact same ledger data that previously crashed: completes successfully now (45,684 accounts / 74,818 pending blocks validated, exits cleanly instead of SIGABRT).

Note

That test run reported some unrelated pre-existing validation findings (Incorrect source epoch for block ...) on this still-syncing ledger. Those are a separate concern, unaffected by and unrelated to this fix — this PR is scoped only to eliminating the crash so --validate_blocks can actually run to completion and report whatever it finds, rather than aborting immediately.

check_account() unconditionally called .value() on the std::optional
returned by block_balance(transaction, block->previous()) when
processing a state block, guarded only by "!node->ledger.pruning ||
prev_balance". When pruning is disabled (the common case), that
guard is true regardless of whether prev_balance actually has a
value, so the .value() calls below ran unconditionally.

For any account open block, previous is the zero hash by definition,
so block_balance() correctly returns an empty optional -- there is no
block at hash zero. This meant --validate_blocks crashed with
std::bad_optional_access (SIGABRT) on the very first state-type open
block it encountered, on any ledger, whenever pruning was disabled.

Fix: default prev_balance to zero for an open block (previous ==
zero), mirroring the pattern already used a few lines above for the
epoch-link signature check. Only fall back to the pruned-block-exists
check when pruning is actually enabled; report a clear error instead
of crashing for the (otherwise unreachable) case of a missing
previous balance with pruning disabled.
@gr0vity-dev-bot

gr0vity-dev-bot commented Jul 2, 2026

Copy link
Copy Markdown

Test Results for Commit b916834

Pull Request 5099: Results
Overall Status:

Test Case Results

  • 5n4pr_conf_10k_bintree: PASS (Duration: 113s)
  • 5n4pr_conf_10k_change: PASS (Duration: 207s)
  • 5n4pr_conf_change_dependant: PASS (Duration: 105s)
  • 5n4pr_conf_change_independant: PASS (Duration: 106s)
  • 5n4pr_conf_send_dependant: PASS (Duration: 118s)
  • 5n4pr_conf_send_independant: PASS (Duration: 110s)
  • 5n4pr_rocks_10k_bintree: PASS (Duration: 117s)
  • 5n4pr_rocks_10k_change: PASS (Duration: 150s)

Last updated: 2026-07-02 12:32:49 UTC

Copilot AI left a comment

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.

Pull request overview

This PR fixes a deterministic crash in nano_node --validate_blocks when validating state-type open blocks with pruning disabled by avoiding std::optional::value() on an empty previous-balance lookup for the zero previous hash.

Changes:

  • Treat state open blocks (previous == 0) as having an implicit previous balance of zero during sideband detail validation.
  • Avoid unconditional .value() access on the previous-balance optional; emit an error message instead when the previous balance is unexpectedly unavailable with pruning disabled.
  • Keep the existing “check pruned previous exists” behavior gated to pruning-enabled mode.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread nano/nano_node/entry.cpp
Comment on lines +1566 to +1567
nano::amount prev_balance{ 0 };
bool prev_balance_known = true;
Comment thread nano/nano_node/entry.cpp
else
{
print_error_message (boost::str (boost::format ("Previous pruned block does not exist %1%\n") % block->previous ().to_string ()));
print_error_message (boost::str (boost::format ("Missing previous block balance for %1%\n") % hash.to_string ()));
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.

3 participants