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 15621e5
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 25 deletions.
2 changes: 1 addition & 1 deletion node/libs/roles/src/proto/validator.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ message LeaderSelectionMode {
}
message RoundRobin{}
message Sticky{
optional PublicKey key = 1; // required
repeated PublicKey keys = 1; // required
}
message Weighted{}
}
Expand Down
13 changes: 9 additions & 4 deletions node/libs/roles/src/validator/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,13 @@ impl ProtoFmt for LeaderSelectionMode {
Ok(LeaderSelectionMode::RoundRobin)
}
proto::leader_selection_mode::Mode::Sticky(inner) => {
let key = required(&inner.key).context("key")?;
Ok(LeaderSelectionMode::Sticky(PublicKey::read(key)?))
let _ = required(&inner.keys.first()).context("key")?;
let pks = inner
.keys
.iter()
.map(PublicKey::read)
.collect::<Result<Vec<_>, _>>()?;
Ok(LeaderSelectionMode::Sticky(pks))
}
proto::leader_selection_mode::Mode::Weighted(_) => Ok(LeaderSelectionMode::Weighted),
}
Expand All @@ -472,10 +477,10 @@ impl ProtoFmt for LeaderSelectionMode {
proto::leader_selection_mode::RoundRobin {},
)),
},
LeaderSelectionMode::Sticky(pk) => proto::LeaderSelectionMode {
LeaderSelectionMode::Sticky(pks) => proto::LeaderSelectionMode {
mode: Some(proto::leader_selection_mode::Mode::Sticky(
proto::leader_selection_mode::Sticky {
key: Some(pk.build()),
keys: pks.iter().map(|pk| pk.build()).collect(),
},
)),
},
Expand Down
19 changes: 12 additions & 7 deletions node/libs/roles/src/validator/messages/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ pub enum LeaderSelectionMode {
/// Select in a round-robin fashion, based on validators' index within the set.
RoundRobin,

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

/// Select pseudo-randomly, based on validators' weights.
Weighted,
Expand Down Expand Up @@ -174,8 +174,9 @@ impl Committee {
}
unreachable!()
}
LeaderSelectionMode::Sticky(pk) => {
let index = self.index(pk).unwrap();
LeaderSelectionMode::Sticky(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 @@ -309,9 +310,13 @@ impl fmt::Debug for Genesis {
impl Genesis {
/// Verifies correctness.
pub fn verify(&self) -> anyhow::Result<()> {
if let LeaderSelectionMode::Sticky(pk) = &self.leader_selection {
if self.validators.index(pk).is_none() {
anyhow::bail!("leader_selection sticky mode public key is not in committee");
if let LeaderSelectionMode::Sticky(pks) = &self.leader_selection {
for pk in pks {
if self.validators.index(pk).is_none() {
anyhow::bail!(
"leader_selection sticky mode public key is not in committee: {pk:?}"
);
}
}
}

Expand Down
21 changes: 14 additions & 7 deletions node/libs/roles/src/validator/messages/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,21 @@ fn test_sticky() {
let ctx = ctx::test_root(&ctx::RealClock);
let rng = &mut ctx.rng();
let committee = validator_committee();
let want = committee
.get(rng.gen_range(0..committee.len()))
.unwrap()
.key
.clone();
let mut want = Vec::new();
for _ in 0..3 {
want.push(
committee
.get(rng.gen_range(0..committee.len()))
.unwrap()
.key
.clone(),
);
}
let sticky = LeaderSelectionMode::Sticky(want.clone());
for _ in 0..100 {
assert_eq!(want, committee.view_leader(rng.gen(), &sticky));
let vn: ViewNumber = rng.gen();
let pk = &want[vn.0 as usize % want.len()];
assert_eq!(*pk, committee.view_leader(vn, &sticky));
}
}

Expand Down Expand Up @@ -203,7 +210,7 @@ mod version1 {
fn genesis_verify_leader_pubkey_not_in_committee() {
let mut rng = StdRng::seed_from_u64(29483920);
let mut genesis = rng.gen::<GenesisRaw>();
genesis.leader_selection = LeaderSelectionMode::Sticky(rng.gen());
genesis.leader_selection = LeaderSelectionMode::Sticky(vec![rng.gen()]);
let genesis = genesis.with_hash();
assert!(genesis.verify().is_err())
}
Expand Down
5 changes: 4 additions & 1 deletion node/libs/roles/src/validator/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,10 @@ impl Distribution<LeaderSelectionMode> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> LeaderSelectionMode {
match rng.gen_range(0..=2) {
0 => LeaderSelectionMode::RoundRobin,
1 => LeaderSelectionMode::Sticky(rng.gen()),
1 => LeaderSelectionMode::Sticky({
let n = rng.gen_range(1..=3);
rng.sample_iter(Standard).take(n).collect()
}),
_ => LeaderSelectionMode::Weighted,
}
}
Expand Down
2 changes: 1 addition & 1 deletion node/libs/roles/src/validator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ fn test_genesis_verify() {
assert!(Genesis::read(&genesis.build()).is_ok());

let mut genesis = (*genesis).clone();
genesis.leader_selection = LeaderSelectionMode::Sticky(rng.gen());
genesis.leader_selection = LeaderSelectionMode::Sticky(vec![rng.gen()]);
let genesis = genesis.with_hash();
assert!(genesis.verify().is_err());
assert!(Genesis::read(&genesis.build()).is_err())
Expand Down
12 changes: 8 additions & 4 deletions node/tools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ impl Distribution<AppConfig> for EncodeDist {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> AppConfig {
let mut genesis: validator::GenesisRaw = rng.gen();
// In order for the genesis to be valid, the sticky leader needs to be in the validator committee.
if let LeaderSelectionMode::Sticky(_) = genesis.leader_selection {
let i = rng.gen_range(0..genesis.validators.len());
genesis.leader_selection =
LeaderSelectionMode::Sticky(genesis.validators.get(i).unwrap().key.clone());
if let LeaderSelectionMode::Sticky(pks) = genesis.leader_selection {
let n = pks.len();
let i = rng.gen_range(0..genesis.committee.len());
let mut pks = Vec::new();
for _ in 0..n {
pks.push(genesis.validators.get(i).unwrap().key.clone());
}
genesis.leader_selection = LeaderSelectionMode::Sticky(pks);
}
AppConfig {
server_addr: self.sample(rng),
Expand Down

0 comments on commit 15621e5

Please sign in to comment.