fix(bft): stake-weighted consensus threshold and vote counting - #502
Merged
RUKAYAT-CODER merged 2 commits intoAug 17, 2026
Merged
Conversation
…de#496) update_consensus_state derived the Byzantine threshold from the active validator *count* (floor(2n/3)+1), so an attacker could cheapen a Sybil attack by registering many low-stake validators. Weight the threshold and the vote tally by stake instead: - byzantine_threshold = floor(2 * total_stake / 3) + 1 (stake units) - vote_on_proposal adds the voting validator's stake to vote_count rather than a flat +1, so consensus requires approving validators to jointly control more than 2/3 of the total staked value - vote_count / required_votes / byzantine_threshold widened to i128 - property tests updated to the stake-weighted formula and a new Sybil-resistance property; new unit test shows many low-stake validators cannot cheaply reach the threshold
The stake_threshold_resists_sybil_count property used prop_assume! to keep the adversary's stake below 2/3 of the total, but that condition rejects the large majority of generated inputs, so proptest aborted with too many rejects. Replace the assumption with an equivalent tautology that holds for every input: the adversary is either below the quorum threshold or genuinely controls more than 2/3 of the total stake. No prop_assume, no rejections.
Contributor
|
Thank you for contributing to the Project |
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.
Closes #496
Summary
bft_consensus.rs::update_consensus_statecomputed the Byzantine threshold purely from the count of active validators (floor(2n/3)+1), even though it already tracks each validator's stake. That let an attacker cheapen a Sybil attack by registering many low-stake validators instead of acquiring a proportional share of stake.This PR makes the threshold and vote counting stake-weighted:
update_consensus_statenow computesbyzantine_threshold = floor(2 * total_stake / 3) + 1(in stake units) instead of a count-based value.vote_on_proposaladds the voting validator's stake tovote_counton approval rather than a flat+1, so a proposal is approved only when the approving validators jointly control more than 2/3 of the total staked value.vote_count,required_votes(BridgeProposal) andbyzantine_threshold(ConsensusState), plus the matchingProposalCreatedEvent.required_votes/ProposalVotedEvent.vote_countevent fields, are widened fromu32toi128to hold stake magnitudes.EVENT_SCHEMA.mdis updated to match.This preserves the classic
2f+1-of-3f+1safety margin, now expressed in stake terms.Sybil resistance
Because the threshold scales with
total_stake, registering additional low-stake validators raises the threshold proportionally, so validator count no longer buys an attacker any advantage — reaching quorum still requires a genuine 2/3 stake majority.Tests
property_based_tests.rs: the BFT threshold property is rewritten for the stake-weighted formula (1 <= threshold <= total_stake), and a new property proves an adversary controlling ≤ 2/3 of the stake can never reach quorum regardless of how many Sybil validators it splits that stake across.threshold_and_votes_are_stake_weighted_and_sybil_resistantregisters one large-stake validator plus three minimum-stake validators and shows the three low-stake validators cannot reach the threshold, while the large-stake validator's vote pushes the approving stake over the line.Acceptance criteria