fix(dwallet-mpc): gate v3 wire behavior for the 1.1.8 rolling upgrade (G2+G3)#1797
Merged
Conversation
… (G2+G3) Two protocol-v3 interop fixes sharing one root cause — a message/value that must not exist on the v3 wire was live at v3: G2: send_status_update_to_consensus emitted IdleStatusUpdate with no protocol gate. last_sent_idle_status starts None, so the first service iteration after every restart submits it at v3. A 1.1.8 peer can't decode that consensus-transaction kind — verify_batch rejects the whole block, and the replay path panics on the undecodable sequenced tx. Gate the emission on internal_presign_sessions_enabled() (the same flag that gates this feature's DB write/read streams). G3: schnorr_presign_second_round_delay=Some(8) lived in the BASE config, applied by signature algorithm to external Taproot/Schnorrkel/EdDSA presigns served at v3 — where 1.1.8 uses no delay. Mixed-window round-2 timing skew on the same session diverges outputs. Move both delays to the v4 arm (None in base); consumers use *_as_option().unwrap_or(0), and a round_delays() helper omits zero entries so v3 passes HashMap::new() to the crypto layer, byte-identical to 1.1.8. v3 protocol-config snapshots drop the delay lines; v4 keeps 8. New test_no_idle_status_update_emitted_below_activation asserts no IdleStatusUpdate is emitted with the feature off; fault-injected (gate reverted -> fails on iteration 1) then reverted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
omersadika
added a commit
that referenced
this pull request
Jul 9, 2026
… (G2+G3) (#1797) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two protocol-v3 interop fixes for the 1.1.8 → 1.2.0 rolling upgrade (findings G2 and G3 in the release review). Both share one root cause: a value/message that must not exist on the v3 wire is live at v3.
G2 — gate the
IdleStatusUpdateconsensus emissionsend_status_update_to_consensusemittedIdleStatusUpdatewhenever the idle flag changed, with no protocol gate.last_sent_idle_statusstartsNone, so the first service-loop iteration after every restart submits the transaction — at v3, unconditionally. A 1.1.8 peer cannot decode that consensus-transaction kind: itsverify_batchrejects the whole block containing one, and its replay path panics on an undecodable sequenced transaction. During the rolling restart that is a consensus-layer interop break, regardless of the feature being unused at v3.Fix: gate the emission on
internal_presign_sessions_enabled()— the same flag that already gates this feature's DB write and read streams (authority_per_epoch_store.rs,dwallet_mpc_service.rs); this one wire emission was the single missed site.G3 — move the Schnorr presign round delay out of the base config
schnorr_presign_second_round_delay: Some(8)lived in the base protocol config, visible at v3. It is applied by signature algorithm, not session type — so it hit plain Taproot / Schnorrkel / EdDSA external presigns, which are user-requestable and served at v3 today. 1.1.8 has no such parameter (zero delay). During the mixed window an upgraded validator would advance round 2 of the same presign session 8 consensus rounds later than a 1.1.8 peer → divergent message sets → byte-divergent outputs → output-quorum split (this codebase's false-malicious / stall failure class). It is also a v3 latency change even post-swap.Fix:
Nonein the base config for both the second- and third-round delays;Some(8)in the4 =>arm (the pool — the delays' actual purpose — is v4-only). Consumers read*_as_option().unwrap_or(0). The delay map is now built by around_delays()helper that omits zero-valued entries, so a v3 presign passesHashMap::new()to the crypto layer — byte-identical to 1.1.8, not an explicit(round, 0).Snapshots
Mainnet/Testnet/version _3snapshots drop both delay lines;_4snapshots keep8. This is the intended, reviewable effect of the gating — v3's protocol-config shape moves back toward 1.1.8.Testing
test_no_idle_status_update_emitted_below_activation(idle_status_voting.rs): withinternal_presign_sessions=false, runs 10 service-loop iterations across 4 validators and asserts noIdleStatusUpdateis ever submitted to consensus nor reaches any epoch store. Fault-injected per the test-testing rule: reverting the G2 gate makes it fail on the first iteration (ungated emission fires immediately becauselast_sent_idle_statusstartsNone), confirming it guards the real property; reverted.ika-protocol-configsnapshot tests updated and green; existing idle-status suite (3 tests) still green; clippy clean.Gates the 1.2.0 release together with G1 (the V1-anchor fix, separate PR).
🤖 Generated with Claude Code