Skip to content

fix(evm): propagate state provider errors in the block assembler - #470

Closed
HusseinAdeiza wants to merge 1 commit into
circlefin:mainfrom
HusseinAdeiza:fix/evm-block-assembler-propagate-provider-error
Closed

HusseinAdeiza wants to merge 1 commit into
circlefin:mainfrom
HusseinAdeiza:fix/evm-block-assembler-propagate-provider-error

Conversation

@HusseinAdeiza

Copy link
Copy Markdown

Summary

ArcBlockAssembler::assemble_block swallowed state provider errors when reading the nextBaseFee slot, turning a transient read failure into a block that every validator rejects.

Details

In crates/evm/src/assembler.rs, the slot read was:

value = input
    .state_provider
    .storage(SYSTEM_ACCOUNTING_ADDRESS, slot)
    .unwrap_or(None)

unwrap_or(None) collapses a ProviderResult error into "slot not set". The else branch then logs Gas value not found for block number: … and leaves input.execution_ctx.extra_data empty.

The consequence is worse than a missing log line. validate_extra_data_base_fee in crates/evm/src/executor.rs:267 compares the decoded extra_data against the computed nextBaseFee and returns BlockExecutionError::Validation on any mismatch, including when extra_data is empty. So a database or trie read failure on a proposer does not fail the build locally, it produces a block that is rejected by the entire network, and the only local trace is a warning that describes an I/O failure as a missing value.

The fix propagates the error:

value = input
    .state_provider
    .storage(SYSTEM_ACCOUNTING_ADDRESS, slot)
    .map_err(BlockExecutionError::other)?

assemble_block already returns Result<Self::Block, BlockExecutionError>, so this needs no signature change and no new error type. It matches the existing use of BlockExecutionError::other at crates/evm/src/executor.rs:188.

The genuine unset-slot case is unchanged: it still logs the warning and assembles the block. The two conditions are now distinguishable, which is the point of the change.

Testing

Two tests added to the existing mod tests in crates/evm/src/assembler.rs, both driving a real BlockAssemblerInput built with BlockAssemblerInput::new against ArcEvmConfig:

  • assemble_block_fails_when_state_provider_errors makes the provider return Err and asserts assemble_block returns Err, and that the error is the ProviderError itself, not a generic wrapper. This is the regression test.
  • assemble_block_warns_and_continues_when_slot_unset makes the provider return Ok(None) and asserts the build still succeeds with empty extra_data, confirming the pre-existing warn-and-continue behaviour is intact.

The stub provider implements only storage with real behaviour; every other method is unimplemented!(), so if assemble_block ever reaches the provider through another path the test panics rather than silently passing.

Sabotage check, to confirm the regression test is not vacuous. With .unwrap_or(None) restored and nothing else changed, the error test fails:

running 3 tests
test assembler::tests::assemble_block_fails_when_state_provider_errors ... FAILED
test assembler::tests::block_assembler_creation ... ok
test assembler::tests::assemble_block_warns_and_continues_when_slot_unset ... ok

---- assembler::tests::assemble_block_fails_when_state_provider_errors stdout ----
thread '...' panicked at crates/evm/src/assembler.rs:347:14:
a provider read error must not yield a block: Block { header: Header {
  ..., number: 7, extra_data: 0x, ... }, body: BlockBody { transactions: [],
  ommers: [], withdrawals: Some(Withdrawals([])) } }

test result: FAILED. 2 passed; 1 failed

The failure output is the bug itself: on unfixed code the provider error produces Ok with extra_data: 0x, the invalid block described above. With the fix restored, all three tests pass.

Full crate suite and lint:

cargo test -p arc-evm --lib    ->  151 passed; 0 failed
cargo clippy -p arc-evm --lib --all-targets  ->  exit 0, no warnings
cargo fmt -p arc-evm  ->  clean

No changelog entry: recent fix commits on main do not touch CHANGELOG.md, which appears to be release-generated.


Closes: #438

`ArcBlockAssembler::assemble_block` read the nextBaseFee slot with
`unwrap_or(None)`, which collapses a `ProviderResult` error into "slot
not set". The `else` branch then logged "Gas value not found" and left
`extra_data` empty, so a transient DB or trie read failure on a proposer
produced a block rather than a failed build. `validate_extra_data_base_fee`
rejects any block whose `extra_data` does not decode to the computed
nextBaseFee, so that block is rejected by every validator, and the only
local trace is a warning that misdescribes an I/O failure as a missing
value.

Propagate the provider error instead. `assemble_block` already returns
`Result<Self::Block, BlockExecutionError>`, so this needs no signature
change, and it matches the existing use of `BlockExecutionError::other` in
crates/evm/src/executor.rs. The genuine unset-slot case keeps its warning
and continues; the two are now distinguishable.

Add two tests: one asserting a provider error fails the build rather than
yielding a block with empty `extra_data`, one asserting the unset-slot
case still assembles.

Closes: circlefin#438
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Unsigned Commits Detected

The following commits are missing a verified signature:

  • 82debf6 by Hussein Adeiza

How to fix: Sign your commits.

@github-actions

Copy link
Copy Markdown
Contributor

Hi @HusseinAdeiza,

Thank you for your interest in contributing to Arc Node.

This PR has been automatically closed because you are not assigned to issue #438. We require contributors to be explicitly assigned to an issue before submitting a PR.

To contribute properly:

  1. Comment on issue bug(evm): block assembler swallows state provider errors when reading the nextBaseFee slot #438 requesting assignment
  2. Wait for maintainer approval
  3. Only submit a PR after you have been assigned

Please see our CONTRIBUTING.md for more details.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(evm): block assembler swallows state provider errors when reading the nextBaseFee slot

1 participant