Skip to content

Commit

Permalink
change: differentiate two types of candidates in selection process (#504
Browse files Browse the repository at this point in the history
)
  • Loading branch information
AmbientTea authored Feb 20, 2025
1 parent fa6528d commit c54ec6e
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 62 deletions.
10 changes: 9 additions & 1 deletion node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,15 @@ impl pallet_session_validator_management::Config for Runtime {
input: AuthoritySelectionInputs,
sidechain_epoch: ScEpochNumber,
) -> Option<BoundedVec<(Self::AuthorityId, Self::AuthorityKeys), Self::MaxValidators>> {
select_authorities(Sidechain::genesis_utxo(), input, sidechain_epoch)
let committee = select_authorities::<Self::AuthorityId, Self::AuthorityKeys>(
Sidechain::genesis_utxo(),
input,
sidechain_epoch,
)?
.into_iter()
.map(|member| (member.account_id().clone(), member.account_keys().clone()))
.collect();
Some(BoundedVec::truncate_from(committee))
}

fn current_epoch_number() -> ScEpochNumber {
Expand Down
2 changes: 1 addition & 1 deletion node/runtime/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl pallet_session_validator_management::Config for Test {
Sidechain::genesis_utxo(),
)
.into_iter()
.map(|c| (c.candidate.account_id, c.candidate.account_keys))
.map(|c| (c.account_id, c.account_keys))
.collect();
if candidates.is_empty() {
None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,43 @@ pub struct RegisterValidatorSignedMessage {
pub registration_utxo: UtxoId,
}

#[derive(Clone, Debug, Encode, Decode)]
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct CandidateWithStake<TAccountId, TAccountKeys> {
pub candidate: Candidate<TAccountId, TAccountKeys>,
pub stake_pool_pub_key: StakePoolPublicKey,
/// Amount of ADA staked/locked by the Authority Candidate
pub stake_delegation: StakeDelegation,
pub account_id: TAccountId,
pub account_keys: TAccountKeys,
}

#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct Candidate<TAccountId, TAccountKeys> {
pub struct PermissionedCandidate<TAccountId, TAccountKeys> {
pub account_id: TAccountId,
pub account_keys: TAccountKeys,
}

#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub enum Candidate<TAccountId, TAccountKeys> {
Permissioned(PermissionedCandidate<TAccountId, TAccountKeys>),
Registered(CandidateWithStake<TAccountId, TAccountKeys>),
}

impl<TAccountId, TAccountKeys> Candidate<TAccountId, TAccountKeys> {
pub fn account_id(&self) -> &TAccountId {
match self {
Candidate::Permissioned(c) => &c.account_id,
Candidate::Registered(c) => &c.account_id,
}
}

pub fn account_keys(&self) -> &TAccountKeys {
match self {
Candidate::Permissioned(c) => &c.account_keys,
Candidate::Registered(c) => &c.account_keys,
}
}
}

/// Get the valid trustless candidates from the registrations from inherent data
pub fn filter_trustless_candidates_registrations<TAccountId, TAccountKeys>(
candidate_registrations: Vec<CandidateRegistrations>,
Expand All @@ -53,7 +77,7 @@ where

pub fn filter_invalid_permissioned_candidates<TAccountId, TAccountKeys>(
permissioned_candidates: Vec<PermissionedCandidateData>,
) -> Vec<Candidate<TAccountId, TAccountKeys>>
) -> Vec<PermissionedCandidate<TAccountId, TAccountKeys>>
where
TAccountKeys: From<(sr25519::Public, ed25519::Public)>,
TAccountId: TryFrom<sidechain_domain::SidechainPublicKey>,
Expand All @@ -64,7 +88,7 @@ where
let (account_id, aura_key, grandpa_key) =
validate_permissioned_candidate_data(candidate).ok()?;
let account_keys = (aura_key, grandpa_key).into();
Some(Candidate { account_id, account_keys })
Some(PermissionedCandidate { account_id, account_keys })
})
.collect()
}
Expand All @@ -78,13 +102,14 @@ where
TAccountKeys: From<(sr25519::Public, ed25519::Public)>,
{
let stake_delegation = validate_stake(candidate_registrations.stake_delegation).ok()?;
let mainchain_pub_key = candidate_registrations.stake_pool_public_key;
let stake_pool_pub_key = candidate_registrations.stake_pool_public_key;

let (candidate_data, _) = candidate_registrations
let ((account_id, account_keys), _) = candidate_registrations
.registrations
.into_iter()
.filter_map(|registration_data| {
match validate_registration_data(&mainchain_pub_key, &registration_data, genesis_utxo) {
match validate_registration_data(&stake_pool_pub_key, &registration_data, genesis_utxo)
{
Ok(candidate) => Some((candidate, registration_data.utxo_info)),
Err(_) => None,
}
Expand All @@ -93,11 +118,10 @@ where
.max_by_key(|(_, utxo_info)| utxo_info.ordering_key())?;

Some(CandidateWithStake {
candidate: Candidate {
account_id: candidate_data.account_id.into(),
account_keys: candidate_data.account_keys.into(),
},
account_id: account_id.into(),
account_keys: account_keys.into(),
stake_delegation,
stake_pool_pub_key,
})
}

Expand Down Expand Up @@ -170,7 +194,7 @@ pub fn validate_registration_data(
stake_pool_pub_key: &StakePoolPublicKey,
registration_data: &RegistrationData,
genesis_utxo: UtxoId,
) -> Result<Candidate<ecdsa::Public, (sr25519::Public, ed25519::Public)>, RegistrationDataError> {
) -> Result<(ecdsa::Public, (sr25519::Public, ed25519::Public)), RegistrationDataError> {
let aura_pub_key = registration_data
.aura_pub_key
.try_into_sr25519()
Expand Down Expand Up @@ -203,7 +227,7 @@ pub fn validate_registration_data(

// TODO - Stake Validation: https://input-output.atlassian.net/browse/ETCM-4082

Ok(Candidate { account_id: sidechain_pub_key, account_keys: (aura_pub_key, grandpa_pub_key) })
Ok((sidechain_pub_key, (aura_pub_key, grandpa_pub_key)))
}

pub fn validate_stake(stake: Option<StakeDelegation>) -> Result<StakeDelegation, StakeError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
use crate::authority_selection_inputs::AuthoritySelectionInputs;
use crate::filter_invalid_candidates::{
filter_invalid_permissioned_candidates, filter_trustless_candidates_registrations, Candidate,
CandidateWithStake,
CandidateWithStake, PermissionedCandidate,
};
use frame_support::BoundedVec;
use log::{info, warn};
use plutus::*;
use selection::{Weight, WeightedRandomSelectionConfig};
use sidechain_domain::{DParameter, ScEpochNumber, UtxoId};
use sp_core::{ecdsa, ed25519, sr25519, Get};
use sp_core::{ecdsa, ed25519, sr25519};

type CandidateWithWeight<A, B> = (Candidate<A, B>, Weight);

Expand All @@ -35,12 +34,11 @@ type CandidateWithWeight<A, B> = (Candidate<A, B>, Weight);
pub fn select_authorities<
TAccountId: Clone + Ord + TryFrom<sidechain_domain::SidechainPublicKey> + From<ecdsa::Public>,
TAccountKeys: Clone + From<(sr25519::Public, ed25519::Public)>,
MaxValidators: Get<u32>,
>(
genesis_utxo: UtxoId,
input: AuthoritySelectionInputs,
sidechain_epoch: ScEpochNumber,
) -> Option<BoundedVec<(TAccountId, TAccountKeys), MaxValidators>> {
) -> Option<Vec<Candidate<TAccountId, TAccountKeys>>> {
let valid_trustless_candidates = filter_trustless_candidates_registrations::<
TAccountId,
TAccountKeys,
Expand All @@ -58,16 +56,15 @@ pub fn select_authorities<
&input.d_parameter,
&valid_trustless_candidates,
));
candidates_with_weight.sort_by(|a, b| a.0.account_id.cmp(&b.0.account_id));
candidates_with_weight.sort_by(|a, b| a.0.account_id().cmp(&b.0.account_id()));

let random_seed =
selection::impls::seed_from_nonce_and_sc_epoch(&input.epoch_nonce, &sidechain_epoch);
let committee_size =
input.d_parameter.num_registered_candidates + input.d_parameter.num_permissioned_candidates;
if let Some(validators) =
weighted_selection(candidates_with_weight, committee_size, random_seed)
if let Some(validators) = (WeightedRandomSelectionConfig { size: committee_size }
.select_authorities(candidates_with_weight, random_seed))
{
let validators = BoundedVec::truncate_from(validators);
info!("💼 Selected committee of {} seats for epoch {} from {} permissioned and {} registered candidates", validators.len(), sidechain_epoch, valid_permissioned_candidates.len(), valid_trustless_candidates.len());
Some(validators)
} else {
Expand All @@ -88,12 +85,14 @@ fn trustless_candidates_with_weights<A: Clone, B: Clone>(
};
trustless_candidates
.iter()
.map(|c| (c.candidate.clone(), u128::from(c.stake_delegation.0) * weight_factor))
.map(|c| {
(Candidate::Registered(c.clone()), u128::from(c.stake_delegation.0) * weight_factor)
})
.collect()
}

fn permissioned_candidates_with_weights<A: Clone, B: Clone>(
permissioned_candidates: &[Candidate<A, B>],
permissioned_candidates: &[PermissionedCandidate<A, B>],
d_parameter: &DParameter,
valid_trustless_candidates: &[CandidateWithStake<A, B>],
) -> Vec<CandidateWithWeight<A, B>> {
Expand All @@ -103,19 +102,8 @@ fn permissioned_candidates_with_weights<A: Clone, B: Clone>(
} else {
1 // if there are no trustless candidates, permissioned candidates should be selected with equal weight
};
permissioned_candidates.iter().map(|c| (c.clone(), weight)).collect::<Vec<_>>()
}

fn weighted_selection<TAccountId: Clone + Ord, TAccountKeys: Clone>(
candidates: Vec<CandidateWithWeight<TAccountId, TAccountKeys>>,
size: u16,
random_seed: [u8; 32],
) -> Option<Vec<(TAccountId, TAccountKeys)>> {
Some(
WeightedRandomSelectionConfig { size }
.select_authorities(candidates, random_seed)?
.into_iter()
.map(|c| (c.account_id, c.account_keys))
.collect(),
)
permissioned_candidates
.iter()
.map(|c| (Candidate::Permissioned(c.clone()), weight))
.collect::<Vec<_>>()
}
50 changes: 30 additions & 20 deletions toolkit/primitives/authority-selection-inherents/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use plutus::ToDatum;
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sidechain_domain::*;
use sp_core::{ecdsa, ed25519, sr25519, ConstU32, Pair};
use sp_core::{ecdsa, ed25519, sr25519, Pair};
use sp_runtime::traits::Zero;
use std::collections::HashMap;

Expand Down Expand Up @@ -194,16 +194,18 @@ fn ariadne_all_permissioned_test() {
&registered_validators,
d_parameter,
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<32>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
);
assert!(calculated_committee.is_some());

let committee = calculated_committee.unwrap();
let committee_names =
committee.iter().map(|(id, _)| account_id_to_name(id)).collect::<Vec<_>>();
let committee_names = committee
.iter()
.map(|member| account_id_to_name(member.account_id()))
.collect::<Vec<_>>();
let expected_committee_names = vec!["bob", "bob", "alice", "bob", "bob", "alice", "bob", "bob"];

assert_eq!(committee_names, expected_committee_names);
Expand All @@ -222,16 +224,18 @@ fn ariadne_only_permissioned_candidates_are_present_test() {
&registered_validators,
d_parameter,
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<32>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
);
assert!(calculated_committee.is_some());

let committee = calculated_committee.unwrap();
let committee_names =
committee.iter().map(|(id, _)| account_id_to_name(id)).collect::<Vec<_>>();
let committee_names = committee
.iter()
.map(|member| account_id_to_name(member.account_id()))
.collect::<Vec<_>>();
let expected_committee_names = vec!["bob", "bob", "alice", "bob", "bob", "alice", "bob", "bob"];

assert_eq!(committee_names, expected_committee_names);
Expand All @@ -250,16 +254,18 @@ fn ariadne_3_to_2_test() {
&registered_validators,
d_parameter,
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<32>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
);
assert!(calculated_committee.is_some());

let committee = calculated_committee.unwrap();
let committee_names =
committee.iter().map(|(id, _)| account_id_to_name(id)).collect::<Vec<_>>();
let committee_names = committee
.iter()
.map(|member| account_id_to_name(member.account_id()))
.collect::<Vec<_>>();
let expected_committee_names = vec!["bob", "charlie", "charlie", "alice", "bob"];

assert_eq!(committee_names, expected_committee_names);
Expand All @@ -278,16 +284,18 @@ fn ariadne_3_to_2_with_more_available_candidates_test() {
&registered_validators,
d_parameter,
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<32>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
);
assert!(calculated_committee.is_some());

let committee = calculated_committee.unwrap();
let committee_names =
committee.iter().map(|(id, _)| account_id_to_name(id)).collect::<Vec<_>>();
let committee_names = committee
.iter()
.map(|member| account_id_to_name(member.account_id()))
.collect::<Vec<_>>();
let expected_committee_names = vec!["bob", "bob", "bob", "alice", "henry"];

assert_eq!(committee_names, expected_committee_names);
Expand All @@ -306,16 +314,18 @@ fn ariadne_4_to_7_test() {
&registered_validators,
d_parameter,
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<32>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
);
assert!(calculated_committee.is_some());

let committee = calculated_committee.unwrap();
let committee_names =
committee.iter().map(|(id, _)| account_id_to_name(id)).collect::<Vec<_>>();
let committee_names = committee
.iter()
.map(|member| account_id_to_name(member.account_id()))
.collect::<Vec<_>>();
let expected_committee_names = vec![
"bob", "charlie", "henry", "ida", "kim", "bob", "alice", "greg", "ida", "ferdie", "henry",
];
Expand All @@ -337,15 +347,15 @@ fn ariadne_selection_statistics_test() {
&registered_validators,
d_parameter,
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<30000>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
);
let committee = calculated_committee.unwrap();
let mut map = HashMap::new();
for (id, _) in &committee {
*map.entry(id).or_insert(0u32) += 1;
for member in &committee {
*map.entry(member.account_id()).or_insert(0u32) += 1;
}
let alice_count = *map.get(&ALICE.account_id()).unwrap_or(&0);
let bob_count = *map.get(&BOB.account_id()).unwrap_or(&0);
Expand All @@ -366,7 +376,7 @@ fn ariadne_does_not_return_empty_committee() {
&[],
DParameter { num_permissioned_candidates: 1, num_registered_candidates: 1 },
);
let calculated_committee = select_authorities::<AccountId, AccountKeys, ConstU32<10>>(
let calculated_committee = select_authorities::<AccountId, AccountKeys>(
UtxoId::default(),
authority_selection_inputs,
ScEpochNumber::zero(),
Expand Down

0 comments on commit c54ec6e

Please sign in to comment.