Fix BAL validation: make BalancePath cross-check unconditional#19656
Merged
Fix BAL validation: make BalancePath cross-check unconditional#19656
Conversation
…ministic BAL without stored body The BalancePath cross-check in VersionMap.validateRead was gated behind vm.HasBAL, meaning it only ran when a stored BAL body was available for pre-population. Without it (e.g. p2p blocks before eth/71), stale "account doesn't exist" reads could pass validation when a concurrent worker had already created the account via a BalancePath flush. This caused non-deterministic BAL computation — phantom accounts appeared in the computed BAL that weren't in the reference, producing hash mismatches at block validation time. Fix: remove the vm.HasBAL guard so the cross-check runs unconditionally. Worker flushes create BalancePath entries just like BAL pre-population, so the check is equally valid in both cases. Also remove the dbBALBytes != nil guard from ProcessBAL so the computed BAL hash is always validated against the header, even for p2p blocks. Verified: 40K+ p2p blocks on bal-devnet-2 with zero mismatches (previously failed at block 8997). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yperbasis
approved these changes
Mar 5, 2026
mh0lt
added a commit
that referenced
this pull request
Mar 6, 2026
## Summary Follow-up to #19628. Two fixes for deterministic BAL computation without a stored BAL body (p2p blocks before eth/71): - **Remove `vm.HasBAL` guard from BalancePath cross-check** (`versionmap.go`): The cross-check was only running when a stored BAL body was available for pre-population. Without it, stale "account doesn't exist" reads passed validation when a concurrent worker had already created the account via a BalancePath flush. This caused phantom accounts in the computed BAL, producing hash mismatches. - **Always validate computed BAL against header** (`bal_create.go`): Remove the `dbBALBytes != nil` guard so the computed BAL hash is validated even for p2p blocks. With the cross-check fix above, parallel execution is deterministic regardless of whether BAL pre-population was used. ## Root Cause When TX N creates an account and TX M (M > N) reads the account as non-existent, the stale read must be invalidated. The BalancePath cross-check catches this: if BalancePath has an entry at a lower txIndex (from BAL pre-population **or worker flush**), the AddressPath read is stale. The bug was that the cross-check was gated behind `vm.HasBAL`, so it only worked with BAL pre-population — not with worker flushes (the `HasBAL=false` case). ## Test plan - [x] New unit test: `TestValidateRead_NoHasBAL_AddressPathCrossCheckWithBalancePath` - [x] All existing `TestValidateRead_*` tests pass - [x] `make lint` clean - [x] Verified on bal-devnet-2: 40K+ p2p blocks with zero BAL mismatches (previously failed at block 8997) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Mark Holt <erigon@dev-bm-e3-ethmainnet-n4.erigon.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #19628. Two fixes for deterministic BAL computation without a stored BAL body (p2p blocks before eth/71):
Remove
vm.HasBALguard from BalancePath cross-check (versionmap.go): The cross-check was only running when a stored BAL body was available for pre-population. Without it, stale "account doesn't exist" reads passed validation when a concurrent worker had already created the account via a BalancePath flush. This caused phantom accounts in the computed BAL, producing hash mismatches.Always validate computed BAL against header (
bal_create.go): Remove thedbBALBytes != nilguard so the computed BAL hash is validated even for p2p blocks. With the cross-check fix above, parallel execution is deterministic regardless of whether BAL pre-population was used.Root Cause
When TX N creates an account and TX M (M > N) reads the account as non-existent, the stale read must be invalidated. The BalancePath cross-check catches this: if BalancePath has an entry at a lower txIndex (from BAL pre-population or worker flush), the AddressPath read is stale.
The bug was that the cross-check was gated behind
vm.HasBAL, so it only worked with BAL pre-population — not with worker flushes (theHasBAL=falsecase).Test plan
TestValidateRead_NoHasBAL_AddressPathCrossCheckWithBalancePathTestValidateRead_*tests passmake lintclean🤖 Generated with Claude Code