Skip to content

MF3-H03: fix(nitronode): skip unsupported NodeBalanceUpdated tokens instead of fatal#827

Merged
philanton merged 2 commits into
fix/audit-findings-finalx3from
fix/mf3-h03
Jun 11, 2026
Merged

MF3-H03: fix(nitronode): skip unsupported NodeBalanceUpdated tokens instead of fatal#827
philanton merged 2 commits into
fix/audit-findings-finalx3from
fix/mf3-h03

Conversation

@philanton

Copy link
Copy Markdown
Contributor

Finding (MF3-H03)

ChannelHubReactor.HandleEvent() treated any handler error as fatal: it returned before StoreContractEvent(), so Listener.processEvents() unsubscribed and main() exited via logger.Fatal().

depositToNode() accepts any ERC20 and emits NodeBalanceUpdated. handleNodeBalanceUpdated() required the token to be configured in the node asset store, so depositing an unconfigured ERC20 was a permissionless, low-cost way to stop nitronode event processing. Since the failing event was never recorded, it replayed on restart — recovery needed manual intervention or a config/code change.

Fix

  • New core.ErrTokenNotSupported sentinel, returned (wrapped) by the memory asset store's GetTokenAsset/GetTokenDecimals when a token is not configured.
  • handleNodeBalanceUpdated() checks errors.Is(err, core.ErrTokenNotSupported), logs a warning, skips the balance update, and returns nil. HandleEvent() then records the event via StoreContractEvent(), so it is not replayed, the listener stays subscribed, and the process does not die.
  • Genuine store errors still propagate — only the deterministic "not configured" case is skipped (typed error, not a catch-all).

Out of scope (per finding's optional suggestion): broader listener-level error classification so handler failures don't all collapse to logger.Fatal. Can follow up separately.

Tests

  • New subtest unsupported token is skipped and recorded: asserts HandleEvent returns nil, StoreContractEvent is called, the balance handler is NOT called, and the processed callback reports success.
  • go test ./pkg/blockchain/evm/... ./nitronode/store/memory/... and go vet pass.

🤖 Generated with Claude Code

…nstead of fatal

A NodeBalanceUpdated event emitted for a token that is not configured in the
node asset store previously caused handleNodeBalanceUpdated() to return an
error. That error propagated through HandleEvent() before StoreContractEvent()
ran, so the event was never recorded; the listener unsubscribed and main()
escalated via logger.Fatal(). Because depositToNode() accepts any ERC20, this
was a permissionless, low-cost way to stop nitronode event processing, and the
unrecorded event would be replayed on restart, requiring manual intervention.

Introduce a typed core.ErrTokenNotSupported sentinel returned by the memory
asset store when a token is not configured. handleNodeBalanceUpdated() now
detects it via errors.Is, logs and skips the balance update, and returns nil so
HandleEvent() records the event and processing continues. Genuine store errors
still propagate, so only the deterministic "not configured" case is skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15e9d5fd-1226-4e17-91fb-49d3a63a42bd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mf3-h03

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@philanton philanton changed the base branch from main to fix/audit-findings-finalx3 June 8, 2026 09:13

@nksazonov nksazonov 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.

Excellent work — the MF3-H03 fix is correct, minimal, and the new subtest locks in the right invariants (HandleEvent returns nil, processedSuccess=true, balance handler not called, StoreContractEvent is called). Verified locally: targeted test passes, go vet clean across the touched packages, CI fully green.

Comment thread pkg/blockchain/evm/channel_hub_reactor_test.go
Comment thread pkg/blockchain/evm/channel_hub_reactor.go Outdated
Comment thread pkg/blockchain/evm/channel_hub_reactor.go Outdated

@ihsraham ihsraham left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one remaining H-03 closure gap. The typed skip is the right shape for configured chains with unknown tokens, but an enabled chain with no token map still returns a generic error and can hit the old fatal listener path. I left the exact branch inline.

Comment thread nitronode/store/memory/memory_store.go
…ed tokens

A ChannelHub-enabled chain with no configured token entries (e.g. world_chain)
caused GetTokenAsset/GetTokenDecimals to return a generic "blockchain not
supported" error that the reactor did not recognize as skippable, letting an
arbitrary NodeBalanceUpdated event bubble out and stop the listener — the same
fatal path MF3-H03 set out to close.

- memory_store: wrap core.ErrTokenNotSupported in the no-token-map branch of
  both GetTokenAsset and GetTokenDecimals
- channel_hub_reactor: extract skipIfUnsupportedToken helper to de-dup the two
  skip-and-record guards; document the no-replay (dedup, not replay) semantics
- tests: cover the GetTokenDecimals skip branch in the reactor and add
  memory_store regression cases for an enabled chain with no token entries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philanton philanton requested a review from ihsraham June 10, 2026 15:33

@ihsraham ihsraham left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved for H-03. The no-token-map branch now returns the same ErrTokenNotSupported sentinel as the unknown-token branch, so the world_chain/chain 480 case skips cleanly and still reaches StoreContractEvent for dedup. The added asset/decimals and reactor tests cover the closure path; no remaining blockers from me.

@nksazonov nksazonov 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.

All three review comments addressed in 887b14e — verified locally: mirror subtest for the GetTokenDecimals skip branch, skipIfUnsupportedToken helper de-dups both guards, helper doc comment spells out the no-replay (dedup, not replay) semantics. Bonus follow-up from @ihsraham on the enabled-chain-no-tokens path (e.g. world_chain 480) also wrapped in core.ErrTokenNotSupported with memory_store regressions. go test ./pkg/blockchain/evm/... ./nitronode/store/memory/... green. LGTM.

@philanton philanton merged commit 7ae8c93 into fix/audit-findings-finalx3 Jun 11, 2026
7 checks passed
@philanton philanton deleted the fix/mf3-h03 branch June 11, 2026 10:23
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