Skip to content

Reporter (block_report API) replay panics on LFS-synced READONLY observers #509

Description

@spreston8

Symptom

A fresh readonly observer attached mid-shard via LFS sync emits panic clusters in tokio-rt-worker whenever the gRPC API server's transfer-enrichment path triggers block_report.trace(block):

WARN Deploy replay failed, returning empty events
  deploy_index=0  error="System runtime error: Unable to consume results of system deploy"
... (one per deploy in the block)
WARN Discarded 0 replay events during precharge error path

thread 'tokio-rt-worker' panicked at rholang/src/rust/interpreter/rho_runtime.rs:386:14:
called `Result::unwrap()` on an `Err` value:
  BugFoundError("Unused COMM event: replayData multimap has 471 elements left")

The reporter creates a fresh RhoReportingRspace over the same store, resets to the block's pre_state_hash, then for each deploy replays the precharge system deploy. consume_system_result returns None (no result on the precharge return channel), each deploy's replay aborts, the rspace's replayData multimap stays full of unconsumed COMM events, and create_checkpoint() panics on the dirty trace.

Bisect

Reproducible across PR branches and rust/staging baseline (panic counts vary slightly with timing; pattern is identical):

Binary Test observer1 panics observer1 deploy failures
rust/staging (bf6a03d2) test_observer_lfs_sync 2 12
feat/lfs-coverage (PR #507) test_observer_lfs_sync 2-4 12-24
feat/bonding-additiveset (this branch) test_observer_lfs_sync 2 12
feat/bonding-additiveset test_bonding_validators (B5) 4 18

The bug pre-exists on rust/staging, not introduced by any of the bonding/LFS PRs in flight. Was hidden until today by a transient-observer scanner gap in the integration-test framework (now fixed on system-integration refactor/integration-test-framework).

Bug class (narrowed)

  • Validators don't trigger the reporter (rejected by validator_opt.is_some() && !dev_mode → ReadOnlyRequired at block_report_api.rs:188)
  • Genesis-attached READONLY does NOT panic — has played every block from genesis, full rspace state on disk
  • LFS-synced READONLY DOES panic — rspace state was reconstructed via the LFS exporter/importer pair
  • LFS-synced JOINER (which becomes a validator after bonding) also doesn't panic — once bonded, its reporter is rejected by the validator-check

So divergence is specific to the (LFS-imported rspace state) × (READONLY = reporter still invoked) combination. The reporter's per-block precharge replay needs some piece of state that LFS sync doesn't faithfully reproduce.

What we ruled out at the unit-test level

Tests added on feat/bonding-additiveset (casper/tests/batch1/multi_parent_casper_reporting_spec.rs):

  • reporting_casper_should_behave_the_same_way_as_multi_parent_casper — single-node freshly-played setup PASSES
  • reporting_casper_works_against_lfs_imported_rspace — basic LFS-import roundtrip (play one trivial deploy, export trie, import to fresh rspace, run reporter against fresh rspace) PASSES

So the bug needs more than the simple LFS-import path to trigger. Production conditions that the unit tests don't yet capture: hundreds of blocks of state churn, multi-block heartbeat cadence, multiple deployer keys, mergeable-channel store interactions across many blocks, concurrent reporter invocations.

Workaround

enable-reporting = false in defaults.conf (PR pending). Tests / deployments that need the reporter API can re-enable per-node via --api-enable-reporting=true once the underlying bug is fixed.

The CLI flag was extended to accept a value (--api-enable-reporting=true|false) so individual nodes can opt back in or out. Bare --api-enable-reporting keeps backwards-compatibility as true.

Repro

# system-integration (any binary that reaches Running on an LFS-synced READONLY observer):
poetry run shardctl test-reset
F1R3FLY_NODE_BINARY=<path-to-rust-staging-or-newer-binary> \
  poetry run pytest \
  integration-tests/test/tests/shared/test_observer_lfs_sync.py \
  --provider=subprocess --keep-running -v -s
# Result: 1 passed, 1 error (test body passes, autouse scanner catches reporter panic on observer1)

Inspect integration-tests/.subprocess-data/<session>/observer1.log for panicked at rholang/src/rust/interpreter/rho_runtime.rs and Deploy replay failed, returning empty events.

Suggested investigation

  1. Add tracing::error! instrumentation in consume_system_result (casper/src/rust/rholang/runtime.rs::eval_system_deploy) printing the channel/pattern on Ok(None) to identify exactly which channel observer1 fails to consume from.
  2. Restart observer1 with the instrumented binary (data dir survives), capture the failing channel.
  3. Query both observer1 and a genesis-attached readonly for the same channel via gRPC to diff the state.
  4. The diff identifies the missing/mismatched state → fix at exporter (rspace++/src/rspace/state/exporters/rspace_exporter_items.rs), importer (rspace++/src/rspace/state/instances/rspace_importer_store.rs), or post-import reset path.

Where the panic surfaces:

  • Bug origin: reporter's reset-to-pre_state + per-deploy precharge replay path. consume_system_result returns None → ConsumeFailedDeploy replay failed warn → eventual create_checkpoint() panic on dirty replayData
  • casper/src/rust/reporting_casper.rs::RhoReporterCasper::trace
  • casper/src/rust/rholang/replay_runtime.rs::process_deploy_with_cost_accounting (precharge phase)
  • casper/src/rust/rholang/runtime.rs::eval_system_deployconsume_system_result returns None
  • rholang/src/rust/interpreter/rho_runtime.rs:386 (the unwrap that panics)

Co-Authored-By: Claude noreply@anthropic.com

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions