dynafed: require at least four-fifths approval for parameter transition
What changed, and why it matters
This commit fixes a voting threshold bug in Elements' dynamic federation (dynafed) feature. The code was supposed to require at least 80% (four-fifths) of recent blocks to approve a change to federation parameters, but due to integer division rounding down, it could approve changes with slightly less than 80% support when the voting epoch length was not evenly divisible by 5. The fix changes the math so the threshold is always rounded up to the next whole number, ensuring the true 80% requirement is met. Currently deployed systems use epoch lengths divisible by 5, so this is a no-op for them, but it corrects the behavior for other configurations.
Review whether any deployed or planned configurations use dynamic_epoch_length values not divisible by 5; if so, prioritize deployment of this fix. Add regression tests covering epoch lengths such as 6, 7, 9, 11, etc., to verify the threshold is always ceil(4N/5). Consider adding an explicit consensus-level invariant or assertion that the threshold equals the intended four-fifths ceiling.
Security signals we found
Consensus threshold under-approximation due to integer floor division
Potential for dynafed parameter transition with less than intended 80% block approval
Overflow-safe ceiling formula used as remediation
Comment explicitly describes the security-relevant threshold correction
Evidence from the diff
In src/dynafed.cpp, NextBlockIsParameterTransition previously computed the dynafed parameter transition threshold as (dynamic_epoch_length * 4) / 5 using integer arithmetic, which floor-divides. For epoch lengths not divisible by 5, this under-approximates the intended ≥80% threshold, allowing a transition to pass with fewer votes than required. The patch replaces this with the overflow-safe ceiling formula N - N/5, which equals ceil(N*4/5). The threshold is now computed once before the loop and used in the short-circuit check. For currently deployed epoch lengths divisible by 5, the threshold value is unchanged.
Changed components
src/dynafed.cppNextBlockIsParameterTransition functionDynamic federation (dynafed) parameter transition voting logicInspect captured patch +5 / −2
### src/dynafed.cpp
@@ -14,6 +14,10 @@ bool NextBlockIsParameterTransition(const CBlockIndex* pindexPrev, const Consens
}
std::map<uint256, uint32_t> vote_tally;
assert(next_height >= consensus.dynamic_epoch_length);
+ // Require at least four-fifths of the epoch's votes. (epoch_length*4)/5
+ // floor-divides, under-approximating the 80% threshold for epoch lengths
+ // not divisible by 5; N - N/5 is the overflow-safe ceiling of N*4/5.
+ const uint32_t threshold = consensus.dynamic_epoch_length - consensus.dynamic_epoch_length / 5;
for (int32_t height = next_height - 1; height >= (int32_t)(next_height - consensus.dynamic_epoch_length); --height) {
const CBlockIndex* p_epoch_walk = pindexPrev->GetAncestor(height);
assert(p_epoch_walk);
@@ -25,8 +29,7 @@ bool NextBlockIsParameterTransition(const CBlockIndex* pindexPrev, const Consens
const uint256 proposal_root = proposal.CalculateRoot();
vote_tally[proposal_root]++;
// Short-circuit once 4/5 threshold is reached
- if (!proposal_root.IsNull() && vote_tally[proposal_root] >=
- (consensus.dynamic_epoch_length*4)/5) {
+ if (!proposal_root.IsNull() && vote_tally[proposal_root] >= threshold) {
winning_entry = proposal;
return true;
}Why this scored 60/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.