From 005d001697b7e5552c8fa0c4a12e8df99f9cd94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20SDPC?= Date: Thu, 26 Dec 2024 11:54:20 +0100 Subject: [PATCH 1/5] ref(data_structures): turn `ARSIdentities` into a more generic `Census` --- data_structures/src/chain/mod.rs | 8 +- data_structures/src/superblock.rs | 117 ++++++++++++++------------- node/src/actors/chain_manager/mod.rs | 4 +- 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/data_structures/src/chain/mod.rs b/data_structures/src/chain/mod.rs index c2445de2f..69120187c 100644 --- a/data_structures/src/chain/mod.rs +++ b/data_structures/src/chain/mod.rs @@ -4888,7 +4888,7 @@ mod tests { use crate::{ proto::versioning::{ProtocolVersion, VersionedHashable}, - superblock::{mining_build_superblock, ARSIdentities}, + superblock::{mining_build_superblock, Census}, transaction::{CommitTransactionBody, RevealTransactionBody, VTTransactionBody}, }; @@ -6829,7 +6829,7 @@ mod tests { let expected_order = vec![p1_bls, p2_bls, p3_bls]; let ordered_identities = rep_engine.get_rep_ordered_ars_list(); - let ars_identities = ARSIdentities::new(ordered_identities); + let ars_identities = Census::new(ordered_identities); assert_eq!( expected_order, @@ -6871,7 +6871,7 @@ mod tests { let expected_order = vec![p1_bls, p2_bls, p3_bls]; let ordered_identities = rep_engine.get_rep_ordered_ars_list(); - let ars_identities = ARSIdentities::new(ordered_identities); + let ars_identities = Census::new(ordered_identities); assert_eq!( expected_order, @@ -6929,7 +6929,7 @@ mod tests { let expected_order = vec![p1_bls, p2_bls, p4_bls, p5_bls, p3_bls]; let ordered_identities = rep_engine.get_rep_ordered_ars_list(); - let ars_identities = ARSIdentities::new(ordered_identities); + let ars_identities = Census::new(ordered_identities); assert_eq!( expected_order, diff --git a/data_structures/src/superblock.rs b/data_structures/src/superblock.rs index b7b296f3c..db48e89d4 100644 --- a/data_structures/src/superblock.rs +++ b/data_structures/src/superblock.rs @@ -64,16 +64,21 @@ pub enum SuperBlockConsensus { Unknown, } -/// ARS identities +/// A list of identities from which we can sample committees. +/// +/// Different protocol versions populate this data differently: +/// - In V1_X, this contained a complete list of all ARS identities. +/// - In V2_X, it is expected to contain all validators instead. #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -pub struct ARSIdentities { - // HashSet of the identities in a specific ARS +pub struct Census { + // HashSet containing the identities identities: HashSet, - // Ordered vector of the identities in a specific ARS + // Ordered vector containing the very same identities. + // The order is determined by the argument of `new()`, i.e. it is up to whoever calls it. ordered_identities: Vec, } -impl ARSIdentities { +impl Census { pub fn len(&self) -> usize { self.identities.len() } @@ -83,7 +88,7 @@ impl ARSIdentities { } pub fn new(ordered_identities: Vec) -> Self { - ARSIdentities { + Census { identities: ordered_identities.iter().cloned().collect(), ordered_identities, } @@ -238,9 +243,9 @@ impl SuperBlockVotesMempool { #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct SuperBlockState { // Structure of the current Active Reputation Set identities - ars_current_identities: ARSIdentities, + ars_current_identities: Census, // Structure of the previous Active Reputation Set identities - ars_previous_identities: ARSIdentities, + ars_previous_identities: Census, /// The most recently created superblock. This one is yet to be voted and decided upon. current_superblock: Option, // Current superblock beacon including the superblock hash created by this node @@ -258,8 +263,8 @@ impl SuperBlockState { pub fn new(superblock_genesis_hash: Hash, bootstrap_committee: Vec) -> Self { Self { signing_committee: bootstrap_committee.clone().into_iter().collect(), - ars_previous_identities: ARSIdentities::new(bootstrap_committee.clone()), - ars_current_identities: ARSIdentities::new(bootstrap_committee), + ars_previous_identities: Census::new(bootstrap_committee.clone()), + ars_current_identities: Census::new(bootstrap_committee), current_superblock_beacon: CheckpointBeacon { checkpoint: 0, hash_prev_block: superblock_genesis_hash, @@ -414,7 +419,7 @@ impl SuperBlockState { } } - fn update_ars_identities(&mut self, new_identities: ARSIdentities) { + fn update_ars_identities(&mut self, new_identities: Census) { self.ars_previous_identities = std::mem::take(&mut self.ars_current_identities); self.ars_current_identities = new_identities; } @@ -428,7 +433,7 @@ impl SuperBlockState { pub fn build_superblock( &mut self, block_headers: &[BlockHeader], - ars_identities: ARSIdentities, + ars_identities: Census, signing_committee_size: u32, superblock_index: u32, last_block_in_previous_superblock: Hash, @@ -592,7 +597,7 @@ impl SuperBlockState { /// Calculates the superblock signing committee for a given superblock hash and ars #[allow(clippy::cast_possible_truncation)] pub fn calculate_superblock_signing_committee( - ars_identities: ARSIdentities, + ars_identities: Census, signing_committee_size: u32, current_superblock_index: u32, superblock_hash: Hash, @@ -1007,7 +1012,7 @@ mod tests { let sb1 = sbs.build_superblock( &block_headers, - ARSIdentities::new(ars2), + Census::new(ars2), 100, 0, Hash::default(), @@ -1056,7 +1061,7 @@ mod tests { let block_headers = vec![BlockHeader::default()]; let pkhs = vec![create_pkh(1)]; let keys = vec![create_bn256(1)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let genesis_hash = Hash::default(); let sb1 = sbs.build_superblock( @@ -1087,7 +1092,7 @@ mod tests { let genesis_hash = Hash::default(); let pkhs = vec![create_pkh(1)]; let keys = vec![create_bn256(1)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let first_superblock = sbs.build_superblock( @@ -1126,7 +1131,7 @@ mod tests { checkpoint: 0, hash_prev_block: expected_superblock_hash, }, - ars_previous_identities: ARSIdentities::default(), + ars_previous_identities: Census::default(), ..Default::default() }; assert_eq!(sbs, expected_sbs); @@ -1139,7 +1144,7 @@ mod tests { let block_headers = vec![BlockHeader::default()]; let pkhs = vec![create_pkh(1)]; let keys = vec![create_bn256(1)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let genesis_hash = Hash::default(); @@ -1208,7 +1213,7 @@ mod tests { let block_headers = vec![BlockHeader::default()]; let pkhs = vec![create_pkh(1)]; let keys = vec![create_bn256(1)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let genesis_hash = Hash::default(); @@ -1265,9 +1270,9 @@ mod tests { let p1 = PublicKey::from_bytes([1; 33]); let pkhs = vec![p1.pkh()]; let keys = vec![create_bn256(1)]; - let ars0 = ARSIdentities::new(vec![]); - let ars1 = ARSIdentities::new(pkhs.clone()); - let ars2 = ARSIdentities::new(pkhs.clone()); + let ars0 = Census::new(vec![]); + let ars1 = Census::new(pkhs.clone()); + let ars2 = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); @@ -1328,9 +1333,9 @@ mod tests { let p1 = PublicKey::from_bytes([1; 33]); let pkhs = vec![p1.pkh()]; let keys = vec![create_bn256(1)]; - let ars0 = ARSIdentities::new(vec![]); - let ars1 = ARSIdentities::new(pkhs.clone()); - let ars2 = ARSIdentities::new(pkhs.clone()); + let ars0 = Census::new(vec![]); + let ars1 = Census::new(pkhs.clone()); + let ars2 = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); // Superblock votes for index 0 cannot be validated because we do not know the ARS for index -1 @@ -1391,9 +1396,9 @@ mod tests { let p1 = PublicKey::from_bytes([1; 33]); let pkhs = vec![p1.pkh()]; let keys = vec![create_bn256(1)]; - let ars0 = ARSIdentities::new(vec![]); - let ars1 = ARSIdentities::new(pkhs.clone()); - let ars2 = ARSIdentities::new(pkhs.clone()); + let ars0 = Census::new(vec![]); + let ars1 = Census::new(pkhs.clone()); + let ars2 = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); // Superblock votes for index 0 cannot be validated because we do not know the ARS for index -1 @@ -1454,9 +1459,9 @@ mod tests { let p1 = PublicKey::from_bytes([1; 33]); let pkhs = vec![p1.pkh()]; let keys = vec![create_bn256(1)]; - let ars0 = ARSIdentities::new(vec![]); - let ars1 = ARSIdentities::new(pkhs.clone()); - let ars2 = ARSIdentities::new(pkhs.clone()); + let ars0 = Census::new(vec![]); + let ars1 = Census::new(pkhs.clone()); + let ars2 = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); // Superblock votes for index 0 cannot be validated because we do not know the ARS for index -1 @@ -1531,11 +1536,11 @@ mod tests { let p2 = PublicKey::from_bytes([2; 33]); let p3 = PublicKey::from_bytes([3; 33]); - let ars0 = ARSIdentities::new(vec![]); - let ars1 = ARSIdentities::new(vec![p1.pkh()]); - let ars2 = ARSIdentities::new(vec![p2.pkh()]); - let ars3 = ARSIdentities::new(vec![p3.pkh()]); - let ars4 = ARSIdentities::new(vec![]); + let ars0 = Census::new(vec![]); + let ars1 = Census::new(vec![p1.pkh()]); + let ars2 = Census::new(vec![p2.pkh()]); + let ars3 = Census::new(vec![p3.pkh()]); + let ars4 = Census::new(vec![]); let pkhs = vec![p1.pkh(), p2.pkh(), p3.pkh()]; let keys = vec![create_bn256(1), create_bn256(2), create_bn256(3)]; let alt_keys = create_alt_keys(pkhs, keys); @@ -1686,7 +1691,7 @@ mod tests { let pkhs = vec![p1.pkh(), p2.pkh(), p3.pkh()]; let keys = vec![create_bn256(1), create_bn256(2), create_bn256(3)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let create_votes = |superblock_hash, superblock_index| { @@ -1817,9 +1822,9 @@ mod tests { ]; let alt_keys = create_alt_keys(pkhs1.clone(), keys); - let ars0 = ARSIdentities::new(pkhs0); - let ars1 = ARSIdentities::new(pkhs1); - let ars2 = ARSIdentities::new(pkhs2); + let ars0 = Census::new(pkhs0); + let ars1 = Census::new(pkhs1); + let ars2 = Census::new(pkhs2); let create_votes = |superblock_hash, superblock_index| { let mut v1 = SuperBlockVote::new_unsigned(superblock_hash, superblock_index); @@ -1946,7 +1951,7 @@ mod tests { let pkhs = vec![p1.pkh(), p2.pkh(), p3.pkh()]; let keys = vec![create_bn256(1), create_bn256(2), create_bn256(3)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let block_headers = vec![BlockHeader::default()]; @@ -2036,7 +2041,7 @@ mod tests { let pkhs = vec![p1.pkh(), p2.pkh(), p3.pkh()]; let keys = vec![create_bn256(1), create_bn256(2), create_bn256(3)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let block_headers = vec![BlockHeader::default()]; @@ -2115,7 +2120,7 @@ mod tests { let block_headers = vec![BlockHeader::default()]; let pkhs = vec![create_pkh(1)]; let keys = vec![create_bn256(1)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let genesis_hash = Hash::default(); @@ -2144,7 +2149,7 @@ mod tests { let block_headers = vec![BlockHeader::default()]; let pkhs = vec![create_pkh(1)]; let keys = vec![create_bn256(1)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let genesis_hash = Hash::default(); @@ -2197,7 +2202,7 @@ mod tests { create_bn256(3), create_bn256(4), ]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let block_headers = vec![BlockHeader::default()]; @@ -2279,7 +2284,7 @@ mod tests { let pkhs = vec![p1.pkh(), p2.pkh(), p3.pkh()]; let keys = vec![create_bn256(1), create_bn256(2), create_bn256(3)]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); let block_headers = vec![BlockHeader::default()]; @@ -2324,7 +2329,7 @@ mod tests { create_bn256(4), create_bn256(5), ]; - let ars = ARSIdentities::new(pkhs.clone()); + let ars = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); sbs.ars_current_identities = ars.clone(); @@ -2399,7 +2404,7 @@ mod tests { p7.pkh(), p8.pkh(), ]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, vec![]); let block_headers = vec![BlockHeader::default()]; @@ -2445,7 +2450,7 @@ mod tests { let p3 = PublicKey::from_bytes([3; 33]); let pkhs = vec![p1.pkh(), p2.pkh(), p3.pkh()]; - let ars_identities = ARSIdentities::new(pkhs.clone()); + let ars_identities = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, vec![]); let block_headers = vec![BlockHeader::default()]; @@ -2626,12 +2631,12 @@ mod tests { #[test] fn test_get_beacon_2() { let superblock_state = SuperBlockState { - ars_current_identities: ARSIdentities::default(), + ars_current_identities: Census::default(), current_superblock_beacon: CheckpointBeacon { checkpoint: 0, hash_prev_block: Hash::SHA256([1; 32]), }, - ars_previous_identities: ARSIdentities::default(), + ars_previous_identities: Census::default(), ..Default::default() }; let beacon = superblock_state.get_beacon(); @@ -2648,12 +2653,12 @@ mod tests { #[test] fn test_get_beacon_3() { let superblock_state = SuperBlockState { - ars_current_identities: ARSIdentities::default(), + ars_current_identities: Census::default(), current_superblock_beacon: CheckpointBeacon { checkpoint: 1, hash_prev_block: Hash::default(), }, - ars_previous_identities: ARSIdentities::default(), + ars_previous_identities: Census::default(), ..Default::default() }; let beacon = superblock_state.get_beacon(); @@ -2704,9 +2709,9 @@ mod tests { create_bn256(4), create_bn256(5), ]; - let ars0 = ARSIdentities::new(vec![]); - let ars1 = ARSIdentities::new(pkhs.clone()); - let ars2 = ARSIdentities::new(pkhs.clone()); + let ars0 = Census::new(vec![]); + let ars1 = Census::new(pkhs.clone()); + let ars2 = Census::new(pkhs.clone()); let alt_keys = create_alt_keys(pkhs, keys); diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 9bb2356a4..1555e1d48 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -72,7 +72,7 @@ use witnet_data_structures::{ radon_report::{RadonReport, ReportContext}, register_protocol_version, staking::prelude::*, - superblock::{ARSIdentities, AddSuperBlockVote, SuperBlockConsensus}, + superblock::{Census, AddSuperBlockVote, SuperBlockConsensus}, transaction::{RevealTransaction, TallyTransaction, Transaction}, types::{ visitor::{StatefulVisitor, Visitor}, @@ -2042,7 +2042,7 @@ impl ChainManager { // Get the list of members of the ARS with reputation greater than 0 // the list itself is ordered by decreasing reputation - let ars_identities = ARSIdentities::new(ars_members); + let ars_identities = Census::new(ars_members); // After the second hard fork, the superblock committee size must be at least 50 let min_committee_size = if after_second_hard_fork(block_epoch, get_environment()) { From b96a1c6b7271e8e9ea1ac6f9a9902c098e063291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20SDPC?= Date: Thu, 26 Dec 2024 12:47:33 +0100 Subject: [PATCH 2/5] ref(data_structures): remove `census` method in `Stakes` --- data_structures/src/staking/stakes.rs | 28 --------------------------- 1 file changed, 28 deletions(-) diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index d161a5a1a..755522359 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -260,34 +260,6 @@ where self.by_key.len() } - /// Obtain a list of stakers, conveniently ordered by one of several strategies. - /// - /// ## Strategies - /// - /// - `All`: retrieve all addresses, ordered by decreasing power. - /// - `StepBy`: retrieve every Nth address, ordered by decreasing power. - /// - `Take`: retrieve the most powerful N addresses, ordered by decreasing power. - /// - `Evenly`: retrieve a total of N addresses, evenly distributed from the index, ordered by - /// decreasing power. - pub fn census( - &self, - capability: Capability, - epoch: Epoch, - strategy: CensusStrategy, - ) -> Box> + '_> { - let iterator = self.by_rank(capability, epoch).map(|(address, _)| address); - - match strategy { - CensusStrategy::All => Box::new(iterator), - CensusStrategy::StepBy(step) => Box::new(iterator.step_by(step)), - CensusStrategy::Take(head) => Box::new(iterator.take(head)), - CensusStrategy::Evenly(count) => { - let collected = iterator.collect::>(); - let step = collected.len() / count; - - Box::new(collected.into_iter().step_by(step).take(count)) - } - } } /// Tells what is the power of an identity in the network on a certain epoch. From 672bacc17f6491efe642c80d9766713f8e0b5163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20SDPC?= Date: Thu, 26 Dec 2024 12:48:10 +0100 Subject: [PATCH 3/5] feat(data_structures): add `validators_count` method for `Stakes` This makes it very simple to count how many different validators are we tracking. --- data_structures/src/staking/stakes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index 755522359..0bac57eb4 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -260,6 +260,9 @@ where self.by_key.len() } + /// Quickly count how many different validators are recorded into this data structure. + pub fn validators_count(&self) -> usize { + self.by_validator.len() } /// Tells what is the power of an identity in the network on a certain epoch. From 0ebb227c1c6c7099e84911d0ce6042ffca7f6d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20SDPC?= Date: Thu, 26 Dec 2024 12:49:04 +0100 Subject: [PATCH 4/5] feat(data_structures): sample V2_0 superblock committee from validators --- data_structures/src/superblock.rs | 43 +++++++++++----------- node/src/actors/chain_manager/mod.rs | 54 +++++++++++++++++----------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/data_structures/src/superblock.rs b/data_structures/src/superblock.rs index db48e89d4..857d51bfe 100644 --- a/data_structures/src/superblock.rs +++ b/data_structures/src/superblock.rs @@ -424,16 +424,20 @@ impl SuperBlockState { self.ars_current_identities = new_identities; } - /// Produces a `SuperBlock` that includes the blocks in `block_headers` if there is at least one of them. - /// `ars_identities` will be used to validate all the superblock votes received for the - /// next superblock. The votes for the current superblock must be validated using them - /// to calculate the superblock_signing_committee. + /// Produces a `SuperBlock` that includes the blocks in `block_headers` if there is at least + /// one of them. + /// + /// `census` will be used to validate all the superblock votes received for the next superblock. + /// + /// The votes for the current superblock must be validated using them to calculate + /// `superblock_signing_committee`. + /// /// The ordered bn256 keys will be merkelized and appended to the superblock #[allow(clippy::cast_possible_truncation, clippy::too_many_arguments)] pub fn build_superblock( &mut self, block_headers: &[BlockHeader], - ars_identities: Census, + census: Census, signing_committee_size: u32, superblock_index: u32, last_block_in_previous_superblock: Hash, @@ -442,28 +446,23 @@ impl SuperBlockState { block_epoch: Epoch, ) -> SuperBlock { let protocol_version = ProtocolVersion::from_epoch(block_epoch); - let key_leaves = hash_key_leaves(&ars_identities.get_rep_ordered_bn256_list(alt_keys)); + let key_leaves = hash_key_leaves(&census.get_rep_ordered_bn256_list(alt_keys)); - self.update_ars_identities(ars_identities); + self.update_ars_identities(census); // Before updating the superblock_beacon, calculate the signing committee - let signing_committee = if let Some(ref sb) = sync_superblock { - calculate_superblock_signing_committee( - self.ars_previous_identities.clone(), - sb.signing_committee_length, - superblock_index, - self.current_superblock_beacon.hash_prev_block, - block_epoch, - ) + let signing_committee_size = if let Some(ref sb) = sync_superblock { + sb.signing_committee_length } else { - calculate_superblock_signing_committee( - self.ars_previous_identities.clone(), - signing_committee_size, - superblock_index, - self.current_superblock_beacon.hash_prev_block, - block_epoch, - ) + signing_committee_size }; + let signing_committee = calculate_superblock_signing_committee( + self.ars_previous_identities.clone(), + signing_committee_size, + superblock_index, + self.current_superblock_beacon.hash_prev_block, + block_epoch, + ); // Override superblock signing committee during each of the different emergency periods let emergency_committee = diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 1555e1d48..4d97ab162 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -72,7 +72,7 @@ use witnet_data_structures::{ radon_report::{RadonReport, ReportContext}, register_protocol_version, staking::prelude::*, - superblock::{Census, AddSuperBlockVote, SuperBlockConsensus}, + superblock::{AddSuperBlockVote, Census, SuperBlockConsensus}, transaction::{RevealTransaction, TallyTransaction, Transaction}, types::{ visitor::{StatefulVisitor, Visitor}, @@ -2022,27 +2022,41 @@ impl ChainManager { let reputation_engine = act.chain_state.reputation_engine.as_ref().unwrap(); let last_superblock_signed_by_bootstrap = last_superblock_signed_by_bootstrap(&chain_info.consensus_constants); - let ars_members = - // Before reaching the epoch activity_period + collateral_age the bootstrap committee signs the superblock - // collateral_age is measured in blocks instead of epochs, but this only means that the period in which - // the bootstrap committee signs is at least epoch activity_period + collateral_age - if let Some(ars_members) = in_emergency_period(superblock_index, get_environment()) { - // Bootstrap committee - ars_members - } else if superblock_index >= last_superblock_signed_by_bootstrap { + // The base census is used during bootstrapping of the network and during emergency + // periods. + let base_census = if let Some(ars_members) = in_emergency_period(superblock_index, get_environment()) { + // Bootstrap committee + Some(ars_members) + } else if superblock_index < last_superblock_signed_by_bootstrap { + Some(chain_info + .consensus_constants + .bootstrapping_committee + .iter() + .map(|add| add.parse().expect("Malformed bootstrapping committee")) + .collect()) + } else { + None + }; + + // Different protocol versions sample the superblock signing committees from census + // that are built off different chain data: + // - In V1_X protocols, the census was sourced from the ARS. Namely, it contains + // all identities with non-zero reputation, ordered by decreasing reputation. + // - In V2_x protocols, the census is instead sourced from the stakes tracker. + // Namely, it contains the two top quartiles of the most powerful validators, + // ordered by power. + // Exceptionally, if a base census has been constructed above, all of this logic is + // skipped, and the base census is used. + let census = Census::new(base_census.unwrap_or( + if ProtocolVersion::from_epoch(block_epoch) < ProtocolVersion::V2_0 { reputation_engine.get_rep_ordered_ars_list() } else { - chain_info - .consensus_constants - .bootstrapping_committee - .iter() - .map(|add| add.parse().expect("Malformed bootstrapping committee")) - .collect() - }; + let all_validators = act.chain_state.stakes.by_rank(Capability::Mining, block_epoch); + let half_count = act.chain_state.stakes.validators_count() / 2; + let top_validators = all_validators.map(|(StakeKey { validator, .. }, _)| validator).take(half_count).collect(); - // Get the list of members of the ARS with reputation greater than 0 - // the list itself is ordered by decreasing reputation - let ars_identities = Census::new(ars_members); + top_validators + })); // After the second hard fork, the superblock committee size must be at least 50 let min_committee_size = if after_second_hard_fork(block_epoch, get_environment()) { @@ -2066,7 +2080,7 @@ impl ChainManager { let superblock = act.chain_state.superblock_state.build_superblock( &block_headers, - ars_identities, + census, committee_size, superblock_index, last_hash, From a9ae6e6514fc59fbde4f1caddf00969519ffad2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20SDPC?= Date: Thu, 26 Dec 2024 14:34:00 +0100 Subject: [PATCH 5/5] feat(data_structures): order validators by stake when sampling superblock commitees --- data_structures/src/staking/helpers.rs | 16 ---------------- data_structures/src/staking/stakes.rs | 14 ++++++++++++++ data_structures/src/superblock.rs | 2 +- node/src/actors/chain_manager/mod.rs | 8 ++++---- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/data_structures/src/staking/helpers.rs b/data_structures/src/staking/helpers.rs index b9426df03..84c2d6275 100644 --- a/data_structures/src/staking/helpers.rs +++ b/data_structures/src/staking/helpers.rs @@ -287,22 +287,6 @@ pub struct CoinsAndAddresses { pub addresses: StakeKey
, } -/// Allows telling the `census` method in `Stakes` to source addresses from its internal `by_coins` -/// following different strategies. -#[repr(u8)] -#[derive(Clone, Copy, Debug)] -pub enum CensusStrategy { - /// Retrieve all addresses, ordered by decreasing power. - All = 0, - /// Retrieve every Nth address, ordered by decreasing power. - StepBy(usize) = 1, - /// Retrieve the most powerful N addresses, ordered by decreasing power. - Take(usize) = 2, - /// Retrieve a total of N addresses, evenly distributed from the index, ordered by decreasing - /// power. - Evenly(usize) = 3, -} - impl Serialize for Stakes where diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index 0bac57eb4..da789805b 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -265,6 +265,20 @@ where self.by_validator.len() } + /// Retrieve all validators, ordered by the total amount of stake that they operate. + pub fn validators_by_stake(&self) -> impl Iterator + Clone + '_ { + self.by_validator + .iter() + .map(|(address, entries)| { + let coins = entries.iter().fold(Coins::zero(), |acc, entry| { + acc + entry.value.read().unwrap().coins + }); + + (address.clone(), coins) + }) + .sorted_by_key(|(_, coins)| *coins) + } + /// Tells what is the power of an identity in the network on a certain epoch. pub fn query_power( &self, diff --git a/data_structures/src/superblock.rs b/data_structures/src/superblock.rs index 857d51bfe..bebce501c 100644 --- a/data_structures/src/superblock.rs +++ b/data_structures/src/superblock.rs @@ -68,7 +68,7 @@ pub enum SuperBlockConsensus { /// /// Different protocol versions populate this data differently: /// - In V1_X, this contained a complete list of all ARS identities. -/// - In V2_X, it is expected to contain all validators instead. +/// - In V2_X, it is expected to contain a list of validators instead. #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct Census { // HashSet containing the identities diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 4d97ab162..b009d169f 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -2043,17 +2043,17 @@ impl ChainManager { // - In V1_X protocols, the census was sourced from the ARS. Namely, it contains // all identities with non-zero reputation, ordered by decreasing reputation. // - In V2_x protocols, the census is instead sourced from the stakes tracker. - // Namely, it contains the two top quartiles of the most powerful validators, - // ordered by power. + // Namely, it contains the two top quartiles of the validators that operate the + // most stake. // Exceptionally, if a base census has been constructed above, all of this logic is // skipped, and the base census is used. let census = Census::new(base_census.unwrap_or( if ProtocolVersion::from_epoch(block_epoch) < ProtocolVersion::V2_0 { reputation_engine.get_rep_ordered_ars_list() } else { - let all_validators = act.chain_state.stakes.by_rank(Capability::Mining, block_epoch); + let all_validators = act.chain_state.stakes.validators_by_stake(); let half_count = act.chain_state.stakes.validators_count() / 2; - let top_validators = all_validators.map(|(StakeKey { validator, .. }, _)| validator).take(half_count).collect(); + let top_validators = all_validators.map(|(validator, _)| validator).take(half_count).collect(); top_validators }));