Skip to content

M3-L01: fix(nitronode): persist home channel challenge state#720

Merged
philanton merged 1 commit into
mainfrom
fix/clearnode-m3-l01
May 4, 2026
Merged

M3-L01: fix(nitronode): persist home channel challenge state#720
philanton merged 1 commit into
mainfrom
fix/clearnode-m3-l01

Conversation

@philanton
Copy link
Copy Markdown
Contributor

@philanton philanton commented May 4, 2026

Summary

  • Restore persistence of Status=Challenged, ChallengeExpiresAt, and StateVersion in HandleHomeChannelChallenged, and refresh the user's enforced balance.
  • Without persistence, CheckOpenChannel (filters status <= Open) kept passing for a disputed channel, so the node could keep accepting and signing states while the contract was in DISPUTED. This addresses the round-2 audit finding flagged after fix: audit findings round 2 #713 (M2-M01).
  • Auto-checkpoint stays disabled per M2-M01: non-checkpointable intents (CLOSE, escrow initiate/finalize, migration) cannot be resolved via ScheduleCheckpoint, so operator action is still required and a warning is emitted.
  • Stale events where event.StateVersion < channel.StateVersion are ignored with a warning — per protocol the challenged version cannot regress below the last known on-chain version.

Test plan

  • go test ./nitronode/event_handlers/...
  • go vet ./nitronode/event_handlers/...
  • New tests:
    • TestHandleHomeChannelChallenged_PersistsChallenge — asserts UpdateChannel (Status=Challenged, expiry set, version bumped) and RefreshUserEnforcedBalance, asserts no auto-checkpoint scheduled.
    • TestHandleHomeChannelChallenged_StaleVersionIgnored — asserts no persistence when challenged version < current.
  • Existing _ChannelNotFound and _TypeMismatch still pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced channel challenge event handling with state version validation to prevent processing outdated challenge events
    • Improved persistence of challenge state information and expiration tracking
    • User balance refresh now occurs automatically when channel challenges are detected

Restore persistence of Status=Challenged, ChallengeExpiresAt, and
StateVersion in HandleHomeChannelChallenged, and refresh the user's
enforced balance. Without this, CheckOpenChannel kept passing for a
disputed channel and the node could keep accepting or signing states
while the contract was in DISPUTED.

Auto-checkpoint stays disabled (M2-M01 policy): non-checkpointable
intents (CLOSE, escrow initiate/finalize, migration) cannot be
resolved via ScheduleCheckpoint, so operator action is required.

Stale events where event.StateVersion < channel.StateVersion are
ignored with a warning: per protocol the challenged version cannot
regress below the last known on-chain version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

The HandleHomeChannelChallenged event handler is updated to persist challenge state on the channel instead of logging-only behavior. The handler now validates event freshness, sets channel status and expiration, persists updates, and refreshes enforced balance. Tests are updated to assert state persistence and handle stale event cases.

Changes

Home Channel Challenge Persistence

Layer / File(s) Summary
Documentation & Guard Logic
nitronode/event_handlers/service.go (lines 118–159)
Block comment clarifies automatic response is disabled and operators must act before expiry. Guard condition added: if event.StateVersion < channel.StateVersion, log warning and skip processing (stale event).
State Persistence
nitronode/event_handlers/service.go (lines 141–159)
For valid challenged events, handler sets channel.StateVersion, updates channel.Status to ChannelStatusChallenged, computes channel.ChallengeExpiresAt from event expiry, and persists via tx.UpdateChannel.
Balance Update & Logging
nitronode/event_handlers/service.go (lines 141–168)
Handler calls tx.RefreshUserEnforcedBalance to refresh user balance; warning log uses precomputed expirationTime variable for the challengeExpiry field.
Tests & Assertions
nitronode/event_handlers/service_test.go (lines 103–184)
Replaced TestHandleHomeChannelChallenged_LogsOnly with TestHandleHomeChannelChallenged_PersistsChallenge asserting UpdateChannel sets challenged state and RefreshUserEnforcedBalance is called; added TestHandleHomeChannelChallenged_StaleVersionIgnored asserting stale events skip persistence.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ihsraham
  • nksazonov
  • dimast-x

Poem

A challenge arrives, no longer ignored—
State now persists, by handlers restored!
Expiry is set, the balance made right,
Stale echoes fade as time sets the light. ⏰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: persisting home channel challenge state in nitronode, which directly reflects the core objective and implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clearnode-m3-l01

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
nitronode/event_handlers/service_test.go (1)

103-148: ⚡ Quick win

Add error-path tests for the two new store calls in HandleHomeChannelChallenged.

There are no tests covering UpdateChannel returning an error or RefreshUserEnforcedBalance returning an error from this handler. These are new production code paths (lines 153-159 of service.go) introduced in this PR and are currently untested.

✅ Suggested additions
func TestHandleHomeChannelChallenged_UpdateChannelError(t *testing.T) {
	mockStore := new(MockStore)
	ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger())
	service := &EventHandlerService{}

	channelID := "0xHomeChannel123"
	challengeExpiry := uint64(time.Now().Add(time.Hour).Unix())
	channel := &core.Channel{
		ChannelID:    channelID,
		UserWallet:   "0x1234567890123456789012345678901234567890",
		Asset:        "usdc",
		Type:         core.ChannelTypeHome,
		Status:       core.ChannelStatusOpen,
		StateVersion: 3,
	}
	event := &core.HomeChannelChallengedEvent{
		ChannelID:       channelID,
		StateVersion:    4,
		ChallengeExpiry: challengeExpiry,
	}

	mockStore.On("GetChannelByID", channelID).Return(channel, nil)
	mockStore.On("UpdateChannel", mock.Anything).Return(errors.New("db error"))

	err := service.HandleHomeChannelChallenged(ctx, mockStore, event)
	require.Error(t, err)
	require.Contains(t, err.Error(), "db error")
	mockStore.AssertExpectations(t)
}

func TestHandleHomeChannelChallenged_RefreshBalanceError(t *testing.T) {
	mockStore := new(MockStore)
	ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger())
	service := &EventHandlerService{}

	channelID := "0xHomeChannel123"
	userWallet := "0x1234567890123456789012345678901234567890"
	challengeExpiry := uint64(time.Now().Add(time.Hour).Unix())
	channel := &core.Channel{
		ChannelID:    channelID,
		UserWallet:   userWallet,
		Asset:        "usdc",
		Type:         core.ChannelTypeHome,
		Status:       core.ChannelStatusOpen,
		StateVersion: 3,
	}
	event := &core.HomeChannelChallengedEvent{
		ChannelID:       channelID,
		StateVersion:    4,
		ChallengeExpiry: challengeExpiry,
	}

	mockStore.On("GetChannelByID", channelID).Return(channel, nil)
	mockStore.On("UpdateChannel", mock.Anything).Return(nil)
	mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(errors.New("refresh error"))

	err := service.HandleHomeChannelChallenged(ctx, mockStore, event)
	require.Error(t, err)
	require.Contains(t, err.Error(), "refresh error")
	mockStore.AssertExpectations(t)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nitronode/event_handlers/service_test.go` around lines 103 - 148, Add two
unit tests covering error paths in HandleHomeChannelChallenged: when
store.UpdateChannel returns an error and when store.RefreshUserEnforcedBalance
returns an error. Create TestHandleHomeChannelChallenged_UpdateChannelError that
stubs GetChannelByID to return the channel and UpdateChannel to return
errors.New("db error"), then assert HandleHomeChannelChallenged(ctx, mockStore,
event) returns an error containing "db error". Create
TestHandleHomeChannelChallenged_RefreshBalanceError that stubs GetChannelByID
and UpdateChannel to succeed but stubs RefreshUserEnforcedBalance to return
errors.New("refresh error"), then assert the handler returns an error containing
"refresh error"; reference the EventHandlerService.HandleHomeChannelChallenged,
UpdateChannel, and RefreshUserEnforcedBalance symbols and ensure
mockStore.AssertExpectations(t) is called in both tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@nitronode/event_handlers/service_test.go`:
- Around line 103-148: Add two unit tests covering error paths in
HandleHomeChannelChallenged: when store.UpdateChannel returns an error and when
store.RefreshUserEnforcedBalance returns an error. Create
TestHandleHomeChannelChallenged_UpdateChannelError that stubs GetChannelByID to
return the channel and UpdateChannel to return errors.New("db error"), then
assert HandleHomeChannelChallenged(ctx, mockStore, event) returns an error
containing "db error". Create
TestHandleHomeChannelChallenged_RefreshBalanceError that stubs GetChannelByID
and UpdateChannel to succeed but stubs RefreshUserEnforcedBalance to return
errors.New("refresh error"), then assert the handler returns an error containing
"refresh error"; reference the EventHandlerService.HandleHomeChannelChallenged,
UpdateChannel, and RefreshUserEnforcedBalance symbols and ensure
mockStore.AssertExpectations(t) is called in both tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd9af022-c3ce-49fc-8d34-2f72b9cdb6e8

📥 Commits

Reviewing files that changed from the base of the PR and between 67cbff6 and 72a1d7d.

📒 Files selected for processing (2)
  • nitronode/event_handlers/service.go
  • nitronode/event_handlers/service_test.go

@philanton philanton merged commit 4d94fac into main May 4, 2026
8 checks passed
@philanton philanton deleted the fix/clearnode-m3-l01 branch May 4, 2026 12:16
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.

1 participant