Skip to content

fix(frost/roast): evaluate attempt feasibility against permanent exclusions only#4116

Merged
mswilkison merged 2 commits into
feat/frost-schnorr-migration-scaffoldfrom
fix/roast-transient-silence-feasibility
Jun 27, 2026
Merged

fix(frost/roast): evaluate attempt feasibility against permanent exclusions only#4116
mswilkison merged 2 commits into
feat/frost-schnorr-migration-scaffoldfrom
fix/roast-transient-silence-feasibility

Conversation

@mswilkison

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #3866 (Go FROST/ROAST coordinator). Fixes a robustness bug in the ROAST NextAttempt policy where a transient event could permanently kill a signing session.

computeNextAttempt's infeasibility check used the post-parking IncludedSet — which subtracts transiently-parked and silenced members — to decide permanent session failure (ErrAttemptInfeasible). Silence-parking deliberately has no accuser-quorum gate (it's meant to be strictly transient), so a single transient mass-silence event — or one byzantine member that is the elected coordinator for one attempt and omits snapshots — could drop the IncludedSet below threshold and permanently fail the session, even though the original signer set could still complete it.

This contradicts the file's own design contract and hands a single byzantine member (elected coordinator for one attempt) the power to permanently kill a session — exactly the grind-to-ErrAttemptInfeasible outcome the accuser-quorum machinery exists to prevent, routed around via the ungated silence path:

  • step 4: "Silence parking (strictly transient)… the attempt after that automatically reinstates them, so a falsely-silenced honest peer recovers without intervention."
  • ErrAttemptInfeasible doc: returned when "the session can no longer make progress with the original signer set."

Fix

Evaluate feasibility against the permanently-available set (original \ ExcludedSet): only permanent exclusions (established reject / conflict / coordinator-equivocation) can render a session infeasible. The next attempt's IncludedSet is still feasible \ parkSet — it may fall below threshold for one attempt, but the parked members are reinstated next attempt (burning one attempt instead of failing the session). When parking would empty the IncludedSet, the parked members are reinstated now rather than producing an unconstructable empty attempt.

Pathological grinding under sustained malicious silence stays bounded by the outer tBTC signingAttemptsLimit — the inner chain no longer needs a permanent-fail to stop grinding.

Tests

The two existing tests asserted the buggy behavior (silence below threshold → permanent fail, using a degenerate n-of-n config). Retargeted to genuine infeasibility and added recovery coverage:

  • TestNextAttempt_InfeasibilityWhenPermanentExclusionsBelowThreshold — permanent exclusion below threshold still fails (correctly).
  • TestSoak_TransientSilenceBelowThresholdRecovers — full multi-coordinator harness: silence does not fail; silenced members are parked, not excluded.
  • TestNextAttempt_TransientSilenceBelowThresholdDoesNotPermanentlyFail — unit: silence parks (transient), never excludes (permanent).
  • TestNextAttempt_TransientSilenceRecoversAcrossTwoAttempts — end-to-end: silence → park → reinstate, included set returns to full.

The "original signer set preserved" invariant (|Inc|+|Exc|+|Park| = original size) holds in both branches. gofmt, go vet, and the full pkg/frost/roast suite pass; builds clean untagged and under -tags 'frost_native frost_tbtc_signer cgo frost_roast_retry'.

Found during review of #3866.

🤖 Generated with Claude Code

…usions only

NextAttempt's infeasibility check used the post-parking IncludedSet (which
subtracts transiently-parked and silenced members) to decide PERMANENT session
failure (ErrAttemptInfeasible). Silence-parking has no accuser-quorum gate, so a
single transient mass-silence event -- or one byzantine member that is the
elected coordinator for one attempt and omits snapshots -- could drop the
IncludedSet below threshold and permanently kill a signing session, even though
the original signer set could still complete it. This contradicts the file's own
step-4 contract ("silence parking is strictly transient; a falsely-silenced
honest peer recovers without intervention") and ErrAttemptInfeasible's own doc
("the session can no longer make progress with the original signer set"), and it
routes a permanent failure around the very accuser-quorum defense that exists to
stop a byzantine minority from grinding the group to ErrAttemptInfeasible.

Evaluate feasibility against the permanently-available set (original \\
ExcludedSet): only permanent exclusions (established reject/conflict/
equivocation) can render a session infeasible. The next attempt's IncludedSet is
still feasible \\ parkSet (it may fall below threshold for one attempt; the
parked members are reinstated next attempt). When parking would empty the
IncludedSet, reinstate the parked members rather than producing an
unconstructable empty attempt. Pathological grinding under sustained malicious
silence stays bounded by the outer tBTC signingAttemptsLimit.

The two existing tests asserted the buggy behavior (silence below threshold ->
permanent fail); retarget them to genuine permanent-exclusion infeasibility and
add transient-silence-recovers coverage (single-step, full harness, and a
two-attempt park-then-reinstate cycle).

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

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 3f305b52-bf96-4d7c-ab27-0a6e062df7e9

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/roast-transient-silence-feasibility

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.

…sient parking

After the transient-silence feasibility fix, NextAttempt's infeasibility test is
on the non-excluded (feasible) set, not the post-parking IncludedSet: a next
IncludedSet can fall below threshold due to transient parking without returning
ErrAttemptInfeasible (parked members reinstate next attempt). Update the
ErrAttemptInfeasible doc + the NextAttempt step-6 contract (and the now-misleading
'included set below threshold' error string) to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mswilkison mswilkison merged commit d47cde3 into feat/frost-schnorr-migration-scaffold Jun 27, 2026
17 checks passed
@mswilkison mswilkison deleted the fix/roast-transient-silence-feasibility branch June 27, 2026 22:06
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