Skip to content

pos mempool: stop duplicate pivot-decision transactions from crashing proposal construction - #3540

Open
peilun-conflux wants to merge 5 commits into
Conflux-Chain:masterfrom
peilun-conflux:fix-pos-duplicate-pivot-decision
Open

pos mempool: stop duplicate pivot-decision transactions from crashing proposal construction#3540
peilun-conflux wants to merge 5 commits into
Conflux-Chain:masterfrom
peilun-conflux:fix-pos-duplicate-pivot-decision

Conversation

@peilun-conflux

@peilun-conflux peilun-conflux commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Pivot-decision transactions are signed over only the PivotBlockDecision payload, not the whole transaction, so a registered validator can produce many valid, distinct-hash transactions for one pivot decision by varying the unsigned chain_id. The mempool stored each as a separate entry for that decision, and proposal construction fed them all into one multisig, where duplicates from a single sender collapse to the same signer index and the aggregation panics — crashing the proposal path on every validator that pulled the set. One registered validator could sustain this to stall PoS proposal construction.

Fix

  • Deduplicate at admission, keyed by sender: keep at most one pivot-decision transaction per (decision, sender), so later distinct-chain_id duplicates are dropped and can't produce the duplicate signer index.
  • Never panic while aggregating: a failed multisig aggregation is logged and skipped, not fatal to proposal construction.
  • Expire one entry at a time: TTL expiry drops only the expiring sender's transaction, not the whole set, so it no longer discards other validators' still-live entries.
  • Scope the quorum check to the committee: admission accepts any registered (node_map) sender, broader than the committee, so a non-committee sender used to fail the whole set with UnknownAuthor; the check now counts only committee members, matching selection and aggregation.

Mempool-only: no change to signed bytes, hashing, or on-chain validation. The dedup leaves a committed set that a node stored under a different chain_id envelope uncleaned until TTL; that cleanup is a follow-up. Bringing chain_id into the pivot-decision signature is tracked separately.

🤖 Generated with Claude Code


This change is Reviewable

A registered validator could submit many distinct mempool transactions for
the same pivot decision by varying the unsigned `chain_id`: the pivot-decision
signature covers only the `PivotBlockDecision` payload, not the rest of the
`RawTransaction`, so each variant carries a valid signature but a different tx
hash. Those entries were counted as separate voting power in `get_block`'s
quorum check and produced a repeated signer index, making
`SignedTransaction::new_multisig().unwrap()` panic ("Duplicate signature
index") and crashing proposal construction on every validator that pulled the
poisoned set.

- Enforce one pivot-decision per (decision, sender) at admission.
- `new_multisig` returns `Result`; `get_block` dedups signer indices and
  logs-and-skips on aggregation error instead of panicking.
- `gc_by_system_ttl` drops only the expiring (sender, hash) entry, not the
  whole pivot-decision set, so one vote's TTL expiry no longer discards other
  validators' live votes.
- `get_block`'s quorum check counts only current-committee voters (matching
  selection and aggregation), so a registered non-committee node's vote is
  ignored rather than failing the set with `UnknownAuthor`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread crates/cfxcore/core/src/pos/mempool/core_mempool/mempool.rs
@Pana

Pana commented Jun 3, 2026

Copy link
Copy Markdown
Member

retest this please

@kilo-code-bot

kilo-code-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Kilo Code Review could not run — your account is out of credits.

Add credits or switch to a free model to enable reviews on this change.

@peilun-conflux
peilun-conflux force-pushed the fix-pos-duplicate-pivot-decision branch 4 times, most recently from da34fa3 to 65c28b6 Compare July 21, 2026 18:02
@peilun-conflux peilun-conflux changed the title pos mempool: stop duplicate pivot-decision votes from crashing proposal construction pos mempool: stop duplicate pivot-decision transactions from crashing proposal construction Jul 21, 2026
@kilo-code-bot

kilo-code-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No New Issues Found | Recommendation: Merge (pending resolution of existing discussion)

Overview

This PR addresses a real consensus-safety bug: a Byzantine validator could craft many distinct-hash pivot-decision transactions (by varying the unsigned chain_id) and flood the mempool, causing SignedTransaction::new_multisig().unwrap() to panic with "Duplicate signature index" and crash every validator's proposal path.

The fixes are well-structured across two commits:

  1. Commit 1 — Admission-level dedup (one pivot tx per sender), new_multisig returns Result, and selective TTL GC (drop one entry, not the whole set).
  2. Commit 2 — Cleanup by committed height instead of envelope hash, PivotDecisionSet struct with height tag, and the commit notification carries committed_pivot_height.

The invariants introduced (one sender → one hash per decision, commit_pivot_height sweeps by height) are structurally enforced and are consistent throughout the call chain (state_computertasksmempooltransaction_store). The seen_signers guard was correctly removed in commit 2 after structural uniqueness was guaranteed by the map key change.

Open Discussion

There is one existing active thread on mempool.rs:205 (the Err branch of new_multisig aggregation). The author has replied that an error log is sufficient since the error path should be unreachable in correct operation. No new findings supersede this discussion.

Files Reviewed (6 files)
  • crates/cfxcore/core/src/pos/consensus/state_computer.rs — no new issues
  • crates/cfxcore/core/src/pos/mempool/core_mempool/mempool.rs — existing discussion at line 205
  • crates/cfxcore/core/src/pos/mempool/core_mempool/transaction_store.rs — no new issues
  • crates/cfxcore/core/src/pos/mempool/shared_mempool/tasks.rs — no new issues
  • crates/cfxcore/core/src/pos/mempool/shared_mempool/types.rs — no new issues
  • crates/pos/types/types/src/transaction/mod.rs — no new issues

Reviewed by claude-sonnet-4.6 · Input: 15 · Output: 8.1K · Cached: 510.1K

@peilun-conflux
peilun-conflux force-pushed the fix-pos-duplicate-pivot-decision branch from 65c28b6 to 1f330e4 Compare July 23, 2026 07:00
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.

2 participants