Skip to content

Commit

Permalink
BFT-463: Change LeaderSelectionMode::Sticky to allow multiple keys
Browse files Browse the repository at this point in the history
  • Loading branch information
aakoshh committed May 21, 2024
1 parent 3f5b4f6 commit d9c1c12
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 1 deletion.
4 changes: 4 additions & 0 deletions node/libs/roles/src/proto/validator.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ message LeaderSelectionMode {
RoundRobin round_robin = 1;
Sticky sticky = 2;
Weighted weighted = 3;
Rota rota = 4;
}
message RoundRobin{}
message Sticky{
optional PublicKey key = 1; // required
}
message Weighted{}
message Rota {
repeated PublicKey keys = 1; // required
}
}


Expand Down
16 changes: 16 additions & 0 deletions node/libs/roles/src/validator/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,15 @@ impl ProtoFmt for LeaderSelectionMode {
Ok(LeaderSelectionMode::Sticky(PublicKey::read(key)?))
}
proto::leader_selection_mode::Mode::Weighted(_) => Ok(LeaderSelectionMode::Weighted),
proto::leader_selection_mode::Mode::Rota(inner) => {
let _ = required(&inner.keys.first()).context("keys")?;
let pks = inner
.keys
.iter()
.map(PublicKey::read)
.collect::<Result<Vec<_>, _>>()?;
Ok(LeaderSelectionMode::Rota(pks))
}
}
}
fn build(&self) -> Self::Proto {
Expand All @@ -484,6 +493,13 @@ impl ProtoFmt for LeaderSelectionMode {
proto::leader_selection_mode::Weighted {},
)),
},
LeaderSelectionMode::Rota(pks) => proto::LeaderSelectionMode {
mode: Some(proto::leader_selection_mode::Mode::Rota(
proto::leader_selection_mode::Rota {
keys: pks.iter().map(|pk| pk.build()).collect(),
},
)),
},
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions node/libs/roles/src/validator/messages/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub enum LeaderSelectionMode {

/// Select pseudo-randomly, based on validators' weights.
Weighted,

/// Select based on a sticky assignment to a non-empty list of specific validators.
Rota(Vec<validator::PublicKey>),
}

/// Calculates the pseudo-random eligibility of a leader based on the input and total weight.
Expand Down Expand Up @@ -178,6 +181,11 @@ impl Committee {
let index = self.index(pk).unwrap();
self.get(index).unwrap().key.clone()
}
LeaderSelectionMode::Rota(pks) => {
let index = view_number.0 as usize % pks.len();
let index = self.index(&pks[index]).unwrap();
self.get(index).unwrap().key.clone()
}
}
}

Expand Down Expand Up @@ -313,6 +321,14 @@ impl Genesis {
if self.validators.index(pk).is_none() {
anyhow::bail!("leader_selection sticky mode public key is not in committee");
}
} else if let LeaderSelectionMode::Rota(pks) = &self.leader_selection {
for pk in pks {
if self.validators.index(pk).is_none() {
anyhow::bail!(
"leader_selection rota mode public key is not in committee: {pk:?}"
);
}
}
}

Ok(())
Expand Down
23 changes: 23 additions & 0 deletions node/libs/roles/src/validator/messages/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,29 @@ fn test_sticky() {
}
}

#[test]
fn test_rota() {
let ctx = ctx::test_root(&ctx::RealClock);
let rng = &mut ctx.rng();
let committee = validator_committee();
let mut want = Vec::new();
for _ in 0..3 {
want.push(
committee
.get(rng.gen_range(0..committee.len()))
.unwrap()
.key
.clone(),
);
}
let rota = LeaderSelectionMode::Rota(want.clone());
for _ in 0..100 {
let vn: ViewNumber = rng.gen();
let pk = &want[vn.0 as usize % want.len()];
assert_eq!(*pk, committee.view_leader(vn, &rota));
}
}

/// Hardcoded view numbers.
fn views() -> impl Iterator<Item = ViewNumber> {
[8394532, 2297897, 9089304, 7203483, 9982111]
Expand Down
6 changes: 5 additions & 1 deletion node/libs/roles/src/validator/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,13 @@ impl Distribution<GenesisHash> for Standard {

impl Distribution<LeaderSelectionMode> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> LeaderSelectionMode {
match rng.gen_range(0..=2) {
match rng.gen_range(0..=3) {
0 => LeaderSelectionMode::RoundRobin,
1 => LeaderSelectionMode::Sticky(rng.gen()),
3 => LeaderSelectionMode::Rota({
let n = rng.gen_range(1..=3);
rng.sample_iter(Standard).take(n).collect()
}),
_ => LeaderSelectionMode::Weighted,
}
}
Expand Down
8 changes: 8 additions & 0 deletions node/tools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ impl Distribution<AppConfig> for EncodeDist {
let i = rng.gen_range(0..genesis.validators.len());
genesis.leader_selection =
LeaderSelectionMode::Sticky(genesis.validators.get(i).unwrap().key.clone());
} else if let LeaderSelectionMode::Rota(pks) = genesis.leader_selection {
let n = pks.len();
let i = rng.gen_range(0..genesis.validators.len());
let mut pks = Vec::new();
for _ in 0..n {
pks.push(genesis.validators.get(i).unwrap().key.clone());
}
genesis.leader_selection = LeaderSelectionMode::Rota(pks);
}
AppConfig {
server_addr: self.sample(rng),
Expand Down

0 comments on commit d9c1c12

Please sign in to comment.