Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Ensure BeforeBestBlockBy voting rule accounts for base #9920

Merged
merged 3 commits into from
Oct 3, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions client/finality-grandpa/src/voting_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,14 @@ where
}

/// A custom voting rule that guarantees that our vote is always behind the best
/// block by at least N blocks. In the best case our vote is exactly N blocks
/// behind the best block.
/// block by at least N blocks, unless the base number is < N blocks behind the
/// best, in which case it votes for the base.
///
/// In the best case our vote is exactly N blocks
/// behind the best block, but if there is a scenario where either
/// >34% of validators run without this rule or the fork-choice rule
/// can prioritize shorter chains over longer ones, the vote may be
/// closer to the best block than N.
#[derive(Clone)]
pub struct BeforeBestBlockBy<N>(N);
impl<Block, B> VotingRule<Block, B> for BeforeBestBlockBy<NumberFor<Block>>
Expand All @@ -92,7 +98,7 @@ where
fn restrict_vote(
&self,
backend: Arc<B>,
_base: &Block::Header,
base: &Block::Header,
best_target: &Block::Header,
current_target: &Block::Header,
) -> VotingRuleResult<Block> {
Expand All @@ -102,6 +108,12 @@ where
return Box::pin(async { None })
}

// Constrain to the base number, if that's the minimal
// vote that can be placed.
if *base.number() + self.0 > *best_target.number() {
return Box::pin(std::future::ready(Some((base.hash(), *base.number()))))
}

// find the target number restricted by this rule
let target_number = best_target.number().saturating_sub(self.0);

Expand Down Expand Up @@ -393,4 +405,34 @@ mod tests {
// only one of the rules is applied.
assert_eq!(number, 150);
}

#[test]
fn before_best_by_has_cutoff_at_base() {
let rule = BeforeBestBlockBy(2);

let mut client = Arc::new(TestClientBuilder::new().build());

for _ in 0..5 {
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;

futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}

let best = client.header(&BlockId::Hash(client.info().best_hash)).unwrap().unwrap();
let best_number = best.number().clone();

for i in 0u32..5 {
let base = client.header(&BlockId::Number(i.into())).unwrap().unwrap();
let (_, number) = futures::executor::block_on(rule.restrict_vote(
client.clone(),
&base,
&best,
&best,
))
.unwrap();

let expected = std::cmp::max(best_number - 2, *base.number());
assert_eq!(number, expected, "best = {}, lag = 2, base = {}", best_number, i);
}
}
}