From ada7cda9010e81a3d6fa661f5722476162983a59 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Apr 2023 11:30:25 +0700 Subject: [PATCH 01/14] masternode update test --- Cargo.lock | 31 +- packages/rs-drive-abci/src/abci/handlers.rs | 3 - packages/rs-drive-abci/src/abci/mimic.rs | 1 - .../tests/strategy_tests/main.rs | 618 ++++++++++++++---- .../strategy_tests/upgrade_fork_tests.rs | 18 +- 5 files changed, 513 insertions(+), 158 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfc1d076046..38da9fb8976 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" dependencies = [ "anstyle", "anstyle-parse", @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -982,7 +982,7 @@ dependencies = [ [[package]] name = "dashcore-rpc" version = "0.15.0" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#fc8d1749f87dcc37e327452101ba986eca6ea917" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#fe9172482382c7415c4b2cef33c7a721306dc7ca" dependencies = [ "dashcore-rpc-json", "env_logger 0.10.0", @@ -995,7 +995,7 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.15.0" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#fc8d1749f87dcc37e327452101ba986eca6ea917" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#fe9172482382c7415c4b2cef33c7a721306dc7ca" dependencies = [ "dashcore", "hex", @@ -1027,9 +1027,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11" +checksum = "05e58dffcdcc8ee7b22f0c1f71a69243d7c2d9ad87b5a14361f2424a1565c219" dependencies = [ "const-oid", "zeroize", @@ -3637,9 +3637,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" dependencies = [ "autocfg", "bytes", @@ -3651,14 +3651,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", @@ -3693,11 +3693,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] diff --git a/packages/rs-drive-abci/src/abci/handlers.rs b/packages/rs-drive-abci/src/abci/handlers.rs index e6be48f0d11..ee01e4d879a 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -166,9 +166,6 @@ where Err(_) => None, }; - // Next we should check for validator set updates - // todo: validator set updates - // TODO: implement all fields, including tx processing; for now, just leaving bare minimum let response = ResponsePrepareProposal { tx_results, diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs index 90f1d440bff..69fee29868d 100644 --- a/packages/rs-drive-abci/src/abci/mimic.rs +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -46,7 +46,6 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { proposer_pro_tx_hash: [u8; 32], current_quorum: &TestQuorumInfo, proposed_version: ProtocolVersion, - _total_hpmns: u32, block_info: BlockInfo, expect_validation_errors: bool, state_transitions: Vec, diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 1feecab4974..77820704f23 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -103,6 +103,8 @@ use drive_abci::rpc::core::MockCoreRPCLike; use drive_abci::test::fixture::abci::static_init_chain_request; use drive_abci::test::helpers::setup::TestPlatformBuilder; use drive_abci::{config::PlatformConfig, test::helpers::setup::TempPlatform}; +use rand::distributions::Distribution; +use rand::distributions::Uniform; use rand::prelude::IteratorRandom; use rand::rngs::StdRng; use rand::seq::SliceRandom; @@ -160,6 +162,33 @@ impl Frequency { (self.times_per_block_range.start + self.times_per_block_range.end) as f64 / 2.0 } } + + pub fn pick_in_range(&self, rng: &mut impl Rng, range: Range) -> Vec { + if !self.is_set() { + return vec![]; + } + let mut picked_numbers = Vec::new(); + let chance_per_block_hit = self + .chance_per_block + .map_or(true, |chance| rng.gen_bool(chance)); + + if chance_per_block_hit { + let mut times_per_block_dist = Uniform::from(self.times_per_block_range.clone()); + + for number in range { + let times_to_pick = times_per_block_dist.sample(rng); + + for _ in 0..times_to_pick { + let num_u16 = number as u16; + if !picked_numbers.contains(&num_u16) { + picked_numbers.push(num_u16); + } + } + } + } + + picked_numbers + } } #[derive(Clone, Debug)] @@ -282,6 +311,65 @@ impl Signer for SimpleSigner { pub type BlockHeight = u64; +#[derive(Clone, Debug, Default)] +pub struct MasternodeListChangesStrategy { + /// How many new masternodes on average per core chain lock increase + new_hpmns: Frequency, + /// How many masternodes leave the system + removed_hpmns: Frequency, + /// How many masternodes update a key + updated_hpmns: Frequency, + /// How many masternodes are banned + banned_hpmns: Frequency, + /// How many masternodes are unbanned + unbanned_hpmns: Frequency, + /// How many new masternodes on average per core chain lock increase + new_masternodes: Frequency, + /// How many masternodes leave the system + removed_masternodes: Frequency, + /// How many masternodes update a key + updated_mastenodes: Frequency, + /// How many masternodes are banned + banned_masternodes: Frequency, + /// How many masternodes are unbanned + unbanned_masternodes: Frequency, +} + +impl MasternodeListChangesStrategy { + fn any_is_set(&self) -> bool { + self.new_hpmns.is_set() + || self.removed_hpmns.is_set() + || self.updated_hpmns.is_set() + || self.banned_hpmns.is_set() + || self.unbanned_hpmns.is_set() + || self.new_masternodes.is_set() + || self.removed_masternodes.is_set() + || self.updated_mastenodes.is_set() + || self.banned_masternodes.is_set() + || self.unbanned_masternodes.is_set() + } + + fn removed_any_masternode_types(&self) -> bool { + self.removed_masternodes.is_set() || self.removed_hpmns.is_set() + } + + fn updated_any_masternode_types(&self) -> bool { + self.updated_mastenodes.is_set() || self.updated_hpmns.is_set() + } + + fn added_any_masternode_types(&self) -> bool { + self.new_masternodes.is_set() || self.new_hpmns.is_set() + } + + fn any_update_is_set(&self) -> bool { + self.updated_hpmns.is_set() + || self.banned_hpmns.is_set() + || self.unbanned_hpmns.is_set() | self.updated_mastenodes.is_set() + || self.banned_masternodes.is_set() + || self.unbanned_masternodes.is_set() + } +} + #[derive(Clone, Debug)] pub struct Strategy { contracts_with_updates: Vec<(Contract, Option>)>, @@ -292,10 +380,7 @@ pub struct Strategy { quorum_count: u16, upgrading_info: Option, core_height_increase: Frequency, - /// how many new proposers on average per core chain lock increase - new_proposers: Frequency, - /// how many proposers leave the system - removed_proposers: Frequency, + proposer_strategy: MasternodeListChangesStrategy, rotate_quorums: bool, } @@ -998,7 +1083,7 @@ pub struct ChainExecutionOutcome<'a> { pub abci_app: AbciApplication<'a, MockCoreRPCLike>, pub masternode_identity_balances: BTreeMap<[u8; 32], Credits>, pub identities: Vec, - pub proposers: Vec, + pub proposers: Vec, pub quorums: BTreeMap, pub current_quorum_hash: QuorumHash, pub current_proposer_versions: Option>, @@ -1012,7 +1097,7 @@ pub struct ChainExecutionParameters { pub block_start: u64, pub core_height_start: u32, pub block_count: u64, - pub proposers: Vec, + pub proposers: Vec, pub quorums: BTreeMap, pub current_quorum_hash: QuorumHash, // the first option is if it is set @@ -1026,14 +1111,81 @@ pub enum StrategyRandomness { RNGEntropy(StdRng), } +pub struct GenerateTestMasternodeUpdates<'a> { + start_core_height: u32, + end_core_height: u32, + update_masternode_frequency: &'a Frequency, + update_hpmn_frequency: &'a Frequency, +} + /// Creates a list of test Masternode identities of size `count` with random data pub fn generate_test_masternodes( masternode_count: u16, hpmn_count: u16, + updates: Option, rng: &mut StdRng, -) -> Vec { - let mut masternodes: Vec = - Vec::with_capacity((masternode_count + hpmn_count) as usize); +) -> ( + Vec, + Vec, +) { + let mut masternodes: Vec = + Vec::with_capacity(masternode_count as usize); + let mut hpmns: Vec = Vec::with_capacity(hpmn_count as usize); + + let (block_height_to_list_masternode_updates, block_height_to_list_hpmns_updates): ( + Option>>, + Option>>, + ) = updates + .map( + |GenerateTestMasternodeUpdates { + start_core_height, + end_core_height, + update_masternode_frequency, + update_hpmn_frequency, + }| { + (start_core_height..=end_core_height) + .into_iter() + .map(|height| { + // we want to pick what nodes will have updated for that block + ( + ( + height, + update_masternode_frequency.pick_in_range(rng, 0..masternode_count), + ), + ( + height, + update_hpmn_frequency.pick_in_range(rng, 0..hpmn_count), + ), + ) + }) + .unzip() + }, + ) + .unzip(); + + fn invert_hashmap(input: HashMap>) -> HashMap> { + let mut output = HashMap::new(); + + for (key, values) in input { + for value in values { + output.entry(value).or_insert_with(Vec::new).push(key); + } + } + + output + } + + let masternode_number_to_heights_updates = block_height_to_list_masternode_updates + .map(|block_height_to_list_masternode_updates| { + invert_hashmap(block_height_to_list_masternode_updates) + }) + .unwrap_or_default(); + + let hpmn_number_to_heights_updates = block_height_to_list_hpmns_updates + .map(|block_height_to_list_hpmns_updates| { + invert_hashmap(block_height_to_list_hpmns_updates) + }) + .unwrap_or_default(); for i in 0..masternode_count { let private_key_operator = @@ -1055,7 +1207,7 @@ pub fn generate_test_masternodes( service: SocketAddr::from_str(format!("1.0.{}.{}:1234", i / 256, i % 256).as_str()) .unwrap(), registered_height: 0, - pose_revived_height: 0, + pose_revived_height: None, pose_ban_height: None, revocation_reason: 0, owner_address: rng.gen::<[u8; 20]>(), @@ -1068,7 +1220,26 @@ pub fn generate_test_masternodes( platform_http_port: None, }, }; - masternodes.push(masternode_list_item); + + let masternode_updates = masternode_number_to_heights_updates + .get(&i) + .map(|heights| { + heights + .into_iter() + .map(|height| { + let masternode_list_item_b = masternode_list_item.clone(); + (*height, masternode_list_item_b) + }) + .collect::>() + }) + .unwrap_or_default(); + + let masternode_with_update = MasternodeListItemWithUpdates { + masternode: masternode_list_item, + updates: masternode_updates, + }; + + masternodes.push(masternode_with_update); } for i in 0..hpmn_count { @@ -1090,7 +1261,7 @@ pub fn generate_test_masternodes( service: SocketAddr::from_str(format!("1.1.{}.{}:1234", i / 256, i % 256).as_str()) .unwrap(), registered_height: 0, - pose_revived_height: 0, + pose_revived_height: None, pose_ban_height: None, revocation_reason: 0, owner_address: rng.gen::<[u8; 20]>(), @@ -1103,25 +1274,46 @@ pub fn generate_test_masternodes( platform_http_port: Some(8080), }, }; - masternodes.push(masternode_list_item); + let masternode_updates = hpmn_number_to_heights_updates + .get(&i) + .map(|heights| { + heights + .into_iter() + .map(|height| { + let masternode_list_item_b = masternode_list_item.clone(); + (*height, masternode_list_item_b) + }) + .collect::>() + }) + .unwrap_or_default(); + + let proposer_with_update = MasternodeListItemWithUpdates { + masternode: masternode_list_item, + updates: masternode_updates, + }; + + hpmns.push(proposer_with_update); } - masternodes + (masternodes, hpmns) } -pub fn generate_test_quorums( +pub fn generate_test_quorums<'a, I>( count: usize, - proposers: &Vec, + proposers: I, quorum_size: usize, rng: &mut StdRng, -) -> BTreeMap { +) -> BTreeMap +where + I: Iterator + Clone, +{ (0..count) .into_iter() .enumerate() .map(|(index, _)| { let quorum_hash: QuorumHash = QuorumHash::from_inner(rng.gen()); let validator_pro_tx_hashes = proposers - .iter() + .clone() .filter(|m| m.node_type == MasternodeType::HighPerformance) .choose_multiple(rng, quorum_size) .iter() @@ -1140,6 +1332,28 @@ pub fn generate_test_quorums( .collect() } +#[derive(Clone, Debug)] +pub struct MasternodeListItemWithUpdates { + masternode: MasternodeListItem, + updates: BTreeMap, +} + +impl MasternodeListItemWithUpdates { + pub(crate) fn pro_tx_hash(&self) -> ProTxHash { + self.masternode.pro_tx_hash + } + + pub(crate) fn get_state_at_height(&self, height: u32) -> &MasternodeListItem { + // Find the closest height less than or equal to the given height + let closest_height = self.updates.range(..=height).rev().next().map(|(k, _)| *k); + + match closest_height { + Some(h) => &self.updates[&h], + None => &self.masternode, + } + } +} + pub(crate) fn run_chain_for_strategy( platform: &mut Platform, block_count: u64, @@ -1152,47 +1366,113 @@ pub(crate) fn run_chain_for_strategy( let mut rng = StdRng::seed_from_u64(seed); - let new_proposers_in_strategy = strategy.new_proposers.is_set(); - let removed_proposers_in_strategy = strategy.removed_proposers.is_set(); - let (initial_proposers, with_extra_proposers) = if new_proposers_in_strategy - || removed_proposers_in_strategy - { - let initial_proposers = - generate_test_masternodes(strategy.extra_normal_mns, strategy.total_hpmns, &mut rng); - let mut all_proposers = initial_proposers.clone(); + let any_changes_in_strategy = strategy.proposer_strategy.any_is_set(); + let updated_proposers_in_strategy = strategy.proposer_strategy.updated_any_masternode_types(); + let any_update_of_proposers_in_strategy = strategy.proposer_strategy.any_update_is_set(); + + let ( + initial_masternodes_with_updates, + initial_hpmns_with_updates, + with_extra_masternodes_with_updates, + with_extra_hpmns_with_updates, + ) = if any_changes_in_strategy { let approximate_end_core_height = ((block_count as f64) * strategy.core_height_increase.average_event_count()) as u32; let end_core_height = approximate_end_core_height * 2; //let's be safe - let extra_proposers_by_block = (config.abci.genesis_core_height..end_core_height) + let generate_updates = if updated_proposers_in_strategy { + Some(GenerateTestMasternodeUpdates { + start_core_height: config.abci.genesis_core_height, + end_core_height, + update_masternode_frequency: &strategy.proposer_strategy.updated_mastenodes, + update_hpmn_frequency: &strategy.proposer_strategy.updated_hpmns, + }) + } else { + None + }; + + let (initial_masternodes, initial_hpmns) = generate_test_masternodes( + strategy.extra_normal_mns, + strategy.total_hpmns, + generate_updates, + &mut rng, + ); + let mut all_masternodes = initial_masternodes.clone(); + let mut all_hpmns = initial_hpmns.clone(); + + let (extra_masternodes_by_block, extra_hpmns_by_block): ( + BTreeMap>, + BTreeMap>, + ) = (config.abci.genesis_core_height..end_core_height) .map(|height| { - let new_proposers = strategy.new_proposers.events_if_hit(&mut rng); - let removed_proposers_count = strategy.removed_proposers.events_if_hit(&mut rng); - let extra_proposers_by_block = - generate_test_masternodes(0, new_proposers, &mut rng); + let new_proposers = strategy.proposer_strategy.new_hpmns.events_if_hit(&mut rng); + let generate_updates = if updated_proposers_in_strategy { + Some(GenerateTestMasternodeUpdates { + start_core_height: height + 1, + end_core_height, + update_masternode_frequency: &strategy.proposer_strategy.updated_mastenodes, + update_hpmn_frequency: &strategy.proposer_strategy.updated_hpmns, + }) + } else { + None + }; - if removed_proposers_in_strategy { + let (extra_masternodes_by_block, extra_hpmns_by_block) = + generate_test_masternodes(0, new_proposers, generate_updates, &mut rng); + + if strategy.proposer_strategy.removed_masternodes.is_set() { + let removed_masternodes_count = strategy + .proposer_strategy + .removed_masternodes + .events_if_hit(&mut rng); let removed_count = - std::cmp::min(removed_proposers_count as usize, all_proposers.len()); - all_proposers.drain(0..removed_count); + std::cmp::min(removed_masternodes_count as usize, all_masternodes.len()); + all_masternodes.drain(0..removed_count); } - all_proposers.extend(extra_proposers_by_block.clone()); - (height, all_proposers.clone()) + if strategy.proposer_strategy.removed_hpmns.is_set() { + let removed_hpmns_count = strategy + .proposer_strategy + .removed_hpmns + .events_if_hit(&mut rng); + let removed_count = + std::cmp::min(removed_hpmns_count as usize, all_hpmns.len()); + all_hpmns.drain(0..removed_count); + } + + all_masternodes.extend(extra_masternodes_by_block.clone()); + all_hpmns.extend(extra_hpmns_by_block.clone()); + ( + (height, all_masternodes.clone()), + (height, all_hpmns.clone()), + ) }) - .collect::>>(); - (initial_proposers, extra_proposers_by_block) + .unzip(); + ( + initial_masternodes, + initial_hpmns, + extra_masternodes_by_block, + extra_hpmns_by_block, + ) } else { + let (initial_masternodes, initial_hpmns) = generate_test_masternodes( + strategy.extra_normal_mns, + strategy.total_hpmns, + None, + &mut rng, + ); ( - generate_test_masternodes(strategy.extra_normal_mns, strategy.total_hpmns, &mut rng), - HashMap::new(), + initial_masternodes, + initial_hpmns, + BTreeMap::new(), + BTreeMap::new(), ) }; - let mut all_proposers = with_extra_proposers + let mut all_hpmns_with_updates = with_extra_hpmns_with_updates .iter() .max_by_key(|(key, _)| *key) .map(|(_, v)| v.clone()) - .unwrap_or(initial_proposers.clone()); + .unwrap_or(initial_hpmns_with_updates.clone()); let total_quorums = if strategy.rotate_quorums { quorum_count * 10 @@ -1202,7 +1482,9 @@ pub(crate) fn run_chain_for_strategy( let quorums = generate_test_quorums( total_quorums as usize, - &initial_proposers, + initial_hpmns_with_updates + .iter() + .map(|hpmn| &hpmn.masternode), quorum_size as usize, &mut rng, ); @@ -1298,6 +1580,12 @@ pub(crate) fn run_chain_for_strategy( .clone()) }); + let initial_all_masternodes: Vec<_> = initial_masternodes_with_updates + .clone() + .into_iter() + .chain(initial_hpmns_with_updates.clone().into_iter()) + .collect(); + platform .core_rpc .expect_get_protx_diff_with_masternodes() @@ -1306,12 +1594,15 @@ pub(crate) fn run_chain_for_strategy( MasternodeListDiff { base_height: base_block, block_height: block, - added_mns: initial_proposers.clone(), + added_mns: initial_all_masternodes + .iter() + .map(|masternode_list_item| masternode_list_item.masternode.clone()) + .collect(), removed_mns: vec![], updated_mns: vec![], } } else { - if !new_proposers_in_strategy { + if !any_changes_in_strategy { // no changes MasternodeListDiff { base_height: base_block, @@ -1322,29 +1613,62 @@ pub(crate) fn run_chain_for_strategy( } } else { // we need to figure out the difference of proposers between two heights - let start_proposers = with_extra_proposers + let start_masternodes = with_extra_masternodes_with_updates .get(&base_block) - .expect("expected start proposers"); - let end_proposers = with_extra_proposers + .expect("expected start proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(base_block)) + .collect::>(); + let end_masternodes = with_extra_masternodes_with_updates .get(&block) - .expect("expected end proposers"); - let added_mns: Vec<_> = end_proposers + .expect("expected end proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(block)) + .collect::>(); + let mut added_masternodes = end_masternodes + .iter() + .filter(|item| !start_masternodes.contains(item)) + .map(|a| (*a).clone()) + .collect::>(); + + let mut removed_masternodes = start_masternodes + .iter() + .filter(|item| !end_masternodes.contains(item)) + .map(|masternode_list_item| masternode_list_item.pro_tx_hash) + .collect::>(); + + let start_hpmns = with_extra_hpmns_with_updates + .get(&base_block) + .expect("expected start proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(base_block)) + .collect::>(); + let end_hpmns = with_extra_hpmns_with_updates + .get(&block) + .expect("expected end proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(block)) + .collect::>(); + let added_hpmns = end_hpmns .iter() - .filter(|item| !start_proposers.contains(item)) - .map(|a| a.clone()) - .collect(); + .filter(|item| !start_hpmns.contains(item)) + .map(|a| (*a).clone()) + .collect::>(); - let removed_mns: Vec<_> = start_proposers + let removed_hpmns = start_hpmns .iter() - .filter(|item| !end_proposers.contains(item)) + .filter(|item| !end_hpmns.contains(item)) .map(|masternode_list_item| masternode_list_item.pro_tx_hash) - .collect(); + .collect::>(); + + added_masternodes.extend(added_hpmns); + removed_masternodes.extend(removed_hpmns); MasternodeListDiff { base_height: base_block, block_height: block, - added_mns, - removed_mns, + added_mns: added_masternodes, + removed_mns: removed_masternodes, updated_mns: vec![], } } @@ -1356,7 +1680,7 @@ pub(crate) fn run_chain_for_strategy( start_chain_for_strategy( platform, block_count, - all_proposers, + all_hpmns_with_updates, quorums, strategy, config, @@ -1367,7 +1691,7 @@ pub(crate) fn run_chain_for_strategy( pub(crate) fn start_chain_for_strategy( platform: &Platform, block_count: u64, - proposers: Vec, + proposers_with_updates: Vec, quorums: BTreeMap, strategy: Strategy, config: PlatformConfig, @@ -1442,7 +1766,7 @@ pub(crate) fn start_chain_for_strategy( block_start: 1, core_height_start: initial_core_height, block_count, - proposers, + proposers: proposers_with_updates, quorums, current_quorum_hash, current_proposer_versions: None, @@ -1466,7 +1790,7 @@ pub(crate) fn continue_chain_for_strategy( block_start, core_height_start, block_count, - proposers, + proposers: proposers_with_updates, quorums, mut current_quorum_hash, current_proposer_versions, @@ -1485,14 +1809,12 @@ pub(crate) fn continue_chain_for_strategy( let blocks_per_epoch = EPOCH_CHANGE_TIME_MS / config.block_spacing_ms; - let proposer_count = proposers.len() as u32; - let proposer_versions = current_proposer_versions.unwrap_or( strategy.upgrading_info.as_ref().map(|upgrading_info| { upgrading_info.apply_to_proposers( - proposers + proposers_with_updates .iter() - .map(|masternode_list_item| masternode_list_item.pro_tx_hash) + .map(|masternode_list_item| masternode_list_item.pro_tx_hash()) .collect(), blocks_per_epoch, &mut rng, @@ -1508,8 +1830,6 @@ pub(crate) fn continue_chain_for_strategy( let mut next_quorum_hash = current_quorum_hash; - let mut next_quorum_with_test_info = quorums.get(&next_quorum_hash).unwrap(); - for block_height in block_start..(block_start + block_count) { let needs_rotation_on_next_block = block_height % quorum_rotation_block_count == 0; if needs_rotation_on_next_block { @@ -1541,10 +1861,6 @@ pub(crate) fn continue_chain_for_strategy( if current_quorum_with_test_info.quorum_hash != current_quorum_hash { current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); } - // - // if next_quorum_with_test_info.quorum_hash != next_quorum_hash { - // next_quorum_with_test_info = quorums.get(&next_quorum_hash).unwrap(); - // } let proposer = current_quorum_with_test_info .validator_set @@ -1585,7 +1901,6 @@ pub(crate) fn continue_chain_for_strategy( proposer.pro_tx_hash.into_inner(), current_quorum_with_test_info, proposed_version, - proposer_count, block_info, false, state_transitions, @@ -1624,9 +1939,9 @@ pub(crate) fn continue_chain_for_strategy( let masternode_identity_balances = platform .drive .fetch_identities_balances( - &proposers + &proposers_with_updates .iter() - .map(|proposer| proposer.pro_tx_hash.into_inner()) + .map(|proposer| proposer.pro_tx_hash().into_inner()) .collect(), None, ) @@ -1646,7 +1961,7 @@ pub(crate) fn continue_chain_for_strategy( abci_app, masternode_identity_balances, identities: current_identities, - proposers, + proposers: proposers_with_updates, quorums, current_quorum_hash, current_proposer_versions: proposer_versions, @@ -1711,8 +2026,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -1757,8 +2071,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -1803,8 +2116,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -1894,8 +2206,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -1949,8 +2260,7 @@ mod tests { times_per_block_range: 1..3, chance_per_block: Some(0.01), }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -1995,8 +2305,7 @@ mod tests { times_per_block_range: 5..6, chance_per_block: Some(0.5), }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: true, }; let config = PlatformConfig { @@ -2078,11 +2387,13 @@ mod tests { times_per_block_range: 1..2, chance_per_block: Some(0.2), }, - new_proposers: Frequency { - times_per_block_range: 1..3, - chance_per_block: Some(0.5), + proposer_strategy: MasternodeListChangesStrategy { + new_hpmns: Frequency { + times_per_block_range: 1..3, + chance_per_block: Some(0.5), + }, + ..Default::default() }, - removed_proposers: Default::default(), rotate_quorums: true, }; let config = PlatformConfig { @@ -2143,13 +2454,82 @@ mod tests { times_per_block_range: 1..2, chance_per_block: Some(0.2), }, - new_proposers: Frequency { - times_per_block_range: 1..3, - chance_per_block: Some(0.5), + proposer_strategy: MasternodeListChangesStrategy { + new_hpmns: Frequency { + times_per_block_range: 1..3, + chance_per_block: Some(0.5), + }, + removed_hpmns: Frequency { + times_per_block_range: 1..3, + chance_per_block: Some(0.5), + }, + ..Default::default() }, - removed_proposers: Frequency { - times_per_block_range: 1..3, - chance_per_block: Some(0.5), + rotate_quorums: true, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 10, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 300, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let ChainExecutionOutcome { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + .. + } = run_chain_for_strategy(&mut platform, 300, strategy, config, 43); + + // With these params if we add new mns the hpmn masternode list would be randomly different than 100. + + let platform = abci_app.platform; + let platform_state = platform.state.read().unwrap(); + + assert_ne!(platform_state.hpmn_masternode_list.len(), 100); + } + + #[test] + fn run_chain_core_height_randomly_increasing_with_quorum_updates_updating_proposers() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![], + identities_inserts: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: 1..2, + chance_per_block: Some(0.2), + }, + proposer_strategy: MasternodeListChangesStrategy { + updated_hpmns: Frequency { + times_per_block_range: 1..3, + chance_per_block: Some(0.5), + }, + ..Default::default() }, rotate_quorums: true, }; @@ -2210,8 +2590,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -2257,8 +2636,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let day_in_ms = 1000 * 60 * 60 * 24; @@ -2316,8 +2694,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -2407,8 +2784,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -2492,8 +2868,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -2557,8 +2932,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let day_in_ms = 1000 * 60 * 60 * 24; @@ -2650,8 +3024,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let day_in_ms = 1000 * 60 * 60 * 24; @@ -2743,8 +3116,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let day_in_ms = 1000 * 60 * 60 * 24; @@ -2837,8 +3209,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; @@ -2949,8 +3320,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; @@ -3012,8 +3382,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -3082,8 +3451,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -3153,8 +3521,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { @@ -3235,8 +3602,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let config = PlatformConfig { diff --git a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs index ecefbdba337..c0616081684 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs @@ -31,8 +31,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let twenty_minutes_in_ms = 1000 * 60 * 20; @@ -263,8 +262,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let hour_in_ms = 1000 * 60 * 60; @@ -485,8 +483,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let hour_in_ms = 1000 * 60 * 60; @@ -641,8 +638,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; @@ -795,8 +791,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; let hour_in_ms = 1000 * 60 * 60; @@ -887,8 +882,7 @@ mod tests { times_per_block_range: Default::default(), chance_per_block: None, }, - new_proposers: Default::default(), - removed_proposers: Default::default(), + proposer_strategy: Default::default(), rotate_quorums: false, }; From 863a4e38f214ab381339d6ca8ce775176e502fed Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Apr 2023 18:26:33 +0700 Subject: [PATCH 02/14] test written --- Cargo.lock | 8 +- .../tests/strategy_tests/main.rs | 149 ++++++++++++++---- .../masternode_list_item_helpers.rs | 68 ++++++++ .../src/drive/identity/fetch/full_identity.rs | 1 - 4 files changed, 191 insertions(+), 35 deletions(-) create mode 100644 packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 38da9fb8976..d7cd7436e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -982,7 +982,7 @@ dependencies = [ [[package]] name = "dashcore-rpc" version = "0.15.0" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#fe9172482382c7415c4b2cef33c7a721306dc7ca" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#ec2619ed40f429bf4d57f03c397141962aeeded1" dependencies = [ "dashcore-rpc-json", "env_logger 0.10.0", @@ -995,7 +995,7 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.15.0" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#fe9172482382c7415c4b2cef33c7a721306dc7ca" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#ec2619ed40f429bf4d57f03c397141962aeeded1" dependencies = [ "dashcore", "hex", @@ -3001,9 +3001,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.14" +version = "0.37.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" +checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" dependencies = [ "bitflags", "errno", diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 77820704f23..8b3afc63482 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -35,7 +35,7 @@ extern crate core; use anyhow::anyhow; use dashcore::{signer, Network, PrivateKey, ProTxHash, QuorumHash, Txid}; use dashcore_rpc::dashcore_rpc_json::{ - Bip9SoftforkInfo, Bip9SoftforkStatus, DMNState, ExtendedQuorumDetails, + Bip9SoftforkInfo, Bip9SoftforkStatus, DMNState, DMNStateDiff, ExtendedQuorumDetails, MasternodeListDiffWithMasternodes, MasternodeListItem, MasternodeType, QuorumInfoResult, QuorumType, }; @@ -94,6 +94,7 @@ use drive::query::DriveQuery; use drive_abci::abci::AbciApplication; use drive_abci::execution::fee_pools::epoch::{EpochInfo, EPOCH_CHANGE_TIME_MS}; +use crate::masternode_list_item_helpers::UpdateMasternodeListItem; use dashcore_rpc::json::{MasternodeListDiff, RemovedMasternodeItem}; use dpp::serialization_traits::PlatformSerializable; use drive_abci::abci::mimic::MimicExecuteBlockOutcome; @@ -117,6 +118,7 @@ use std::str::FromStr; use tenderdash_abci::proto::abci::{ResponseInitChain, ValidatorSetUpdate}; use tenderdash_abci::proto::crypto::public_key::Sum::Bls12381; +mod masternode_list_item_helpers; mod upgrade_fork_tests; #[derive(Clone, Debug, Default)] @@ -164,7 +166,7 @@ impl Frequency { } pub fn pick_in_range(&self, rng: &mut impl Rng, range: Range) -> Vec { - if !self.is_set() { + if !self.is_set() || range.is_empty() { return vec![]; } let mut picked_numbers = Vec::new(); @@ -173,18 +175,14 @@ impl Frequency { .map_or(true, |chance| rng.gen_bool(chance)); if chance_per_block_hit { - let mut times_per_block_dist = Uniform::from(self.times_per_block_range.clone()); - - for number in range { - let times_to_pick = times_per_block_dist.sample(rng); - - for _ in 0..times_to_pick { - let num_u16 = number as u16; - if !picked_numbers.contains(&num_u16) { - picked_numbers.push(num_u16); - } - } - } + let times_per_block_dist = Uniform::from(self.times_per_block_range.clone()); + let num_elements_to_pick = times_per_block_dist.sample(rng); + let range_dist = Uniform::from(range); + + picked_numbers = rng + .sample_iter(&range_dist) + .take(num_elements_to_pick as usize) + .collect(); } picked_numbers @@ -1221,13 +1219,17 @@ pub fn generate_test_masternodes( }, }; + let mut latest_masternode_list_item = masternode_list_item.clone(); + let masternode_updates = masternode_number_to_heights_updates .get(&i) .map(|heights| { heights .into_iter() .map(|height| { - let masternode_list_item_b = masternode_list_item.clone(); + let mut masternode_list_item_b = latest_masternode_list_item.clone(); + masternode_list_item_b.random_keys_update(None, rng); + latest_masternode_list_item = masternode_list_item_b.clone(); (*height, masternode_list_item_b) }) .collect::>() @@ -1274,13 +1276,18 @@ pub fn generate_test_masternodes( platform_http_port: Some(8080), }, }; + + let mut latest_masternode_list_item = masternode_list_item.clone(); + let masternode_updates = hpmn_number_to_heights_updates .get(&i) .map(|heights| { heights .into_iter() .map(|height| { - let masternode_list_item_b = masternode_list_item.clone(); + let mut masternode_list_item_b = latest_masternode_list_item.clone(); + masternode_list_item_b.random_keys_update(None, rng); + latest_masternode_list_item = masternode_list_item_b.clone(); (*height, masternode_list_item_b) }) .collect::>() @@ -1368,7 +1375,6 @@ pub(crate) fn run_chain_for_strategy( let any_changes_in_strategy = strategy.proposer_strategy.any_is_set(); let updated_proposers_in_strategy = strategy.proposer_strategy.updated_any_masternode_types(); - let any_update_of_proposers_in_strategy = strategy.proposer_strategy.any_update_is_set(); let ( initial_masternodes_with_updates, @@ -1404,7 +1410,11 @@ pub(crate) fn run_chain_for_strategy( BTreeMap>, ) = (config.abci.genesis_core_height..end_core_height) .map(|height| { - let new_proposers = strategy.proposer_strategy.new_hpmns.events_if_hit(&mut rng); + let new_masternodes = strategy + .proposer_strategy + .new_masternodes + .events_if_hit(&mut rng); + let new_hpmns = strategy.proposer_strategy.new_hpmns.events_if_hit(&mut rng); let generate_updates = if updated_proposers_in_strategy { Some(GenerateTestMasternodeUpdates { start_core_height: height + 1, @@ -1416,8 +1426,12 @@ pub(crate) fn run_chain_for_strategy( None }; - let (extra_masternodes_by_block, extra_hpmns_by_block) = - generate_test_masternodes(0, new_proposers, generate_updates, &mut rng); + let (extra_masternodes_by_block, extra_hpmns_by_block) = generate_test_masternodes( + new_masternodes, + new_hpmns, + generate_updates, + &mut rng, + ); if strategy.proposer_strategy.removed_masternodes.is_set() { let removed_masternodes_count = strategy @@ -1612,6 +1626,7 @@ pub(crate) fn run_chain_for_strategy( updated_mns: vec![], } } else { + // we need to figure out the difference of proposers between two heights // we need to figure out the difference of proposers between two heights let start_masternodes = with_extra_masternodes_with_updates .get(&base_block) @@ -1625,18 +1640,45 @@ pub(crate) fn run_chain_for_strategy( .iter() .map(|masternode| masternode.get_state_at_height(block)) .collect::>(); + + let start_pro_tx_hashes: Vec<&ProTxHash> = start_masternodes + .iter() + .map(|item| &item.pro_tx_hash) + .collect(); + let end_pro_tx_hashes: Vec<&ProTxHash> = end_masternodes + .iter() + .map(|item| &item.pro_tx_hash) + .collect(); + let mut added_masternodes = end_masternodes .iter() - .filter(|item| !start_masternodes.contains(item)) + .filter(|item| !start_pro_tx_hashes.contains(&&item.pro_tx_hash)) .map(|a| (*a).clone()) .collect::>(); let mut removed_masternodes = start_masternodes .iter() - .filter(|item| !end_masternodes.contains(item)) - .map(|masternode_list_item| masternode_list_item.pro_tx_hash) + .filter(|item| !end_pro_tx_hashes.contains(&&item.pro_tx_hash)) + .map(|masternode_list_item| masternode_list_item.pro_tx_hash.clone()) .collect::>(); + let mut updated_masternodes: Vec<(ProTxHash, DMNStateDiff)> = start_masternodes + .iter() + .filter_map(|start_masternode| { + end_masternodes + .iter() + .find(|end_masternode| { + start_masternode.pro_tx_hash == end_masternode.pro_tx_hash + }) + .and_then(|end_masternode| { + start_masternode + .state + .compare_to_newer_dmn_state(&end_masternode.state) + .map(|diff| (end_masternode.pro_tx_hash.clone(), diff)) + }) + }) + .collect(); + let start_hpmns = with_extra_hpmns_with_updates .get(&base_block) .expect("expected start proposers") @@ -1649,27 +1691,50 @@ pub(crate) fn run_chain_for_strategy( .iter() .map(|masternode| masternode.get_state_at_height(block)) .collect::>(); - let added_hpmns = end_hpmns + let start_pro_tx_hashes: Vec<&ProTxHash> = + start_hpmns.iter().map(|item| &item.pro_tx_hash).collect(); + let end_pro_tx_hashes: Vec<&ProTxHash> = + end_hpmns.iter().map(|item| &item.pro_tx_hash).collect(); + + let mut added_hpmns = end_hpmns .iter() - .filter(|item| !start_hpmns.contains(item)) + .filter(|item| !start_pro_tx_hashes.contains(&&item.pro_tx_hash)) .map(|a| (*a).clone()) .collect::>(); - let removed_hpmns = start_hpmns + let mut removed_hpmns = start_hpmns .iter() - .filter(|item| !end_hpmns.contains(item)) - .map(|masternode_list_item| masternode_list_item.pro_tx_hash) + .filter(|item| !end_pro_tx_hashes.contains(&&item.pro_tx_hash)) + .map(|masternode_list_item| masternode_list_item.pro_tx_hash.clone()) .collect::>(); + let mut updated_hpmns: Vec<(ProTxHash, DMNStateDiff)> = start_hpmns + .iter() + .filter_map(|start_masternode| { + end_hpmns + .iter() + .find(|end_masternode| { + start_masternode.pro_tx_hash == end_masternode.pro_tx_hash + }) + .and_then(|end_masternode| { + start_masternode + .state + .compare_to_newer_dmn_state(&end_masternode.state) + .map(|diff| (end_masternode.pro_tx_hash.clone(), diff)) + }) + }) + .collect(); + added_masternodes.extend(added_hpmns); removed_masternodes.extend(removed_hpmns); + updated_masternodes.extend(updated_hpmns); MasternodeListDiff { base_height: base_block, block_height: block, added_mns: added_masternodes, removed_mns: removed_masternodes, - updated_mns: vec![], + updated_mns: updated_masternodes, } } }; @@ -2570,7 +2635,31 @@ mod tests { let platform = abci_app.platform; let platform_state = platform.state.read().unwrap(); - assert_ne!(platform_state.hpmn_masternode_list.len(), 100); + // We need to find if any masternode has ever had their keys disabled. + + let hpmns = platform + .drive + .fetch_full_identities( + proposers + .into_iter() + .map(|proposer| proposer.masternode.pro_tx_hash.into_inner()) + .collect(), + None, + ) + .expect("expected to fetch identities"); + + let has_disabled_keys = hpmns.values().any(|identity| { + identity + .as_ref() + .map(|identity| { + identity + .public_keys + .values() + .any(|key| key.disabled_at.is_some()) + }) + .unwrap_or_default() + }); + assert!(has_disabled_keys); } #[test] diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs new file mode 100644 index 00000000000..8bff97a1119 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs @@ -0,0 +1,68 @@ +use crate::BlsPrivateKey; +use dashcore_rpc::json::MasternodeListItem; +use rand::prelude::IteratorRandom; +use rand::rngs::StdRng; +use rand::Rng; + +pub trait UpdateMasternodeListItem { + fn random_keys_update(&mut self, num_fields_to_change: Option, rng: &mut StdRng); +} + +impl UpdateMasternodeListItem for MasternodeListItem { + fn random_keys_update(&mut self, num_fields_to_change: Option, rng: &mut StdRng) { + let mut available_fields: Vec = (0..8) + .filter(|&field_idx| match field_idx { + 4 => self.state.operator_payout_address.is_some(), + 5 => self.state.platform_node_id.is_some(), + 6 => self.state.platform_p2p_port.is_some(), + 7 => self.state.platform_http_port.is_some(), + _ => true, + }) + .collect(); + + let fields_to_change = + num_fields_to_change.unwrap_or(rng.gen_range(1..=available_fields.len())); + + for _ in 0..fields_to_change { + let field_idx = available_fields.iter().choose(rng).cloned().unwrap(); + available_fields.retain(|&idx| idx != field_idx); + + match field_idx { + 0 => self.state.owner_address = rng.gen::<[u8; 20]>(), + 1 => self.state.voting_address = rng.gen::<[u8; 20]>(), + 2 => self.state.payout_address = rng.gen::<[u8; 20]>(), + 3 => { + let private_key_operator = BlsPrivateKey::generate_dash(rng) + .expect("expected to generate a private key"); + let pub_key_operator = private_key_operator + .g1_element() + .expect("expected to get public key") + .to_bytes() + .to_vec(); + self.state.pub_key_operator = pub_key_operator; + } + 4 => { + if let Some(ref mut address) = self.state.operator_payout_address { + *address = rng.gen::<[u8; 20]>(); + } + } + 5 => { + if let Some(ref mut address) = self.state.platform_node_id { + *address = rng.gen::<[u8; 20]>(); + } + } + 6 => { + if let Some(ref mut port) = self.state.platform_p2p_port { + *port = rng.gen_range(1024..=65535); + } + } + 7 => { + if let Some(ref mut port) = self.state.platform_http_port { + *port = rng.gen_range(1024..=65535); + } + } + _ => unreachable!(), + } + } + } +} diff --git a/packages/rs-drive/src/drive/identity/fetch/full_identity.rs b/packages/rs-drive/src/drive/identity/fetch/full_identity.rs index a0da7a3d2bb..58fc9f80ae7 100644 --- a/packages/rs-drive/src/drive/identity/fetch/full_identity.rs +++ b/packages/rs-drive/src/drive/identity/fetch/full_identity.rs @@ -84,7 +84,6 @@ impl Drive { // } /// Fetches identities with all its information from storage. - #[deprecated(since = "0.24.0", note = "please use exact fetching")] pub fn fetch_full_identities( &self, identity_ids: Vec<[u8; 32]>, From 6357df1cb8e3c0471251854718a197b8aad289f5 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Apr 2023 19:36:38 +0700 Subject: [PATCH 03/14] test is working better --- .../rs-drive-abci/src/execution/engine.rs | 3 ++- .../rs-drive-abci/src/execution/helpers.rs | 23 ++++++++++++------- .../src/execution/initialization.rs | 1 + .../execution/masternode_identities/mod.rs | 17 +++++++++++--- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/engine.rs b/packages/rs-drive-abci/src/execution/engine.rs index 138c8708deb..b869ee76a82 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -252,7 +252,6 @@ where let is_initialization = state.initialization_information.is_some(); let mut block_platform_state = state.clone(); - drop(state); // Init block execution context let block_state_info = @@ -295,6 +294,7 @@ where if !is_initialization { // Update the masternode list and create masternode identities and also update the active quorums self.update_core_info( + Some(&state), &mut block_platform_state, block_proposal.core_chain_locked_height, false, @@ -302,6 +302,7 @@ where transaction, )?; } + drop(state); // Update the validator proposed app version self.drive diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index 2a1f4983c16..f55262b23fb 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -247,28 +247,33 @@ where /// there is a problem fetching the masternode list difference or updating the state. fn update_masternode_list( &self, - state: &mut PlatformState, + platform_state: Option<&PlatformState>, + block_platform_state: &mut PlatformState, core_block_height: u32, is_init_chain: bool, block_info: &BlockInfo, transaction: &Transaction, ) -> Result<(), Error> { - if let Some(last_commited_block_info) = state.last_committed_block_info.as_ref() { + if let Some(last_commited_block_info) = + block_platform_state.last_committed_block_info.as_ref() + { if core_block_height == last_commited_block_info.core_height { return Ok(()); // no need to do anything } } - if state.last_committed_block_info.is_some() || is_init_chain { + if block_platform_state.last_committed_block_info.is_some() || is_init_chain { let UpdateStateMasternodeListOutcome { masternode_list_diff, removed_masternodes, - } = self.update_state_masternode_list(state, core_block_height, false)?; + } = + self.update_state_masternode_list(block_platform_state, core_block_height, false)?; self.update_masternode_identities( masternode_list_diff, &removed_masternodes, block_info, - state, + platform_state, + block_platform_state, transaction, )?; @@ -305,20 +310,22 @@ where /// there is a problem updating the masternode list, quorum information, or the state. pub(crate) fn update_core_info( &self, - state: &mut PlatformState, + platform_state: Option<&PlatformState>, + block_platform_state: &mut PlatformState, core_block_height: u32, is_init_chain: bool, block_info: &BlockInfo, transaction: &Transaction, ) -> Result<(), Error> { self.update_masternode_list( - state, + platform_state, + block_platform_state, core_block_height, is_init_chain, block_info, transaction, )?; - self.update_quorum_info(state, core_block_height, false) + self.update_quorum_info(block_platform_state, core_block_height, false) } } diff --git a/packages/rs-drive-abci/src/execution/initialization.rs b/packages/rs-drive-abci/src/execution/initialization.rs index 06e6acebdb8..14d02b7e278 100644 --- a/packages/rs-drive-abci/src/execution/initialization.rs +++ b/packages/rs-drive-abci/src/execution/initialization.rs @@ -53,6 +53,7 @@ where let mut state_cache = self.state.write().unwrap(); self.update_core_info( + None, &mut state_cache, fork_info.since, true, diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index ca4d8e81deb..f60f56aa034 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -34,7 +34,8 @@ where masternode_diff: MasternodeListDiff, removed_masternodes: &BTreeMap, block_info: &BlockInfo, - state: &PlatformState, + platform_state: Option<&PlatformState>, + block_platform_state: &PlatformState, transaction: &Transaction, ) -> Result<(), Error> { let MasternodeListDiff { @@ -69,8 +70,18 @@ where //todo: batch updates as well for update in updated_mns.iter() { self.update_owner_identity(update, block_info, Some(transaction))?; - self.update_voter_identity(update, block_info, state, Some(transaction))?; - self.update_operator_identity(update, block_info, state, Some(transaction))?; + self.update_voter_identity( + update, + block_info, + block_platform_state, + Some(transaction), + )?; + self.update_operator_identity( + update, + block_info, + block_platform_state, + Some(transaction), + )?; } for (_, masternode) in removed_masternodes.iter() { From f2334357ab2aa13eeaa2af745b78bc2d49f4995e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 27 Apr 2023 03:53:00 +0700 Subject: [PATCH 04/14] more work --- packages/rs-dpp/Cargo.toml | 2 +- .../rs-dpp/src/data_contract/data_contract.rs | 19 ++++++++++++++++--- .../payloads/contract/dashpay-contract.json | 3 ++- packages/rs-drive-abci/src/abci/handlers.rs | 1 - 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 60b1c0866ba..16d7f6a9f06 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -61,6 +61,6 @@ tokio = { version = "1.17", features = ["full"] } pretty_assertions = { version = "1.3.0" } [features] -default = ["fixtures-and-mocks"] +default = ["fixtures-and-mocks", "cbor"] cbor = ["ciborium"] fixtures-and-mocks = ["mockall"] diff --git a/packages/rs-dpp/src/data_contract/data_contract.rs b/packages/rs-dpp/src/data_contract/data_contract.rs index f9208367ac7..7aac47d884f 100644 --- a/packages/rs-dpp/src/data_contract/data_contract.rs +++ b/packages/rs-dpp/src/data_contract/data_contract.rs @@ -818,7 +818,8 @@ mod test { } #[test] - fn conversion_to_buffer_from_buffer() { + #[cfg(feature = "cbor")] + fn conversion_to_cbor_buffer_from_cbor_buffer() { init(); let data_contract = get_data_contract_fixture(None); @@ -848,7 +849,8 @@ mod test { } #[test] - fn conversion_to_buffer_from_buffer_high_version() { + #[cfg(feature = "cbor")] + fn conversion_to_cbor_buffer_from_cbor_buffer_high_version() { init(); let mut data_contract = get_data_contract_fixture(None); data_contract.protocol_version = 10000; @@ -880,7 +882,7 @@ mod test { } #[test] - fn conversion_to_buffer_from_buffer_too_high_version() { + fn conversion_to_cbor_buffer_from_cbor_buffer_too_high_version() { init(); let data_contract = get_data_contract_fixture(None); @@ -1046,4 +1048,15 @@ mod test { assert_eq!(hex::encode(data_contract_cbor), hex::encode(serialized)); } + + #[test] + fn serialize_deterministically_serialize_to_bincode() { + let data_contract_cbor = get_data_contract_cbor_bytes(); + + let data_contract = DataContract::from_cbor_buffer(&data_contract_cbor).unwrap(); + + let serialized = data_contract.to_cbor_buffer().unwrap(); + + assert_eq!(hex::encode(data_contract_cbor), hex::encode(serialized)); + } } diff --git a/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json b/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json index 48a48db48c2..00ef3317dec 100644 --- a/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json +++ b/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json @@ -157,7 +157,8 @@ "type": "array", "byteArray": true, "minItems": 32, - "maxItems": 32 + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier" }, "encryptedPublicKey": { "type": "array", diff --git a/packages/rs-drive-abci/src/abci/handlers.rs b/packages/rs-drive-abci/src/abci/handlers.rs index 9c20565e427..e0ae6eb773e 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -175,7 +175,6 @@ where let tx_results = tx_results.into_iter().flatten().collect(); - // TODO: implement all fields, including tx processing; for now, just leaving bare minimum let response = ResponsePrepareProposal { tx_results, From 59f5a77a2f2b049c558ef7f89d5228251dae0334 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 27 Apr 2023 04:16:08 +0700 Subject: [PATCH 05/14] refactored strategy tests --- .../tests/strategy_tests/execution.rs | 707 ++++++ .../tests/strategy_tests/frequency.rs | 72 + .../tests/strategy_tests/main.rs | 1940 +---------------- .../tests/strategy_tests/masternodes.rs | 264 +++ .../tests/strategy_tests/operations.rs | 57 + .../tests/strategy_tests/signer.rs | 79 + .../tests/strategy_tests/strategy.rs | 662 ++++++ .../tests/strategy_tests/transitions.rs | 203 ++ .../strategy_tests/upgrade_fork_tests.rs | 11 +- 9 files changed, 2072 insertions(+), 1923 deletions(-) create mode 100644 packages/rs-drive-abci/tests/strategy_tests/execution.rs create mode 100644 packages/rs-drive-abci/tests/strategy_tests/frequency.rs create mode 100644 packages/rs-drive-abci/tests/strategy_tests/masternodes.rs create mode 100644 packages/rs-drive-abci/tests/strategy_tests/operations.rs create mode 100644 packages/rs-drive-abci/tests/strategy_tests/signer.rs create mode 100644 packages/rs-drive-abci/tests/strategy_tests/strategy.rs create mode 100644 packages/rs-drive-abci/tests/strategy_tests/transitions.rs diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs new file mode 100644 index 00000000000..4dbd6ee48e4 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -0,0 +1,707 @@ +use crate::masternodes; +use crate::masternodes::{GenerateTestMasternodeUpdates, MasternodeListItemWithUpdates}; +use crate::operations::FinalizeBlockOperation::IdentityAddKeys; +use crate::signer::SimpleSigner; +use crate::strategy::{ + ChainExecutionOutcome, ChainExecutionParameters, Strategy, StrategyRandomness, + ValidatorVersionMigration, +}; +use dashcore::hashes::hex::ToHex; +use dashcore::hashes::Hash; +use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore_rpc_json::{ + Bip9SoftforkInfo, Bip9SoftforkStatus, DMNStateDiff, ExtendedQuorumDetails, MasternodeListDiff, + MasternodeListItem, QuorumInfoResult, QuorumType, +}; +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; +use drive_abci::abci::mimic::MimicExecuteBlockOutcome; +use drive_abci::abci::AbciApplication; +use drive_abci::config::PlatformConfig; +use drive_abci::execution::fee_pools::epoch::{EpochInfo, EPOCH_CHANGE_TIME_MS}; +use drive_abci::execution::test_quorum::TestQuorumInfo; +use drive_abci::platform::Platform; +use drive_abci::rpc::core::MockCoreRPCLike; +use drive_abci::test::fixture::abci::static_init_chain_request; +use rand::prelude::{SliceRandom, StdRng}; +use rand::SeedableRng; +use std::collections::{BTreeMap, HashMap}; +use tenderdash_abci::proto::abci::{ResponseInitChain, ValidatorSetUpdate}; +use tenderdash_abci::proto::crypto::public_key::Sum::Bls12381; + +pub(crate) fn run_chain_for_strategy( + platform: &mut Platform, + block_count: u64, + strategy: Strategy, + config: PlatformConfig, + seed: u64, +) -> ChainExecutionOutcome { + let quorum_count = strategy.quorum_count; // We assume 24 quorums + let quorum_size = config.quorum_size; + + let mut rng = StdRng::seed_from_u64(seed); + + let any_changes_in_strategy = strategy.proposer_strategy.any_is_set(); + let updated_proposers_in_strategy = strategy.proposer_strategy.updated_any_masternode_types(); + + let ( + initial_masternodes_with_updates, + initial_hpmns_with_updates, + with_extra_masternodes_with_updates, + with_extra_hpmns_with_updates, + ) = if any_changes_in_strategy { + let approximate_end_core_height = + ((block_count as f64) * strategy.core_height_increase.average_event_count()) as u32; + let end_core_height = approximate_end_core_height * 2; //let's be safe + let generate_updates = if updated_proposers_in_strategy { + Some(GenerateTestMasternodeUpdates { + start_core_height: config.abci.genesis_core_height, + end_core_height, + update_masternode_frequency: &strategy.proposer_strategy.updated_mastenodes, + update_hpmn_frequency: &strategy.proposer_strategy.updated_hpmns, + }) + } else { + None + }; + + let (initial_masternodes, initial_hpmns) = masternodes::generate_test_masternodes( + strategy.extra_normal_mns, + strategy.total_hpmns, + generate_updates, + &mut rng, + ); + let mut all_masternodes = initial_masternodes.clone(); + let mut all_hpmns = initial_hpmns.clone(); + + let (extra_masternodes_by_block, extra_hpmns_by_block): ( + BTreeMap>, + BTreeMap>, + ) = (config.abci.genesis_core_height..end_core_height) + .map(|height| { + let new_masternodes = strategy + .proposer_strategy + .new_masternodes + .events_if_hit(&mut rng); + let new_hpmns = strategy.proposer_strategy.new_hpmns.events_if_hit(&mut rng); + let generate_updates = if updated_proposers_in_strategy { + Some(GenerateTestMasternodeUpdates { + start_core_height: height + 1, + end_core_height, + update_masternode_frequency: &strategy.proposer_strategy.updated_mastenodes, + update_hpmn_frequency: &strategy.proposer_strategy.updated_hpmns, + }) + } else { + None + }; + + let (extra_masternodes_by_block, extra_hpmns_by_block) = + masternodes::generate_test_masternodes( + new_masternodes, + new_hpmns, + generate_updates, + &mut rng, + ); + + if strategy.proposer_strategy.removed_masternodes.is_set() { + let removed_masternodes_count = strategy + .proposer_strategy + .removed_masternodes + .events_if_hit(&mut rng); + let removed_count = + std::cmp::min(removed_masternodes_count as usize, all_masternodes.len()); + all_masternodes.drain(0..removed_count); + } + + if strategy.proposer_strategy.removed_hpmns.is_set() { + let removed_hpmns_count = strategy + .proposer_strategy + .removed_hpmns + .events_if_hit(&mut rng); + let removed_count = + std::cmp::min(removed_hpmns_count as usize, all_hpmns.len()); + all_hpmns.drain(0..removed_count); + } + + all_masternodes.extend(extra_masternodes_by_block.clone()); + all_hpmns.extend(extra_hpmns_by_block.clone()); + ( + (height, all_masternodes.clone()), + (height, all_hpmns.clone()), + ) + }) + .unzip(); + ( + initial_masternodes, + initial_hpmns, + extra_masternodes_by_block, + extra_hpmns_by_block, + ) + } else { + let (initial_masternodes, initial_hpmns) = masternodes::generate_test_masternodes( + strategy.extra_normal_mns, + strategy.total_hpmns, + None, + &mut rng, + ); + ( + initial_masternodes, + initial_hpmns, + BTreeMap::new(), + BTreeMap::new(), + ) + }; + + let mut all_hpmns_with_updates = with_extra_hpmns_with_updates + .iter() + .max_by_key(|(key, _)| *key) + .map(|(_, v)| v.clone()) + .unwrap_or(initial_hpmns_with_updates.clone()); + + let total_quorums = if strategy.rotate_quorums { + quorum_count * 10 + } else { + quorum_count + }; + + let quorums = masternodes::generate_test_quorums( + total_quorums as usize, + initial_hpmns_with_updates + .iter() + .map(|hpmn| &hpmn.masternode), + quorum_size as usize, + &mut rng, + ); + + let quorums_clone: HashMap = quorums + .keys() + .map(|quorum_hash| { + ( + *quorum_hash, + ExtendedQuorumDetails { + creation_height: 0, + quorum_index: None, + mined_block_hash: Default::default(), + num_valid_members: 0, + health_ratio: 0.0, + }, + ) + }) + .collect(); + + let start_core_height = platform.config.abci.genesis_core_height; + + platform + .core_rpc + .expect_get_fork_info() + .returning(move |_| { + Ok(Some(Bip9SoftforkInfo { + status: Bip9SoftforkStatus::Active, + bit: None, + start_time: 0, + timeout: 0, + since: start_core_height, // block height 1 + statistics: None, + })) + }); + + platform + .core_rpc + .expect_get_quorum_listextended() + .returning(move |core_height: Option| { + if !strategy.rotate_quorums { + Ok(dashcore_rpc::dashcore_rpc_json::ExtendedQuorumListResult { + quorums_by_type: HashMap::from([( + QuorumType::Llmq100_67, + quorums_clone.clone(), + )]), + }) + } else { + let core_height = core_height.expect("expected a core height"); + // if we rotate quorums we shouldn't give back the same ones every time + let start_range = core_height / 24; + let end_range = start_range + quorum_count as u32; + let start_range = start_range % total_quorums as u32; + let end_range = end_range % total_quorums as u32; + + let quorums = if end_range > start_range { + quorums_clone + .iter() + .skip(start_range as usize) + .take((end_range - start_range) as usize) + .map(|(quorum_hash, quorum)| (quorum_hash.clone(), quorum.clone())) + .collect() + } else { + let first_range = quorums_clone + .iter() + .skip(start_range as usize) + .take((total_quorums as u32 - start_range) as usize); + let second_range = quorums_clone.iter().take(end_range as usize); + first_range + .chain(second_range) + .map(|(quorum_hash, quorum)| (quorum_hash.clone(), quorum.clone())) + .collect() + }; + + Ok(dashcore_rpc::dashcore_rpc_json::ExtendedQuorumListResult { + quorums_by_type: HashMap::from([(QuorumType::Llmq100_67, quorums)]), + }) + } + }); + + let quorums_info: HashMap = quorums + .iter() + .map(|(quorum_hash, test_quorum_info)| (*quorum_hash, test_quorum_info.into())) + .collect(); + + platform + .core_rpc + .expect_get_quorum_info() + .returning(move |_, quorum_hash: &QuorumHash, _| { + Ok(quorums_info + .get(quorum_hash) + .unwrap_or_else(|| panic!("expected to get quorum {}", quorum_hash.to_hex())) + .clone()) + }); + + let initial_all_masternodes: Vec<_> = initial_masternodes_with_updates + .clone() + .into_iter() + .chain(initial_hpmns_with_updates.clone().into_iter()) + .collect(); + + platform + .core_rpc + .expect_get_protx_diff_with_masternodes() + .returning(move |base_block, block| { + let diff = if base_block == 0 { + MasternodeListDiff { + base_height: base_block, + block_height: block, + added_mns: initial_all_masternodes + .iter() + .map(|masternode_list_item| masternode_list_item.masternode.clone()) + .collect(), + removed_mns: vec![], + updated_mns: vec![], + } + } else { + if !any_changes_in_strategy { + // no changes + MasternodeListDiff { + base_height: base_block, + block_height: block, + added_mns: vec![], + removed_mns: vec![], + updated_mns: vec![], + } + } else { + // we need to figure out the difference of proposers between two heights + // we need to figure out the difference of proposers between two heights + let start_masternodes = with_extra_masternodes_with_updates + .get(&base_block) + .expect("expected start proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(base_block)) + .collect::>(); + let end_masternodes = with_extra_masternodes_with_updates + .get(&block) + .expect("expected end proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(block)) + .collect::>(); + + let start_pro_tx_hashes: Vec<&ProTxHash> = start_masternodes + .iter() + .map(|item| &item.pro_tx_hash) + .collect(); + let end_pro_tx_hashes: Vec<&ProTxHash> = end_masternodes + .iter() + .map(|item| &item.pro_tx_hash) + .collect(); + + let mut added_masternodes = end_masternodes + .iter() + .filter(|item| !start_pro_tx_hashes.contains(&&item.pro_tx_hash)) + .map(|a| (*a).clone()) + .collect::>(); + + let mut removed_masternodes = start_masternodes + .iter() + .filter(|item| !end_pro_tx_hashes.contains(&&item.pro_tx_hash)) + .map(|masternode_list_item| masternode_list_item.pro_tx_hash.clone()) + .collect::>(); + + let mut updated_masternodes: Vec<(ProTxHash, DMNStateDiff)> = start_masternodes + .iter() + .filter_map(|start_masternode| { + end_masternodes + .iter() + .find(|end_masternode| { + start_masternode.pro_tx_hash == end_masternode.pro_tx_hash + }) + .and_then(|end_masternode| { + start_masternode + .state + .compare_to_newer_dmn_state(&end_masternode.state) + .map(|diff| (end_masternode.pro_tx_hash.clone(), diff)) + }) + }) + .collect(); + + let start_hpmns = with_extra_hpmns_with_updates + .get(&base_block) + .expect("expected start proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(base_block)) + .collect::>(); + let end_hpmns = with_extra_hpmns_with_updates + .get(&block) + .expect("expected end proposers") + .iter() + .map(|masternode| masternode.get_state_at_height(block)) + .collect::>(); + let start_pro_tx_hashes: Vec<&ProTxHash> = + start_hpmns.iter().map(|item| &item.pro_tx_hash).collect(); + let end_pro_tx_hashes: Vec<&ProTxHash> = + end_hpmns.iter().map(|item| &item.pro_tx_hash).collect(); + + let mut added_hpmns = end_hpmns + .iter() + .filter(|item| !start_pro_tx_hashes.contains(&&item.pro_tx_hash)) + .map(|a| (*a).clone()) + .collect::>(); + + let mut removed_hpmns = start_hpmns + .iter() + .filter(|item| !end_pro_tx_hashes.contains(&&item.pro_tx_hash)) + .map(|masternode_list_item| masternode_list_item.pro_tx_hash.clone()) + .collect::>(); + + let mut updated_hpmns: Vec<(ProTxHash, DMNStateDiff)> = start_hpmns + .iter() + .filter_map(|start_masternode| { + end_hpmns + .iter() + .find(|end_masternode| { + start_masternode.pro_tx_hash == end_masternode.pro_tx_hash + }) + .and_then(|end_masternode| { + start_masternode + .state + .compare_to_newer_dmn_state(&end_masternode.state) + .map(|diff| (end_masternode.pro_tx_hash.clone(), diff)) + }) + }) + .collect(); + + added_masternodes.extend(added_hpmns); + removed_masternodes.extend(removed_hpmns); + updated_masternodes.extend(updated_hpmns); + + MasternodeListDiff { + base_height: base_block, + block_height: block, + added_mns: added_masternodes, + removed_mns: removed_masternodes, + updated_mns: updated_masternodes, + } + } + }; + + Ok(diff) + }); + + start_chain_for_strategy( + platform, + block_count, + all_hpmns_with_updates, + quorums, + strategy, + config, + rng, + ) +} + +pub(crate) fn start_chain_for_strategy( + platform: &Platform, + block_count: u64, + proposers_with_updates: Vec, + quorums: BTreeMap, + strategy: Strategy, + config: PlatformConfig, + mut rng: StdRng, +) -> ChainExecutionOutcome { + let abci_application = AbciApplication::new(platform).expect("expected new abci application"); + + let quorum_hashes: Vec<&QuorumHash> = quorums.keys().collect(); + + let mut current_quorum_hash = **quorum_hashes + .choose(&mut rng) + .expect("expected quorums to be initialized"); + + let current_quorum_with_test_info = quorums + .get(¤t_quorum_hash) + .expect("expected a quorum to be found"); + + // init chain + let mut init_chain_request = static_init_chain_request(); + + init_chain_request.initial_core_height = config.abci.genesis_core_height; + init_chain_request.validator_set = Some(ValidatorSetUpdate { + validator_updates: current_quorum_with_test_info + .validator_set + .iter() + .map( + |validator_in_quorum| tenderdash_abci::proto::abci::ValidatorUpdate { + pub_key: Some(tenderdash_abci::proto::crypto::PublicKey { + sum: Some(Bls12381(validator_in_quorum.public_key.to_bytes().to_vec())), + }), + power: 100, + pro_tx_hash: validator_in_quorum.pro_tx_hash.to_vec(), + node_address: "".to_string(), + }, + ) + .collect(), + threshold_public_key: Some(tenderdash_abci::proto::crypto::PublicKey { + sum: Some(Bls12381( + current_quorum_with_test_info.public_key.to_bytes().to_vec(), + )), + }), + quorum_hash: current_quorum_hash.to_vec(), + }); + + abci_application.start_transaction(); + + let binding = abci_application.transaction.read().unwrap(); + + let transaction = binding.as_ref().expect("expected a transaction"); + + let ResponseInitChain { + initial_core_height, + .. + } = platform + .init_chain(init_chain_request, transaction) + .expect("should init chain"); + + // initialization will change the current quorum hash + current_quorum_hash = platform + .state + .read() + .unwrap() + .current_validator_set_quorum_hash; + + platform.create_mn_shares_contract(Some(transaction)); + + drop(binding); + + continue_chain_for_strategy( + abci_application, + ChainExecutionParameters { + block_start: 1, + core_height_start: initial_core_height, + block_count, + proposers: proposers_with_updates, + quorums, + current_quorum_hash, + current_proposer_versions: None, + current_time_ms: 1681094380000, + }, + strategy, + config, + StrategyRandomness::RNGEntropy(rng), + ) +} + +pub(crate) fn continue_chain_for_strategy( + abci_app: AbciApplication, + chain_execution_parameters: ChainExecutionParameters, + mut strategy: Strategy, + config: PlatformConfig, + seed: StrategyRandomness, +) -> ChainExecutionOutcome { + let platform = abci_app.platform; + let ChainExecutionParameters { + block_start, + core_height_start, + block_count, + proposers: proposers_with_updates, + quorums, + mut current_quorum_hash, + current_proposer_versions, + mut current_time_ms, + } = chain_execution_parameters; + let mut rng = match seed { + StrategyRandomness::SeedEntropy(seed) => StdRng::seed_from_u64(seed), + StrategyRandomness::RNGEntropy(rng) => rng, + }; + let quorum_size = config.quorum_size; + let quorum_rotation_block_count = config.validator_set_quorum_rotation_block_count as u64; + let first_block_time = 0; + let mut current_identities = vec![]; + let mut signer = SimpleSigner::default(); + let mut i = 0; + + let blocks_per_epoch = EPOCH_CHANGE_TIME_MS / config.block_spacing_ms; + + let proposer_versions = current_proposer_versions.unwrap_or( + strategy.upgrading_info.as_ref().map(|upgrading_info| { + upgrading_info.apply_to_proposers( + proposers_with_updates + .iter() + .map(|masternode_list_item| masternode_list_item.pro_tx_hash()) + .collect(), + blocks_per_epoch, + &mut rng, + ) + }), + ); + + let mut current_core_height = core_height_start; + + let mut total_withdrawals = vec![]; + + let mut current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); + + let mut next_quorum_hash = current_quorum_hash; + + for block_height in block_start..(block_start + block_count) { + let needs_rotation_on_next_block = block_height % quorum_rotation_block_count == 0; + if needs_rotation_on_next_block { + let quorum_hashes: Vec<&QuorumHash> = quorums.keys().collect(); + + next_quorum_hash = **quorum_hashes.choose(&mut rng).unwrap(); + } + let epoch_info = EpochInfo::calculate( + first_block_time, + current_time_ms, + platform + .state + .read() + .expect("lock is poisoned") + .last_committed_block_info + .as_ref() + .map(|block_info| block_info.time_ms), + ) + .expect("should calculate epoch info"); + + current_core_height += strategy.core_height_increase.events_if_hit(&mut rng) as u32; + + let block_info = BlockInfo { + time_ms: current_time_ms, + height: block_height, + core_height: current_core_height, + epoch: Epoch::new(epoch_info.current_epoch_index).unwrap(), + }; + if current_quorum_with_test_info.quorum_hash != current_quorum_hash { + current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); + } + + let proposer = current_quorum_with_test_info + .validator_set + .get(i as usize) + .unwrap(); + let (state_transitions, finalize_block_operations) = strategy + .state_transitions_for_block_with_new_identities( + platform, + &block_info, + &mut current_identities, + &mut signer, + &mut rng, + ); + + let proposed_version = proposer_versions + .as_ref() + .map(|proposer_versions| { + let ValidatorVersionMigration { + current_protocol_version, + next_protocol_version, + change_block_height, + } = proposer_versions + .get(&proposer.pro_tx_hash) + .expect("expected to have version"); + if &block_height >= change_block_height { + *next_protocol_version + } else { + *current_protocol_version + } + }) + .unwrap_or(1); + + let MimicExecuteBlockOutcome { + withdrawal_transactions: mut withdrawals_this_block, + next_validator_set_hash, + } = abci_app + .mimic_execute_block( + proposer.pro_tx_hash.into_inner(), + current_quorum_with_test_info, + proposed_version, + block_info, + false, + state_transitions, + ) + .expect("expected to execute a block"); + + total_withdrawals.append(&mut withdrawals_this_block); + + for finalize_block_operation in finalize_block_operations { + match finalize_block_operation { + IdentityAddKeys(identifier, keys) => { + let identity = current_identities + .iter_mut() + .find(|identity| identity.id == identifier) + .expect("expected to find an identity"); + identity + .public_keys + .extend(keys.into_iter().map(|key| (key.id, key))); + } + } + } + signer.commit_block_keys(); + + current_time_ms += config.block_spacing_ms; + + let next_quorum_hash = QuorumHash::from_inner(next_validator_set_hash.try_into().unwrap()); + if current_quorum_hash != next_quorum_hash { + current_quorum_hash = next_quorum_hash; + i = 0; + } else { + i += 1; + i %= quorum_size; //todo: this could be variable + } + } + + let masternode_identity_balances = platform + .drive + .fetch_identities_balances( + &proposers_with_updates + .iter() + .map(|proposer| proposer.pro_tx_hash().into_inner()) + .collect(), + None, + ) + .expect("expected to get balances"); + + let end_epoch_index = platform + .state + .read() + .expect("lock is poisoned") + .last_committed_block_info + .as_ref() + .unwrap() + .epoch + .index; + + ChainExecutionOutcome { + abci_app, + masternode_identity_balances, + identities: current_identities, + proposers: proposers_with_updates, + quorums, + current_quorum_hash, + current_proposer_versions: proposer_versions, + end_epoch_index, + end_time_ms: current_time_ms, + strategy, + withdrawals: total_withdrawals, + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/frequency.rs b/packages/rs-drive-abci/tests/strategy_tests/frequency.rs new file mode 100644 index 00000000000..71d8b98d2ac --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/frequency.rs @@ -0,0 +1,72 @@ +use rand::distributions::{Distribution, Uniform}; +use rand::prelude::StdRng; +use rand::Rng; +use std::ops::Range; + +#[derive(Clone, Debug, Default)] +pub struct Frequency { + pub times_per_block_range: Range, //insertion count when block is chosen + pub chance_per_block: Option, //chance of insertion if set +} + +impl Frequency { + pub fn is_set(&self) -> bool { + self.chance_per_block.is_some() || !self.times_per_block_range.is_empty() + } + + pub fn check_hit(&self, rng: &mut StdRng) -> bool { + match self.chance_per_block { + None => true, + Some(chance) => rng.gen_bool(chance), + } + } + + pub fn events(&self, rng: &mut StdRng) -> u16 { + if self.times_per_block_range.is_empty() { + 0 + } else { + rng.gen_range(self.times_per_block_range.clone()) + } + } + + pub fn events_if_hit(&self, rng: &mut StdRng) -> u16 { + if self.check_hit(rng) { + self.events(rng) + } else { + 0 + } + } + + pub fn average_event_count(&self) -> f64 { + if let Some(chance_per_block) = self.chance_per_block { + let avg_times_per_block_range = + (self.times_per_block_range.start + self.times_per_block_range.end) as f64 / 2.0; + avg_times_per_block_range * chance_per_block + } else { + (self.times_per_block_range.start + self.times_per_block_range.end) as f64 / 2.0 + } + } + + pub fn pick_in_range(&self, rng: &mut impl Rng, range: Range) -> Vec { + if !self.is_set() || range.is_empty() { + return vec![]; + } + let mut picked_numbers = Vec::new(); + let chance_per_block_hit = self + .chance_per_block + .map_or(true, |chance| rng.gen_bool(chance)); + + if chance_per_block_hit { + let times_per_block_dist = Uniform::from(self.times_per_block_range.clone()); + let num_elements_to_pick = times_per_block_dist.sample(rng); + let range_dist = Uniform::from(range); + + picked_numbers = rng + .sample_iter(&range_dist) + .take(num_elements_to_pick as usize) + .collect(); + } + + picked_numbers + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 8b3afc63482..85ce4315198 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -33,7 +33,7 @@ extern crate core; use anyhow::anyhow; -use dashcore::{signer, Network, PrivateKey, ProTxHash, QuorumHash, Txid}; +use dashcore::{Network, PrivateKey, ProTxHash, QuorumHash, Txid}; use dashcore_rpc::dashcore_rpc_json::{ Bip9SoftforkInfo, Bip9SoftforkStatus, DMNState, DMNStateDiff, ExtendedQuorumDetails, MasternodeListDiffWithMasternodes, MasternodeListItem, MasternodeType, QuorumInfoResult, @@ -46,7 +46,6 @@ use dpp::document::document_transition::{ Action, DocumentCreateTransition, DocumentDeleteTransition, DocumentReplaceTransition, }; use dpp::document::DocumentsBatchTransition; -use dpp::identity::signer::Signer; use dpp::block::block_info::BlockInfo; use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransition; @@ -69,10 +68,10 @@ use drive::dpp::util::deserializer::ProtocolVersion; use drive::drive::defaults::PROTOCOL_VERSION; use drive::drive::flags::StorageFlags::SingleEpoch; -use crate::FinalizeBlockOperation::IdentityAddKeys; use dashcore::hashes::hex::ToHex; use dashcore::hashes::Hash; use dashcore::secp256k1::SecretKey; +use operations::FinalizeBlockOperation::IdentityAddKeys; use dpp::block::epoch::Epoch; use dpp::data_contract::document_type::random_document_type::RandomDocumentTypeParameters; @@ -104,1943 +103,48 @@ use drive_abci::rpc::core::MockCoreRPCLike; use drive_abci::test::fixture::abci::static_init_chain_request; use drive_abci::test::helpers::setup::TestPlatformBuilder; use drive_abci::{config::PlatformConfig, test::helpers::setup::TempPlatform}; +use frequency::Frequency; +use masternodes::GenerateTestMasternodeUpdates; use rand::distributions::Distribution; use rand::distributions::Uniform; use rand::prelude::IteratorRandom; use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; +use signer::SimpleSigner; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; use std::net::SocketAddr; use std::ops::Range; use std::str::FromStr; +use strategy::{ + ChainExecutionOutcome, ChainExecutionParameters, Strategy, StrategyRandomness, + ValidatorVersionMigration, +}; use tenderdash_abci::proto::abci::{ResponseInitChain, ValidatorSetUpdate}; use tenderdash_abci::proto::crypto::public_key::Sum::Bls12381; +mod execution; +mod frequency; mod masternode_list_item_helpers; +mod masternodes; +mod operations; +mod signer; +mod strategy; +mod transitions; mod upgrade_fork_tests; -#[derive(Clone, Debug, Default)] -pub struct Frequency { - pub times_per_block_range: Range, //insertion count when block is chosen - pub chance_per_block: Option, //chance of insertion if set -} - -impl Frequency { - fn is_set(&self) -> bool { - self.chance_per_block.is_some() || !self.times_per_block_range.is_empty() - } - - fn check_hit(&self, rng: &mut StdRng) -> bool { - match self.chance_per_block { - None => true, - Some(chance) => rng.gen_bool(chance), - } - } - - fn events(&self, rng: &mut StdRng) -> u16 { - if self.times_per_block_range.is_empty() { - 0 - } else { - rng.gen_range(self.times_per_block_range.clone()) - } - } - - fn events_if_hit(&self, rng: &mut StdRng) -> u16 { - if self.check_hit(rng) { - self.events(rng) - } else { - 0 - } - } - - fn average_event_count(&self) -> f64 { - if let Some(chance_per_block) = self.chance_per_block { - let avg_times_per_block_range = - (self.times_per_block_range.start + self.times_per_block_range.end) as f64 / 2.0; - avg_times_per_block_range * chance_per_block - } else { - (self.times_per_block_range.start + self.times_per_block_range.end) as f64 / 2.0 - } - } - - pub fn pick_in_range(&self, rng: &mut impl Rng, range: Range) -> Vec { - if !self.is_set() || range.is_empty() { - return vec![]; - } - let mut picked_numbers = Vec::new(); - let chance_per_block_hit = self - .chance_per_block - .map_or(true, |chance| rng.gen_bool(chance)); - - if chance_per_block_hit { - let times_per_block_dist = Uniform::from(self.times_per_block_range.clone()); - let num_elements_to_pick = times_per_block_dist.sample(rng); - let range_dist = Uniform::from(range); - - picked_numbers = rng - .sample_iter(&range_dist) - .take(num_elements_to_pick as usize) - .collect(); - } - - picked_numbers - } -} - -#[derive(Clone, Debug)] -pub enum DocumentAction { - DocumentActionInsert, - DocumentActionDelete, - DocumentActionReplace, -} - -#[derive(Clone, Debug)] -pub struct DocumentOp { - pub contract: Contract, - pub document_type: DocumentType, - pub action: DocumentAction, -} - -#[derive(Clone, Debug)] -pub struct Operation { - op_type: OperationType, - frequency: Frequency, -} - -#[derive(Clone, Debug)] -pub enum IdentityUpdateOp { - IdentityUpdateAddKeys(u16), - IdentityUpdateDisableKey(u16), -} - -pub type DocumentTypeNewFieldsOptionalCountRange = Range; -pub type DocumentTypeCount = Range; - -#[derive(Clone, Debug)] -pub enum DataContractUpdateOp { - DataContractNewDocumentTypes(RandomDocumentTypeParameters), // How many fields should it have - DataContractNewOptionalFields(DocumentTypeNewFieldsOptionalCountRange, DocumentTypeCount), // How many new fields on how many document types -} - -#[derive(Clone, Debug)] -pub enum OperationType { - Document(DocumentOp), - IdentityTopUp, - IdentityUpdate(IdentityUpdateOp), - IdentityWithdrawal, - ContractCreate(RandomDocumentTypeParameters, DocumentTypeCount), - ContractUpdate(DataContractUpdateOp), -} - -#[derive(Clone, Debug)] -pub enum FinalizeBlockOperation { - IdentityAddKeys(Identifier, Vec), -} - -/// This simple signer is only to be used in tests -#[derive(Default, Debug)] -pub struct SimpleSigner { - /// Private keys is a map from the public key to the Private key bytes - private_keys: HashMap>, - /// Private keys to be added at the end of a block - private_keys_in_creation: HashMap>, -} - -impl SimpleSigner { - fn add_key(&mut self, public_key: IdentityPublicKey, private_key: Vec) { - self.private_keys.insert(public_key, private_key); - } - - fn add_keys)>>(&mut self, keys: I) { - self.private_keys.extend(keys) - } - - fn commit_block_keys(&mut self) { - self.private_keys - .extend(self.private_keys_in_creation.drain()) - } -} - -impl Signer for SimpleSigner { - fn sign( - &self, - identity_public_key: &IdentityPublicKey, - data: &[u8], - ) -> Result { - let private_key = self - .private_keys - .get(identity_public_key) - .or_else(|| self.private_keys_in_creation.get(identity_public_key)) - .ok_or(ProtocolError::InvalidSignaturePublicKeyError( - InvalidSignaturePublicKeyError::new(identity_public_key.data.to_vec()), - ))?; - match identity_public_key.key_type { - KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => { - let signature = signer::sign(data, private_key)?; - Ok(signature.to_vec().into()) - } - KeyType::BLS12_381 => { - let pk = - bls_signatures::PrivateKey::from_bytes(private_key, false).map_err(|_e| { - ProtocolError::Error(anyhow!("bls private key from bytes isn't correct")) - })?; - Ok(pk.sign(data).to_bytes().to_vec().into()) - } - KeyType::EDDSA_25519_HASH160 => { - let key: [u8; 32] = private_key.clone().try_into().expect("expected 32 bytes"); - let pk = ed25519_dalek::SigningKey::try_from(&key).map_err(|_e| { - ProtocolError::Error(anyhow!( - "eddsa 25519 private key from bytes isn't correct" - )) - })?; - Ok(pk.sign(data).to_vec().into()) - } - // the default behavior from - // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187 - // is to return the error for the BIP13_SCRIPT_HASH - KeyType::BIP13_SCRIPT_HASH => Err(ProtocolError::InvalidIdentityPublicKeyTypeError( - InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type), - )), - } - } -} - pub type BlockHeight = u64; -#[derive(Clone, Debug, Default)] -pub struct MasternodeListChangesStrategy { - /// How many new masternodes on average per core chain lock increase - new_hpmns: Frequency, - /// How many masternodes leave the system - removed_hpmns: Frequency, - /// How many masternodes update a key - updated_hpmns: Frequency, - /// How many masternodes are banned - banned_hpmns: Frequency, - /// How many masternodes are unbanned - unbanned_hpmns: Frequency, - /// How many new masternodes on average per core chain lock increase - new_masternodes: Frequency, - /// How many masternodes leave the system - removed_masternodes: Frequency, - /// How many masternodes update a key - updated_mastenodes: Frequency, - /// How many masternodes are banned - banned_masternodes: Frequency, - /// How many masternodes are unbanned - unbanned_masternodes: Frequency, -} - -impl MasternodeListChangesStrategy { - fn any_is_set(&self) -> bool { - self.new_hpmns.is_set() - || self.removed_hpmns.is_set() - || self.updated_hpmns.is_set() - || self.banned_hpmns.is_set() - || self.unbanned_hpmns.is_set() - || self.new_masternodes.is_set() - || self.removed_masternodes.is_set() - || self.updated_mastenodes.is_set() - || self.banned_masternodes.is_set() - || self.unbanned_masternodes.is_set() - } - - fn removed_any_masternode_types(&self) -> bool { - self.removed_masternodes.is_set() || self.removed_hpmns.is_set() - } - - fn updated_any_masternode_types(&self) -> bool { - self.updated_mastenodes.is_set() || self.updated_hpmns.is_set() - } - - fn added_any_masternode_types(&self) -> bool { - self.new_masternodes.is_set() || self.new_hpmns.is_set() - } - - fn any_update_is_set(&self) -> bool { - self.updated_hpmns.is_set() - || self.banned_hpmns.is_set() - || self.unbanned_hpmns.is_set() | self.updated_mastenodes.is_set() - || self.banned_masternodes.is_set() - || self.unbanned_masternodes.is_set() - } -} - -#[derive(Clone, Debug)] -pub struct Strategy { - contracts_with_updates: Vec<(Contract, Option>)>, - operations: Vec, - identities_inserts: Frequency, - total_hpmns: u16, - extra_normal_mns: u16, - quorum_count: u16, - upgrading_info: Option, - core_height_increase: Frequency, - proposer_strategy: MasternodeListChangesStrategy, - rotate_quorums: bool, -} - -#[derive(Clone, Debug)] -pub struct UpgradingInfo { - current_protocol_version: ProtocolVersion, - proposed_protocol_versions_with_weight: Vec<(ProtocolVersion, u16)>, - /// The upgrade three quarters life is the expected amount of blocks in the window - /// for three quarters of the network to upgrade - /// if it is 1, there is a 50/50% chance that the network will upgrade in the first window - /// if it lower than 1 there is a high chance it will upgrade in the first window - /// the higher it is the lower the chance it will upgrade in the first window - upgrade_three_quarters_life: f64, -} - -#[derive(Clone, Debug)] -pub struct ValidatorVersionMigration { - current_protocol_version: ProtocolVersion, - next_protocol_version: ProtocolVersion, - change_block_height: BlockHeight, -} - -impl UpgradingInfo { - fn apply_to_proposers( - &self, - proposers: Vec, - blocks_per_epoch: u64, - rng: &mut StdRng, - ) -> HashMap { - let expected_blocks = blocks_per_epoch as f64 * self.upgrade_three_quarters_life; - proposers - .into_iter() - .map(|pro_tx_hash| { - let next_version = self - .proposed_protocol_versions_with_weight - .choose_weighted(rng, |item| item.1) - .unwrap() - .0; - // we generate a random number between 0 and 1 - let u: f64 = rng.gen(); - // we want to alter the randomness so that 75% of time we get - let change_block_height = - (expected_blocks * 0.75 * f64::ln(1.0 - u) / f64::ln(0.5)) as u64; - ( - pro_tx_hash, - ValidatorVersionMigration { - current_protocol_version: self.current_protocol_version, - next_protocol_version: next_version, - change_block_height, - }, - ) - }) - .collect() - } -} - -impl Strategy { - // TODO: This belongs to `DocumentOp` - fn add_strategy_contracts_into_drive(&mut self, drive: &Drive) { - for op in &self.operations { - match &op.op_type { - OperationType::Document(doc_op) => { - let serialize = doc_op.contract.serialize().expect("expected to serialize"); - drive - .apply_contract_with_serialization( - &doc_op.contract, - serialize, - BlockInfo::default(), - true, - Some(Cow::Owned(SingleEpoch(0))), - None, - ) - .expect("expected to be able to add contract"); - } - _ => {} - } - } - } - - fn identity_state_transitions_for_block( - &self, - signer: &mut SimpleSigner, - rng: &mut StdRng, - ) -> Vec<(Identity, StateTransition)> { - let frequency = &self.identities_inserts; - if frequency.check_hit(rng) { - let count = frequency.events(rng); - create_identities_state_transitions(count, 5, signer, rng) - } else { - vec![] - } - } - - fn contract_state_transitions( - &mut self, - current_identities: &Vec, - signer: &SimpleSigner, - rng: &mut StdRng, - ) -> Vec { - self.contracts_with_updates - .iter_mut() - .map(|(contract, contract_updates)| { - let identity_num = rng.gen_range(0..current_identities.len()); - let identity = current_identities - .get(identity_num) - .unwrap() - .clone() - .into_partial_identity_info(); - - contract.owner_id = identity.id; - let old_id = contract.id; - contract.id = generate_data_contract_id(identity.id, contract.entropy); - contract - .document_types - .iter_mut() - .for_each(|(_, document_type)| document_type.data_contract_id = contract.id); - - if let Some(contract_updates) = contract_updates { - for (_, updated_contract) in contract_updates.iter_mut() { - updated_contract.id = contract.id; - updated_contract.owner_id = contract.owner_id; - updated_contract.document_types.iter_mut().for_each( - |(_, document_type)| document_type.data_contract_id = contract.id, - ); - } - } - - // since we are changing the id, we need to update all the strategy - self.operations.iter_mut().for_each(|operation| { - if let OperationType::Document(document_op) = &mut operation.op_type { - if document_op.contract.id == old_id { - document_op.contract.id = contract.id; - document_op.contract.document_types.iter_mut().for_each( - |(_, document_type)| document_type.data_contract_id = contract.id, - ); - document_op.document_type.data_contract_id = contract.id; - } - } - }); - - let state_transition = DataContractCreateTransition::new_from_data_contract( - contract.clone(), - &identity, - 1, //key id 1 should always be a high or critical auth key in these tests - signer, - ) - .expect("expected to create a create state transition from a data contract"); - state_transition.into() - }) - .collect() - } - - fn contract_update_state_transitions( - &mut self, - current_identities: &Vec, - block_height: u64, - signer: &SimpleSigner, - ) -> Vec { - self.contracts_with_updates - .iter_mut() - .filter_map(|(_, contract_updates)| { - let Some(contract_updates) = contract_updates else { - return None; - }; - let Some(contract_update) = contract_updates.get(&block_height) else { - return None - }; - let identity = current_identities - .iter() - .find(|identity| identity.id == contract_update.owner_id) - .expect("expected to find an identity") - .clone() - .into_partial_identity_info(); - - let state_transition = DataContractUpdateTransition::new_from_data_contract( - contract_update.clone(), - &identity, - 1, //key id 1 should always be a high or critical auth key in these tests - signer, - ) - .expect("expected to create a create state transition from a data contract"); - Some(state_transition.into()) - }) - .collect() - } - - // TODO: this belongs to `DocumentOp`, also randomization details are common for all operations - // and could be moved out of here - fn state_transitions_for_block( - &self, - platform: &Platform, - block_info: &BlockInfo, - current_identities: &mut Vec, - signer: &mut SimpleSigner, - rng: &mut StdRng, - ) -> (Vec, Vec) { - let mut operations = vec![]; - let mut finalize_block_operations = vec![]; - for op in &self.operations { - if op.frequency.check_hit(rng) { - let count = rng.gen_range(op.frequency.times_per_block_range.clone()); - match &op.op_type { - OperationType::Document(DocumentOp { - action: DocumentAction::DocumentActionInsert, - document_type, - contract, - }) => { - let documents = document_type.random_documents_with_params( - count as u32, - current_identities, - block_info.time_ms, - rng, - ); - documents - .into_iter() - .for_each(|(document, identity, entropy)| { - let document_create_transition = DocumentCreateTransition { - base: DocumentBaseTransition { - id: document.id, - document_type_name: document_type.name.clone(), - action: Action::Create, - data_contract_id: contract.id, - data_contract: contract.clone(), - }, - entropy: entropy.to_buffer(), - created_at: document.created_at, - updated_at: document.created_at, - data: document.properties.into(), - }; - - let mut document_batch_transition = DocumentsBatchTransition { - protocol_version: LATEST_VERSION, - transition_type: StateTransitionType::DocumentsBatch, - owner_id: identity.id, - transitions: vec![document_create_transition.into()], - signature_public_key_id: None, - signature: None, - }; - - let identity_public_key = identity - .get_first_public_key_matching( - Purpose::AUTHENTICATION, - HashSet::from([ - SecurityLevel::HIGH, - SecurityLevel::CRITICAL, - ]), - HashSet::from([ - KeyType::ECDSA_SECP256K1, - KeyType::BLS12_381, - ]), - ) - .expect("expected to get a signing key"); - - document_batch_transition - .sign_external(identity_public_key, signer) - .expect("expected to sign"); - - operations.push(document_batch_transition.into()); - }); - } - OperationType::Document(DocumentOp { - action: DocumentAction::DocumentActionDelete, - document_type, - contract, - }) => { - let any_item_query = DriveQuery::any_item_query(contract, document_type); - let mut items = platform - .drive - .query_documents_as_serialized( - any_item_query, - Some(&block_info.epoch), - None, - ) - .expect("expect to execute query") - .items; - - if !items.is_empty() { - let first_item = items.remove(0); - let document = - Document::from_bytes(first_item.as_slice(), document_type) - .expect("expected to deserialize document"); - - //todo: fix this into a search key request for the following - //let search_key_request = BTreeMap::from([(Purpose::AUTHENTICATION as u8, BTreeMap::from([(SecurityLevel::HIGH as u8, AllKeysOfKindRequest)]))]); - - let request = IdentityKeysRequest { - identity_id: document.owner_id.to_buffer(), - request_type: KeyRequestType::SpecificKeys(vec![1]), - limit: Some(1), - offset: None, - }; - let identity = platform - .drive - .fetch_identity_balance_with_keys(request, None) - .expect("expected to be able to get identity") - .expect("expected to get an identity"); - let document_delete_transition = DocumentDeleteTransition { - base: DocumentBaseTransition { - id: document.id, - document_type_name: document_type.name.clone(), - action: Action::Delete, - data_contract_id: contract.id, - data_contract: contract.clone(), - }, - }; - - let mut document_batch_transition = DocumentsBatchTransition { - protocol_version: LATEST_VERSION, - transition_type: StateTransitionType::DocumentsBatch, - owner_id: identity.id, - transitions: vec![document_delete_transition.into()], - signature_public_key_id: None, - signature: None, - }; - - let identity_public_key = identity - .loaded_public_keys - .values() - .next() - .expect("expected a key"); - - document_batch_transition - .sign_external(identity_public_key, signer) - .expect("expected to sign"); - - operations.push(document_batch_transition.into()); - } - } - OperationType::Document(DocumentOp { - action: DocumentAction::DocumentActionReplace, - document_type, - contract, - }) => { - let any_item_query = DriveQuery::any_item_query(contract, document_type); - let mut items = platform - .drive - .query_documents_as_serialized( - any_item_query, - Some(&block_info.epoch), - None, - ) - .expect("expect to execute query") - .items; - - if !items.is_empty() { - let first_item = items.remove(0); - let document = - Document::from_bytes(first_item.as_slice(), document_type) - .expect("expected to deserialize document"); - - //todo: fix this into a search key request for the following - //let search_key_request = BTreeMap::from([(Purpose::AUTHENTICATION as u8, BTreeMap::from([(SecurityLevel::HIGH as u8, AllKeysOfKindRequest)]))]); - - let random_new_document = document_type.random_document_with_rng(rng); - let request = IdentityKeysRequest { - identity_id: document.owner_id.to_buffer(), - request_type: KeyRequestType::SpecificKeys(vec![1]), - limit: Some(1), - offset: None, - }; - let identity = platform - .drive - .fetch_identity_balance_with_keys(request, None) - .expect("expected to be able to get identity") - .expect("expected to get an identity"); - let document_replace_transition = DocumentReplaceTransition { - base: DocumentBaseTransition { - id: document.id, - document_type_name: document_type.name.clone(), - action: Action::Replace, - data_contract_id: contract.id, - data_contract: contract.clone(), - }, - revision: document.revision.expect("expected to unwrap revision") - + 1, - updated_at: Some(block_info.time_ms), - data: Some(random_new_document.properties), - }; - - let mut document_batch_transition = DocumentsBatchTransition { - protocol_version: LATEST_VERSION, - transition_type: StateTransitionType::DocumentsBatch, - owner_id: identity.id, - transitions: vec![document_replace_transition.into()], - signature_public_key_id: None, - signature: None, - }; - - let identity_public_key = identity - .loaded_public_keys - .values() - .next() - .expect("expected a key"); - - document_batch_transition - .sign_external(identity_public_key, signer) - .expect("expected to sign"); - - operations.push(document_batch_transition.into()); - } - } - OperationType::IdentityTopUp if !current_identities.is_empty() => { - let indices: Vec = - (0..current_identities.len()).choose_multiple(rng, count as usize); - let random_identities: Vec<&Identity> = indices - .into_iter() - .map(|index| ¤t_identities[index]) - .collect(); - - for random_identity in random_identities { - operations - .push(create_identity_top_up_transition(rng, random_identity)); - } - } - OperationType::IdentityUpdate(update_op) if !current_identities.is_empty() => { - let indices: Vec = - (0..current_identities.len()).choose_multiple(rng, count as usize); - for index in indices { - let random_identity = current_identities.get_mut(index).unwrap(); - match update_op { - IdentityUpdateOp::IdentityUpdateAddKeys(count) => { - let (state_transition, keys_to_add_at_end_block) = - create_identity_update_transition_add_keys( - random_identity, - *count, - signer, - rng, - ); - operations.push(state_transition); - finalize_block_operations.push(IdentityAddKeys( - keys_to_add_at_end_block.0, - keys_to_add_at_end_block.1, - )) - } - IdentityUpdateOp::IdentityUpdateDisableKey(count) => { - let state_transition = - create_identity_update_transition_disable_keys( - random_identity, - *count, - block_info.time_ms, - signer, - rng, - ); - if let Some(state_transition) = state_transition { - operations.push(state_transition); - } - } - } - } - } - OperationType::IdentityWithdrawal if !current_identities.is_empty() => { - let indices: Vec = - (0..current_identities.len()).choose_multiple(rng, count as usize); - for index in indices { - let random_identity = current_identities.get_mut(index).unwrap(); - let state_transition = - create_identity_withdrawal_transition(random_identity, signer, rng); - operations.push(state_transition); - } - } - // OperationType::ContractCreate(new_fields_optional_count_range, new_fields_required_count_range, new_index_count_range, document_type_count) - // if !current_identities.is_empty() => { - // DataContract::; - // - // DocumentType::random_document() - // } - // OperationType::ContractUpdate(DataContractNewDocumentTypes(count)) - // if !current_identities.is_empty() => { - // - // } - _ => {} - } - } - } - (operations, finalize_block_operations) - } - - fn state_transitions_for_block_with_new_identities( - &mut self, - platform: &Platform, - block_info: &BlockInfo, - current_identities: &mut Vec, - signer: &mut SimpleSigner, - rng: &mut StdRng, - ) -> (Vec, Vec) { - let mut finalize_block_operations = vec![]; - let identity_state_transitions = self.identity_state_transitions_for_block(signer, rng); - let (mut identities, mut state_transitions): (Vec, Vec) = - identity_state_transitions.into_iter().unzip(); - current_identities.append(&mut identities); - - if block_info.height == 1 { - // add contracts on block 1 - let mut contract_state_transitions = - self.contract_state_transitions(current_identities, signer, rng); - state_transitions.append(&mut contract_state_transitions); - } else { - // Don't do any state transitions on block 1 - let (mut document_state_transitions, mut add_to_finalize_block_operations) = self - .state_transitions_for_block(platform, block_info, current_identities, signer, rng); - finalize_block_operations.append(&mut add_to_finalize_block_operations); - state_transitions.append(&mut document_state_transitions); - - // There can also be contract updates - - let mut contract_update_state_transitions = self.contract_update_state_transitions( - current_identities, - block_info.height, - signer, - ); - state_transitions.append(&mut contract_update_state_transitions); - } - - (state_transitions, finalize_block_operations) - } -} - -fn create_identity_top_up_transition(rng: &mut StdRng, identity: &Identity) -> StateTransition { - let (_, pk) = ECDSA_SECP256K1.random_public_and_private_key_data(rng); - let sk: [u8; 32] = pk.try_into().unwrap(); - let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap(); - let asset_lock_proof = - instant_asset_lock_proof_fixture(Some(PrivateKey::new(secret_key, Network::Dash))); - - StateTransition::IdentityTopUp( - IdentityTopUpTransition::try_from_identity( - identity.clone(), - asset_lock_proof, - secret_key.as_ref(), - &NativeBlsModule::default(), - ) - .expect("expected to create top up transition"), - ) -} - -fn create_identity_update_transition_add_keys( - identity: &mut Identity, - count: u16, - signer: &mut SimpleSigner, - rng: &mut StdRng, -) -> (StateTransition, (Identifier, Vec)) { - identity.revision += 1; - let keys = IdentityPublicKey::random_authentication_keys_with_private_keys_with_rng( - identity.public_keys.len() as KeyID, - count as u32, - rng, - ); - - let add_public_keys: Vec = keys.iter().map(|(key, _)| key.clone()).collect(); - signer.private_keys_in_creation.extend(keys); - let (key_id, _) = identity - .public_keys - .iter() - .find(|(_, key)| key.security_level == MASTER) - .expect("expected to have a master key"); - - let state_transition = StateTransition::IdentityUpdate( - IdentityUpdateTransition::try_from_identity_with_signer( - identity, - key_id, - add_public_keys.clone(), - vec![], - None, - signer, - ) - .expect("expected to create top up transition"), - ); - - (state_transition, (identity.id, add_public_keys)) -} - -fn create_identity_update_transition_disable_keys( - identity: &mut Identity, - count: u16, - block_time: u64, - signer: &mut SimpleSigner, - rng: &mut StdRng, -) -> Option { - identity.revision += 1; - // we want to find keys that are not disabled - let key_ids_we_could_disable = identity - .public_keys - .iter() - .filter(|(_, key)| { - key.disabled_at.is_none() - && (key.security_level != MASTER - && !(key.security_level == CRITICAL - && key.purpose == AUTHENTICATION - && key.key_type == ECDSA_SECP256K1)) - }) - .map(|(key_id, _)| *key_id) - .collect::>(); - - if key_ids_we_could_disable.is_empty() { - identity.revision -= 1; //since we added 1 before - return None; - } - let indices: Vec<_> = (0..key_ids_we_could_disable.len()).choose_multiple(rng, count as usize); - - let key_ids_to_disable: Vec<_> = indices - .into_iter() - .map(|index| key_ids_we_could_disable[index]) - .collect(); - - identity.public_keys.iter_mut().for_each(|(key_id, key)| { - if key_ids_to_disable.contains(key_id) { - key.disabled_at = Some(block_time); - } - }); - - let (key_id, _) = identity - .public_keys - .iter() - .find(|(_, key)| key.security_level == MASTER) - .expect("expected to have a master key"); - - let state_transition = StateTransition::IdentityUpdate( - IdentityUpdateTransition::try_from_identity_with_signer( - identity, - key_id, - vec![], - key_ids_to_disable, - Some(block_time), - signer, - ) - .expect("expected to create top up transition"), - ); - - Some(state_transition) -} - -fn create_identity_withdrawal_transition( - identity: &mut Identity, - signer: &mut SimpleSigner, - rng: &mut StdRng, -) -> StateTransition { - identity.revision += 1; - let mut withdrawal = IdentityCreditWithdrawalTransition { - protocol_version: LATEST_VERSION, - transition_type: StateTransitionType::IdentityCreditWithdrawal, - identity_id: identity.id, - amount: 100000000, // 0.001 Dash - core_fee_per_byte: 1, - pooling: Pooling::Never, - output_script: CoreScript::random_p2sh(rng), - revision: identity.revision, - signature_public_key_id: 0, - signature: Default::default(), - }; - - let identity_public_key = identity - .get_first_public_key_matching( - Purpose::AUTHENTICATION, - HashSet::from([SecurityLevel::HIGH, SecurityLevel::CRITICAL]), - HashSet::from([KeyType::ECDSA_SECP256K1, KeyType::BLS12_381]), - ) - .expect("expected to get a signing key"); - - withdrawal - .sign_external(identity_public_key, signer) - .expect("expected to sign withdrawal"); - - withdrawal.into() -} - -fn create_identities_state_transitions( - count: u16, - key_count: KeyID, - signer: &mut SimpleSigner, - rng: &mut StdRng, -) -> Vec<(Identity, StateTransition)> { - let (identities, keys) = - Identity::random_identities_with_private_keys_with_rng::>(count, key_count, rng) - .expect("expected to create identities"); - signer.add_keys(keys); - identities - .into_iter() - .map(|mut identity| { - let (_, pk) = ECDSA_SECP256K1.random_public_and_private_key_data(rng); - let sk: [u8; 32] = pk.clone().try_into().unwrap(); - let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap(); - let asset_lock_proof = - instant_asset_lock_proof_fixture(Some(PrivateKey::new(secret_key, Network::Dash))); - let identity_create_transition = - IdentityCreateTransition::try_from_identity_with_signer( - identity.clone(), - asset_lock_proof, - pk.as_slice(), - signer, - &NativeBlsModule::default(), - ) - .expect("expected to transform identity into identity create transition"); - identity.id = *identity_create_transition.get_identity_id(); - (identity, identity_create_transition.into()) - }) - .collect() -} - -pub struct ChainExecutionOutcome<'a> { - pub abci_app: AbciApplication<'a, MockCoreRPCLike>, - pub masternode_identity_balances: BTreeMap<[u8; 32], Credits>, - pub identities: Vec, - pub proposers: Vec, - pub quorums: BTreeMap, - pub current_quorum_hash: QuorumHash, - pub current_proposer_versions: Option>, - pub end_epoch_index: u16, - pub end_time_ms: u64, - pub strategy: Strategy, - pub withdrawals: Vec, -} - -pub struct ChainExecutionParameters { - pub block_start: u64, - pub core_height_start: u32, - pub block_count: u64, - pub proposers: Vec, - pub quorums: BTreeMap, - pub current_quorum_hash: QuorumHash, - // the first option is if it is set - // the second option is if we are even upgrading - pub current_proposer_versions: Option>>, - pub current_time_ms: u64, -} - -pub enum StrategyRandomness { - SeedEntropy(u64), - RNGEntropy(StdRng), -} - -pub struct GenerateTestMasternodeUpdates<'a> { - start_core_height: u32, - end_core_height: u32, - update_masternode_frequency: &'a Frequency, - update_hpmn_frequency: &'a Frequency, -} - -/// Creates a list of test Masternode identities of size `count` with random data -pub fn generate_test_masternodes( - masternode_count: u16, - hpmn_count: u16, - updates: Option, - rng: &mut StdRng, -) -> ( - Vec, - Vec, -) { - let mut masternodes: Vec = - Vec::with_capacity(masternode_count as usize); - let mut hpmns: Vec = Vec::with_capacity(hpmn_count as usize); - - let (block_height_to_list_masternode_updates, block_height_to_list_hpmns_updates): ( - Option>>, - Option>>, - ) = updates - .map( - |GenerateTestMasternodeUpdates { - start_core_height, - end_core_height, - update_masternode_frequency, - update_hpmn_frequency, - }| { - (start_core_height..=end_core_height) - .into_iter() - .map(|height| { - // we want to pick what nodes will have updated for that block - ( - ( - height, - update_masternode_frequency.pick_in_range(rng, 0..masternode_count), - ), - ( - height, - update_hpmn_frequency.pick_in_range(rng, 0..hpmn_count), - ), - ) - }) - .unzip() - }, - ) - .unzip(); - - fn invert_hashmap(input: HashMap>) -> HashMap> { - let mut output = HashMap::new(); - - for (key, values) in input { - for value in values { - output.entry(value).or_insert_with(Vec::new).push(key); - } - } - - output - } - - let masternode_number_to_heights_updates = block_height_to_list_masternode_updates - .map(|block_height_to_list_masternode_updates| { - invert_hashmap(block_height_to_list_masternode_updates) - }) - .unwrap_or_default(); - - let hpmn_number_to_heights_updates = block_height_to_list_hpmns_updates - .map(|block_height_to_list_hpmns_updates| { - invert_hashmap(block_height_to_list_hpmns_updates) - }) - .unwrap_or_default(); - - for i in 0..masternode_count { - let private_key_operator = - BlsPrivateKey::generate_dash(rng).expect("expected to generate a private key"); - let pub_key_operator = private_key_operator - .g1_element() - .expect("expected to get public key") - .to_bytes() - .to_vec(); - let pro_tx_hash = ProTxHash::from_inner(rng.gen::<[u8; 32]>()); - let masternode_list_item = MasternodeListItem { - node_type: MasternodeType::Regular, - pro_tx_hash, - collateral_hash: Txid::from_inner(rng.gen::<[u8; 32]>()), - collateral_index: 0, - collateral_address: [0; 20], - operator_reward: 0, - state: DMNState { - service: SocketAddr::from_str(format!("1.0.{}.{}:1234", i / 256, i % 256).as_str()) - .unwrap(), - registered_height: 0, - pose_revived_height: None, - pose_ban_height: None, - revocation_reason: 0, - owner_address: rng.gen::<[u8; 20]>(), - voting_address: rng.gen::<[u8; 20]>(), - payout_address: rng.gen::<[u8; 20]>(), - pub_key_operator, - operator_payout_address: None, - platform_node_id: None, - platform_p2p_port: None, - platform_http_port: None, - }, - }; - - let mut latest_masternode_list_item = masternode_list_item.clone(); - - let masternode_updates = masternode_number_to_heights_updates - .get(&i) - .map(|heights| { - heights - .into_iter() - .map(|height| { - let mut masternode_list_item_b = latest_masternode_list_item.clone(); - masternode_list_item_b.random_keys_update(None, rng); - latest_masternode_list_item = masternode_list_item_b.clone(); - (*height, masternode_list_item_b) - }) - .collect::>() - }) - .unwrap_or_default(); - - let masternode_with_update = MasternodeListItemWithUpdates { - masternode: masternode_list_item, - updates: masternode_updates, - }; - - masternodes.push(masternode_with_update); - } - - for i in 0..hpmn_count { - let private_key_operator = - BlsPrivateKey::generate_dash(rng).expect("expected to generate a private key"); - let pub_key_operator = private_key_operator - .g1_element() - .expect("expected to get public key") - .to_bytes() - .to_vec(); - let masternode_list_item = MasternodeListItem { - node_type: MasternodeType::HighPerformance, - pro_tx_hash: ProTxHash::from_inner(rng.gen::<[u8; 32]>()), - collateral_hash: Txid::from_inner(rng.gen::<[u8; 32]>()), - collateral_index: 0, - collateral_address: [0; 20], - operator_reward: 0, - state: DMNState { - service: SocketAddr::from_str(format!("1.1.{}.{}:1234", i / 256, i % 256).as_str()) - .unwrap(), - registered_height: 0, - pose_revived_height: None, - pose_ban_height: None, - revocation_reason: 0, - owner_address: rng.gen::<[u8; 20]>(), - voting_address: rng.gen::<[u8; 20]>(), - payout_address: rng.gen::<[u8; 20]>(), - pub_key_operator, - operator_payout_address: None, - platform_node_id: Some(rng.gen::<[u8; 20]>()), - platform_p2p_port: Some(3010), - platform_http_port: Some(8080), - }, - }; - - let mut latest_masternode_list_item = masternode_list_item.clone(); - - let masternode_updates = hpmn_number_to_heights_updates - .get(&i) - .map(|heights| { - heights - .into_iter() - .map(|height| { - let mut masternode_list_item_b = latest_masternode_list_item.clone(); - masternode_list_item_b.random_keys_update(None, rng); - latest_masternode_list_item = masternode_list_item_b.clone(); - (*height, masternode_list_item_b) - }) - .collect::>() - }) - .unwrap_or_default(); - - let proposer_with_update = MasternodeListItemWithUpdates { - masternode: masternode_list_item, - updates: masternode_updates, - }; - - hpmns.push(proposer_with_update); - } - - (masternodes, hpmns) -} - -pub fn generate_test_quorums<'a, I>( - count: usize, - proposers: I, - quorum_size: usize, - rng: &mut StdRng, -) -> BTreeMap -where - I: Iterator + Clone, -{ - (0..count) - .into_iter() - .enumerate() - .map(|(index, _)| { - let quorum_hash: QuorumHash = QuorumHash::from_inner(rng.gen()); - let validator_pro_tx_hashes = proposers - .clone() - .filter(|m| m.node_type == MasternodeType::HighPerformance) - .choose_multiple(rng, quorum_size) - .iter() - .map(|masternode| masternode.pro_tx_hash) - .collect(); //choose multiple chooses out of order (as we would like) - ( - quorum_hash, - TestQuorumInfo::from_quorum_hash_and_pro_tx_hashes( - index as u32 * 24, - quorum_hash, - validator_pro_tx_hashes, - rng, - ), - ) - }) - .collect() -} - -#[derive(Clone, Debug)] -pub struct MasternodeListItemWithUpdates { - masternode: MasternodeListItem, - updates: BTreeMap, -} - -impl MasternodeListItemWithUpdates { - pub(crate) fn pro_tx_hash(&self) -> ProTxHash { - self.masternode.pro_tx_hash - } - - pub(crate) fn get_state_at_height(&self, height: u32) -> &MasternodeListItem { - // Find the closest height less than or equal to the given height - let closest_height = self.updates.range(..=height).rev().next().map(|(k, _)| *k); - - match closest_height { - Some(h) => &self.updates[&h], - None => &self.masternode, - } - } -} - -pub(crate) fn run_chain_for_strategy( - platform: &mut Platform, - block_count: u64, - strategy: Strategy, - config: PlatformConfig, - seed: u64, -) -> ChainExecutionOutcome { - let quorum_count = strategy.quorum_count; // We assume 24 quorums - let quorum_size = config.quorum_size; - - let mut rng = StdRng::seed_from_u64(seed); - - let any_changes_in_strategy = strategy.proposer_strategy.any_is_set(); - let updated_proposers_in_strategy = strategy.proposer_strategy.updated_any_masternode_types(); - - let ( - initial_masternodes_with_updates, - initial_hpmns_with_updates, - with_extra_masternodes_with_updates, - with_extra_hpmns_with_updates, - ) = if any_changes_in_strategy { - let approximate_end_core_height = - ((block_count as f64) * strategy.core_height_increase.average_event_count()) as u32; - let end_core_height = approximate_end_core_height * 2; //let's be safe - let generate_updates = if updated_proposers_in_strategy { - Some(GenerateTestMasternodeUpdates { - start_core_height: config.abci.genesis_core_height, - end_core_height, - update_masternode_frequency: &strategy.proposer_strategy.updated_mastenodes, - update_hpmn_frequency: &strategy.proposer_strategy.updated_hpmns, - }) - } else { - None - }; - - let (initial_masternodes, initial_hpmns) = generate_test_masternodes( - strategy.extra_normal_mns, - strategy.total_hpmns, - generate_updates, - &mut rng, - ); - let mut all_masternodes = initial_masternodes.clone(); - let mut all_hpmns = initial_hpmns.clone(); - - let (extra_masternodes_by_block, extra_hpmns_by_block): ( - BTreeMap>, - BTreeMap>, - ) = (config.abci.genesis_core_height..end_core_height) - .map(|height| { - let new_masternodes = strategy - .proposer_strategy - .new_masternodes - .events_if_hit(&mut rng); - let new_hpmns = strategy.proposer_strategy.new_hpmns.events_if_hit(&mut rng); - let generate_updates = if updated_proposers_in_strategy { - Some(GenerateTestMasternodeUpdates { - start_core_height: height + 1, - end_core_height, - update_masternode_frequency: &strategy.proposer_strategy.updated_mastenodes, - update_hpmn_frequency: &strategy.proposer_strategy.updated_hpmns, - }) - } else { - None - }; - - let (extra_masternodes_by_block, extra_hpmns_by_block) = generate_test_masternodes( - new_masternodes, - new_hpmns, - generate_updates, - &mut rng, - ); - - if strategy.proposer_strategy.removed_masternodes.is_set() { - let removed_masternodes_count = strategy - .proposer_strategy - .removed_masternodes - .events_if_hit(&mut rng); - let removed_count = - std::cmp::min(removed_masternodes_count as usize, all_masternodes.len()); - all_masternodes.drain(0..removed_count); - } - - if strategy.proposer_strategy.removed_hpmns.is_set() { - let removed_hpmns_count = strategy - .proposer_strategy - .removed_hpmns - .events_if_hit(&mut rng); - let removed_count = - std::cmp::min(removed_hpmns_count as usize, all_hpmns.len()); - all_hpmns.drain(0..removed_count); - } - - all_masternodes.extend(extra_masternodes_by_block.clone()); - all_hpmns.extend(extra_hpmns_by_block.clone()); - ( - (height, all_masternodes.clone()), - (height, all_hpmns.clone()), - ) - }) - .unzip(); - ( - initial_masternodes, - initial_hpmns, - extra_masternodes_by_block, - extra_hpmns_by_block, - ) - } else { - let (initial_masternodes, initial_hpmns) = generate_test_masternodes( - strategy.extra_normal_mns, - strategy.total_hpmns, - None, - &mut rng, - ); - ( - initial_masternodes, - initial_hpmns, - BTreeMap::new(), - BTreeMap::new(), - ) - }; - - let mut all_hpmns_with_updates = with_extra_hpmns_with_updates - .iter() - .max_by_key(|(key, _)| *key) - .map(|(_, v)| v.clone()) - .unwrap_or(initial_hpmns_with_updates.clone()); - - let total_quorums = if strategy.rotate_quorums { - quorum_count * 10 - } else { - quorum_count - }; - - let quorums = generate_test_quorums( - total_quorums as usize, - initial_hpmns_with_updates - .iter() - .map(|hpmn| &hpmn.masternode), - quorum_size as usize, - &mut rng, - ); - - let quorums_clone: HashMap = quorums - .keys() - .map(|quorum_hash| { - ( - *quorum_hash, - ExtendedQuorumDetails { - creation_height: 0, - quorum_index: None, - mined_block_hash: Default::default(), - num_valid_members: 0, - health_ratio: 0.0, - }, - ) - }) - .collect(); - - let start_core_height = platform.config.abci.genesis_core_height; - - platform - .core_rpc - .expect_get_fork_info() - .returning(move |_| { - Ok(Some(Bip9SoftforkInfo { - status: Bip9SoftforkStatus::Active, - bit: None, - start_time: 0, - timeout: 0, - since: start_core_height, // block height 1 - statistics: None, - })) - }); - - platform - .core_rpc - .expect_get_quorum_listextended() - .returning(move |core_height: Option| { - if !strategy.rotate_quorums { - Ok(dashcore_rpc::dashcore_rpc_json::ExtendedQuorumListResult { - quorums_by_type: HashMap::from([( - QuorumType::Llmq100_67, - quorums_clone.clone(), - )]), - }) - } else { - let core_height = core_height.expect("expected a core height"); - // if we rotate quorums we shouldn't give back the same ones every time - let start_range = core_height / 24; - let end_range = start_range + quorum_count as u32; - let start_range = start_range % total_quorums as u32; - let end_range = end_range % total_quorums as u32; - - let quorums = if end_range > start_range { - quorums_clone - .iter() - .skip(start_range as usize) - .take((end_range - start_range) as usize) - .map(|(quorum_hash, quorum)| (quorum_hash.clone(), quorum.clone())) - .collect() - } else { - let first_range = quorums_clone - .iter() - .skip(start_range as usize) - .take((total_quorums as u32 - start_range) as usize); - let second_range = quorums_clone.iter().take(end_range as usize); - first_range - .chain(second_range) - .map(|(quorum_hash, quorum)| (quorum_hash.clone(), quorum.clone())) - .collect() - }; - - Ok(dashcore_rpc::dashcore_rpc_json::ExtendedQuorumListResult { - quorums_by_type: HashMap::from([(QuorumType::Llmq100_67, quorums)]), - }) - } - }); - - let quorums_info: HashMap = quorums - .iter() - .map(|(quorum_hash, test_quorum_info)| (*quorum_hash, test_quorum_info.into())) - .collect(); - - platform - .core_rpc - .expect_get_quorum_info() - .returning(move |_, quorum_hash: &QuorumHash, _| { - Ok(quorums_info - .get(quorum_hash) - .unwrap_or_else(|| panic!("expected to get quorum {}", quorum_hash.to_hex())) - .clone()) - }); - - let initial_all_masternodes: Vec<_> = initial_masternodes_with_updates - .clone() - .into_iter() - .chain(initial_hpmns_with_updates.clone().into_iter()) - .collect(); - - platform - .core_rpc - .expect_get_protx_diff_with_masternodes() - .returning(move |base_block, block| { - let diff = if base_block == 0 { - MasternodeListDiff { - base_height: base_block, - block_height: block, - added_mns: initial_all_masternodes - .iter() - .map(|masternode_list_item| masternode_list_item.masternode.clone()) - .collect(), - removed_mns: vec![], - updated_mns: vec![], - } - } else { - if !any_changes_in_strategy { - // no changes - MasternodeListDiff { - base_height: base_block, - block_height: block, - added_mns: vec![], - removed_mns: vec![], - updated_mns: vec![], - } - } else { - // we need to figure out the difference of proposers between two heights - // we need to figure out the difference of proposers between two heights - let start_masternodes = with_extra_masternodes_with_updates - .get(&base_block) - .expect("expected start proposers") - .iter() - .map(|masternode| masternode.get_state_at_height(base_block)) - .collect::>(); - let end_masternodes = with_extra_masternodes_with_updates - .get(&block) - .expect("expected end proposers") - .iter() - .map(|masternode| masternode.get_state_at_height(block)) - .collect::>(); - - let start_pro_tx_hashes: Vec<&ProTxHash> = start_masternodes - .iter() - .map(|item| &item.pro_tx_hash) - .collect(); - let end_pro_tx_hashes: Vec<&ProTxHash> = end_masternodes - .iter() - .map(|item| &item.pro_tx_hash) - .collect(); - - let mut added_masternodes = end_masternodes - .iter() - .filter(|item| !start_pro_tx_hashes.contains(&&item.pro_tx_hash)) - .map(|a| (*a).clone()) - .collect::>(); - - let mut removed_masternodes = start_masternodes - .iter() - .filter(|item| !end_pro_tx_hashes.contains(&&item.pro_tx_hash)) - .map(|masternode_list_item| masternode_list_item.pro_tx_hash.clone()) - .collect::>(); - - let mut updated_masternodes: Vec<(ProTxHash, DMNStateDiff)> = start_masternodes - .iter() - .filter_map(|start_masternode| { - end_masternodes - .iter() - .find(|end_masternode| { - start_masternode.pro_tx_hash == end_masternode.pro_tx_hash - }) - .and_then(|end_masternode| { - start_masternode - .state - .compare_to_newer_dmn_state(&end_masternode.state) - .map(|diff| (end_masternode.pro_tx_hash.clone(), diff)) - }) - }) - .collect(); - - let start_hpmns = with_extra_hpmns_with_updates - .get(&base_block) - .expect("expected start proposers") - .iter() - .map(|masternode| masternode.get_state_at_height(base_block)) - .collect::>(); - let end_hpmns = with_extra_hpmns_with_updates - .get(&block) - .expect("expected end proposers") - .iter() - .map(|masternode| masternode.get_state_at_height(block)) - .collect::>(); - let start_pro_tx_hashes: Vec<&ProTxHash> = - start_hpmns.iter().map(|item| &item.pro_tx_hash).collect(); - let end_pro_tx_hashes: Vec<&ProTxHash> = - end_hpmns.iter().map(|item| &item.pro_tx_hash).collect(); - - let mut added_hpmns = end_hpmns - .iter() - .filter(|item| !start_pro_tx_hashes.contains(&&item.pro_tx_hash)) - .map(|a| (*a).clone()) - .collect::>(); - - let mut removed_hpmns = start_hpmns - .iter() - .filter(|item| !end_pro_tx_hashes.contains(&&item.pro_tx_hash)) - .map(|masternode_list_item| masternode_list_item.pro_tx_hash.clone()) - .collect::>(); - - let mut updated_hpmns: Vec<(ProTxHash, DMNStateDiff)> = start_hpmns - .iter() - .filter_map(|start_masternode| { - end_hpmns - .iter() - .find(|end_masternode| { - start_masternode.pro_tx_hash == end_masternode.pro_tx_hash - }) - .and_then(|end_masternode| { - start_masternode - .state - .compare_to_newer_dmn_state(&end_masternode.state) - .map(|diff| (end_masternode.pro_tx_hash.clone(), diff)) - }) - }) - .collect(); - - added_masternodes.extend(added_hpmns); - removed_masternodes.extend(removed_hpmns); - updated_masternodes.extend(updated_hpmns); - - MasternodeListDiff { - base_height: base_block, - block_height: block, - added_mns: added_masternodes, - removed_mns: removed_masternodes, - updated_mns: updated_masternodes, - } - } - }; - - Ok(diff) - }); - - start_chain_for_strategy( - platform, - block_count, - all_hpmns_with_updates, - quorums, - strategy, - config, - rng, - ) -} - -pub(crate) fn start_chain_for_strategy( - platform: &Platform, - block_count: u64, - proposers_with_updates: Vec, - quorums: BTreeMap, - strategy: Strategy, - config: PlatformConfig, - mut rng: StdRng, -) -> ChainExecutionOutcome { - let abci_application = AbciApplication::new(platform).expect("expected new abci application"); - - let quorum_hashes: Vec<&QuorumHash> = quorums.keys().collect(); - - let mut current_quorum_hash = **quorum_hashes - .choose(&mut rng) - .expect("expected quorums to be initialized"); - - let current_quorum_with_test_info = quorums - .get(¤t_quorum_hash) - .expect("expected a quorum to be found"); - - // init chain - let mut init_chain_request = static_init_chain_request(); - - init_chain_request.initial_core_height = config.abci.genesis_core_height; - init_chain_request.validator_set = Some(ValidatorSetUpdate { - validator_updates: current_quorum_with_test_info - .validator_set - .iter() - .map( - |validator_in_quorum| tenderdash_abci::proto::abci::ValidatorUpdate { - pub_key: Some(tenderdash_abci::proto::crypto::PublicKey { - sum: Some(Bls12381(validator_in_quorum.public_key.to_bytes().to_vec())), - }), - power: 100, - pro_tx_hash: validator_in_quorum.pro_tx_hash.to_vec(), - node_address: "".to_string(), - }, - ) - .collect(), - threshold_public_key: Some(tenderdash_abci::proto::crypto::PublicKey { - sum: Some(Bls12381( - current_quorum_with_test_info.public_key.to_bytes().to_vec(), - )), - }), - quorum_hash: current_quorum_hash.to_vec(), - }); - - abci_application.start_transaction(); - - let binding = abci_application.transaction.read().unwrap(); - - let transaction = binding.as_ref().expect("expected a transaction"); - - let ResponseInitChain { - initial_core_height, - .. - } = platform - .init_chain(init_chain_request, transaction) - .expect("should init chain"); - - // initialization will change the current quorum hash - current_quorum_hash = platform - .state - .read() - .unwrap() - .current_validator_set_quorum_hash; - - platform.create_mn_shares_contract(Some(transaction)); - - drop(binding); - - continue_chain_for_strategy( - abci_application, - ChainExecutionParameters { - block_start: 1, - core_height_start: initial_core_height, - block_count, - proposers: proposers_with_updates, - quorums, - current_quorum_hash, - current_proposer_versions: None, - current_time_ms: 1681094380000, - }, - strategy, - config, - StrategyRandomness::RNGEntropy(rng), - ) -} - -pub(crate) fn continue_chain_for_strategy( - abci_app: AbciApplication, - chain_execution_parameters: ChainExecutionParameters, - mut strategy: Strategy, - config: PlatformConfig, - seed: StrategyRandomness, -) -> ChainExecutionOutcome { - let platform = abci_app.platform; - let ChainExecutionParameters { - block_start, - core_height_start, - block_count, - proposers: proposers_with_updates, - quorums, - mut current_quorum_hash, - current_proposer_versions, - mut current_time_ms, - } = chain_execution_parameters; - let mut rng = match seed { - StrategyRandomness::SeedEntropy(seed) => StdRng::seed_from_u64(seed), - StrategyRandomness::RNGEntropy(rng) => rng, - }; - let quorum_size = config.quorum_size; - let quorum_rotation_block_count = config.validator_set_quorum_rotation_block_count as u64; - let first_block_time = 0; - let mut current_identities = vec![]; - let mut signer = SimpleSigner::default(); - let mut i = 0; - - let blocks_per_epoch = EPOCH_CHANGE_TIME_MS / config.block_spacing_ms; - - let proposer_versions = current_proposer_versions.unwrap_or( - strategy.upgrading_info.as_ref().map(|upgrading_info| { - upgrading_info.apply_to_proposers( - proposers_with_updates - .iter() - .map(|masternode_list_item| masternode_list_item.pro_tx_hash()) - .collect(), - blocks_per_epoch, - &mut rng, - ) - }), - ); - - let mut current_core_height = core_height_start; - - let mut total_withdrawals = vec![]; - - let mut current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); - - let mut next_quorum_hash = current_quorum_hash; - - for block_height in block_start..(block_start + block_count) { - let needs_rotation_on_next_block = block_height % quorum_rotation_block_count == 0; - if needs_rotation_on_next_block { - let quorum_hashes: Vec<&QuorumHash> = quorums.keys().collect(); - - next_quorum_hash = **quorum_hashes.choose(&mut rng).unwrap(); - } - let epoch_info = EpochInfo::calculate( - first_block_time, - current_time_ms, - platform - .state - .read() - .expect("lock is poisoned") - .last_committed_block_info - .as_ref() - .map(|block_info| block_info.time_ms), - ) - .expect("should calculate epoch info"); - - current_core_height += strategy.core_height_increase.events_if_hit(&mut rng) as u32; - - let block_info = BlockInfo { - time_ms: current_time_ms, - height: block_height, - core_height: current_core_height, - epoch: Epoch::new(epoch_info.current_epoch_index).unwrap(), - }; - if current_quorum_with_test_info.quorum_hash != current_quorum_hash { - current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); - } - - let proposer = current_quorum_with_test_info - .validator_set - .get(i as usize) - .unwrap(); - let (state_transitions, finalize_block_operations) = strategy - .state_transitions_for_block_with_new_identities( - platform, - &block_info, - &mut current_identities, - &mut signer, - &mut rng, - ); - - let proposed_version = proposer_versions - .as_ref() - .map(|proposer_versions| { - let ValidatorVersionMigration { - current_protocol_version, - next_protocol_version, - change_block_height, - } = proposer_versions - .get(&proposer.pro_tx_hash) - .expect("expected to have version"); - if &block_height >= change_block_height { - *next_protocol_version - } else { - *current_protocol_version - } - }) - .unwrap_or(1); - - let MimicExecuteBlockOutcome { - withdrawal_transactions: mut withdrawals_this_block, - next_validator_set_hash, - } = abci_app - .mimic_execute_block( - proposer.pro_tx_hash.into_inner(), - current_quorum_with_test_info, - proposed_version, - block_info, - false, - state_transitions, - ) - .expect("expected to execute a block"); - - total_withdrawals.append(&mut withdrawals_this_block); - - for finalize_block_operation in finalize_block_operations { - match finalize_block_operation { - IdentityAddKeys(identifier, keys) => { - let identity = current_identities - .iter_mut() - .find(|identity| identity.id == identifier) - .expect("expected to find an identity"); - identity - .public_keys - .extend(keys.into_iter().map(|key| (key.id, key))); - } - } - } - signer.commit_block_keys(); - - current_time_ms += config.block_spacing_ms; - - let next_quorum_hash = QuorumHash::from_inner(next_validator_set_hash.try_into().unwrap()); - if current_quorum_hash != next_quorum_hash { - current_quorum_hash = next_quorum_hash; - i = 0; - } else { - i += 1; - i %= quorum_size; //todo: this could be variable - } - } - - let masternode_identity_balances = platform - .drive - .fetch_identities_balances( - &proposers_with_updates - .iter() - .map(|proposer| proposer.pro_tx_hash().into_inner()) - .collect(), - None, - ) - .expect("expected to get balances"); - - let end_epoch_index = platform - .state - .read() - .expect("lock is poisoned") - .last_committed_block_info - .as_ref() - .unwrap() - .epoch - .index; - - ChainExecutionOutcome { - abci_app, - masternode_identity_balances, - identities: current_identities, - proposers: proposers_with_updates, - quorums, - current_quorum_hash, - current_proposer_versions: proposer_versions, - end_epoch_index, - end_time_ms: current_time_ms, - strategy, - withdrawals: total_withdrawals, - } -} - #[cfg(test)] mod tests { use super::*; - use crate::DocumentAction::DocumentActionReplace; + use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy}; + use crate::operations::DocumentAction::DocumentActionReplace; + use crate::operations::{ + DocumentAction, DocumentOp, IdentityUpdateOp, Operation, OperationType, + }; + use crate::strategy::MasternodeListChangesStrategy; use dashcore::hashes::Hash; use dashcore::BlockHash; use dashcore_rpc::dashcore_rpc_json::{ diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs new file mode 100644 index 00000000000..5d72a108f99 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs @@ -0,0 +1,264 @@ +use crate::frequency::Frequency; +use crate::masternode_list_item_helpers::UpdateMasternodeListItem; +use dashcore::hashes::Hash; +use dashcore::{ProTxHash, QuorumHash, Txid}; +use dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType}; +use dpp::bls_signatures::PrivateKey as BlsPrivateKey; +use drive_abci::execution::test_quorum::TestQuorumInfo; +use rand::prelude::{IteratorRandom, StdRng}; +use rand::Rng; +use std::collections::{BTreeMap, HashMap}; +use std::net::SocketAddr; +use std::str::FromStr; + +pub struct GenerateTestMasternodeUpdates<'a> { + pub start_core_height: u32, + pub end_core_height: u32, + pub update_masternode_frequency: &'a Frequency, + pub update_hpmn_frequency: &'a Frequency, +} + +/// Creates a list of test Masternode identities of size `count` with random data +pub fn generate_test_masternodes( + masternode_count: u16, + hpmn_count: u16, + updates: Option, + rng: &mut StdRng, +) -> ( + Vec, + Vec, +) { + let mut masternodes: Vec = + Vec::with_capacity(masternode_count as usize); + let mut hpmns: Vec = Vec::with_capacity(hpmn_count as usize); + + let (block_height_to_list_masternode_updates, block_height_to_list_hpmns_updates): ( + Option>>, + Option>>, + ) = updates + .map( + |GenerateTestMasternodeUpdates { + start_core_height, + end_core_height, + update_masternode_frequency, + update_hpmn_frequency, + }| { + (start_core_height..=end_core_height) + .into_iter() + .map(|height| { + // we want to pick what nodes will have updated for that block + ( + ( + height, + update_masternode_frequency.pick_in_range(rng, 0..masternode_count), + ), + ( + height, + update_hpmn_frequency.pick_in_range(rng, 0..hpmn_count), + ), + ) + }) + .unzip() + }, + ) + .unzip(); + + fn invert_hashmap(input: HashMap>) -> HashMap> { + let mut output = HashMap::new(); + + for (key, values) in input { + for value in values { + output.entry(value).or_insert_with(Vec::new).push(key); + } + } + + output + } + + let masternode_number_to_heights_updates = block_height_to_list_masternode_updates + .map(|block_height_to_list_masternode_updates| { + invert_hashmap(block_height_to_list_masternode_updates) + }) + .unwrap_or_default(); + + let hpmn_number_to_heights_updates = block_height_to_list_hpmns_updates + .map(|block_height_to_list_hpmns_updates| { + invert_hashmap(block_height_to_list_hpmns_updates) + }) + .unwrap_or_default(); + + for i in 0..masternode_count { + let private_key_operator = + BlsPrivateKey::generate_dash(rng).expect("expected to generate a private key"); + let pub_key_operator = private_key_operator + .g1_element() + .expect("expected to get public key") + .to_bytes() + .to_vec(); + let pro_tx_hash = ProTxHash::from_inner(rng.gen::<[u8; 32]>()); + let masternode_list_item = MasternodeListItem { + node_type: MasternodeType::Regular, + pro_tx_hash, + collateral_hash: Txid::from_inner(rng.gen::<[u8; 32]>()), + collateral_index: 0, + collateral_address: [0; 20], + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str(format!("1.0.{}.{}:1234", i / 256, i % 256).as_str()) + .unwrap(), + registered_height: 0, + pose_revived_height: None, + pose_ban_height: None, + revocation_reason: 0, + owner_address: rng.gen::<[u8; 20]>(), + voting_address: rng.gen::<[u8; 20]>(), + payout_address: rng.gen::<[u8; 20]>(), + pub_key_operator, + operator_payout_address: None, + platform_node_id: None, + platform_p2p_port: None, + platform_http_port: None, + }, + }; + + let mut latest_masternode_list_item = masternode_list_item.clone(); + + let masternode_updates = masternode_number_to_heights_updates + .get(&i) + .map(|heights| { + heights + .into_iter() + .map(|height| { + let mut masternode_list_item_b = latest_masternode_list_item.clone(); + masternode_list_item_b.random_keys_update(None, rng); + latest_masternode_list_item = masternode_list_item_b.clone(); + (*height, masternode_list_item_b) + }) + .collect::>() + }) + .unwrap_or_default(); + + let masternode_with_update = MasternodeListItemWithUpdates { + masternode: masternode_list_item, + updates: masternode_updates, + }; + + masternodes.push(masternode_with_update); + } + + for i in 0..hpmn_count { + let private_key_operator = + BlsPrivateKey::generate_dash(rng).expect("expected to generate a private key"); + let pub_key_operator = private_key_operator + .g1_element() + .expect("expected to get public key") + .to_bytes() + .to_vec(); + let masternode_list_item = MasternodeListItem { + node_type: MasternodeType::HighPerformance, + pro_tx_hash: ProTxHash::from_inner(rng.gen::<[u8; 32]>()), + collateral_hash: Txid::from_inner(rng.gen::<[u8; 32]>()), + collateral_index: 0, + collateral_address: [0; 20], + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str(format!("1.1.{}.{}:1234", i / 256, i % 256).as_str()) + .unwrap(), + registered_height: 0, + pose_revived_height: None, + pose_ban_height: None, + revocation_reason: 0, + owner_address: rng.gen::<[u8; 20]>(), + voting_address: rng.gen::<[u8; 20]>(), + payout_address: rng.gen::<[u8; 20]>(), + pub_key_operator, + operator_payout_address: None, + platform_node_id: Some(rng.gen::<[u8; 20]>()), + platform_p2p_port: Some(3010), + platform_http_port: Some(8080), + }, + }; + + let mut latest_masternode_list_item = masternode_list_item.clone(); + + let masternode_updates = hpmn_number_to_heights_updates + .get(&i) + .map(|heights| { + heights + .into_iter() + .map(|height| { + let mut masternode_list_item_b = latest_masternode_list_item.clone(); + masternode_list_item_b.random_keys_update(None, rng); + latest_masternode_list_item = masternode_list_item_b.clone(); + (*height, masternode_list_item_b) + }) + .collect::>() + }) + .unwrap_or_default(); + + let proposer_with_update = MasternodeListItemWithUpdates { + masternode: masternode_list_item, + updates: masternode_updates, + }; + + hpmns.push(proposer_with_update); + } + + (masternodes, hpmns) +} + +pub fn generate_test_quorums<'a, I>( + count: usize, + proposers: I, + quorum_size: usize, + rng: &mut StdRng, +) -> BTreeMap +where + I: Iterator + Clone, +{ + (0..count) + .into_iter() + .enumerate() + .map(|(index, _)| { + let quorum_hash: QuorumHash = QuorumHash::from_inner(rng.gen()); + let validator_pro_tx_hashes = proposers + .clone() + .filter(|m| m.node_type == MasternodeType::HighPerformance) + .choose_multiple(rng, quorum_size) + .iter() + .map(|masternode| masternode.pro_tx_hash) + .collect(); //choose multiple chooses out of order (as we would like) + ( + quorum_hash, + TestQuorumInfo::from_quorum_hash_and_pro_tx_hashes( + index as u32 * 24, + quorum_hash, + validator_pro_tx_hashes, + rng, + ), + ) + }) + .collect() +} + +#[derive(Clone, Debug)] +pub struct MasternodeListItemWithUpdates { + pub masternode: MasternodeListItem, + pub updates: BTreeMap, +} + +impl MasternodeListItemWithUpdates { + pub(crate) fn pro_tx_hash(&self) -> ProTxHash { + self.masternode.pro_tx_hash + } + + pub(crate) fn get_state_at_height(&self, height: u32) -> &MasternodeListItem { + // Find the closest height less than or equal to the given height + let closest_height = self.updates.range(..=height).rev().next().map(|(k, _)| *k); + + match closest_height { + Some(h) => &self.updates[&h], + None => &self.masternode, + } + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/operations.rs b/packages/rs-drive-abci/tests/strategy_tests/operations.rs new file mode 100644 index 00000000000..04c41eea980 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/operations.rs @@ -0,0 +1,57 @@ +use crate::frequency::Frequency; +use dpp::data_contract::document_type::random_document_type::RandomDocumentTypeParameters; +use dpp::data_contract::document_type::DocumentType; +use dpp::data_contract::DataContract as Contract; +use dpp::identifier::Identifier; +use dpp::identity::IdentityPublicKey; +use std::ops::Range; + +#[derive(Clone, Debug)] +pub enum DocumentAction { + DocumentActionInsert, + DocumentActionDelete, + DocumentActionReplace, +} + +#[derive(Clone, Debug)] +pub struct DocumentOp { + pub contract: Contract, + pub document_type: DocumentType, + pub action: DocumentAction, +} + +#[derive(Clone, Debug)] +pub struct Operation { + pub op_type: OperationType, + pub frequency: Frequency, +} + +#[derive(Clone, Debug)] +pub enum IdentityUpdateOp { + IdentityUpdateAddKeys(u16), + IdentityUpdateDisableKey(u16), +} + +pub type DocumentTypeNewFieldsOptionalCountRange = Range; +pub type DocumentTypeCount = Range; + +#[derive(Clone, Debug)] +pub enum DataContractUpdateOp { + DataContractNewDocumentTypes(RandomDocumentTypeParameters), // How many fields should it have + DataContractNewOptionalFields(DocumentTypeNewFieldsOptionalCountRange, DocumentTypeCount), // How many new fields on how many document types +} + +#[derive(Clone, Debug)] +pub enum OperationType { + Document(DocumentOp), + IdentityTopUp, + IdentityUpdate(IdentityUpdateOp), + IdentityWithdrawal, + ContractCreate(RandomDocumentTypeParameters, DocumentTypeCount), + ContractUpdate(DataContractUpdateOp), +} + +#[derive(Clone, Debug)] +pub enum FinalizeBlockOperation { + IdentityAddKeys(Identifier, Vec), +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/signer.rs b/packages/rs-drive-abci/tests/strategy_tests/signer.rs new file mode 100644 index 00000000000..7f9e8bf4a42 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/signer.rs @@ -0,0 +1,79 @@ +use anyhow::anyhow; +use dashcore::signer; +use dpp::ed25519_dalek::Signer as BlsSigner; +use dpp::identity::signer::Signer; +use dpp::identity::{IdentityPublicKey, KeyType}; +use dpp::platform_value::BinaryData; +use dpp::state_transition::errors::{ + InvalidIdentityPublicKeyTypeError, InvalidSignaturePublicKeyError, +}; +use dpp::{bls_signatures, ed25519_dalek, ProtocolError}; +use std::collections::HashMap; + +/// This simple signer is only to be used in tests +#[derive(Default, Debug)] +pub struct SimpleSigner { + /// Private keys is a map from the public key to the Private key bytes + pub private_keys: HashMap>, + /// Private keys to be added at the end of a block + pub private_keys_in_creation: HashMap>, +} + +impl SimpleSigner { + pub fn add_key(&mut self, public_key: IdentityPublicKey, private_key: Vec) { + self.private_keys.insert(public_key, private_key); + } + + pub fn add_keys)>>(&mut self, keys: I) { + self.private_keys.extend(keys) + } + + pub fn commit_block_keys(&mut self) { + self.private_keys + .extend(self.private_keys_in_creation.drain()) + } +} + +impl Signer for SimpleSigner { + fn sign( + &self, + identity_public_key: &IdentityPublicKey, + data: &[u8], + ) -> Result { + let private_key = self + .private_keys + .get(identity_public_key) + .or_else(|| self.private_keys_in_creation.get(identity_public_key)) + .ok_or(ProtocolError::InvalidSignaturePublicKeyError( + InvalidSignaturePublicKeyError::new(identity_public_key.data.to_vec()), + ))?; + match identity_public_key.key_type { + KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => { + let signature = signer::sign(data, private_key)?; + Ok(signature.to_vec().into()) + } + KeyType::BLS12_381 => { + let pk = + bls_signatures::PrivateKey::from_bytes(private_key, false).map_err(|_e| { + ProtocolError::Error(anyhow!("bls private key from bytes isn't correct")) + })?; + Ok(pk.sign(data).to_bytes().to_vec().into()) + } + KeyType::EDDSA_25519_HASH160 => { + let key: [u8; 32] = private_key.clone().try_into().expect("expected 32 bytes"); + let pk = ed25519_dalek::SigningKey::try_from(&key).map_err(|_e| { + ProtocolError::Error(anyhow!( + "eddsa 25519 private key from bytes isn't correct" + )) + })?; + Ok(pk.sign(data).to_vec().into()) + } + // the default behavior from + // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187 + // is to return the error for the BIP13_SCRIPT_HASH + KeyType::BIP13_SCRIPT_HASH => Err(ProtocolError::InvalidIdentityPublicKeyTypeError( + InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type), + )), + } + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs new file mode 100644 index 00000000000..34314411839 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -0,0 +1,662 @@ +use crate::frequency::Frequency; +use crate::masternodes::MasternodeListItemWithUpdates; +use crate::operations::FinalizeBlockOperation::IdentityAddKeys; +use crate::operations::{ + DocumentAction, DocumentOp, FinalizeBlockOperation, IdentityUpdateOp, Operation, OperationType, +}; +use crate::signer::SimpleSigner; +use crate::BlockHeight; +use dashcore::{ProTxHash, QuorumHash}; +use dpp::block::block_info::BlockInfo; +use dpp::data_contract::document_type::random_document::CreateRandomDocument; +use dpp::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransition; +use dpp::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; +use dpp::data_contract::{generate_data_contract_id, DataContract as Contract}; +use dpp::document::document_transition::document_base_transition::DocumentBaseTransition; +use dpp::document::document_transition::{ + Action, DocumentCreateTransition, DocumentDeleteTransition, DocumentReplaceTransition, +}; +use dpp::document::{Document, DocumentsBatchTransition}; +use dpp::identity::{Identity, KeyType, Purpose, SecurityLevel}; +use dpp::serialization_traits::PlatformSerializable; +use dpp::state_transition::{StateTransition, StateTransitionIdentitySigned, StateTransitionType}; +use dpp::util::deserializer::ProtocolVersion; +use dpp::version::LATEST_VERSION; +use drive::drive::flags::StorageFlags::SingleEpoch; +use drive::drive::identity::key::fetch::{IdentityKeysRequest, KeyRequestType}; +use drive::drive::Drive; +use drive::fee::credits::Credits; +use drive::query::DriveQuery; +use drive_abci::abci::AbciApplication; +use drive_abci::execution::test_quorum::TestQuorumInfo; +use drive_abci::platform::Platform; +use drive_abci::rpc::core::MockCoreRPCLike; +use rand::prelude::{IteratorRandom, SliceRandom, StdRng}; +use rand::Rng; +use std::borrow::Cow; +use std::collections::{BTreeMap, HashMap, HashSet}; + +#[derive(Clone, Debug, Default)] +pub struct MasternodeListChangesStrategy { + /// How many new masternodes on average per core chain lock increase + pub new_hpmns: Frequency, + /// How many masternodes leave the system + pub removed_hpmns: Frequency, + /// How many masternodes update a key + pub updated_hpmns: Frequency, + /// How many masternodes are banned + pub banned_hpmns: Frequency, + /// How many masternodes are unbanned + pub unbanned_hpmns: Frequency, + /// How many new masternodes on average per core chain lock increase + pub new_masternodes: Frequency, + /// How many masternodes leave the system + pub removed_masternodes: Frequency, + /// How many masternodes update a key + pub updated_mastenodes: Frequency, + /// How many masternodes are banned + pub banned_masternodes: Frequency, + /// How many masternodes are unbanned + pub unbanned_masternodes: Frequency, +} + +impl MasternodeListChangesStrategy { + pub fn any_is_set(&self) -> bool { + self.new_hpmns.is_set() + || self.removed_hpmns.is_set() + || self.updated_hpmns.is_set() + || self.banned_hpmns.is_set() + || self.unbanned_hpmns.is_set() + || self.new_masternodes.is_set() + || self.removed_masternodes.is_set() + || self.updated_mastenodes.is_set() + || self.banned_masternodes.is_set() + || self.unbanned_masternodes.is_set() + } + + pub fn removed_any_masternode_types(&self) -> bool { + self.removed_masternodes.is_set() || self.removed_hpmns.is_set() + } + + pub fn updated_any_masternode_types(&self) -> bool { + self.updated_mastenodes.is_set() || self.updated_hpmns.is_set() + } + + pub fn added_any_masternode_types(&self) -> bool { + self.new_masternodes.is_set() || self.new_hpmns.is_set() + } + + pub fn any_update_is_set(&self) -> bool { + self.updated_hpmns.is_set() + || self.banned_hpmns.is_set() + || self.unbanned_hpmns.is_set() | self.updated_mastenodes.is_set() + || self.banned_masternodes.is_set() + || self.unbanned_masternodes.is_set() + } +} + +#[derive(Clone, Debug)] +pub struct Strategy { + pub contracts_with_updates: Vec<(Contract, Option>)>, + pub operations: Vec, + pub identities_inserts: Frequency, + pub total_hpmns: u16, + pub extra_normal_mns: u16, + pub quorum_count: u16, + pub upgrading_info: Option, + pub core_height_increase: Frequency, + pub proposer_strategy: MasternodeListChangesStrategy, + pub rotate_quorums: bool, +} + +#[derive(Clone, Debug)] +pub struct UpgradingInfo { + pub current_protocol_version: ProtocolVersion, + pub proposed_protocol_versions_with_weight: Vec<(ProtocolVersion, u16)>, + /// The upgrade three quarters life is the expected amount of blocks in the window + /// for three quarters of the network to upgrade + /// if it is 1, there is a 50/50% chance that the network will upgrade in the first window + /// if it lower than 1 there is a high chance it will upgrade in the first window + /// the higher it is the lower the chance it will upgrade in the first window + pub upgrade_three_quarters_life: f64, +} + +impl UpgradingInfo { + pub fn apply_to_proposers( + &self, + proposers: Vec, + blocks_per_epoch: u64, + rng: &mut StdRng, + ) -> HashMap { + let expected_blocks = blocks_per_epoch as f64 * self.upgrade_three_quarters_life; + proposers + .into_iter() + .map(|pro_tx_hash| { + let next_version = self + .proposed_protocol_versions_with_weight + .choose_weighted(rng, |item| item.1) + .unwrap() + .0; + // we generate a random number between 0 and 1 + let u: f64 = rng.gen(); + // we want to alter the randomness so that 75% of time we get + let change_block_height = + (expected_blocks * 0.75 * f64::ln(1.0 - u) / f64::ln(0.5)) as u64; + ( + pro_tx_hash, + ValidatorVersionMigration { + current_protocol_version: self.current_protocol_version, + next_protocol_version: next_version, + change_block_height, + }, + ) + }) + .collect() + } +} + +impl Strategy { + // TODO: This belongs to `DocumentOp` + pub fn add_strategy_contracts_into_drive(&mut self, drive: &Drive) { + for op in &self.operations { + match &op.op_type { + OperationType::Document(doc_op) => { + let serialize = doc_op.contract.serialize().expect("expected to serialize"); + drive + .apply_contract_with_serialization( + &doc_op.contract, + serialize, + BlockInfo::default(), + true, + Some(Cow::Owned(SingleEpoch(0))), + None, + ) + .expect("expected to be able to add contract"); + } + _ => {} + } + } + } + + pub fn identity_state_transitions_for_block( + &self, + signer: &mut SimpleSigner, + rng: &mut StdRng, + ) -> Vec<(Identity, StateTransition)> { + let frequency = &self.identities_inserts; + if frequency.check_hit(rng) { + let count = frequency.events(rng); + crate::transitions::create_identities_state_transitions(count, 5, signer, rng) + } else { + vec![] + } + } + + pub fn contract_state_transitions( + &mut self, + current_identities: &Vec, + signer: &SimpleSigner, + rng: &mut StdRng, + ) -> Vec { + self.contracts_with_updates + .iter_mut() + .map(|(contract, contract_updates)| { + let identity_num = rng.gen_range(0..current_identities.len()); + let identity = current_identities + .get(identity_num) + .unwrap() + .clone() + .into_partial_identity_info(); + + contract.owner_id = identity.id; + let old_id = contract.id; + contract.id = generate_data_contract_id(identity.id, contract.entropy); + contract + .document_types + .iter_mut() + .for_each(|(_, document_type)| document_type.data_contract_id = contract.id); + + if let Some(contract_updates) = contract_updates { + for (_, updated_contract) in contract_updates.iter_mut() { + updated_contract.id = contract.id; + updated_contract.owner_id = contract.owner_id; + updated_contract.document_types.iter_mut().for_each( + |(_, document_type)| document_type.data_contract_id = contract.id, + ); + } + } + + // since we are changing the id, we need to update all the strategy + self.operations.iter_mut().for_each(|operation| { + if let OperationType::Document(document_op) = &mut operation.op_type { + if document_op.contract.id == old_id { + document_op.contract.id = contract.id; + document_op.contract.document_types.iter_mut().for_each( + |(_, document_type)| document_type.data_contract_id = contract.id, + ); + document_op.document_type.data_contract_id = contract.id; + } + } + }); + + let state_transition = DataContractCreateTransition::new_from_data_contract( + contract.clone(), + &identity, + 1, //key id 1 should always be a high or critical auth key in these tests + signer, + ) + .expect("expected to create a create state transition from a data contract"); + state_transition.into() + }) + .collect() + } + + pub fn contract_update_state_transitions( + &mut self, + current_identities: &Vec, + block_height: u64, + signer: &SimpleSigner, + ) -> Vec { + self.contracts_with_updates + .iter_mut() + .filter_map(|(_, contract_updates)| { + let Some(contract_updates) = contract_updates else { + return None; + }; + let Some(contract_update) = contract_updates.get(&block_height) else { + return None + }; + let identity = current_identities + .iter() + .find(|identity| identity.id == contract_update.owner_id) + .expect("expected to find an identity") + .clone() + .into_partial_identity_info(); + + let state_transition = DataContractUpdateTransition::new_from_data_contract( + contract_update.clone(), + &identity, + 1, //key id 1 should always be a high or critical auth key in these tests + signer, + ) + .expect("expected to create a create state transition from a data contract"); + Some(state_transition.into()) + }) + .collect() + } + + // TODO: this belongs to `DocumentOp`, also randomization details are common for all operations + // and could be moved out of here + pub fn state_transitions_for_block( + &self, + platform: &Platform, + block_info: &BlockInfo, + current_identities: &mut Vec, + signer: &mut SimpleSigner, + rng: &mut StdRng, + ) -> (Vec, Vec) { + let mut operations = vec![]; + let mut finalize_block_operations = vec![]; + for op in &self.operations { + if op.frequency.check_hit(rng) { + let count = rng.gen_range(op.frequency.times_per_block_range.clone()); + match &op.op_type { + OperationType::Document(DocumentOp { + action: DocumentAction::DocumentActionInsert, + document_type, + contract, + }) => { + let documents = document_type.random_documents_with_params( + count as u32, + current_identities, + block_info.time_ms, + rng, + ); + documents + .into_iter() + .for_each(|(document, identity, entropy)| { + let document_create_transition = DocumentCreateTransition { + base: DocumentBaseTransition { + id: document.id, + document_type_name: document_type.name.clone(), + action: Action::Create, + data_contract_id: contract.id, + data_contract: contract.clone(), + }, + entropy: entropy.to_buffer(), + created_at: document.created_at, + updated_at: document.created_at, + data: document.properties.into(), + }; + + let mut document_batch_transition = DocumentsBatchTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DocumentsBatch, + owner_id: identity.id, + transitions: vec![document_create_transition.into()], + signature_public_key_id: None, + signature: None, + }; + + let identity_public_key = identity + .get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([ + SecurityLevel::HIGH, + SecurityLevel::CRITICAL, + ]), + HashSet::from([ + KeyType::ECDSA_SECP256K1, + KeyType::BLS12_381, + ]), + ) + .expect("expected to get a signing key"); + + document_batch_transition + .sign_external(identity_public_key, signer) + .expect("expected to sign"); + + operations.push(document_batch_transition.into()); + }); + } + OperationType::Document(DocumentOp { + action: DocumentAction::DocumentActionDelete, + document_type, + contract, + }) => { + let any_item_query = DriveQuery::any_item_query(contract, document_type); + let mut items = platform + .drive + .query_documents_as_serialized( + any_item_query, + Some(&block_info.epoch), + None, + ) + .expect("expect to execute query") + .items; + + if !items.is_empty() { + let first_item = items.remove(0); + let document = + Document::from_bytes(first_item.as_slice(), document_type) + .expect("expected to deserialize document"); + + //todo: fix this into a search key request for the following + //let search_key_request = BTreeMap::from([(Purpose::AUTHENTICATION as u8, BTreeMap::from([(SecurityLevel::HIGH as u8, AllKeysOfKindRequest)]))]); + + let request = IdentityKeysRequest { + identity_id: document.owner_id.to_buffer(), + request_type: KeyRequestType::SpecificKeys(vec![1]), + limit: Some(1), + offset: None, + }; + let identity = platform + .drive + .fetch_identity_balance_with_keys(request, None) + .expect("expected to be able to get identity") + .expect("expected to get an identity"); + let document_delete_transition = DocumentDeleteTransition { + base: DocumentBaseTransition { + id: document.id, + document_type_name: document_type.name.clone(), + action: Action::Delete, + data_contract_id: contract.id, + data_contract: contract.clone(), + }, + }; + + let mut document_batch_transition = DocumentsBatchTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DocumentsBatch, + owner_id: identity.id, + transitions: vec![document_delete_transition.into()], + signature_public_key_id: None, + signature: None, + }; + + let identity_public_key = identity + .loaded_public_keys + .values() + .next() + .expect("expected a key"); + + document_batch_transition + .sign_external(identity_public_key, signer) + .expect("expected to sign"); + + operations.push(document_batch_transition.into()); + } + } + OperationType::Document(DocumentOp { + action: DocumentAction::DocumentActionReplace, + document_type, + contract, + }) => { + let any_item_query = DriveQuery::any_item_query(contract, document_type); + let mut items = platform + .drive + .query_documents_as_serialized( + any_item_query, + Some(&block_info.epoch), + None, + ) + .expect("expect to execute query") + .items; + + if !items.is_empty() { + let first_item = items.remove(0); + let document = + Document::from_bytes(first_item.as_slice(), document_type) + .expect("expected to deserialize document"); + + //todo: fix this into a search key request for the following + //let search_key_request = BTreeMap::from([(Purpose::AUTHENTICATION as u8, BTreeMap::from([(SecurityLevel::HIGH as u8, AllKeysOfKindRequest)]))]); + + let random_new_document = document_type.random_document_with_rng(rng); + let request = IdentityKeysRequest { + identity_id: document.owner_id.to_buffer(), + request_type: KeyRequestType::SpecificKeys(vec![1]), + limit: Some(1), + offset: None, + }; + let identity = platform + .drive + .fetch_identity_balance_with_keys(request, None) + .expect("expected to be able to get identity") + .expect("expected to get an identity"); + let document_replace_transition = DocumentReplaceTransition { + base: DocumentBaseTransition { + id: document.id, + document_type_name: document_type.name.clone(), + action: Action::Replace, + data_contract_id: contract.id, + data_contract: contract.clone(), + }, + revision: document.revision.expect("expected to unwrap revision") + + 1, + updated_at: Some(block_info.time_ms), + data: Some(random_new_document.properties), + }; + + let mut document_batch_transition = DocumentsBatchTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DocumentsBatch, + owner_id: identity.id, + transitions: vec![document_replace_transition.into()], + signature_public_key_id: None, + signature: None, + }; + + let identity_public_key = identity + .loaded_public_keys + .values() + .next() + .expect("expected a key"); + + document_batch_transition + .sign_external(identity_public_key, signer) + .expect("expected to sign"); + + operations.push(document_batch_transition.into()); + } + } + OperationType::IdentityTopUp if !current_identities.is_empty() => { + let indices: Vec = + (0..current_identities.len()).choose_multiple(rng, count as usize); + let random_identities: Vec<&Identity> = indices + .into_iter() + .map(|index| ¤t_identities[index]) + .collect(); + + for random_identity in random_identities { + operations.push(crate::transitions::create_identity_top_up_transition( + rng, + random_identity, + )); + } + } + OperationType::IdentityUpdate(update_op) if !current_identities.is_empty() => { + let indices: Vec = + (0..current_identities.len()).choose_multiple(rng, count as usize); + for index in indices { + let random_identity = current_identities.get_mut(index).unwrap(); + match update_op { + IdentityUpdateOp::IdentityUpdateAddKeys(count) => { + let (state_transition, keys_to_add_at_end_block) = + crate::transitions::create_identity_update_transition_add_keys( + random_identity, + *count, + signer, + rng, + ); + operations.push(state_transition); + finalize_block_operations.push(IdentityAddKeys( + keys_to_add_at_end_block.0, + keys_to_add_at_end_block.1, + )) + } + IdentityUpdateOp::IdentityUpdateDisableKey(count) => { + let state_transition = + crate::transitions::create_identity_update_transition_disable_keys( + random_identity, + *count, + block_info.time_ms, + signer, + rng, + ); + if let Some(state_transition) = state_transition { + operations.push(state_transition); + } + } + } + } + } + OperationType::IdentityWithdrawal if !current_identities.is_empty() => { + let indices: Vec = + (0..current_identities.len()).choose_multiple(rng, count as usize); + for index in indices { + let random_identity = current_identities.get_mut(index).unwrap(); + let state_transition = + crate::transitions::create_identity_withdrawal_transition( + random_identity, + signer, + rng, + ); + operations.push(state_transition); + } + } + // OperationType::ContractCreate(new_fields_optional_count_range, new_fields_required_count_range, new_index_count_range, document_type_count) + // if !current_identities.is_empty() => { + // DataContract::; + // + // DocumentType::random_document() + // } + // OperationType::ContractUpdate(DataContractNewDocumentTypes(count)) + // if !current_identities.is_empty() => { + // + // } + _ => {} + } + } + } + (operations, finalize_block_operations) + } + + pub fn state_transitions_for_block_with_new_identities( + &mut self, + platform: &Platform, + block_info: &BlockInfo, + current_identities: &mut Vec, + signer: &mut SimpleSigner, + rng: &mut StdRng, + ) -> (Vec, Vec) { + let mut finalize_block_operations = vec![]; + let identity_state_transitions = self.identity_state_transitions_for_block(signer, rng); + let (mut identities, mut state_transitions): (Vec, Vec) = + identity_state_transitions.into_iter().unzip(); + current_identities.append(&mut identities); + + if block_info.height == 1 { + // add contracts on block 1 + let mut contract_state_transitions = + self.contract_state_transitions(current_identities, signer, rng); + state_transitions.append(&mut contract_state_transitions); + } else { + // Don't do any state transitions on block 1 + let (mut document_state_transitions, mut add_to_finalize_block_operations) = self + .state_transitions_for_block(platform, block_info, current_identities, signer, rng); + finalize_block_operations.append(&mut add_to_finalize_block_operations); + state_transitions.append(&mut document_state_transitions); + + // There can also be contract updates + + let mut contract_update_state_transitions = self.contract_update_state_transitions( + current_identities, + block_info.height, + signer, + ); + state_transitions.append(&mut contract_update_state_transitions); + } + + (state_transitions, finalize_block_operations) + } +} + +pub enum StrategyRandomness { + SeedEntropy(u64), + RNGEntropy(StdRng), +} + +#[derive(Clone, Debug)] +pub struct ValidatorVersionMigration { + pub current_protocol_version: ProtocolVersion, + pub next_protocol_version: ProtocolVersion, + pub change_block_height: BlockHeight, +} + +pub struct ChainExecutionOutcome<'a> { + pub abci_app: AbciApplication<'a, MockCoreRPCLike>, + pub masternode_identity_balances: BTreeMap<[u8; 32], Credits>, + pub identities: Vec, + pub proposers: Vec, + pub quorums: BTreeMap, + pub current_quorum_hash: QuorumHash, + pub current_proposer_versions: Option>, + pub end_epoch_index: u16, + pub end_time_ms: u64, + pub strategy: Strategy, + pub withdrawals: Vec, +} + +pub struct ChainExecutionParameters { + pub block_start: u64, + pub core_height_start: u32, + pub block_count: u64, + pub proposers: Vec, + pub quorums: BTreeMap, + pub current_quorum_hash: QuorumHash, + // the first option is if it is set + // the second option is if we are even upgrading + pub current_proposer_versions: Option>>, + pub current_time_ms: u64, +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/transitions.rs b/packages/rs-drive-abci/tests/strategy_tests/transitions.rs new file mode 100644 index 00000000000..4562480b58a --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/transitions.rs @@ -0,0 +1,203 @@ +use crate::signer::SimpleSigner; +use dashcore::secp256k1::SecretKey; +use dashcore::{Network, PrivateKey}; +use dpp::identifier::Identifier; +use dpp::identity::core_script::CoreScript; +use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransition; +use dpp::identity::state_transition::identity_credit_withdrawal_transition::{ + IdentityCreditWithdrawalTransition, Pooling, +}; +use dpp::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; +use dpp::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; +use dpp::identity::KeyType::ECDSA_SECP256K1; +use dpp::identity::Purpose::AUTHENTICATION; +use dpp::identity::SecurityLevel::{CRITICAL, MASTER}; +use dpp::identity::{Identity, IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel}; +use dpp::state_transition::{StateTransition, StateTransitionIdentitySigned, StateTransitionType}; +use dpp::tests::fixtures::instant_asset_lock_proof_fixture; +use dpp::version::LATEST_VERSION; +use dpp::NativeBlsModule; +use rand::prelude::{IteratorRandom, StdRng}; +use std::collections::HashSet; +use std::str::FromStr; + +pub fn create_identity_top_up_transition(rng: &mut StdRng, identity: &Identity) -> StateTransition { + let (_, pk) = ECDSA_SECP256K1.random_public_and_private_key_data(rng); + let sk: [u8; 32] = pk.try_into().unwrap(); + let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap(); + let asset_lock_proof = + instant_asset_lock_proof_fixture(Some(PrivateKey::new(secret_key, Network::Dash))); + + StateTransition::IdentityTopUp( + IdentityTopUpTransition::try_from_identity( + identity.clone(), + asset_lock_proof, + secret_key.as_ref(), + &NativeBlsModule::default(), + ) + .expect("expected to create top up transition"), + ) +} + +pub fn create_identity_update_transition_add_keys( + identity: &mut Identity, + count: u16, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> (StateTransition, (Identifier, Vec)) { + identity.revision += 1; + let keys = IdentityPublicKey::random_authentication_keys_with_private_keys_with_rng( + identity.public_keys.len() as KeyID, + count as u32, + rng, + ); + + let add_public_keys: Vec = keys.iter().map(|(key, _)| key.clone()).collect(); + signer.private_keys_in_creation.extend(keys); + let (key_id, _) = identity + .public_keys + .iter() + .find(|(_, key)| key.security_level == MASTER) + .expect("expected to have a master key"); + + let state_transition = StateTransition::IdentityUpdate( + IdentityUpdateTransition::try_from_identity_with_signer( + identity, + key_id, + add_public_keys.clone(), + vec![], + None, + signer, + ) + .expect("expected to create top up transition"), + ); + + (state_transition, (identity.id, add_public_keys)) +} + +pub fn create_identity_update_transition_disable_keys( + identity: &mut Identity, + count: u16, + block_time: u64, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> Option { + identity.revision += 1; + // we want to find keys that are not disabled + let key_ids_we_could_disable = identity + .public_keys + .iter() + .filter(|(_, key)| { + key.disabled_at.is_none() + && (key.security_level != MASTER + && !(key.security_level == CRITICAL + && key.purpose == AUTHENTICATION + && key.key_type == ECDSA_SECP256K1)) + }) + .map(|(key_id, _)| *key_id) + .collect::>(); + + if key_ids_we_could_disable.is_empty() { + identity.revision -= 1; //since we added 1 before + return None; + } + let indices: Vec<_> = (0..key_ids_we_could_disable.len()).choose_multiple(rng, count as usize); + + let key_ids_to_disable: Vec<_> = indices + .into_iter() + .map(|index| key_ids_we_could_disable[index]) + .collect(); + + identity.public_keys.iter_mut().for_each(|(key_id, key)| { + if key_ids_to_disable.contains(key_id) { + key.disabled_at = Some(block_time); + } + }); + + let (key_id, _) = identity + .public_keys + .iter() + .find(|(_, key)| key.security_level == MASTER) + .expect("expected to have a master key"); + + let state_transition = StateTransition::IdentityUpdate( + IdentityUpdateTransition::try_from_identity_with_signer( + identity, + key_id, + vec![], + key_ids_to_disable, + Some(block_time), + signer, + ) + .expect("expected to create top up transition"), + ); + + Some(state_transition) +} + +pub fn create_identity_withdrawal_transition( + identity: &mut Identity, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> StateTransition { + identity.revision += 1; + let mut withdrawal = IdentityCreditWithdrawalTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityCreditWithdrawal, + identity_id: identity.id, + amount: 100000000, // 0.001 Dash + core_fee_per_byte: 1, + pooling: Pooling::Never, + output_script: CoreScript::random_p2sh(rng), + revision: identity.revision, + signature_public_key_id: 0, + signature: Default::default(), + }; + + let identity_public_key = identity + .get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([SecurityLevel::HIGH, SecurityLevel::CRITICAL]), + HashSet::from([KeyType::ECDSA_SECP256K1, KeyType::BLS12_381]), + ) + .expect("expected to get a signing key"); + + withdrawal + .sign_external(identity_public_key, signer) + .expect("expected to sign withdrawal"); + + withdrawal.into() +} + +pub fn create_identities_state_transitions( + count: u16, + key_count: KeyID, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> Vec<(Identity, StateTransition)> { + let (identities, keys) = + Identity::random_identities_with_private_keys_with_rng::>(count, key_count, rng) + .expect("expected to create identities"); + signer.add_keys(keys); + identities + .into_iter() + .map(|mut identity| { + let (_, pk) = ECDSA_SECP256K1.random_public_and_private_key_data(rng); + let sk: [u8; 32] = pk.clone().try_into().unwrap(); + let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap(); + let asset_lock_proof = + instant_asset_lock_proof_fixture(Some(PrivateKey::new(secret_key, Network::Dash))); + let identity_create_transition = + IdentityCreateTransition::try_from_identity_with_signer( + identity.clone(), + asset_lock_proof, + pk.as_slice(), + signer, + &NativeBlsModule::default(), + ) + .expect("expected to transform identity into identity create transition"); + identity.id = *identity_create_transition.get_identity_id(); + (identity, identity_create_transition.into()) + }) + .collect() +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs index c0616081684..afaa6103f7a 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { - use crate::{ - continue_chain_for_strategy, run_chain_for_strategy, ChainExecutionOutcome, - ChainExecutionParameters, Frequency, Strategy, StrategyRandomness, UpgradingInfo, - }; - use tenderdash_abci::proto::types::CoreChainLock; + use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy}; + use crate::frequency::Frequency; + use crate::strategy::{ + ChainExecutionOutcome, ChainExecutionParameters, Strategy, StrategyRandomness, + UpgradingInfo, + }; use drive_abci::config::{PlatformConfig, PlatformTestConfig}; use drive_abci::test::helpers::setup::TestPlatformBuilder; From 21a19da7519ae267dea5c6ec49fe845ad5b2fc1c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 27 Apr 2023 14:54:02 +0700 Subject: [PATCH 06/14] more work --- .../identity/identity_public_key/key_type.rs | 11 + .../identity/identity_public_key/purpose.rs | 5 +- .../rs-drive-abci/src/execution/helpers.rs | 1 - .../execution/masternode_identities/mod.rs | 780 ++++++++++-------- .../drive/batch/drive_op_batch/identity.rs | 29 +- .../identity/identity_create_transition.rs | 1 + .../identity/identity_update_transition.rs | 11 +- .../drive/identity/contract_info/insert.rs | 1 + .../src/drive/identity/estimation_costs.rs | 3 + .../fetch/fetch_by_public_key_hashes.rs | 54 +- .../rs-drive/src/drive/identity/key/insert.rs | 65 +- .../key/insert_key_hash_identity_reference.rs | 36 +- .../rs-drive/src/drive/identity/update.rs | 90 +- .../identity/identity_public_key/purpose.rs | 3 + 14 files changed, 725 insertions(+), 365 deletions(-) diff --git a/packages/rs-dpp/src/identity/identity_public_key/key_type.rs b/packages/rs-dpp/src/identity/identity_public_key/key_type.rs index 86d3705d94a..3896d8cec20 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/key_type.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/key_type.rs @@ -64,6 +64,17 @@ impl KeyType { KEY_TYPE_SIZES[self] } + /// Are keys of this type unique? + pub fn is_unique_key_type(&self) -> bool { + match self { + KeyType::ECDSA_SECP256K1 => true, + KeyType::BLS12_381 => true, + KeyType::ECDSA_HASH160 => false, + KeyType::BIP13_SCRIPT_HASH => false, + KeyType::EDDSA_25519_HASH160 => false, + } + } + //todo: put this in a specific feature /// Gets the default size of the public key pub fn random_public_key_data(&self, rng: &mut StdRng) -> Vec { diff --git a/packages/rs-dpp/src/identity/identity_public_key/purpose.rs b/packages/rs-dpp/src/identity/identity_public_key/purpose.rs index cfcf02f62e1..d92363802ba 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/purpose.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/purpose.rs @@ -1,4 +1,4 @@ -use crate::identity::Purpose::{AUTHENTICATION, DECRYPTION, ENCRYPTION, SYSTEM, WITHDRAW}; +use crate::identity::Purpose::{AUTHENTICATION, DECRYPTION, ENCRYPTION, SYSTEM, VOTING, WITHDRAW}; use anyhow::bail; use bincode::{Decode, Encode}; #[cfg(feature = "cbor")] @@ -32,6 +32,8 @@ pub enum Purpose { WITHDRAW = 3, /// this key cannot be used for signing documents SYSTEM = 4, + /// this key cannot be used for signing documents + VOTING = 5, } impl TryFrom for Purpose { @@ -43,6 +45,7 @@ impl TryFrom for Purpose { 2 => Ok(DECRYPTION), 3 => Ok(WITHDRAW), 4 => Ok(SYSTEM), + 5 => Ok(VOTING), value => bail!("unrecognized purpose: {}", value), } } diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index f55262b23fb..ef4abec0454 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -273,7 +273,6 @@ where &removed_masternodes, block_info, platform_state, - block_platform_state, transaction, )?; diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index f60f56aa034..536eef7247c 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -1,4 +1,5 @@ use crate::abci::AbciError; +use crate::error::execution::ExecutionError; use crate::error::Error; use crate::platform::Platform; use crate::rpc::core::CoreRPCLike; @@ -14,15 +15,23 @@ use dashcore_rpc::json::{ use dpp::block::block_info::BlockInfo; use dpp::identifier::Identifier; use dpp::identity::factory::IDENTITY_PROTOCOL_VERSION; +use dpp::identity::Purpose::WITHDRAW; use dpp::identity::{ Identity, IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel, TimestampMillis, }; use dpp::platform_value::BinaryData; +use drive::drive::batch::DriveOperation; use drive::drive::batch::DriveOperation::IdentityOperation; -use drive::drive::batch::IdentityOperationType::AddNewIdentity; +use drive::drive::batch::IdentityOperationType::{ + AddNewIdentity, AddNewKeysToIdentity, DisableIdentityKeys, ReEnableIdentityKeys, +}; +use drive::drive::identity::key::fetch::{ + IdentityKeysRequest, KeyIDIdentityPublicKeyPairBTreeMap, KeyIDIdentityPublicKeyPairVec, + KeyIDVec, KeyRequestType, +}; use drive::grovedb::Transaction; use sha2::{Digest, Sha256}; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; impl Platform where @@ -35,7 +44,6 @@ where removed_masternodes: &BTreeMap, block_info: &BlockInfo, platform_state: Option<&PlatformState>, - block_platform_state: &PlatformState, transaction: &Transaction, ) -> Result<(), Error> { let MasternodeListDiff { @@ -48,7 +56,8 @@ where for masternode in added_mns { let owner_identity = self.create_owner_identity(&masternode)?; - let voter_identity = self.create_voter_identity(&masternode)?; + let voter_identity = + self.create_voter_identity_from_masternode_list_item(&masternode)?; let operator_identity = self.create_operator_identity(&masternode)?; drive_operations.push(IdentityOperation(AddNewIdentity { @@ -64,318 +73,361 @@ where })); } - self.drive - .apply_drive_operations(drive_operations, true, block_info, Some(transaction))?; - - //todo: batch updates as well - for update in updated_mns.iter() { - self.update_owner_identity(update, block_info, Some(transaction))?; - self.update_voter_identity( - update, - block_info, - block_platform_state, - Some(transaction), - )?; - self.update_operator_identity( - update, - block_info, - block_platform_state, - Some(transaction), - )?; + if let Some(platform_state) = platform_state { + // On initialization there is no platform state, but we also don't need to update + // masternode identities. + for update in updated_mns.iter() { + self.update_owner_withdrawal_address( + update, + block_info, + transaction, + &mut drive_operations, + )?; + self.update_voter_identity( + update, + block_info, + platform_state, + transaction, + &mut drive_operations, + )?; + self.update_operator_identity( + update, + block_info, + platform_state, + transaction, + &mut drive_operations, + )?; + } + + for masternode in removed_masternodes.values() { + self.disable_identity_keys( + masternode, + block_info, + transaction, + &mut drive_operations, + )?; + } } - for (_, masternode) in removed_masternodes.iter() { - self.disable_identity_keys(masternode, block_info, Some(transaction))?; - } + self.drive + .apply_drive_operations(drive_operations, true, block_info, Some(transaction))?; Ok(()) } - fn update_owner_identity( + fn update_owner_withdrawal_address( &self, masternode: &(ProTxHash, DMNStateDiff), block_info: &BlockInfo, - transaction: Option<&Transaction>, + transaction: &Transaction, + drive_operations: &mut Vec, ) -> Result<(), Error> { let (pro_tx_hash, state_diff) = masternode; - if state_diff.payout_address.is_none() { + let Some(new_withdrawal_address) = state_diff.payout_address else { return Ok(()); - } + }; let owner_identifier: [u8; 32] = pro_tx_hash.into_inner(); - let owner_identity = self - .drive - .fetch_full_identity(owner_identifier, transaction)? - .ok_or_else(|| { - Error::Abci(AbciError::InvalidState( - "expected identity to be in state".to_string(), - )) - })?; - // TODO: extract the diff function - // now we need to figure out which of the keys to disable - let _new_key_id: KeyID = owner_identity - .public_keys - .last_key_value() - .map(|(last_key_id, _)| last_key_id + 1) - .unwrap_or(0); - let to_disable = owner_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none()) - .map(|(id, _)| *id) - .collect::>(); - - let new_owner_key = - Self::get_owner_identity_key(state_diff.payout_address.expect("confirmed is some"), 0)?; - let current_time = Utc::now().timestamp_millis() as TimestampMillis; - - self.drive.disable_identity_keys( - owner_identifier, - to_disable, - current_time, - block_info, - true, - transaction, - )?; - // add the new key - self.drive.add_new_non_unique_keys_to_identity( - owner_identifier, - vec![new_owner_key], - block_info, - true, - transaction, - )?; - Ok(()) - } + let key_request = IdentityKeysRequest { + identity_id: owner_identifier, + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; - fn update_voter_identity( - &self, - masternode: &(ProTxHash, DMNStateDiff), - block_info: &BlockInfo, - state: &PlatformState, - transaction: Option<&Transaction>, - ) -> Result<(), Error> { - let (pro_tx_hash, state_diff) = masternode; - if state_diff.voting_address.is_none() { - return Ok(()); + let old_withdrawal_identity_keys = self + .drive + .fetch_identity_keys::( + key_request, + Some(transaction), + )?; + + if old_withdrawal_identity_keys.is_empty() { + return Err(Error::Execution(ExecutionError::DriveMissingData( + "expected masternode owner identity to be in state", + ))); } - let old_masternode = state.full_masternode_list.get(pro_tx_hash).ok_or_else(|| { - Error::Abci(AbciError::InvalidState( - "expected masternode to be in state".to_string(), - )) - })?; + let last_key_id = *old_withdrawal_identity_keys.keys().max().unwrap(); //todo + + let key_ids_to_disable = old_withdrawal_identity_keys + .into_iter() + .filter_map(|(key_id, key)| { + if key.disabled_at.is_some() { + None //No need to disable it again + } else { + Some(key_id) + } + }) + .collect(); + + drive_operations.push(IdentityOperation(DisableIdentityKeys { + identity_id: owner_identifier, + keys_ids: key_ids_to_disable, + disable_at: block_info.time_ms, + })); - let voter_identifier = Self::get_voter_identifier(old_masternode)?; + // add the new key + let new_owner_key = Self::get_owner_identity_key(new_withdrawal_address, last_key_id + 1)?; - let voter_identity = self - .drive - .fetch_full_identity(voter_identifier, transaction)? - .ok_or_else(|| { - Error::Abci(AbciError::InvalidState( - "expected identity to be in state".to_string(), - )) - })?; + drive_operations.push(IdentityOperation(AddNewKeysToIdentity { + identity_id: owner_identifier, + unique_keys_to_add: vec![], + non_unique_keys_to_add: vec![new_owner_key], + })); - // TODO: extract the diff function - // now we need to figure out which of the keys to disable - let new_key_id: KeyID = voter_identity - .public_keys - .last_key_value() - .map(|(last_key_id, _)| last_key_id + 1) - .unwrap_or(0); - let to_disable = voter_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none()) - .map(|(id, _)| *id) - .collect::>(); - - // we need to build the new key - let new_voter_key = Self::get_voter_identity_key( - state_diff.voting_address.expect("confirmed is some"), - new_key_id, - )?; - - let current_time = Utc::now().timestamp_millis() as TimestampMillis; - - self.drive.disable_identity_keys( - voter_identifier, - to_disable, - current_time, - block_info, - true, - transaction, - )?; - // add the new key - self.drive.add_new_non_unique_keys_to_identity( - voter_identifier, - vec![new_voter_key], - block_info, - true, - transaction, - )?; Ok(()) } - fn update_operator_identity( + /// When a voter identity is updated the following events need to happen + /// The old identity key is disabled (which might make the identity unusable) + /// A new identity is added with the new key, this new key is a non unique key. + fn update_voter_identity( &self, masternode: &(ProTxHash, DMNStateDiff), block_info: &BlockInfo, - state: &PlatformState, - transaction: Option<&Transaction>, + platform_state: &PlatformState, + transaction: &Transaction, + drive_operations: &mut Vec, ) -> Result<(), Error> { let (pro_tx_hash, state_diff) = masternode; - // TODO: key type seems fragile might be better to use purpose - - if state_diff.pub_key_operator.is_none() - && state_diff.operator_payout_address.is_none() - && state_diff.platform_node_id.is_none() - { + let Some(new_voting_address) = state_diff.voting_address else { return Ok(()); - } - - // we will perform at least one update, proceed to get the current identity - // TODO: masternode is not really in state right, this error is not appropriate - let old_masternode = state.full_masternode_list.get(pro_tx_hash).ok_or_else(|| { - Error::Abci(AbciError::InvalidState( - "expected masternode to be in state".to_string(), - )) - })?; - let operator_identifier = Self::get_operator_identifier(old_masternode)?; + }; - let operator_identity = self - .drive - .fetch_full_identity(operator_identifier, transaction)? + let old_masternode = platform_state + .full_masternode_list + .get(pro_tx_hash) .ok_or_else(|| { - Error::Abci(AbciError::InvalidState( - "expected identity to be in state".to_string(), + Error::Execution(ExecutionError::CorruptedCachedState( + "expected masternode to be in state", )) })?; - let mut new_key_id: KeyID = operator_identity - .public_keys - .last_key_value() - .map(|(last_key_id, _)| last_key_id + 1) - .unwrap_or(0); - - let mut keys_to_disable: HashSet = HashSet::new(); - let mut keys_to_create: Vec = Vec::new(); - - // now we need to handle each key - if state_diff.pub_key_operator.is_some() { - // we need to get the keys to disable - let to_disable = operator_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::BLS12_381) - .map(|(id, _)| *id) - .collect::>(); - keys_to_disable.extend(to_disable); - - let new_key = IdentityPublicKey { - id: new_key_id, - key_type: KeyType::BLS12_381, - purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? - security_level: SecurityLevel::CRITICAL, - read_only: true, - data: BinaryData::new( - state_diff - .pub_key_operator - .clone() - .expect("confirmed is some"), - ), - disabled_at: None, - }; - keys_to_create.push(new_key); - new_key_id += 1; - } + let old_voter_identifier = + Self::get_voter_identifier_from_masternode_list_item(old_masternode)?; - if state_diff.operator_payout_address.is_some() { - let to_disable = operator_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_HASH160) - .map(|(id, _)| *id) - .collect::>(); - keys_to_disable.extend(to_disable); - - let new_key = IdentityPublicKey { - id: new_key_id, - // key_type: KeyType::ECDSA_HASH160, - // TODO: commented version is the correct one, disable to get it building - key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? - security_level: SecurityLevel::CRITICAL, - read_only: true, - // TODO: can this be Some(None) - data: BinaryData::new( - state_diff - .operator_payout_address - .expect("confirmed is some") - .unwrap() - .to_vec(), - ), - disabled_at: None, - }; - keys_to_create.push(new_key); - new_key_id += 1; - } + let key_request = IdentityKeysRequest { + identity_id: old_voter_identifier, + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; - if state_diff.platform_node_id.is_some() { - let to_disable = operator_identity - .public_keys - .iter() - .filter(|(_, pk)| { - pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_SECP256K1 - }) - .map(|(id, _)| *id) - .collect::>(); - keys_to_disable.extend(to_disable); - - let new_key = IdentityPublicKey { - id: new_key_id, - // key_type: KeyType::EDDSA_25519_HASH160, - // TODO: commented version is the correct one, disable to get it building - key_type: KeyType::ECDSA_SECP256K1, - // purpose: Purpose::SYSTEM, - // TODO: commented version is the correct one, disable to get it building - purpose: Purpose::DECRYPTION, - security_level: SecurityLevel::CRITICAL, - read_only: true, - // TODO: this should be the node id - data: BinaryData::new( - state_diff - .payout_address - .expect("confirmed is some") - .to_vec(), - ), - disabled_at: None, - }; - keys_to_create.push(new_key); - new_key_id += 1; + let old_voter_identity_key_ids = self + .drive + .fetch_identity_keys::(key_request, Some(transaction))?; + + if old_voter_identity_key_ids.is_empty() { + return Err(Error::Execution(ExecutionError::DriveMissingData( + "expected masternode voter identity to be in state", + ))); } - let current_time = Utc::now().timestamp_millis() as TimestampMillis; - - self.drive.disable_identity_keys( - operator_identifier, - keys_to_disable.into_iter().collect(), - current_time, - block_info, - true, - transaction, - )?; - // add the new keys - self.drive.add_new_non_unique_keys_to_identity( - operator_identifier, - keys_to_create, - block_info, - true, - transaction, - )?; + drive_operations.push(IdentityOperation(DisableIdentityKeys { + identity_id: old_voter_identifier, + keys_ids: old_voter_identity_key_ids, + disable_at: block_info.time_ms, + })); + + // Part 2 : Create or Update Voting identity based on new key + let new_voter_identity = + Self::create_voter_identity(pro_tx_hash.as_inner(), &new_voting_address)?; + + // Let's check if the voting identity already exists + let key_request = IdentityKeysRequest { + identity_id: pro_tx_hash.into_inner(), + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + let new_voter_identity_key_ids = self + .drive + .fetch_identity_keys::(key_request, Some(transaction))?; + + // two possibilities + if !new_voter_identity_key_ids.is_empty() { + // first is that the new voter key already existed + // if it is disabled re-enable it + + if new_voter_identity_key_ids.len() > 1 { + return Err(Error::Execution(ExecutionError::DriveIncoherence( + "more than one masternode voter identity for an address and pro_tx_hash pair", + ))); + } + + drive_operations.push(IdentityOperation(ReEnableIdentityKeys { + identity_id: old_voter_identifier, + keys_ids: new_voter_identity_key_ids, + })); + } else { + // other is that the + drive_operations.push(IdentityOperation(AddNewIdentity { + identity: new_voter_identity, + })); + } + Ok(()) + } + fn update_operator_identity( + &self, + masternode: &(ProTxHash, DMNStateDiff), + block_info: &BlockInfo, + platform_state: &PlatformState, + transaction: &Transaction, + drive_operations: &mut Vec, + ) -> Result<(), Error> { + // let (pro_tx_hash, state_diff) = masternode; + // // TODO: key type seems fragile might be better to use purpose + // + // if state_diff.pub_key_operator.is_none() + // && state_diff.operator_payout_address.is_none() + // && state_diff.platform_node_id.is_none() + // { + // return Ok(()); + // } + // + // // we will perform at least one update, proceed to get the current identity + // let old_masternode = platform_state.full_masternode_list.get(pro_tx_hash).ok_or_else(|| { + // Error::Execution(ExecutionError::CorruptedCachedState( + // "expected masternode to be in state" + // )) + // })?; + // let operator_identifier = Self::get_operator_identifier_from_masternode_list_item(old_masternode)?; + // + // let operator_identity = self + // .drive + // .fetch_full_identity(operator_identifier, transaction)? + // .ok_or_else(|| { + // Error::Execution(ExecutionError::DriveMissingData( + // "expected masternode operator identity to be in state" + // )) + // })?; + // + // let mut new_key_id: KeyID = operator_identity + // .public_keys + // .last_key_value() + // .map(|(last_key_id, _)| last_key_id + 1) + // .unwrap_or(0); + // + // let mut keys_to_disable: HashSet = HashSet::new(); + // let mut keys_to_create: Vec = Vec::new(); + // + // // now we need to handle each key + // if state_diff.pub_key_operator.is_some() { + // // we need to get the keys to disable + // let to_disable = operator_identity + // .public_keys + // .iter() + // .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::BLS12_381) + // .map(|(id, _)| *id) + // .collect::>(); + // keys_to_disable.extend(to_disable); + // + // let new_key = IdentityPublicKey { + // id: new_key_id, + // key_type: KeyType::BLS12_381, + // purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? + // security_level: SecurityLevel::CRITICAL, + // read_only: true, + // data: BinaryData::new( + // state_diff + // .pub_key_operator + // .clone() + // .expect("confirmed is some"), + // ), + // disabled_at: None, + // }; + // keys_to_create.push(new_key); + // new_key_id += 1; + // } + // + // if state_diff.operator_payout_address.is_some() { + // let to_disable = operator_identity + // .public_keys + // .iter() + // .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_HASH160) + // .map(|(id, _)| *id) + // .collect::>(); + // keys_to_disable.extend(to_disable); + // + // let new_key = IdentityPublicKey { + // id: new_key_id, + // // key_type: KeyType::ECDSA_HASH160, + // // TODO: commented version is the correct one, disable to get it building + // key_type: KeyType::ECDSA_HASH160, + // purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + // security_level: SecurityLevel::CRITICAL, + // read_only: true, + // // TODO: can this be Some(None) + // data: BinaryData::new( + // state_diff + // .operator_payout_address + // .expect("confirmed is some") + // .unwrap() + // .to_vec(), + // ), + // disabled_at: None, + // }; + // keys_to_create.push(new_key); + // new_key_id += 1; + // } + // + // if state_diff.platform_node_id.is_some() { + // let to_disable = operator_identity + // .public_keys + // .iter() + // .filter(|(_, pk)| { + // pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_SECP256K1 + // }) + // .map(|(id, _)| *id) + // .collect::>(); + // keys_to_disable.extend(to_disable); + // + // let new_key = IdentityPublicKey { + // id: new_key_id, + // // key_type: KeyType::EDDSA_25519_HASH160, + // // TODO: commented version is the correct one, disable to get it building + // key_type: KeyType::ECDSA_SECP256K1, + // // purpose: Purpose::SYSTEM, + // // TODO: commented version is the correct one, disable to get it building + // purpose: Purpose::DECRYPTION, + // security_level: SecurityLevel::CRITICAL, + // read_only: true, + // // TODO: this should be the node id + // data: BinaryData::new( + // state_diff + // .payout_address + // .expect("confirmed is some") + // .to_vec(), + // ), + // disabled_at: None, + // }; + // keys_to_create.push(new_key); + // new_key_id += 1; + // } + // + // let current_time = Utc::now().timestamp_millis() as TimestampMillis; + // + // self.drive.disable_identity_keys( + // operator_identifier, + // keys_to_disable.into_iter().collect(), + // current_time, + // block_info, + // true, + // transaction, + // )?; + // // add the new keys + // self.drive.add_new_non_unique_keys_to_identity( + // operator_identifier, + // keys_to_create, + // block_info, + // true, + // transaction, + // )?; + // + // Ok(()) Ok(()) } @@ -383,58 +435,72 @@ where &self, old_masternode: &MasternodeListItem, block_info: &BlockInfo, - transaction: Option<&Transaction>, + transaction: &Transaction, + drive_operations: &mut Vec, ) -> Result<(), Error> { - let owner_identifier = Self::get_owner_identifier(old_masternode)?; - let operator_identifier = Self::get_operator_identifier(old_masternode)?; - let voter_identifier = Self::get_voter_identifier(old_masternode)?; + let operator_identifier = + Self::get_operator_identifier_from_masternode_list_item(old_masternode)?; + let voter_identifier = + Self::get_voter_identifier_from_masternode_list_item(old_masternode)?; + + let operator_key_request = IdentityKeysRequest { + identity_id: operator_identifier, + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; - let owner_identity = self - .drive - .fetch_full_identity(owner_identifier, transaction)? - .unwrap(); - let operator_identity = self + let voter_key_request = IdentityKeysRequest { + identity_id: voter_identifier, + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let operator_identity_keys = self .drive - .fetch_full_identity(operator_identifier, transaction)? - .unwrap(); - let voter_identity = self + .fetch_identity_keys::( + operator_key_request, + Some(transaction), + )? + .into_iter() + .filter_map(|(key_id, key)| { + if key.is_disabled() { + None //No need to disable it again + } else if key.purpose == WITHDRAW { + None //Don't disable withdrawal keys + } else { + Some(key_id) + } + }) + .collect(); + let voter_identity_keys = self .drive - .fetch_full_identity(voter_identifier, transaction)? - .unwrap(); - - let mut keys_to_disable = HashSet::new(); - keys_to_disable.extend( - owner_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none()) - .map(|(id, _)| *id), - ); - keys_to_disable.extend( - operator_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none()) - .map(|(id, _)| *id), - ); - keys_to_disable.extend( - voter_identity - .public_keys - .iter() - .filter(|(_, pk)| pk.disabled_at.is_none()) - .map(|(id, _)| *id), - ); - - let current_time = Utc::now().timestamp_millis() as TimestampMillis; - - self.drive.disable_identity_keys( - operator_identifier, - keys_to_disable.into_iter().collect(), - current_time, - block_info, - true, - transaction, - )?; + .fetch_identity_keys::( + voter_key_request, + Some(transaction), + )? + .into_iter() + .filter_map(|(key_id, key)| { + if key.is_disabled() { + None //No need to disable it again + } else { + Some(key_id) + } + }) + .collect(); + + drive_operations.push(IdentityOperation(DisableIdentityKeys { + identity_id: operator_identifier, + keys_ids: operator_identity_keys, + disable_at: block_info.time_ms, + })); + + drive_operations.push(IdentityOperation(DisableIdentityKeys { + identity_id: operator_identifier, + keys_ids: voter_identity_keys, + disable_at: block_info.time_ms, + })); Ok(()) } @@ -449,18 +515,29 @@ where Ok(identity) } - fn create_voter_identity(&self, masternode: &MasternodeListItem) -> Result { - let voting_identifier = Self::get_voter_identifier(masternode)?; + fn create_voter_identity( + pro_tx_hash: &[u8; 32], + voting_key: &[u8; 20], + ) -> Result { + let voting_identifier = Self::get_voter_identifier(pro_tx_hash, voting_key)?; let mut identity = Self::create_basic_identity(voting_identifier); - identity.add_public_keys([Self::get_voter_identity_key( - masternode.state.voting_address, - 0, - )?]); + identity.add_public_keys([Self::get_voter_identity_key(*voting_key, 0)?]); Ok(identity) } + fn create_voter_identity_from_masternode_list_item( + &self, + masternode: &MasternodeListItem, + ) -> Result { + Self::create_voter_identity( + masternode.pro_tx_hash.as_inner(), + &masternode.state.voting_address, + ) + } + fn create_operator_identity(&self, masternode: &MasternodeListItem) -> Result { - let operator_identifier = Self::get_operator_identifier(masternode)?; + let operator_identifier = + Self::get_operator_identifier_from_masternode_list_item(masternode)?; let mut identity = Self::create_basic_identity(operator_identifier); identity.add_public_keys(self.get_operator_identity_keys( masternode.state.pub_key_operator.clone(), @@ -479,7 +556,7 @@ where id: key_id, key_type: KeyType::ECDSA_HASH160, purpose: Purpose::WITHDRAW, - security_level: SecurityLevel::MASTER, + security_level: SecurityLevel::CRITICAL, read_only: true, data: BinaryData::new(payout_address.to_vec()), disabled_at: None, @@ -493,8 +570,8 @@ where Ok(IdentityPublicKey { id: key_id, key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? - security_level: SecurityLevel::MASTER, + purpose: Purpose::VOTING, + security_level: SecurityLevel::HIGH, read_only: true, data: BinaryData::new(voting_address.to_vec()), disabled_at: None, @@ -510,7 +587,7 @@ where let mut identity_public_keys = vec![IdentityPublicKey { id: 0, key_type: KeyType::BLS12_381, - purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? + purpose: Purpose::SYSTEM, security_level: SecurityLevel::CRITICAL, read_only: true, data: BinaryData::new(pub_key_operator), @@ -519,13 +596,10 @@ where if let Some(operator_payout_address) = operator_payout_address { identity_public_keys.push(IdentityPublicKey { id: 1, - // key_type: KeyType::ECDSA_HASH160, - // TODO: commented version is the correct one, disable to get it building key_type: KeyType::ECDSA_HASH160, - purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + purpose: Purpose::WITHDRAW, security_level: SecurityLevel::CRITICAL, read_only: true, - // TODO: this should be the operator payout address data: BinaryData::new(operator_payout_address.to_vec()), disabled_at: None, }); @@ -550,20 +624,38 @@ where Ok(masternode_identifier) } - fn get_operator_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { - let pro_tx_hash = &masternode.pro_tx_hash.into_inner(); - let operator_pub_key = masternode.state.pub_key_operator.as_slice(); + fn get_operator_identifier( + pro_tx_hash: &[u8; 32], + operator_pub_key: &[u8], + ) -> Result<[u8; 32], Error> { let operator_identifier = Self::hash_concat_protxhash(pro_tx_hash, operator_pub_key)?; Ok(operator_identifier) } - fn get_voter_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + fn get_operator_identifier_from_masternode_list_item( + masternode: &MasternodeListItem, + ) -> Result<[u8; 32], Error> { let pro_tx_hash = &masternode.pro_tx_hash.into_inner(); - let voting_address = masternode.state.voting_address.as_slice(); + let operator_pub_key = masternode.state.pub_key_operator.as_slice(); + Self::get_operator_identifier(pro_tx_hash, operator_pub_key) + } + + fn get_voter_identifier( + pro_tx_hash: &[u8; 32], + voting_address: &[u8; 20], + ) -> Result<[u8; 32], Error> { let voting_identifier = Self::hash_concat_protxhash(pro_tx_hash, voting_address)?; Ok(voting_identifier) } + fn get_voter_identifier_from_masternode_list_item( + masternode: &MasternodeListItem, + ) -> Result<[u8; 32], Error> { + let pro_tx_hash = &masternode.pro_tx_hash.into_inner(); + let voting_address = &masternode.state.voting_address; + Self::get_voter_identifier(pro_tx_hash, voting_address) + } + fn hash_concat_protxhash(pro_tx_hash: &[u8; 32], key_data: &[u8]) -> Result<[u8; 32], Error> { let mut hasher = Sha256::new(); hasher.update(pro_tx_hash); diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs index 36739d185d3..61f13ecf5fe 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs @@ -36,8 +36,10 @@ pub enum IdentityOperationType { AddNewKeysToIdentity { /// The identity id of the identity identity_id: [u8; 32], - /// The keys to be added - keys_to_add: Vec, + /// The unique keys to be added + unique_keys_to_add: Vec, + /// The non unique keys to be added + non_unique_keys_to_add: Vec, }, /// Disable Identity Keys DisableIdentityKeys { @@ -49,6 +51,14 @@ pub enum IdentityOperationType { disable_at: TimestampMillis, }, + /// Re-Enable Identity Keys + ReEnableIdentityKeys { + /// The identity id of the identity + identity_id: [u8; 32], + /// The keys to be added + keys_ids: Vec, + }, + /// Updates an identities revision. UpdateIdentityRevision { /// The revision id @@ -97,10 +107,12 @@ impl DriveLowLevelOperationConverter for IdentityOperationType { ), IdentityOperationType::AddNewKeysToIdentity { identity_id, - keys_to_add, + unique_keys_to_add, + non_unique_keys_to_add, } => drive.add_new_keys_to_identity_operations( identity_id, - keys_to_add, + unique_keys_to_add, + non_unique_keys_to_add, true, estimated_costs_only_with_layer_info, transaction, @@ -116,6 +128,15 @@ impl DriveLowLevelOperationConverter for IdentityOperationType { estimated_costs_only_with_layer_info, transaction, ), + IdentityOperationType::ReEnableIdentityKeys { + identity_id, + keys_ids, + } => drive.re_enable_identity_keys_operations( + identity_id, + keys_ids, + estimated_costs_only_with_layer_info, + transaction, + ), IdentityOperationType::UpdateIdentityRevision { identity_id, revision, diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs index 8d8b72835f2..aaa276947f4 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs @@ -6,6 +6,7 @@ use crate::drive::defaults::PROTOCOL_VERSION; use crate::error::Error; use dpp::block::epoch::Epoch; use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransitionAction; +use dpp::identity::IdentityPublicKey; use dpp::prelude::Identity; impl DriveHighLevelOperationConverter for IdentityCreateTransitionAction { diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs index e14449b0e7c..4d88c3692c2 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs @@ -4,6 +4,7 @@ use crate::drive::batch::{DriveOperation, IdentityOperationType}; use crate::error::Error; use dpp::block::epoch::Epoch; +use dpp::identity::IdentityPublicKey; use dpp::identity::state_transition::identity_update_transition::IdentityUpdateTransitionAction; @@ -21,6 +22,11 @@ impl DriveHighLevelOperationConverter for IdentityUpdateTransitionAction { .. } = self; + let (unique_keys, non_unique_keys): (Vec, Vec) = + add_public_keys + .into_iter() + .partition(|key| key.key_type.is_unique_key_type()); + let mut drive_operations = vec![]; drive_operations.push(IdentityOperation( @@ -30,11 +36,12 @@ impl DriveHighLevelOperationConverter for IdentityUpdateTransitionAction { }, )); - if !add_public_keys.is_empty() { + if !unique_keys.is_empty() || !non_unique_keys.is_empty() { drive_operations.push(IdentityOperation( IdentityOperationType::AddNewKeysToIdentity { identity_id: identity_id.to_buffer(), - keys_to_add: add_public_keys, + unique_keys_to_add: unique_keys, + non_unique_keys_to_add: non_unique_keys, }, )); } diff --git a/packages/rs-drive/src/drive/identity/contract_info/insert.rs b/packages/rs-drive/src/drive/identity/contract_info/insert.rs index 0a841504afc..5c2f3645167 100644 --- a/packages/rs-drive/src/drive/identity/contract_info/insert.rs +++ b/packages/rs-drive/src/drive/identity/contract_info/insert.rs @@ -60,6 +60,7 @@ impl Drive { self.add_new_keys_to_identity_operations( identity_id, keys, + vec![], false, estimated_costs_only_with_layer_info, transaction, diff --git a/packages/rs-drive/src/drive/identity/estimation_costs.rs b/packages/rs-drive/src/drive/identity/estimation_costs.rs index 8af5cbba729..77b4154ad1d 100644 --- a/packages/rs-drive/src/drive/identity/estimation_costs.rs +++ b/packages/rs-drive/src/drive/identity/estimation_costs.rs @@ -290,6 +290,7 @@ impl Drive { } Purpose::WITHDRAW => ApproximateElements(1), Purpose::SYSTEM => ApproximateElements(1), + Purpose::VOTING => ApproximateElements(1), }; let estimated_layer_sizes = match purpose { @@ -302,6 +303,7 @@ impl Drive { } Purpose::WITHDRAW => AllReference(1, KEY_REFERENCE_SIZE, None), Purpose::SYSTEM => AllReference(1, KEY_REFERENCE_SIZE, None), + Purpose::VOTING => AllReference(1, KEY_REFERENCE_SIZE, None), }; // we then need to insert the identity keys layer estimated_costs_only_with_layer_info.insert( @@ -329,6 +331,7 @@ impl Drive { identity_id.as_slice(), security_level, )), + //todo: revisit EstimatedLayerInformation { is_sum_tree: false, estimated_layer_count: ApproximateElements(4), //we can estimate that each security level will only have 4 keys diff --git a/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs index 54ce6380aab..f08703ca37f 100644 --- a/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs @@ -1,12 +1,13 @@ use crate::drive::grove_operations::DirectQueryType::StatefulDirectQuery; use crate::drive::{ - non_unique_key_hashes_sub_tree_path, non_unique_key_hashes_tree_path, - unique_key_hashes_tree_path, unique_key_hashes_tree_path_vec, Drive, + non_unique_key_hashes_sub_tree_path, non_unique_key_hashes_sub_tree_path_vec, + non_unique_key_hashes_tree_path, unique_key_hashes_tree_path, unique_key_hashes_tree_path_vec, + Drive, }; use crate::error::drive::DriveError; use crate::error::Error; use crate::fee::op::LowLevelDriveOperation; -use crate::query::QueryResultEncoding; +use crate::query::{QueryItem, QueryResultEncoding}; use dpp::identity::Identity; use dpp::platform_value::Value; use dpp::Convertible; @@ -15,6 +16,7 @@ use grovedb::query_result_type::QueryResultType; use grovedb::Element::Item; use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; use std::collections::BTreeMap; +use std::ops::RangeFull; impl Drive { /// Fetches an identity id with all its information from storage. @@ -31,6 +33,20 @@ impl Drive { ) } + /// Fetches identity ids from storage. + pub fn fetch_identity_ids_by_non_unique_public_key_hash( + &self, + public_key_hash: [u8; 20], + transaction: TransactionArg, + ) -> Result, Error> { + let mut drive_operations: Vec = vec![]; + self.fetch_identity_ids_by_non_unique_public_key_hash_operations( + public_key_hash, + transaction, + &mut drive_operations, + ) + } + /// Given an identity, fetches the identity with its flags from storage. pub(crate) fn fetch_identity_id_by_unique_public_key_hash_operations( &self, @@ -66,6 +82,38 @@ impl Drive { } } + /// Gets identity ids from non unique public key hashes. + pub(crate) fn fetch_identity_ids_by_non_unique_public_key_hash_operations( + &self, + public_key_hash: [u8; 20], + transaction: TransactionArg, + drive_operations: &mut Vec, + ) -> Result, Error> { + let non_unique_key_hashes = non_unique_key_hashes_sub_tree_path_vec(public_key_hash); + let path_query = PathQuery::new_single_query_item( + non_unique_key_hashes, + QueryItem::RangeFull(RangeFull), + ); + let (results, _) = self.grove_get_path_query( + &path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + drive_operations, + )?; + results + .to_keys() + .into_iter() + .map(|key| { + key.try_into().map_err(|_| { + Error::Drive(DriveError::CorruptedDriveState( + "non unique public key hashes should point to identity ids of 32 bytes" + .to_string(), + )) + }) + }) + .collect() + } + /// Fetches identity ids with all its information from storage. pub fn fetch_identity_ids_by_unique_public_key_hashes( &self, diff --git a/packages/rs-drive/src/drive/identity/key/insert.rs b/packages/rs-drive/src/drive/identity/key/insert.rs index bf3d37f93db..a1fe07c8206 100644 --- a/packages/rs-drive/src/drive/identity/key/insert.rs +++ b/packages/rs-drive/src/drive/identity/key/insert.rs @@ -178,7 +178,53 @@ impl Drive { transaction: TransactionArg, drive_operations: &mut Vec, ) -> Result<(), Error> { - drive_operations.append(&mut self.insert_reference_to_key_operations( + drive_operations.append(&mut self.insert_reference_to_unique_key_operations( + identity_id, + &identity_key, + estimated_costs_only_with_layer_info, + transaction, + )?); + + let key_id_bytes = identity_key.id.encode_var_vec(); + + self.insert_key_to_storage_operations( + identity_id, + &identity_key, + key_id_bytes.as_slice(), + drive_operations, + )?; + + if with_references + && matches!( + identity_key.purpose, + Purpose::AUTHENTICATION | Purpose::WITHDRAW + ) + { + self.insert_key_searchable_references_operations( + identity_id, + &identity_key, + key_id_bytes.as_slice(), + estimated_costs_only_with_layer_info, + transaction, + drive_operations, + )?; + } + Ok(()) + } + + /// Insert a new non unique key into an identity operations + pub(crate) fn insert_new_non_unique_key_operations( + &self, + identity_id: [u8; 32], + identity_key: IdentityPublicKey, + with_references: bool, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + drive_operations: &mut Vec, + ) -> Result<(), Error> { + drive_operations.append(&mut self.insert_reference_to_non_unique_key_operations( identity_id, &identity_key, estimated_costs_only_with_layer_info, @@ -250,7 +296,11 @@ impl Drive { &mut batch_operations, )?; - for key in keys.into_iter() { + let (unique_keys, non_unique_keys): (Vec, Vec) = keys + .into_iter() + .partition(|key| key.key_type.is_unique_key_type()); + + for key in unique_keys.into_iter() { self.insert_new_unique_key_operations( identity_id, key, @@ -260,6 +310,17 @@ impl Drive { &mut batch_operations, )?; } + + for key in non_unique_keys.into_iter() { + self.insert_new_non_unique_key_operations( + identity_id, + key, + true, + estimated_costs_only_with_layer_info, + transaction, + &mut batch_operations, + )?; + } Ok(batch_operations) } diff --git a/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs b/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs index e5749316d32..3ed7d04b2cd 100644 --- a/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs +++ b/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs @@ -27,7 +27,7 @@ impl Drive { /// Insert a public key hash reference that contains an identity id /// Contrary to the name this is not a reference but an Item containing the identity /// identifier - pub(crate) fn insert_reference_to_key_operations( + pub(crate) fn insert_reference_to_unique_key_operations( &self, identity_id: [u8; 32], identity_key: &IdentityPublicKey, @@ -58,6 +58,40 @@ impl Drive { ) } + /// Insert a public key hash reference that contains an identity id + /// Contrary to the name this is not a reference but an Item containing the identity + /// identifier + pub(crate) fn insert_reference_to_non_unique_key_operations( + &self, + identity_id: [u8; 32], + identity_key: &IdentityPublicKey, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + ) -> Result, Error> { + let mut drive_operations = vec![]; + let hash_vec = identity_key.hash()?; + let key_hash = hash_vec.as_slice().try_into().map_err(|_| { + Error::Drive(DriveError::CorruptedCodeExecution("key hash not 20 bytes")) + })?; + + let key_len = identity_key.data.len(); + drive_operations.push(FunctionOperation(FunctionOp::new_with_byte_count( + HashFunction::Sha256RipeMD160, + key_len as u16, + ))); + + //todo: check if key is unique + + self.insert_non_unique_public_key_hash_reference_to_identity_operations( + identity_id, + key_hash, + estimated_costs_only_with_layer_info, + transaction, + ) + } + /// Adds the estimation costs for the insertion of a unique public key hash reference fn add_estimation_costs_for_insert_unique_public_key_hash_reference( estimated_costs_only_with_layer_info: &mut HashMap, diff --git a/packages/rs-drive/src/drive/identity/update.rs b/packages/rs-drive/src/drive/identity/update.rs index 2b76679cd95..7e9e04c69a1 100644 --- a/packages/rs-drive/src/drive/identity/update.rs +++ b/packages/rs-drive/src/drive/identity/update.rs @@ -200,6 +200,68 @@ impl Drive { Ok(drive_operations) } + pub(crate) fn re_enable_identity_keys_operations( + &self, + identity_id: [u8; 32], + key_ids: Vec, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + ) -> Result, Error> { + let mut drive_operations = vec![]; + + let key_ids_len = key_ids.len(); + + let keys: KeyIDIdentityPublicKeyPairVec = if let Some( + estimated_costs_only_with_layer_info, + ) = estimated_costs_only_with_layer_info + { + Self::add_estimation_costs_for_keys_for_identity_id( + identity_id, + estimated_costs_only_with_layer_info, + ); + key_ids + .into_iter() + .map(|key_id| (key_id, IdentityPublicKey::max_possible_size_key(key_id))) + .collect() + } else { + let key_request = IdentityKeysRequest { + identity_id, + request_type: KeyRequestType::SpecificKeys(key_ids), + limit: Some(key_ids_len as u16), + offset: None, + }; + + self.fetch_identity_keys_operations(key_request, transaction, &mut drive_operations)? + }; + + if keys.len() != key_ids_len { + // TODO Choose / add an appropriate error + return Err(Error::Drive(DriveError::UpdatingDocumentThatDoesNotExist( + "key to re-enable with specified ID is not found", + ))); + } + + const RE_ENABLE_KEY_TIME_BYTE_COST: i32 = 9; + + for (_, mut key) in keys { + key.disabled_at = None; + + let key_id_bytes = key.id.encode_var_vec(); + + self.replace_key_in_storage_operations( + identity_id.as_slice(), + &key, + &key_id_bytes, + RE_ENABLE_KEY_TIME_BYTE_COST, + &mut drive_operations, + )?; + } + + Ok(drive_operations) + } + /// Add new non unique keys to an identity pub fn add_new_non_unique_keys_to_identity( &self, @@ -216,6 +278,7 @@ impl Drive { }; let batch_operations = self.add_new_keys_to_identity_operations( identity_id, + vec![], keys_to_add, true, &mut estimated_costs_only_with_layer_info, @@ -233,7 +296,7 @@ impl Drive { } /// Add new keys to an identity - pub fn add_new_keys_to_identity( + pub fn add_new_unique_keys_to_identity( &self, identity_id: [u8; 32], keys_to_add: Vec, @@ -249,6 +312,7 @@ impl Drive { let batch_operations = self.add_new_keys_to_identity_operations( identity_id, keys_to_add, + vec![], true, &mut estimated_costs_only_with_layer_info, transaction, @@ -268,7 +332,8 @@ impl Drive { pub(crate) fn add_new_keys_to_identity_operations( &self, identity_id: [u8; 32], - keys_to_add: Vec, + unique_keys_to_add: Vec, + non_unique_keys_to_add: Vec, with_references: bool, estimated_costs_only_with_layer_info: &mut Option< HashMap, @@ -283,7 +348,7 @@ impl Drive { ); } - for key in keys_to_add { + for key in unique_keys_to_add { self.insert_new_unique_key_operations( identity_id, key, @@ -293,6 +358,17 @@ impl Drive { &mut drive_operations, )?; } + + for key in non_unique_keys_to_add { + self.insert_new_non_unique_key_operations( + identity_id, + key, + with_references, + estimated_costs_only_with_layer_info, + transaction, + &mut drive_operations, + )?; + } Ok(drive_operations) } } @@ -325,7 +401,7 @@ mod tests { let db_transaction = drive.grove.start_transaction(); let fee_result = drive - .add_new_keys_to_identity( + .add_new_unique_keys_to_identity( identity.id.to_buffer(), new_keys_to_add, &block, @@ -373,7 +449,7 @@ mod tests { let db_transaction = drive.grove.start_transaction(); let fee_result = drive - .add_new_keys_to_identity( + .add_new_unique_keys_to_identity( identity.id.to_buffer(), new_keys_to_add, &block, @@ -421,7 +497,7 @@ mod tests { .expect("should return app hash"); let fee_result = drive - .add_new_keys_to_identity( + .add_new_unique_keys_to_identity( identity.id.to_buffer(), new_keys_to_add, &block, @@ -469,7 +545,7 @@ mod tests { let new_keys_to_add = IdentityPublicKey::random_keys(5, 2, Some(15)); drive - .add_new_keys_to_identity( + .add_new_unique_keys_to_identity( identity.id.to_buffer(), new_keys_to_add.clone(), &block_info, diff --git a/packages/wasm-dpp/src/identity/identity_public_key/purpose.rs b/packages/wasm-dpp/src/identity/identity_public_key/purpose.rs index ac5a3eacdba..0623438ef2c 100644 --- a/packages/wasm-dpp/src/identity/identity_public_key/purpose.rs +++ b/packages/wasm-dpp/src/identity/identity_public_key/purpose.rs @@ -13,6 +13,8 @@ pub enum PurposeWasm { WITHDRAW = 3, /// this key cannot be used for signing documents SYSTEM = 4, + /// this key cannot be used for signing documents + VOTING = 5, } impl From for PurposeWasm { @@ -23,6 +25,7 @@ impl From for PurposeWasm { Purpose::DECRYPTION => PurposeWasm::DECRYPTION, Purpose::WITHDRAW => PurposeWasm::WITHDRAW, Purpose::SYSTEM => PurposeWasm::SYSTEM, + Purpose::VOTING => PurposeWasm::VOTING, } } } From 817719282f0bb206c57f80440893589cb44a83e0 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 07:42:36 +0700 Subject: [PATCH 07/14] work on masternode identities --- .../execution/masternode_identities/mod.rs | 385 +++++++++++------- 1 file changed, 228 insertions(+), 157 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index 536eef7247c..87d0278c1a1 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -7,7 +7,8 @@ use crate::state::PlatformState; use chrono::Utc; use dashcore::hashes::Hash; use dashcore::ProTxHash; -use dashcore_rpc::dashcore_rpc_json::MasternodeListDiff; +use dashcore_rpc::dashcore_rpc_json::MasternodeType::{HighPerformance, Regular}; +use dashcore_rpc::dashcore_rpc_json::{MasternodeListDiff, MasternodeType}; use dashcore_rpc::json::{ DMNStateDiff, MasternodeListDiffWithMasternodes, MasternodeListItem, RemovedMasternodeItem, UpdatedMasternodeItem, @@ -31,7 +32,7 @@ use drive::drive::identity::key::fetch::{ }; use drive::grovedb::Transaction; use sha2::{Digest, Sha256}; -use std::collections::{BTreeMap, BTreeSet, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; impl Platform where @@ -278,156 +279,223 @@ where transaction: &Transaction, drive_operations: &mut Vec, ) -> Result<(), Error> { - // let (pro_tx_hash, state_diff) = masternode; - // // TODO: key type seems fragile might be better to use purpose - // - // if state_diff.pub_key_operator.is_none() - // && state_diff.operator_payout_address.is_none() - // && state_diff.platform_node_id.is_none() - // { - // return Ok(()); - // } - // - // // we will perform at least one update, proceed to get the current identity - // let old_masternode = platform_state.full_masternode_list.get(pro_tx_hash).ok_or_else(|| { - // Error::Execution(ExecutionError::CorruptedCachedState( - // "expected masternode to be in state" - // )) - // })?; - // let operator_identifier = Self::get_operator_identifier_from_masternode_list_item(old_masternode)?; - // - // let operator_identity = self - // .drive - // .fetch_full_identity(operator_identifier, transaction)? - // .ok_or_else(|| { - // Error::Execution(ExecutionError::DriveMissingData( - // "expected masternode operator identity to be in state" - // )) - // })?; - // - // let mut new_key_id: KeyID = operator_identity - // .public_keys - // .last_key_value() - // .map(|(last_key_id, _)| last_key_id + 1) - // .unwrap_or(0); - // - // let mut keys_to_disable: HashSet = HashSet::new(); - // let mut keys_to_create: Vec = Vec::new(); - // - // // now we need to handle each key - // if state_diff.pub_key_operator.is_some() { - // // we need to get the keys to disable - // let to_disable = operator_identity - // .public_keys - // .iter() - // .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::BLS12_381) - // .map(|(id, _)| *id) - // .collect::>(); - // keys_to_disable.extend(to_disable); - // - // let new_key = IdentityPublicKey { - // id: new_key_id, - // key_type: KeyType::BLS12_381, - // purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? - // security_level: SecurityLevel::CRITICAL, - // read_only: true, - // data: BinaryData::new( - // state_diff - // .pub_key_operator - // .clone() - // .expect("confirmed is some"), - // ), - // disabled_at: None, - // }; - // keys_to_create.push(new_key); - // new_key_id += 1; - // } - // - // if state_diff.operator_payout_address.is_some() { - // let to_disable = operator_identity - // .public_keys - // .iter() - // .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_HASH160) - // .map(|(id, _)| *id) - // .collect::>(); - // keys_to_disable.extend(to_disable); - // - // let new_key = IdentityPublicKey { - // id: new_key_id, - // // key_type: KeyType::ECDSA_HASH160, - // // TODO: commented version is the correct one, disable to get it building - // key_type: KeyType::ECDSA_HASH160, - // purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? - // security_level: SecurityLevel::CRITICAL, - // read_only: true, - // // TODO: can this be Some(None) - // data: BinaryData::new( - // state_diff - // .operator_payout_address - // .expect("confirmed is some") - // .unwrap() - // .to_vec(), - // ), - // disabled_at: None, - // }; - // keys_to_create.push(new_key); - // new_key_id += 1; - // } - // - // if state_diff.platform_node_id.is_some() { - // let to_disable = operator_identity - // .public_keys - // .iter() - // .filter(|(_, pk)| { - // pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_SECP256K1 - // }) - // .map(|(id, _)| *id) - // .collect::>(); - // keys_to_disable.extend(to_disable); - // - // let new_key = IdentityPublicKey { - // id: new_key_id, - // // key_type: KeyType::EDDSA_25519_HASH160, - // // TODO: commented version is the correct one, disable to get it building - // key_type: KeyType::ECDSA_SECP256K1, - // // purpose: Purpose::SYSTEM, - // // TODO: commented version is the correct one, disable to get it building - // purpose: Purpose::DECRYPTION, - // security_level: SecurityLevel::CRITICAL, - // read_only: true, - // // TODO: this should be the node id - // data: BinaryData::new( - // state_diff - // .payout_address - // .expect("confirmed is some") - // .to_vec(), - // ), - // disabled_at: None, - // }; - // keys_to_create.push(new_key); - // new_key_id += 1; - // } - // - // let current_time = Utc::now().timestamp_millis() as TimestampMillis; - // - // self.drive.disable_identity_keys( - // operator_identifier, - // keys_to_disable.into_iter().collect(), - // current_time, - // block_info, - // true, - // transaction, - // )?; - // // add the new keys - // self.drive.add_new_non_unique_keys_to_identity( - // operator_identifier, - // keys_to_create, - // block_info, - // true, - // transaction, - // )?; - // - // Ok(()) + let (pro_tx_hash, state_diff) = masternode; + + if state_diff.pub_key_operator.is_none() + && state_diff.operator_payout_address.is_none() + && state_diff.platform_node_id.is_none() + { + return Ok(()); + } + + let needs_change_operator = state_diff.pub_key_operator.is_some(); + let needs_change_operator_payout_address = state_diff.operator_payout_address.is_some(); + let needs_change_platform_node_id = state_diff.platform_node_id.is_some(); + + let mut old_masternode = platform_state + .full_masternode_list + .get(pro_tx_hash) + .ok_or_else(|| { + Error::Execution(ExecutionError::CorruptedCachedState( + "expected masternode to be in state", + )) + })?; + + let old_operator_identifier = + Self::get_operator_identifier_from_masternode_list_item(old_masternode)?; + + let mut new_masternode = old_masternode.clone(); + + new_masternode.state.apply_diff(state_diff.clone()); + + let new_operator_identifier = + Self::get_operator_identifier_from_masternode_list_item(&new_masternode)?; + + let key_request = IdentityKeysRequest { + identity_id: old_operator_identifier, + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let old_identity_keys = self + .drive + .fetch_identity_keys::( + key_request, + Some(transaction), + )?; + + // two possibilities, same identity or identity switch. + if new_operator_identifier == old_operator_identifier { + // we are on same identity for platform + + let mut old_operator_node_id_to_re_enable = None; + + let mut old_operator_payout_address_to_re_enable = None; + + let last_key_id = *old_identity_keys.keys().max().unwrap(); //todo + + let old_operator_identity_key_ids_to_disable: Vec = old_identity_keys + .into_iter() + .filter_map(|(key_id, key)| { + // We can disable previous withdrawal keys as we are adding a new one + if needs_change_operator_payout_address { + if Some(key.data.as_slice()) + == old_masternode + .state + .operator_payout_address + .as_ref() + .map(|bytes| bytes.as_slice()) + { + return Some(key_id); + } else if let Some(operator_payout_address) = + state_diff.operator_payout_address.as_ref().unwrap() + { + // an old key that we need to re-enable + if key.data.as_slice() == operator_payout_address.as_slice() { + old_operator_payout_address_to_re_enable = Some(key_id); + } + } + } + if needs_change_platform_node_id + && old_masternode.state.platform_node_id.is_some() + { + if key.data.as_slice() + == old_masternode.state.platform_node_id.as_ref().unwrap() + { + return Some(key_id); + } else if state_diff.platform_node_id.as_ref().unwrap().as_slice() + == key.data.as_slice() + { + old_operator_node_id_to_re_enable = Some(key_id); + } + } + None + }) + .collect(); + + if !old_operator_identity_key_ids_to_disable.is_empty() { + drive_operations.push(IdentityOperation(DisableIdentityKeys { + identity_id: new_operator_identifier, + keys_ids: old_operator_identity_key_ids_to_disable, + disable_at: block_info.time_ms, + })); + } + + let mut keys_to_re_enable = vec![]; + let mut unique_keys_to_add = vec![]; + let mut non_unique_keys_to_add = vec![]; + + let mut new_key_id = last_key_id + 1; + + if let Some(old_operator_pub_key_to_re_enable) = old_operator_node_id_to_re_enable { + keys_to_re_enable.push(old_operator_pub_key_to_re_enable); + } else if needs_change_platform_node_id { + let key = IdentityPublicKey { + id: new_key_id, + key_type: KeyType::EDDSA_25519_HASH160, + purpose: Purpose::SYSTEM, + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new( + state_diff + .platform_node_id + .as_ref() + .expect("platform node id confirmed is some") + .to_vec(), + ), + disabled_at: None, + }; + unique_keys_to_add.push(key); + new_key_id += 1; + } + + if let Some(old_operator_payout_address_to_re_enable) = + old_operator_payout_address_to_re_enable + { + keys_to_re_enable.push(old_operator_payout_address_to_re_enable); + } else if needs_change_operator_payout_address { + if let Some(new_operator_payout_address) = state_diff + .operator_payout_address + .as_ref() + .expect("operator_payout_address confirmed is some") + { + let key = IdentityPublicKey { + id: new_key_id, + key_type: KeyType::ECDSA_HASH160, + purpose: WITHDRAW, + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new(new_operator_payout_address.to_vec()), + disabled_at: None, + }; + non_unique_keys_to_add.push(key); + new_key_id += 1; + } + } + + drive_operations.push(IdentityOperation(AddNewKeysToIdentity { + identity_id: new_operator_identifier, + unique_keys_to_add, + non_unique_keys_to_add, + })); + } else { + // We can not disable previous withdrawal keys, + // Let's disable other two keys + let old_operator_identity_key_ids_to_disable: Vec = old_identity_keys + .into_iter() + .filter_map(|(key_id, key)| { + if key.data.as_slice() == old_masternode.state.pub_key_operator { + //the old key + return Some(key_id); + } + if old_masternode.state.platform_node_id.is_some() { + if key.data.as_slice() + == old_masternode.state.platform_node_id.as_ref().unwrap() + { + return Some(key_id); + } + } + None + }) + .collect(); + + if !old_operator_identity_key_ids_to_disable.is_empty() { + drive_operations.push(IdentityOperation(DisableIdentityKeys { + identity_id: old_operator_identifier, + keys_ids: old_operator_identity_key_ids_to_disable, + disable_at: block_info.time_ms, + })); + } + let new_payout_address = + if let Some(operator_payout_address) = state_diff.operator_payout_address { + operator_payout_address + } else { + // we need to use the old pub_key_operator + old_masternode.state.operator_payout_address + }; + + let new_platform_node_id = if let Some(platform_node_id) = state_diff.platform_node_id { + // if it changed it means it always existed + Some(platform_node_id) + } else { + // we need to use the old pub_key_operator + old_masternode.state.platform_node_id + }; + // Now we need to create the new operator identity with the new keys + let mut identity = Self::create_basic_identity(new_operator_identifier); + identity.add_public_keys( + self.get_operator_identity_keys( + state_diff + .pub_key_operator + .as_ref() + .expect("expected a pub key operator") + .clone(), + new_payout_address, + new_platform_node_id, + )?, + ); + } Ok(()) } @@ -626,9 +694,9 @@ where fn get_operator_identifier( pro_tx_hash: &[u8; 32], - operator_pub_key: &[u8], + pub_key_operator: &[u8], ) -> Result<[u8; 32], Error> { - let operator_identifier = Self::hash_concat_protxhash(pro_tx_hash, operator_pub_key)?; + let operator_identifier = Self::hash_concat_protxhash(pro_tx_hash, pub_key_operator)?; Ok(operator_identifier) } @@ -636,8 +704,7 @@ where masternode: &MasternodeListItem, ) -> Result<[u8; 32], Error> { let pro_tx_hash = &masternode.pro_tx_hash.into_inner(); - let operator_pub_key = masternode.state.pub_key_operator.as_slice(); - Self::get_operator_identifier(pro_tx_hash, operator_pub_key) + Self::get_operator_identifier(pro_tx_hash, masternode.state.pub_key_operator.as_slice()) } fn get_voter_identifier( @@ -657,11 +724,15 @@ where } fn hash_concat_protxhash(pro_tx_hash: &[u8; 32], key_data: &[u8]) -> Result<[u8; 32], Error> { + // todo: maybe change hash functions let mut hasher = Sha256::new(); hasher.update(pro_tx_hash); hasher.update(key_data); // TODO: handle unwrap, use custom error - Ok(hasher.finalize().try_into().unwrap()) + Ok(hasher + .finalize() + .try_into() + .expect("expected a 32 byte hash")) } fn create_basic_identity(id: [u8; 32]) -> Identity { From 8edd6d50c0606f0a6327498dc45fc9f9b7289a9b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 10:28:22 +0700 Subject: [PATCH 08/14] fixed non determinism issues for updates --- packages/rs-drive-abci/src/error/execution.rs | 2 +- .../execution/fee_pools/fee_distribution.rs | 2 +- .../execution/masternode_identities/mod.rs | 30 ++-- .../tests/strategy_tests/execution.rs | 7 +- .../tests/strategy_tests/main.rs | 2 +- .../masternode_list_item_helpers.rs | 64 +++++++++ .../tests/strategy_tests/masternodes.rs | 133 ++++++++++++++++-- 7 files changed, 216 insertions(+), 24 deletions(-) diff --git a/packages/rs-drive-abci/src/error/execution.rs b/packages/rs-drive-abci/src/error/execution.rs index 7548eaa65fd..70574b00d28 100644 --- a/packages/rs-drive-abci/src/error/execution.rs +++ b/packages/rs-drive-abci/src/error/execution.rs @@ -47,7 +47,7 @@ pub enum ExecutionError { /// Data is missing from the drive. #[error("drive missing data error: {0}")] - DriveMissingData(&'static str), + DriveMissingData(String), /// Corrupted credits are not balanced. #[error("corrupted credits not balanced error: {0}")] diff --git a/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs b/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs index 661a812dd1b..5cefa4e9c5d 100644 --- a/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs +++ b/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs @@ -285,7 +285,7 @@ impl Platform { .properties .get("percentage") .ok_or(Error::Execution(ExecutionError::DriveMissingData( - "percentage property is missing", + "percentage property is missing".to_string(), )))? .to_integer() .map_err(|_| { diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index 87d0278c1a1..4bf16669754 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -48,11 +48,19 @@ where transaction: &Transaction, ) -> Result<(), Error> { let MasternodeListDiff { - added_mns, - updated_mns, + mut added_mns, + mut updated_mns, .. } = masternode_diff; + // We should don't trust the order of added mns or updated mns + + // Sort added_mns based on pro_tx_hash + added_mns.sort_by(|a, b| a.pro_tx_hash.cmp(&b.pro_tx_hash)); + + // Sort updated_mns based on pro_tx_hash (the first element of the tuple) + updated_mns.sort_by(|a, b| a.0.cmp(&b.0)); + let mut drive_operations = vec![]; for masternode in added_mns { @@ -91,13 +99,13 @@ where transaction, &mut drive_operations, )?; - self.update_operator_identity( - update, - block_info, - platform_state, - transaction, - &mut drive_operations, - )?; + // self.update_operator_identity( + // update, + // block_info, + // platform_state, + // transaction, + // &mut drive_operations, + // )?; } for masternode in removed_masternodes.values() { @@ -146,7 +154,7 @@ where if old_withdrawal_identity_keys.is_empty() { return Err(Error::Execution(ExecutionError::DriveMissingData( - "expected masternode owner identity to be in state", + "expected masternode owner identity to be in state".to_string(), ))); } @@ -222,7 +230,7 @@ where if old_voter_identity_key_ids.is_empty() { return Err(Error::Execution(ExecutionError::DriveMissingData( - "expected masternode voter identity to be in state", + "expected masternode voter identity to be in state".to_string(), ))); } diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs index 4dbd6ee48e4..8acc25424fb 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -70,6 +70,7 @@ pub(crate) fn run_chain_for_strategy( generate_updates, &mut rng, ); + let mut all_masternodes = initial_masternodes.clone(); let mut all_hpmns = initial_hpmns.clone(); @@ -398,13 +399,15 @@ pub(crate) fn run_chain_for_strategy( removed_masternodes.extend(removed_hpmns); updated_masternodes.extend(updated_hpmns); - MasternodeListDiff { + let diff = MasternodeListDiff { base_height: base_block, block_height: block, added_mns: added_masternodes, removed_mns: removed_masternodes, updated_mns: updated_masternodes, - } + }; + dbg!(&diff); + diff } }; diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 85ce4315198..0a90485b9dc 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -409,7 +409,7 @@ mod tests { .expect("expected to fetch balances") .expect("expected to have an identity to get balance from"); - assert_eq!(balance, 99873699460) + assert_eq!(balance, 99867074480) } #[test] diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs index 8bff97a1119..8f6f2a6a006 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs @@ -66,3 +66,67 @@ impl UpdateMasternodeListItem for MasternodeListItem { } } } + +#[cfg(test)] +mod tests { + use super::*; + use dashcore::hashes::Hash; + use dashcore::{ProTxHash, Txid}; + use dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeType}; + use rand::prelude::*; + use rand::SeedableRng; + use std::net::SocketAddr; + use std::str::FromStr; + + #[test] + fn test_random_keys_update_determinism() { + let mut rng = StdRng::seed_from_u64(0); + let i = 0; + let pro_tx_hash = ProTxHash::from_inner(rng.gen::<[u8; 32]>()); + let private_key_operator = + BlsPrivateKey::generate_dash(&mut rng).expect("expected to generate a private key"); + let pub_key_operator = private_key_operator + .g1_element() + .expect("expected to get public key") + .to_bytes() + .to_vec(); + let masternode_list_item = MasternodeListItem { + node_type: MasternodeType::Regular, + pro_tx_hash, + collateral_hash: Txid::from_inner(rng.gen::<[u8; 32]>()), + collateral_index: 0, + collateral_address: [0; 20], + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str(format!("1.0.{}.{}:1234", i / 256, i % 256).as_str()) + .unwrap(), + registered_height: 0, + pose_revived_height: None, + pose_ban_height: None, + revocation_reason: 0, + owner_address: rng.gen::<[u8; 20]>(), + voting_address: rng.gen::<[u8; 20]>(), + payout_address: rng.gen::<[u8; 20]>(), + pub_key_operator, + operator_payout_address: None, + platform_node_id: None, + platform_p2p_port: None, + platform_http_port: None, + }, + }; + + for _ in 0..100 { + let rng_seed = rng.gen(); + let mut rng1 = StdRng::seed_from_u64(rng_seed); + let mut rng2 = StdRng::seed_from_u64(rng_seed); + + let mut masternode_list_item1 = masternode_list_item.clone(); // Add a constructor for creating a new instance + let mut masternode_list_item2 = masternode_list_item.clone(); + + masternode_list_item1.random_keys_update(None, &mut rng1); + masternode_list_item2.random_keys_update(None, &mut rng2); + + assert_eq!(masternode_list_item1, masternode_list_item2); + } + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs index 5d72a108f99..fe333dff3fd 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs @@ -7,10 +7,11 @@ use dpp::bls_signatures::PrivateKey as BlsPrivateKey; use drive_abci::execution::test_quorum::TestQuorumInfo; use rand::prelude::{IteratorRandom, StdRng}; use rand::Rng; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::net::SocketAddr; use std::str::FromStr; +#[derive(Clone, Debug)] pub struct GenerateTestMasternodeUpdates<'a> { pub start_core_height: u32, pub end_core_height: u32, @@ -33,8 +34,8 @@ pub fn generate_test_masternodes( let mut hpmns: Vec = Vec::with_capacity(hpmn_count as usize); let (block_height_to_list_masternode_updates, block_height_to_list_hpmns_updates): ( - Option>>, - Option>>, + Option>>, + Option>>, ) = updates .map( |GenerateTestMasternodeUpdates { @@ -63,8 +64,8 @@ pub fn generate_test_masternodes( ) .unzip(); - fn invert_hashmap(input: HashMap>) -> HashMap> { - let mut output = HashMap::new(); + fn invert_btreemap(input: BTreeMap>) -> BTreeMap> { + let mut output = BTreeMap::new(); for (key, values) in input { for value in values { @@ -77,13 +78,13 @@ pub fn generate_test_masternodes( let masternode_number_to_heights_updates = block_height_to_list_masternode_updates .map(|block_height_to_list_masternode_updates| { - invert_hashmap(block_height_to_list_masternode_updates) + invert_btreemap(block_height_to_list_masternode_updates) }) .unwrap_or_default(); let hpmn_number_to_heights_updates = block_height_to_list_hpmns_updates .map(|block_height_to_list_hpmns_updates| { - invert_hashmap(block_height_to_list_hpmns_updates) + invert_btreemap(block_height_to_list_hpmns_updates) }) .unwrap_or_default(); @@ -241,7 +242,7 @@ where .collect() } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct MasternodeListItemWithUpdates { pub masternode: MasternodeListItem, pub updates: BTreeMap, @@ -262,3 +263,119 @@ impl MasternodeListItemWithUpdates { } } } + +#[cfg(test)] +mod tests { + use super::*; + use rand::SeedableRng; + use std::ops::Range; + + #[test] + fn verify_generate_test_masternodes_is_deterministic_no_updates() { + let masternode_count = 100; + let hpmn_count = 50; + let mut rng1 = StdRng::seed_from_u64(12345); + let mut rng2 = StdRng::seed_from_u64(12345); + + let (masternodes1, hpmn1) = + generate_test_masternodes(masternode_count, hpmn_count, None, &mut rng1); + let (masternodes2, hpmn2) = + generate_test_masternodes(masternode_count, hpmn_count, None, &mut rng2); + + assert_eq!(masternodes1, masternodes2); + assert_eq!(hpmn1, hpmn2); + } + + #[test] + fn verify_generate_test_masternodes_is_deterministic_with_updates() { + let masternode_count = 100; + let hpmn_count = 50; + let updates = Some(GenerateTestMasternodeUpdates { + start_core_height: 10, + end_core_height: 20, + update_masternode_frequency: &Frequency { + times_per_block_range: Range { start: 1, end: 3 }, + chance_per_block: Some(0.5), + }, + update_hpmn_frequency: &Frequency { + times_per_block_range: Range { start: 1, end: 3 }, + chance_per_block: Some(0.5), + }, + }); + let mut rng1 = StdRng::seed_from_u64(12345); + let mut rng2 = StdRng::seed_from_u64(12345); + + let (masternodes1, hpmn1) = + generate_test_masternodes(masternode_count, hpmn_count, updates.clone(), &mut rng1); + let (masternodes2, hpmn2) = + generate_test_masternodes(masternode_count, hpmn_count, updates.clone(), &mut rng2); + + assert_eq!(masternodes1, masternodes2); + assert_eq!(hpmn1, hpmn2); + } + + #[test] + fn verify_generate_test_masternodes_is_deterministic_no_updates_with_random_seeds() { + for _ in 0..20 { + let mut rng = StdRng::seed_from_u64(0); + let seed = rng.gen(); + + let mut rng1 = StdRng::seed_from_u64(seed); + let mut rng2 = StdRng::seed_from_u64(seed); + + let masternode_count = if rng.gen::() { + 0 + } else { + rng.gen_range(25..=100) + }; + let hpmn_count = rng.gen_range(50..=150); + + let (masternodes1, hpmn1) = + generate_test_masternodes(masternode_count, hpmn_count, None, &mut rng1); + let (masternodes2, hpmn2) = + generate_test_masternodes(masternode_count, hpmn_count, None, &mut rng2); + + assert_eq!(masternodes1, masternodes2); + assert_eq!(hpmn1, hpmn2); + } + } + + #[test] + fn verify_generate_test_masternodes_is_deterministic_with_updates_with_random_seeds() { + for _ in 0..20 { + let mut rng = StdRng::seed_from_u64(0); + let seed = rng.gen(); + + let mut rng1 = StdRng::seed_from_u64(seed); + let mut rng2 = StdRng::seed_from_u64(seed); + + let masternode_count = if rng.gen::() { + 0 + } else { + rng.gen_range(25..=100) + }; + let hpmn_count = rng.gen_range(50..=150); + + let updates = Some(GenerateTestMasternodeUpdates { + start_core_height: 10, + end_core_height: 20, + update_masternode_frequency: &Frequency { + times_per_block_range: Range { start: 1, end: 3 }, + chance_per_block: Some(0.5), + }, + update_hpmn_frequency: &Frequency { + times_per_block_range: Range { start: 1, end: 3 }, + chance_per_block: Some(0.5), + }, + }); + + let (masternodes1, hpmn1) = + generate_test_masternodes(masternode_count, hpmn_count, updates.clone(), &mut rng1); + let (masternodes2, hpmn2) = + generate_test_masternodes(masternode_count, hpmn_count, updates.clone(), &mut rng2); + + assert_eq!(masternodes1, masternodes2); + assert_eq!(hpmn1, hpmn2); + } + } +} From 32b51124d82cd4d23a915367ce1658129c658cdb Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 11:43:42 +0700 Subject: [PATCH 09/14] got update test passing --- .../execution/masternode_identities/mod.rs | 20 +++++++++---------- .../tests/strategy_tests/execution.rs | 2 +- packages/rs-drive-verify-c-binding/src/lib.rs | 2 -- .../rs-drive/src/drive/document/delete.rs | 2 -- .../key/insert_key_hash_identity_reference.rs | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index 4bf16669754..a2b3391372a 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -99,13 +99,13 @@ where transaction, &mut drive_operations, )?; - // self.update_operator_identity( - // update, - // block_info, - // platform_state, - // transaction, - // &mut drive_operations, - // )?; + self.update_operator_identity( + update, + block_info, + platform_state, + transaction, + &mut drive_operations, + )?; } for masternode in removed_masternodes.values() { @@ -246,7 +246,7 @@ where // Let's check if the voting identity already exists let key_request = IdentityKeysRequest { - identity_id: pro_tx_hash.into_inner(), + identity_id: new_voter_identity.id.to_buffer(), request_type: KeyRequestType::AllKeys, limit: None, offset: None, @@ -296,7 +296,6 @@ where return Ok(()); } - let needs_change_operator = state_diff.pub_key_operator.is_some(); let needs_change_operator_payout_address = state_diff.operator_payout_address.is_some(); let needs_change_platform_node_id = state_diff.platform_node_id.is_some(); @@ -414,7 +413,7 @@ where ), disabled_at: None, }; - unique_keys_to_add.push(key); + non_unique_keys_to_add.push(key); new_key_id += 1; } @@ -503,6 +502,7 @@ where new_platform_node_id, )?, ); + drive_operations.push(IdentityOperation(AddNewIdentity { identity })); } Ok(()) } diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs index 8acc25424fb..11a823bd04e 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -406,7 +406,7 @@ pub(crate) fn run_chain_for_strategy( removed_mns: removed_masternodes, updated_mns: updated_masternodes, }; - dbg!(&diff); + // dbg!(&diff); diff } }; diff --git a/packages/rs-drive-verify-c-binding/src/lib.rs b/packages/rs-drive-verify-c-binding/src/lib.rs index 047b90961cc..af6300fd10e 100644 --- a/packages/rs-drive-verify-c-binding/src/lib.rs +++ b/packages/rs-drive-verify-c-binding/src/lib.rs @@ -661,7 +661,6 @@ mod tests { Drive::verify_full_identities_by_public_key_hashes(proof, key_hashes) .expect("expect that this be verified"); assert_eq!(proved_identities.len(), 10); - dbg!(proved_identities.values()); } #[test] @@ -671,7 +670,6 @@ mod tests { 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, ]; - dbg!(hex::encode(identity_id)); let (_root_hash, maybe_identity) = Drive::verify_full_identity_by_identity_id(proof, true, identity_id) .expect("verification failed"); diff --git a/packages/rs-drive/src/drive/document/delete.rs b/packages/rs-drive/src/drive/document/delete.rs index 5797ffa049e..54a37aebfb9 100644 --- a/packages/rs-drive/src/drive/document/delete.rs +++ b/packages/rs-drive/src/drive/document/delete.rs @@ -1035,7 +1035,6 @@ mod tests { .document_type_for_name("person") .expect("expected to get a document type"); - dbg!(document_type); let random_owner_id0 = rand::thread_rng().gen::<[u8; 32]>(); let person_document0 = json_document_to_document( @@ -1048,7 +1047,6 @@ mod tests { let serialized = person_document0 .serialize(&document_type) .expect("expected to serialize"); - dbg!(hex::encode(&serialized)); let deserialized = Document::from_bytes(&serialized, &document_type).expect("expected to deserialize"); } diff --git a/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs b/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs index 3ed7d04b2cd..15b4b051ba7 100644 --- a/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs +++ b/packages/rs-drive/src/drive/identity/key/insert_key_hash_identity_reference.rs @@ -231,7 +231,7 @@ impl Drive { if already_exists_in_unique { return Err(Error::Identity(IdentityError::UniqueKeyAlreadyExists( - "the key already exists in the unique set", + "the key already exists in the unique set while we are inserting it to the non unique set", ))); } From 3a205f0ea5049ff4caffdf77849b90c8a9213b27 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 12:52:16 +0700 Subject: [PATCH 10/14] all tests passing --- .../fetch/fetch_by_public_key_hashes.rs | 17 +++-- .../full_identities_by_public_key_hashes.rs | 67 ++++++++++--------- .../identity_ids_by_public_key_hashes.rs | 39 ++++++----- .../rs-drive/src/drive/identity/update.rs | 4 +- .../rs-platform-value/src/system_bytes.rs | 12 ++-- 5 files changed, 79 insertions(+), 60 deletions(-) diff --git a/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs index f08703ca37f..c9e56053315 100644 --- a/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs @@ -438,11 +438,18 @@ mod tests { .expect("expected to get hash") .try_into() .expect("expected 20 bytes"); - let identity_id = drive - .fetch_identity_id_by_unique_public_key_hash(hash, Some(&transaction)) - .expect("expected to fetch identity_id") - .expect("expected to get an identity id"); - assert_eq!(identity_id, identity.id.to_buffer()); + if key.key_type.is_unique_key_type() { + let identity_id = drive + .fetch_identity_id_by_unique_public_key_hash(hash, Some(&transaction)) + .expect("expected to fetch identity_id") + .expect("expected to get an identity id"); + assert_eq!(identity_id, identity.id.to_buffer()); + } else { + let identity_ids = drive + .fetch_identity_ids_by_non_unique_public_key_hash(hash, Some(&transaction)) + .expect("expected to get identity ids"); + assert!(identity_ids.contains(&identity.id.to_buffer())); + } } } } diff --git a/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs index c757bb3f9be..5d060745727 100644 --- a/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs @@ -72,15 +72,18 @@ mod tests { drive .add_new_identity(identity.clone(), &BlockInfo::default(), true, None) .expect("expected to add an identity"); + let first_key_hash = identity .public_keys - .first_key_value() - .expect("expected a key") - .1 + .values() + .filter(|public_key| public_key.key_type.is_unique_key_type()) + .next() + .expect("expected a unique key") .hash() - .expect("expected to hash first_key") + .expect("expected to hash data") .try_into() .expect("expected to be 20 bytes"); + let proof = drive .prove_full_identity_by_unique_public_key_hash(first_key_hash, None) .expect("should not error when proving an identity"); @@ -115,38 +118,42 @@ mod tests { let key_hashes_to_identities = identities .values() .into_iter() - .map(|identity| { - ( - identity - .public_keys - .first_key_value() - .expect("expected a key") - .1 - .hash() - .expect("expected to hash first_key") - .try_into() - .expect("expected to be 20 bytes"), - Some(identity.clone()), - ) + .flat_map(|identity| { + identity + .public_keys + .values() + .filter(|public_key| public_key.key_type.is_unique_key_type()) + .map(move |public_key| { + ( + public_key + .hash() + .expect("expected to hash data") + .try_into() + .expect("expected to be 20 bytes"), + Some(identity.clone()), + ) + }) }) .collect::>>(); let key_hashes_to_identity_ids = identities .values() .into_iter() - .map(|identity| { - ( - identity - .public_keys - .first_key_value() - .expect("expected a key") - .1 - .hash() - .expect("expected to hash first_key") - .try_into() - .expect("expected to be 20 bytes"), - Some(identity.id.to_buffer()), - ) + .flat_map(|identity| { + identity + .public_keys + .values() + .filter(|public_key| public_key.key_type.is_unique_key_type()) + .map(move |public_key| { + ( + public_key + .hash() + .expect("expected to hash data") + .try_into() + .expect("expected to be 20 bytes"), + Some(identity.id.to_buffer()), + ) + }) }) .collect::>>(); diff --git a/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs index f32db2e7696..95ec2cebfa6 100644 --- a/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs @@ -46,15 +46,18 @@ mod tests { drive .add_new_identity(identity.clone(), &BlockInfo::default(), true, None) .expect("expected to add an identity"); + let first_key_hash = identity .public_keys - .first_key_value() - .expect("expected a key") - .1 + .values() + .filter(|public_key| public_key.key_type.is_unique_key_type()) + .next() + .expect("expected a unique key") .hash() - .expect("expected to hash first_key") + .expect("expected to hash data") .try_into() .expect("expected to be 20 bytes"); + let proof = drive .prove_identity_id_by_unique_public_key_hash(first_key_hash, None) .expect("should not error when proving an identity"); @@ -92,19 +95,21 @@ mod tests { let key_hashes_to_identity_ids = identities .values() .into_iter() - .map(|identity| { - ( - identity - .public_keys - .first_key_value() - .expect("expected a key") - .1 - .hash() - .expect("expected to hash first_key") - .try_into() - .expect("expected to be 20 bytes"), - Some(identity.id.to_buffer()), - ) + .flat_map(|identity| { + identity + .public_keys + .values() + .filter(|public_key| public_key.key_type.is_unique_key_type()) + .map(move |public_key| { + ( + public_key + .hash() + .expect("expected to hash data") + .try_into() + .expect("expected to be 20 bytes"), + Some(identity.id.to_buffer()), + ) + }) }) .collect::>>(); diff --git a/packages/rs-drive/src/drive/identity/update.rs b/packages/rs-drive/src/drive/identity/update.rs index 7e9e04c69a1..1b6f1ec22d0 100644 --- a/packages/rs-drive/src/drive/identity/update.rs +++ b/packages/rs-drive/src/drive/identity/update.rs @@ -414,7 +414,7 @@ mod tests { fee_result, FeeResult { storage_fee: 14148000, - processing_fee: 2432090, + processing_fee: 2409690, ..Default::default() } ); @@ -462,7 +462,7 @@ mod tests { fee_result, FeeResult { storage_fee: 346086000, - processing_fee: 9556170, + processing_fee: 9288050, ..Default::default() } ); diff --git a/packages/rs-platform-value/src/system_bytes.rs b/packages/rs-platform-value/src/system_bytes.rs index c1f37db18a0..0ffdd7d76ca 100644 --- a/packages/rs-platform-value/src/system_bytes.rs +++ b/packages/rs-platform-value/src/system_bytes.rs @@ -338,16 +338,16 @@ impl Value { /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); /// /// /// let value = Value::Text("WRdZY72e8yUfNJ5VC9ckzga7ysE=".to_string()); - /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); + /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([89, 23, 89, 99, 189, 158, 243, 37, 31, 52, 158, 85, 11, 215, 36, 206, 6, 187, 202, 193]))); /// /// let value = Value::Text("a811".to_string()); - /// assert_eq!(value.into_bytes_20(), Err(Error::ByteLengthNot36BytesError("buffer was not 36 bytes long".to_string()))); + /// assert_eq!(value.into_bytes_20(), Err(Error::ByteLengthNot20BytesError("buffer was not 20 bytes long".to_string()))); /// /// let value = Value::Text("a811Ii".to_string()); /// assert_eq!(value.into_bytes_20(), Err(Error::StructureError("value was a string, but could not be decoded from base 64".to_string()))); /// /// let value = Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104)]); - /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101]))); + /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104]))); /// /// let value = Value::Bool(true); /// assert_eq!(value.into_bytes_20(), Err(Error::StructureError("value are not bytes, a string, or an array of values representing bytes".to_string()))); @@ -386,16 +386,16 @@ impl Value { /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); /// /// /// let value = Value::Text("WRdZY72e8yUfNJ5VC9ckzga7ysE=".to_string()); - /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); + /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([89, 23, 89, 99, 189, 158, 243, 37, 31, 52, 158, 85, 11, 215, 36, 206, 6, 187, 202, 193]))); /// /// let value = Value::Text("a811".to_string()); - /// assert_eq!(value.to_bytes_20(), Err(Error::ByteLengthNot36BytesError("buffer was not 36 bytes long".to_string()))); + /// assert_eq!(value.to_bytes_20(), Err(Error::ByteLengthNot20BytesError("buffer was not 20 bytes long".to_string()))); /// /// let value = Value::Text("a811Ii".to_string()); /// assert_eq!(value.to_bytes_20(), Err(Error::StructureError("value was a string, but could not be decoded from base 64".to_string()))); /// /// let value = Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104)]); - /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101]))); + /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104]))); /// /// let value = Value::Bool(true); /// assert_eq!(value.to_bytes_20(), Err(Error::StructureError("value are not bytes, a string, or an array of values representing bytes".to_string()))); From f03fa7e2526cb20e7c0598e0d54d5ea1b2739f51 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 13:36:09 +0700 Subject: [PATCH 11/14] more work on block hash --- Cargo.lock | 6 +++--- packages/rs-drive-abci/Cargo.toml | 2 +- packages/rs-drive-abci/src/abci/handlers.rs | 4 ++-- packages/rs-drive-abci/src/abci/mimic.rs | 7 +++++-- packages/rs-drive-abci/src/block.rs | 10 +++++----- packages/rs-drive-abci/src/execution/engine.rs | 4 ++-- .../rs-drive-abci/tests/strategy_tests/execution.rs | 6 +++--- .../strategy_tests/masternode_list_item_helpers.rs | 4 ++-- .../rs-drive-abci/tests/strategy_tests/masternodes.rs | 4 ++-- packages/rs-drive-abci/tests/strategy_tests/signer.rs | 2 +- .../rs-drive-abci/tests/strategy_tests/strategy.rs | 3 ++- .../rs-drive-abci/tests/strategy_tests/transitions.rs | 4 ++-- 12 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eabcc933c5e..ece3fbd530e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3509,7 +3509,7 @@ dependencies = [ [[package]] name = "tenderdash-abci" version = "0.12.0-dev.2" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=fix/signatures#e1de2dcab2b38f242162e7e5a36cbc56582457d6" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=master#a177503c21c196ec38e4086ef347fd11ed8cb355" dependencies = [ "bytes", "hex", @@ -3526,7 +3526,7 @@ dependencies = [ [[package]] name = "tenderdash-proto" version = "0.12.0-dev.2" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=fix/signatures#e1de2dcab2b38f242162e7e5a36cbc56582457d6" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=master#a177503c21c196ec38e4086ef347fd11ed8cb355" dependencies = [ "bytes", "chrono", @@ -3544,7 +3544,7 @@ dependencies = [ [[package]] name = "tenderdash-proto-compiler" version = "0.1.0" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=fix/signatures#e1de2dcab2b38f242162e7e5a36cbc56582457d6" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=master#a177503c21c196ec38e4086ef347fd11ed8cb355" dependencies = [ "fs_extra", "prost-build", diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index c011709a1e5..0266d8b2366 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -43,7 +43,7 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = "ansi", ], optional = true } atty = { version = "0.2.14", optional = true } -tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", branch = "fix/signatures", optional = true } +tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", branch = "master", optional = true } # tenderdash-abci = { path = "../../../rs-tenderdash-abci/abci", optional = true } anyhow = { version = "1.0.70" } lazy_static = "1.4.0" diff --git a/packages/rs-drive-abci/src/abci/handlers.rs b/packages/rs-drive-abci/src/abci/handlers.rs index d13f69535cc..28c0b4879e7 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -269,7 +269,7 @@ where return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( "received request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", height, round, block_hash.to_hex(), - block_state_info.height, block_state_info.round, block_state_info.block_hash.to_hex() + block_state_info.height, block_state_info.round, block_state_info.block_hash.map(|block_hash| block_hash.to_hex()).unwrap_or("None".to_string()) ))) .into()); } else { @@ -319,7 +319,7 @@ where return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( "received request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", height, round,block_hash.to_hex(), - block_state_info.height, block_state_info.round, block_state_info.block_hash.to_hex() + block_state_info.height, block_state_info.round, block_state_info.block_hash.map(|block_hash| block_hash.to_hex()).unwrap_or("None".to_string()) ))) .into()); } diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs index e33bdaf3edc..895ad6af650 100644 --- a/packages/rs-drive-abci/src/abci/mimic.rs +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -12,6 +12,8 @@ use dashcore_rpc::dashcore::blockdata::transaction::special_transaction::Transac use dashcore_rpc::dashcore::bls_sig_utils::BLSSignature; use dashcore_rpc::dashcore::consensus::Decodable; use dashcore_rpc::dashcore; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; use dpp::block::block_info::BlockInfo; use dpp::serialization_traits::PlatformSerializable; use dpp::state_transition::StateTransition; @@ -51,6 +53,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { expect_validation_errors: bool, state_transitions: Vec, ) -> Result { + let mut rng = StdRng::seed_from_u64(block_info.height); let serialized_state_transitions = state_transitions .into_iter() .map(|st| st.serialize().map_err(Error::Protocol)) @@ -214,7 +217,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { // We need to sign the block hash - let block_hash = [0; 32]; //todo + let block_hash : [u8;32] = rng.gen(); //todo let chain_id = "strategy_tests".to_string(); let quorum_type = self.platform.config.quorum_type(); @@ -267,7 +270,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { let request_finalize_block = RequestFinalizeBlock { commit: Some(commit_info), misbehavior: vec![], - hash: app_hash.clone(), //todo: change this to block hash + hash: block_hash.to_vec(), height: height as i64, round: 0, block: Some(Block { diff --git a/packages/rs-drive-abci/src/block.rs b/packages/rs-drive-abci/src/block.rs index 40e023db0ba..5f5f125044f 100644 --- a/packages/rs-drive-abci/src/block.rs +++ b/packages/rs-drive-abci/src/block.rs @@ -53,8 +53,8 @@ pub struct BlockStateInfo { pub proposer_pro_tx_hash: [u8; 32], /// Core chain locked height pub core_chain_locked_height: u32, - /// Block hash - pub block_hash: [u8; 32], + /// Block hash, The block hash need not be known in the case for finalize block on the proposer + pub block_hash: Option<[u8; 32]>, /// Application hash pub app_hash: Option<[u8; 32]>, } @@ -81,7 +81,7 @@ impl BlockStateInfo { previous_block_time_ms, proposer_pro_tx_hash: proposal.proposer_pro_tx_hash, core_chain_locked_height: proposal.core_chain_locked_height, - block_hash: proposal.block_hash.unwrap_or_default(), // we will set it later + block_hash: None, app_hash: None, } } @@ -109,7 +109,7 @@ impl BlockStateInfo { )) })?; // the order is important here, don't verify commit hash before height and round - Ok(self.height == height && self.round == round && self.block_hash == received_hash) + Ok(self.height == height && self.round == round && self.block_hash.is_some() && self.block_hash.unwrap() == received_hash) } /// Does this match a height and round? @@ -140,7 +140,7 @@ impl BlockStateInfo { && self.round == round && self.core_chain_locked_height == core_block_height && self.proposer_pro_tx_hash == proposer_pro_tx_hash - && self.block_hash == received_hash) + && self.block_hash.is_some() && self.block_hash.unwrap() == received_hash) } } /// Block execution context diff --git a/packages/rs-drive-abci/src/execution/engine.rs b/packages/rs-drive-abci/src/execution/engine.rs index 437954a3c21..2e98e0ae1b0 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -678,14 +678,14 @@ where )? { // we are on the wrong height or round validation_result.add_error(AbciError::WrongFinalizeBlockReceived(format!( - "received a block for h: {} r: {}, hash: {}, core height: {}, expected h: {} r: {}, hash: {}, core height: {}", + "received a block for h: {} r: {}, block hash: {}, core height: {}, expected h: {} r: {}, block hash: {}, core height: {}", height, round, hash.to_hex(), block_header.core_chain_locked_height, block_state_info.height, block_state_info.round, - block_state_info.block_hash.to_hex(), + block_state_info.block_hash.map(|a| a.to_hex()).unwrap_or("None".to_string()), block_state_info.core_chain_locked_height ))); return Ok(validation_result.into()); diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs index 11a823bd04e..6793c2c86aa 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -6,9 +6,9 @@ use crate::strategy::{ ChainExecutionOutcome, ChainExecutionParameters, Strategy, StrategyRandomness, ValidatorVersionMigration, }; -use dashcore::hashes::hex::ToHex; -use dashcore::hashes::Hash; -use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore::hashes::hex::ToHex; +use dashcore_rpc::dashcore::hashes::Hash; +use dashcore_rpc::dashcore::{ProTxHash, QuorumHash}; use dashcore_rpc::dashcore_rpc_json::{ Bip9SoftforkInfo, Bip9SoftforkStatus, DMNStateDiff, ExtendedQuorumDetails, MasternodeListDiff, MasternodeListItem, QuorumInfoResult, QuorumType, diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs index 8f6f2a6a006..edc12fc1147 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs @@ -70,8 +70,8 @@ impl UpdateMasternodeListItem for MasternodeListItem { #[cfg(test)] mod tests { use super::*; - use dashcore::hashes::Hash; - use dashcore::{ProTxHash, Txid}; + use dashcore_rpc::dashcore::hashes::Hash; + use dashcore_rpc::dashcore::{ProTxHash, Txid}; use dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeType}; use rand::prelude::*; use rand::SeedableRng; diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs index fe333dff3fd..5b0c45bf3c6 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs @@ -1,7 +1,7 @@ use crate::frequency::Frequency; use crate::masternode_list_item_helpers::UpdateMasternodeListItem; -use dashcore::hashes::Hash; -use dashcore::{ProTxHash, QuorumHash, Txid}; +use dashcore_rpc::dashcore::hashes::Hash; +use dashcore_rpc::dashcore::{ProTxHash, QuorumHash, Txid}; use dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType}; use dpp::bls_signatures::PrivateKey as BlsPrivateKey; use drive_abci::execution::test_quorum::TestQuorumInfo; diff --git a/packages/rs-drive-abci/tests/strategy_tests/signer.rs b/packages/rs-drive-abci/tests/strategy_tests/signer.rs index 7f9e8bf4a42..57c4298b1c0 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/signer.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/signer.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use dashcore::signer; +use dashcore_rpc::dashcore::signer; use dpp::ed25519_dalek::Signer as BlsSigner; use dpp::identity::signer::Signer; use dpp::identity::{IdentityPublicKey, KeyType}; diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs index 34314411839..522973df17e 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -6,7 +6,7 @@ use crate::operations::{ }; use crate::signer::SimpleSigner; use crate::BlockHeight; -use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore::{ProTxHash, QuorumHash}; use dpp::block::block_info::BlockInfo; use dpp::data_contract::document_type::random_document::CreateRandomDocument; use dpp::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransition; @@ -35,6 +35,7 @@ use rand::prelude::{IteratorRandom, SliceRandom, StdRng}; use rand::Rng; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; +use dashcore_rpc::dashcore; #[derive(Clone, Debug, Default)] pub struct MasternodeListChangesStrategy { diff --git a/packages/rs-drive-abci/tests/strategy_tests/transitions.rs b/packages/rs-drive-abci/tests/strategy_tests/transitions.rs index 4562480b58a..b977bac8a7e 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/transitions.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/transitions.rs @@ -1,6 +1,6 @@ use crate::signer::SimpleSigner; -use dashcore::secp256k1::SecretKey; -use dashcore::{Network, PrivateKey}; +use dashcore_rpc::dashcore::secp256k1::SecretKey; +use dashcore_rpc::dashcore::{Network, PrivateKey}; use dpp::identifier::Identifier; use dpp::identity::core_script::CoreScript; use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransition; From b5c4c4ed23adee12d98bf356325b436bc347dfc3 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 14:25:18 +0700 Subject: [PATCH 12/14] more fixes --- packages/rs-drive-abci/src/abci/handlers.rs | 39 +++++++++++- packages/rs-drive-abci/src/abci/mimic.rs | 61 +++++++++++++------ packages/rs-drive-abci/src/block.rs | 13 +++- .../rs-drive-abci/src/execution/engine.rs | 1 + .../rs-drive-abci/src/execution/helpers.rs | 7 ++- .../tests/strategy_tests/strategy.rs | 2 +- 6 files changed, 97 insertions(+), 26 deletions(-) diff --git a/packages/rs-drive-abci/src/abci/handlers.rs b/packages/rs-drive-abci/src/abci/handlers.rs index 28c0b4879e7..3b88997858e 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -185,6 +185,14 @@ where ..Default::default() }; + let mut block_execution_context_guard = + self.platform.block_execution_context.write().unwrap(); + + let block_execution_context = block_execution_context_guard + .as_mut() + .expect("expected that a block execution context was set"); + block_execution_context.proposer_results = Some(response.clone()); + Ok(response) } @@ -192,6 +200,33 @@ where &self, mut request: RequestProcessProposal, ) -> Result { + let mut block_execution_context_guard = + self.platform.block_execution_context.write().unwrap(); + + if let Some(block_execution_context) = block_execution_context_guard.as_mut() { + // We are already in a block + // This only makes sense if we were the proposer + let Some(proposal_info) = block_execution_context.proposer_results.as_ref() else { + return Err(Error::Abci(AbciError::BadRequest( + "received a process proposal request twice".to_string(), + )))?; + }; + // We need to set the block hash + block_execution_context.block_state_info.block_hash = + Some(request.hash.clone().try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "block hash is not 32 bytes in process proposal".to_string(), + )) + })?); + return Ok(ResponseProcessProposal { + status: proto::response_process_proposal::ProposalStatus::Accept.into(), + app_hash: proposal_info.app_hash.clone(), + tx_results: proposal_info.tx_results.clone(), + consensus_param_updates: proposal_info.consensus_param_updates.clone(), + validator_set_update: proposal_info.validator_set_update.clone(), + }); + } + let transaction_guard = if request.height == self.platform.config.abci.genesis_height as i64 { // special logic on init chain @@ -267,7 +302,7 @@ where block_hash.clone(), )? { return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( - "received request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", + "received extend vote request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", height, round, block_hash.to_hex(), block_state_info.height, block_state_info.round, block_state_info.block_hash.map(|block_hash| block_hash.to_hex()).unwrap_or("None".to_string()) ))) @@ -317,7 +352,7 @@ where block_hash.clone(), )? { return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( - "received request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", + "received verify vote request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", height, round,block_hash.to_hex(), block_state_info.height, block_state_info.round, block_state_info.block_hash.map(|block_hash| block_hash.to_hex()).unwrap_or("None".to_string()) ))) diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs index 895ad6af650..69f02dec7b9 100644 --- a/packages/rs-drive-abci/src/abci/mimic.rs +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -19,10 +19,7 @@ use dpp::serialization_traits::PlatformSerializable; use dpp::state_transition::StateTransition; use dpp::util::deserializer::ProtocolVersion; use tenderdash_abci::proto::abci::response_verify_vote_extension::VerifyStatus; -use tenderdash_abci::proto::abci::{ - CommitInfo, RequestExtendVote, RequestFinalizeBlock, RequestPrepareProposal, - RequestVerifyVoteExtension, ResponsePrepareProposal, ValidatorSetUpdate, -}; +use tenderdash_abci::proto::abci::{CommitInfo, RequestExtendVote, RequestFinalizeBlock, RequestPrepareProposal, RequestProcessProposal, RequestVerifyVoteExtension, ResponsePrepareProposal, ResponseProcessProposal, ValidatorSetUpdate}; use tenderdash_abci::proto::google::protobuf::Timestamp; use tenderdash_abci::proto::types::{ Block, BlockId, Data, EvidenceList, Header, PartSetHeader, VoteExtension, VoteExtensionType, @@ -30,7 +27,7 @@ use tenderdash_abci::proto::types::{ use tenderdash_abci::{ signatures::SignDigest, proto::{self, version::Consensus}, - Application + Application, }; /// The outcome struct when mimicking block execution @@ -54,6 +51,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { state_transitions: Vec, ) -> Result { let mut rng = StdRng::seed_from_u64(block_info.height); + let block_hash: [u8; 32] = rng.gen(); // We fake a block hash for the test let serialized_state_transitions = state_transitions .into_iter() .map(|st| st.serialize().map_err(Error::Protocol)) @@ -66,9 +64,11 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { epoch: _, } = block_info; + // PREPARE (also processes internally) + let request_prepare_proposal = RequestPrepareProposal { max_tx_bytes: 0, - txs: serialized_state_transitions, + txs: serialized_state_transitions.clone(), local_last_commit: None, misbehavior: vec![], height: height as i64, @@ -119,10 +119,41 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { })?; } + // PROCESS + + let request_process_proposal = RequestProcessProposal { + txs: serialized_state_transitions, + proposed_last_commit: None, + misbehavior: vec![], + hash: block_hash.to_vec(), + height: height as i64, + time: Some(Timestamp { + seconds: (time_ms / 1000) as i64, + nanos: ((time_ms % 1000) * 1000) as i32, + }), + next_validators_hash: vec![], + round: 0, + core_chain_locked_height: core_height, + core_chain_lock_update, + proposer_pro_tx_hash: proposer_pro_tx_hash.to_vec(), + proposed_app_version: proposed_version as u64, + version: Some(Consensus { block: 0, app: 0 }), + quorum_hash: current_quorum.quorum_hash.to_vec(), + }; + + //we must call process proposal so the app hash is set + self.process_proposal(request_process_proposal) + .unwrap_or_else(|e| { + panic!( + "should skip processing (because we prepared it) block #{} at time #{} : {:?}", + block_info.height, block_info.time_ms, e + ) + }); + let tx_order_for_finalize_block = tx_records.into_iter().map(|record| record.tx).collect(); let request_extend_vote = RequestExtendVote { - hash: [0; 32].to_vec(), //todo + hash: block_hash.to_vec(), height: height as i64, round: 0, }; @@ -140,7 +171,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { for validator in current_quorum.validator_set.iter() { let request_verify_vote_extension = RequestVerifyVoteExtension { - hash: [0; 32].to_vec(), //todo + hash: block_hash.to_vec(), validator_pro_tx_hash: validator.pro_tx_hash.to_vec(), height: height as i64, round: 0, @@ -216,8 +247,6 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { drop(guarded_block_execution_context); // We need to sign the block hash - - let block_hash : [u8;32] = rng.gen(); //todo let chain_id = "strategy_tests".to_string(); let quorum_type = self.platform.config.quorum_type(); @@ -245,15 +274,11 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { //if not in testing this will default to true if self.platform.config.testing_configs.block_signing { - let quorum_hash:[u8;32] = current_quorum.quorum_hash[..].try_into().expect("wrong quorum hash len"); + let quorum_hash: [u8; 32] = current_quorum.quorum_hash[..] + .try_into() + .expect("wrong quorum hash len"); let digest = commit - .sign_digest( - &chain_id, - quorum_type as u8, - &quorum_hash, - height as i64, - 0, - ) + .sign_digest(&chain_id, quorum_type as u8, &quorum_hash, height as i64, 0) .expect("expected to sign digest"); let block_signature = current_quorum.private_key.sign(digest.as_slice()); diff --git a/packages/rs-drive-abci/src/block.rs b/packages/rs-drive-abci/src/block.rs index 5f5f125044f..c0eb2a19c80 100644 --- a/packages/rs-drive-abci/src/block.rs +++ b/packages/rs-drive-abci/src/block.rs @@ -37,6 +37,7 @@ use dashcore_rpc::dashcore::Txid; use dpp::block::block_info::BlockInfo; use dpp::block::epoch::Epoch; use std::collections::BTreeMap; +use tenderdash_abci::proto::abci::ResponsePrepareProposal; /// Block info #[derive(Debug)] @@ -81,7 +82,7 @@ impl BlockStateInfo { previous_block_time_ms, proposer_pro_tx_hash: proposal.proposer_pro_tx_hash, core_chain_locked_height: proposal.core_chain_locked_height, - block_hash: None, + block_hash: proposal.block_hash, app_hash: None, } } @@ -109,7 +110,10 @@ impl BlockStateInfo { )) })?; // the order is important here, don't verify commit hash before height and round - Ok(self.height == height && self.round == round && self.block_hash.is_some() && self.block_hash.unwrap() == received_hash) + Ok(self.height == height + && self.round == round + && self.block_hash.is_some() + && self.block_hash.unwrap() == received_hash) } /// Does this match a height and round? @@ -140,7 +144,8 @@ impl BlockStateInfo { && self.round == round && self.core_chain_locked_height == core_block_height && self.proposer_pro_tx_hash == proposer_pro_tx_hash - && self.block_hash.is_some() && self.block_hash.unwrap() == received_hash) + && self.block_hash.is_some() + && self.block_hash.unwrap() == received_hash) } } /// Block execution context @@ -155,4 +160,6 @@ pub struct BlockExecutionContext { pub withdrawal_transactions: BTreeMap>, /// Block state pub block_platform_state: PlatformState, + /// The response prepare proposal if proposed by us + pub proposer_results: Option, } diff --git a/packages/rs-drive-abci/src/execution/engine.rs b/packages/rs-drive-abci/src/execution/engine.rs index 2e98e0ae1b0..b2ca2b0f1a2 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -319,6 +319,7 @@ where hpmn_count: hpmn_list_len as u32, withdrawal_transactions: BTreeMap::new(), block_platform_state, + proposer_results: None, }; // If last synced Core block height is not set instead of scanning diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index b97da25f741..fd7c60d8390 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -264,8 +264,11 @@ where let UpdateStateMasternodeListOutcome { masternode_list_diff, removed_masternodes, - } = - self.update_state_masternode_list(block_platform_state, core_block_height, is_init_chain)?; + } = self.update_state_masternode_list( + block_platform_state, + core_block_height, + is_init_chain, + )?; self.update_masternode_identities( masternode_list_diff, diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs index 522973df17e..209e5710885 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -6,6 +6,7 @@ use crate::operations::{ }; use crate::signer::SimpleSigner; use crate::BlockHeight; +use dashcore_rpc::dashcore; use dashcore_rpc::dashcore::{ProTxHash, QuorumHash}; use dpp::block::block_info::BlockInfo; use dpp::data_contract::document_type::random_document::CreateRandomDocument; @@ -35,7 +36,6 @@ use rand::prelude::{IteratorRandom, SliceRandom, StdRng}; use rand::Rng; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; -use dashcore_rpc::dashcore; #[derive(Clone, Debug, Default)] pub struct MasternodeListChangesStrategy { From eb3b8a57869d39103fc10717f55dca1f0b92b436 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 15:39:26 +0700 Subject: [PATCH 13/14] all tests passing --- packages/rs-drive-abci/src/abci/commit.rs | 2 +- packages/rs-drive-abci/src/abci/mimic.rs | 11 ++++++----- .../src/execution/fee_pools/process_block_fees.rs | 4 ++-- .../src/execution/finalize_block_cleaned_request.rs | 1 - packages/rs-drive-abci/src/execution/helpers.rs | 11 ++++++----- .../src/execution/masternode_identities/mod.rs | 2 +- packages/rs-drive-abci/src/execution/quorum.rs | 2 +- .../src/identity_credit_withdrawal/mod.rs | 6 ++++-- packages/rs-drive-abci/src/rpc/core.rs | 7 ++++--- .../rs-drive-abci/tests/strategy_tests/execution.rs | 5 +++-- 10 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/rs-drive-abci/src/abci/commit.rs b/packages/rs-drive-abci/src/abci/commit.rs index 5aa4102e565..d720f3870e2 100644 --- a/packages/rs-drive-abci/src/abci/commit.rs +++ b/packages/rs-drive-abci/src/abci/commit.rs @@ -36,7 +36,7 @@ impl Commit { height: height as i64, round: ci.round as i32, // we need to "un-reverse" quorum hash, as it was reversed in [CleanedCommitInfo::try_from] - quorum_hash: ci.quorum_hash.iter().rev().cloned().collect(), + quorum_hash: ci.quorum_hash.to_vec(), threshold_block_signature: ci.block_signature.to_vec(), threshold_vote_extensions: ci.threshold_vote_extensions.to_vec(), }, diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs index 69f02dec7b9..1ce704f6083 100644 --- a/packages/rs-drive-abci/src/abci/mimic.rs +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -256,9 +256,12 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { state_id: [0; 32].to_vec(), //todo }; + let mut quorum_hash = current_quorum.quorum_hash.to_vec(); + // quorum_hash.reverse(); + let mut commit_info = CommitInfo { round: 0, - quorum_hash: current_quorum.quorum_hash.to_vec(), + quorum_hash: quorum_hash.clone(), block_signature: Default::default(), threshold_vote_extensions: extensions, }; @@ -267,16 +270,14 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { block_id: Some(block_id.clone()), height: height as i64, round: 0, - quorum_hash: current_quorum.quorum_hash.to_vec(), + quorum_hash: quorum_hash.clone(), threshold_block_signature: Default::default(), threshold_vote_extensions: Default::default(), }; //if not in testing this will default to true if self.platform.config.testing_configs.block_signing { - let quorum_hash: [u8; 32] = current_quorum.quorum_hash[..] - .try_into() - .expect("wrong quorum hash len"); + let quorum_hash: [u8; 32] = quorum_hash.try_into().expect("wrong quorum hash len"); let digest = commit .sign_digest(&chain_id, quorum_type as u8, &quorum_hash, height as i64, 0) .expect("expected to sign digest"); diff --git a/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs b/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs index 9939bb5ebf7..464488fa279 100644 --- a/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs +++ b/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs @@ -317,7 +317,7 @@ mod tests { previous_block_time_ms, proposer_pro_tx_hash, core_chain_locked_height: 1, - block_hash, + block_hash: None, app_hash: None, }; @@ -499,7 +499,7 @@ mod tests { previous_block_time_ms, proposer_pro_tx_hash, core_chain_locked_height: 1, - block_hash: [0; 32], + block_hash: None, app_hash: None, }; diff --git a/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs b/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs index 5c897857838..c2596462e1f 100644 --- a/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs +++ b/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs @@ -43,7 +43,6 @@ impl TryFrom for CleanedCommitInfo { "commit info quorum hash is not 32 bytes long".to_string(), )) })?; - quorum_hash.reverse(); let block_signature: [u8; 96] = block_signature.try_into().map_err(|_| { Error::Abci(AbciError::BadRequestDataSize( diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index fd7c60d8390..b835a5c0a3d 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -150,13 +150,14 @@ where ) -> Result { let previous_core_height = if start_from_scratch { // baseBlock must be a chain height and not 0 - 1 + None } else { - state.core_height() + let state_core_height = state.core_height(); + if core_block_height == state_core_height { + return Ok(UpdateStateMasternodeListOutcome::default()); // no need to do anything + } + Some(state_core_height) }; - if core_block_height == previous_core_height { - return Ok(UpdateStateMasternodeListOutcome::default()); // no need to do anything - } let masternode_diff = self .core_rpc diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs index e1231cea81a..fb01a88acaa 100644 --- a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -616,7 +616,7 @@ where Self::get_operator_identifier_from_masternode_list_item(masternode)?; let mut identity = Self::create_basic_identity(operator_identifier); identity.add_public_keys(self.get_operator_identity_keys( - maybe_hexdecode(&masternode.state.pub_key_operator.clone(), 48), + masternode.state.pub_key_operator.clone(), masternode.state.operator_payout_address, masternode.state.platform_node_id, )?); diff --git a/packages/rs-drive-abci/src/execution/quorum.rs b/packages/rs-drive-abci/src/execution/quorum.rs index 7f63bd59e3c..71dbdb58345 100644 --- a/packages/rs-drive-abci/src/execution/quorum.rs +++ b/packages/rs-drive-abci/src/execution/quorum.rs @@ -75,7 +75,7 @@ impl From for ValidatorSetUpdate { /// We need to decide on a consistent approach to endianness and follow it. fn reverse(data: &[u8]) -> Vec { let mut data = data.to_vec(); - data.reverse(); + // data.reverse(); data } diff --git a/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs index 32230cbea46..8f6419099b7 100644 --- a/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs +++ b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs @@ -639,7 +639,7 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, ], core_chain_locked_height: 96, - block_hash: [0; 32], + block_hash: None, app_hash: None, }, epoch_info: EpochInfo { @@ -660,6 +660,7 @@ mod tests { hpmn_masternode_list: Default::default(), initialization_information: None, }, + proposer_results: None, }; let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) @@ -806,7 +807,7 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, ], core_chain_locked_height: 96, - block_hash: [0; 32], + block_hash: None, app_hash: None, }, epoch_info: EpochInfo { @@ -827,6 +828,7 @@ mod tests { hpmn_masternode_list: Default::default(), initialization_information: None, }, + proposer_results: None, }); let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) diff --git a/packages/rs-drive-abci/src/rpc/core.rs b/packages/rs-drive-abci/src/rpc/core.rs index f15185deeca..5655e5870ae 100644 --- a/packages/rs-drive-abci/src/rpc/core.rs +++ b/packages/rs-drive-abci/src/rpc/core.rs @@ -63,7 +63,7 @@ pub trait CoreRPCLike { /// Get the difference in masternode list, return masternodes as diff elements fn get_protx_diff_with_masternodes( &self, - base_block: u32, + base_block: Option, block: u32, ) -> Result; @@ -151,9 +151,10 @@ impl CoreRPCLike for DefaultCoreRPC { fn get_protx_diff_with_masternodes( &self, - base_block: u32, + base_block: Option, block: u32, ) -> Result { - self.inner.get_protx_listdiff(base_block, block) + self.inner + .get_protx_listdiff(base_block.unwrap_or(1), block) } } diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs index 6793c2c86aa..377b99b76d7 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -274,9 +274,9 @@ pub(crate) fn run_chain_for_strategy( .core_rpc .expect_get_protx_diff_with_masternodes() .returning(move |base_block, block| { - let diff = if base_block == 0 { + let diff = if base_block == None { MasternodeListDiff { - base_height: base_block, + base_height: 0, block_height: block, added_mns: initial_all_masternodes .iter() @@ -286,6 +286,7 @@ pub(crate) fn run_chain_for_strategy( updated_mns: vec![], } } else { + let base_block = base_block.unwrap(); if !any_changes_in_strategy { // no changes MasternodeListDiff { From 709e6c0cc02de84e1ab97754cf3d272ba9047e12 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Apr 2023 16:22:36 +0700 Subject: [PATCH 14/14] small updates after review --- packages/rs-drive-abci/src/abci/mimic.rs | 5 +++-- packages/rs-drive-abci/src/execution/helpers.rs | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs index 1ce704f6083..9c078291e02 100644 --- a/packages/rs-drive-abci/src/abci/mimic.rs +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -52,6 +52,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { ) -> Result { let mut rng = StdRng::seed_from_u64(block_info.height); let block_hash: [u8; 32] = rng.gen(); // We fake a block hash for the test + let next_validators_hash: [u8; 32] = rng.gen(); // We fake a block hash for the test let serialized_state_transitions = state_transitions .into_iter() .map(|st| st.serialize().map_err(Error::Protocol)) @@ -76,7 +77,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { seconds: (time_ms / 1000) as i64, nanos: ((time_ms % 1000) * 1000) as i32, }), - next_validators_hash: vec![], + next_validators_hash: next_validators_hash.to_vec(), round: 0, core_chain_locked_height: core_height, proposer_pro_tx_hash: proposer_pro_tx_hash.to_vec(), @@ -131,7 +132,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { seconds: (time_ms / 1000) as i64, nanos: ((time_ms % 1000) * 1000) as i32, }), - next_validators_hash: vec![], + next_validators_hash: next_validators_hash.to_vec(), round: 0, core_chain_locked_height: core_height, core_chain_lock_update, diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index b835a5c0a3d..09951587161 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -300,7 +300,9 @@ where /// /// # Arguments /// - /// * state - A mutable reference to the platform state to be updated. + /// * platform_state - A reference to the platform state before execution of current block. + /// * block_platform_state - A mutable reference to the current platform state in the block + /// execution context to be updated. /// * core_block_height - The current block height in the Dash Core. /// * is_init_chain - A boolean indicating if the chain is being initialized. /// * block_info - A reference to the block information.