From 87b0213ee4fdafe75c9940a2953a3850ac4c374d Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 14 Jun 2024 11:56:32 +0000 Subject: [PATCH 01/11] fix(validations): first compare staker power to decide which block proposal is the best one --- node/src/actors/chain_manager/mod.rs | 18 +++ .../src/tests/compare_block_candidates.rs | 106 ++++++++++++++++++ validations/src/validations.rs | 25 +++-- 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 8f17f7ebf..4b0b0a1e3 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -825,16 +825,34 @@ impl ChainManager { // than the other to avoid the "activeness" comparison is_active }; + let power = match self.chain_state.stakes.query_power( + *block_pkh, + Capability::Mining, + block.block_header.beacon.checkpoint, + ) { + Ok(power) => power, + Err(_) => 0, + }; + let best_candidate_power = match self.chain_state.stakes.query_power( + best_pkh, + Capability::Mining, + best_candidate.block.block_header.beacon.checkpoint, + ) { + Ok(power) => power, + Err(_) => 0, + }; if compare_block_candidates( hash_block, reputation, vrf_proof, is_active, + power, best_hash, best_candidate.reputation, best_candidate.vrf_proof, best_candidate_is_active, + best_candidate_power, &target_vrf_slots, protocol_version, ) != Ordering::Greater diff --git a/validations/src/tests/compare_block_candidates.rs b/validations/src/tests/compare_block_candidates.rs index 16ad90897..758a5925e 100644 --- a/validations/src/tests/compare_block_candidates.rs +++ b/validations/src/tests/compare_block_candidates.rs @@ -1,6 +1,7 @@ use witnet_data_structures::{ chain::{tapi::current_active_wips, Hash, Reputation}, proto::versioning::ProtocolVersion, + staking::prelude::Power, }; use std::cmp::Ordering; @@ -17,6 +18,8 @@ fn test_compare_candidate_same_section() { let vrf_2 = Hash::SHA256([2; 32]); // Only one section and all VRFs are valid let vrf_sections = VrfSlots::default(); + // Dummy zero power variable for tests before Witnet 2.0 + let power_zero = Power::from(0 as u64); // The candidate with reputation always wins for &bh_i in &[bh_1, bh_2] { @@ -31,10 +34,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_i, act_i, + power_zero, bh_j, rep_2, vrf_j, act_j, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -46,10 +51,12 @@ fn test_compare_candidate_same_section() { rep_2, vrf_i, act_i, + power_zero, bh_j, rep_1, vrf_j, act_j, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -73,10 +80,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_i, true, + power_zero, bh_j, rep_1, vrf_j, false, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -88,10 +97,12 @@ fn test_compare_candidate_same_section() { rep_2, vrf_i, false, + power_zero, bh_j, rep_2, vrf_j, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -111,10 +122,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_j, rep_1, vrf_2, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -126,10 +139,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_2, true, + power_zero, bh_j, rep_1, vrf_1, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -145,10 +160,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_2, rep_1, vrf_1, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -160,10 +177,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_1, rep_1, vrf_1, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -177,10 +196,12 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_1, rep_1, vrf_1, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -200,6 +221,8 @@ fn test_compare_candidate_different_section() { let vrf_1 = vrf_sections.target_hashes()[0]; // Candidate 2 is in section 1 let vrf_2 = vrf_sections.target_hashes()[1]; + // Dummy zero power variable for tests before Witnet 2.0 + let power_zero = Power::from(0 as u64); // The candidate in the lower section always wins for &bh_i in &[bh_1, bh_2] { @@ -214,10 +237,12 @@ fn test_compare_candidate_different_section() { rep_i, vrf_1, act_i, + power_zero, bh_j, rep_j, vrf_2, act_j, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -229,10 +254,12 @@ fn test_compare_candidate_different_section() { rep_i, vrf_2, act_i, + power_zero, bh_j, rep_j, vrf_1, act_j, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -256,6 +283,8 @@ fn test_compare_candidate_different_reputation_bigger_than_zero() { let vrf_2 = Hash::SHA256([2; 32]); // Only one section and all VRFs are valid let vrf_sections = VrfSlots::default(); + // Dummy zero power variable for tests before Witnet 2.0 + let power_zero = Power::from(0 as u64); // In case of active nodes with reputation, the difference will be the vrf not the reputation assert_eq!( @@ -264,10 +293,12 @@ fn test_compare_candidate_different_reputation_bigger_than_zero() { rep_1, vrf_1, true, + power_zero, bh_2, rep_2, vrf_2, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), @@ -280,13 +311,88 @@ fn test_compare_candidate_different_reputation_bigger_than_zero() { rep_1, vrf_2, true, + power_zero, bh_2, rep_2, vrf_1, true, + power_zero, &vrf_sections, ProtocolVersion::V1_7, ), Ordering::Less ); } + +#[test] +fn test_compare_candidates_witnet_pos() { + let bh_1 = Hash::SHA256([10; 32]); + let bh_2 = Hash::SHA256([20; 32]); + let rep_1 = Reputation(0); + let rep_2 = Reputation(0); + let vrf_1 = Hash::SHA256([1; 32]); + let vrf_2 = Hash::SHA256([2; 32]); + let vrf_sections = VrfSlots::default(); + let power_1 = Power::from(10 as u64); + let power_2 = Power::from(5 as u64); + + // The first staker proposing the first block wins because his power is higher or vrf and block hash are lower + for power in &[power_1, power_2] { + for vrf in &[vrf_1, vrf_2] { + for bh in &[bh_1, bh_2] { + let ordering = if *power == power_2 && *vrf == vrf_2 && *bh == bh_2 { + Ordering::Equal + } else { + Ordering::Greater + }; + assert_eq!( + compare_block_candidates( + *bh, + rep_1, + *vrf, + true, + *power, + bh_2, + rep_2, + vrf_2, + true, + power_2, + &vrf_sections, + ProtocolVersion::V2_0, + ), + ordering + ); + } + } + } + + // The second staker proposing the second block wins because his power is higher or vrf and block hash are lower + for power in &[power_1, power_2] { + for vrf in &[vrf_1, vrf_2] { + for bh in &[bh_1, bh_2] { + let ordering = if *power == power_2 && *vrf == vrf_2 && *bh == bh_2 { + Ordering::Equal + } else { + Ordering::Less + }; + assert_eq!( + compare_block_candidates( + bh_2, + rep_2, + vrf_2, + true, + power_2, + *bh, + rep_1, + *vrf, + true, + *power, + &vrf_sections, + ProtocolVersion::V2_0, + ), + ordering + ); + } + } + } +} diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 1fd1c99c5..320edf76e 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -35,7 +35,7 @@ use witnet_data_structures::{ get_protocol_version, proto::versioning::{ProtocolVersion, VersionedHashable}, radon_report::{RadonReport, ReportContext}, - staking::stakes::Stakes, + staking::{prelude::Power, stakes::Stakes}, transaction::{ CommitTransaction, DRTransaction, MintTransaction, RevealTransaction, StakeTransaction, TallyTransaction, Transaction, UnstakeTransaction, VTTransaction, @@ -2346,20 +2346,31 @@ pub fn compare_block_candidates( b1_rep: Reputation, b1_vrf_hash: Hash, b1_is_active: bool, + b1_power: Power, b2_hash: Hash, b2_rep: Reputation, b2_vrf_hash: Hash, b2_is_active: bool, + b2_power: Power, s: &VrfSlots, version: ProtocolVersion, ) -> Ordering { if version == ProtocolVersion::V2_0 { - // Bigger vrf hash implies worse block candidate - b1_vrf_hash - .cmp(&b2_vrf_hash) - .reverse() - // Bigger block implies worse block candidate - .then(b1_hash.cmp(&b2_hash).reverse()) + // Higher power wins + if b1_power > b2_power { + Ordering::Greater + // Lower power loses + } else if b1_power < b2_power { + Ordering::Less + // Equal power, first compare VRF hash and finally the block hash + } else { + // Bigger vrf hash implies worse block candidate + b1_vrf_hash + .cmp(&b2_vrf_hash) + .reverse() + // Bigger block implies worse block candidate + .then(b1_hash.cmp(&b2_hash).reverse()) + } } else { let section1 = s.slot(&b1_vrf_hash); let section2 = s.slot(&b2_vrf_hash); From bcf725ecba180fffed965af150f1743f20ef5f9c Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 14 Jun 2024 14:29:02 +0000 Subject: [PATCH 02/11] feature(mining): slash validators' power when no valid block was proposed in the previous epoch --- data_structures/src/staking/helpers.rs | 8 ++- data_structures/src/staking/stakes.rs | 88 ++++++++++++++++++++--- node/src/actors/chain_manager/handlers.rs | 26 +++++++ node/src/actors/chain_manager/mod.rs | 2 +- validations/src/eligibility/current.rs | 2 + 5 files changed, 115 insertions(+), 11 deletions(-) diff --git a/data_structures/src/staking/helpers.rs b/data_structures/src/staking/helpers.rs index 1dc08f40b..9fe633cee 100644 --- a/data_structures/src/staking/helpers.rs +++ b/data_structures/src/staking/helpers.rs @@ -244,7 +244,9 @@ where + Saturating + Send + Sub - + Sync, + + Sync + + Add + + Div, Power: Add + Copy + Default + DeserializeOwned + Div + Ord + Sum, u64: From + From, @@ -292,7 +294,9 @@ where + Send + Saturating + Sub - + Sync, + + Sync + + Add + + Div, Power: Add + Copy + Default + Div + Ord + Sum, u64: From + From, { diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index 98bc24cce..65dea3842 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -116,7 +116,9 @@ where + Debug + Display + Send - + Sync, + + Sync + + Add + + Div, Power: Copy + Default + Ord + Add + Div + Sum, u64: From + From, { @@ -284,6 +286,7 @@ where validator: ISK, capability: Capability, current_epoch: Epoch, + reset_factor: u32, ) -> StakesResult<(), Address, Coins, Epoch> where ISK: Into
, @@ -295,12 +298,14 @@ where .get_mut(&validator) .ok_or(StakesError::ValidatorNotFound { validator })?; stakes.iter_mut().for_each(|stake| { + let old_epoch = stake.value.write().unwrap().epochs.get(capability); + let update_epoch = (current_epoch - old_epoch) / Epoch::from(reset_factor); stake .value .write() .unwrap() .epochs - .update(capability, current_epoch) + .update(capability, old_epoch + update_epoch); }); Ok(()) @@ -501,7 +506,9 @@ where + Debug + Display + Send - + Sync, + + Sync + + Add + + Div, Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, @@ -547,7 +554,9 @@ where + Debug + Display + Send - + Sync, + + Sync + + Add + + Div, Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, @@ -591,7 +600,9 @@ where + Debug + Send + Sync - + Display, + + Display + + Add + + Div, Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, @@ -619,7 +630,9 @@ where + Debug + Send + Sync - + Display, + + Display + + Add + + Div, Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, @@ -818,7 +831,9 @@ mod tests { ); // Now let's slash Charlie's mining coin age right after - stakes.reset_age(charlie, Capability::Mining, 101).unwrap(); + stakes + .reset_age(charlie, Capability::Mining, 101, 1) + .unwrap(); assert_eq!( stakes.query_power(alice, Capability::Mining, 101), Ok(1_010) @@ -894,6 +909,63 @@ mod tests { ); } + #[test] + fn test_rank_proportional_reset() { + // First, lets create a setup with a few stakers + let mut stakes = Stakes::::with_minimum(5); + let alice = "Alice"; + let bob = "Bob"; + let charlie = "Charlie"; + let david = "David"; + let erin = "Erin"; + + let alice_bob = (alice, bob); + let bob_charlie = (bob, charlie); + let charlie_david = (charlie, david); + let david_erin = (david, erin); + let erin_alice = (erin, alice); + + stakes.add_stake(alice_bob, 10, 0).unwrap(); + stakes.add_stake(bob_charlie, 20, 10).unwrap(); + stakes.add_stake(charlie_david, 30, 20).unwrap(); + stakes.add_stake(david_erin, 40, 30).unwrap(); + stakes.add_stake(erin_alice, 50, 40).unwrap(); + + // Power of validators at epoch 90: + // alice_bob: 10 * (90 - 0) = 900 + // bob_charlie: 20 * (90 - 10) = 1600 + // charlie_david: 30 * (90 - 20) = 2100 + // david_erin: 40 * (90 - 30) = 2400 + // erin_alice: 50 * (90 - 40) = 2500 + let rank_subset: Vec<_> = stakes + .rank(Capability::Mining, 90) + .take(4) + .map(|sk| sk) + .collect(); + for (i, (stake_key, _)) in rank_subset.into_iter().enumerate() { + let _ = stakes.reset_age( + stake_key.validator, + Capability::Mining, + 90, + (i + 1).try_into().unwrap(), + ); + } + + // Slashed with a factor 1 / 1 + assert_eq!(stakes.query_power(erin, Capability::Mining, 90), Ok(0)); + // Slashed with a factor 1 / 2 + assert_eq!(stakes.query_power(david, Capability::Mining, 90), Ok(1200)); + // Slashed with a factor 1 / 3 + assert_eq!( + stakes.query_power(charlie, Capability::Mining, 90), + Ok(1410) + ); + // Slashed with a factor 1 / 4 + assert_eq!(stakes.query_power(bob, Capability::Mining, 90), Ok(1200)); + // Not slashed + assert_eq!(stakes.query_power(alice, Capability::Mining, 90), Ok(900)); + } + #[test] fn test_query_stakes() { // First, lets create a setup with a few stakers @@ -981,7 +1053,7 @@ mod tests { bincode::deserialize(serialized.as_slice()).unwrap(); deserialized - .reset_age(alice.clone(), Capability::Mining, 789) + .reset_age(alice.clone(), Capability::Mining, 789, 1) .ok(); deserialized.query_by_validator(alice).unwrap(); diff --git a/node/src/actors/chain_manager/handlers.rs b/node/src/actors/chain_manager/handlers.rs index 9f0fb9506..a2318de35 100644 --- a/node/src/actors/chain_manager/handlers.rs +++ b/node/src/actors/chain_manager/handlers.rs @@ -14,11 +14,14 @@ use witnet_config::defaults::{ PSEUDO_CONSENSUS_CONSTANTS_WIP0027_COLLATERAL_AGE, }; use witnet_data_structures::{ + capabilities::Capability, chain::{ tapi::ActiveWips, Block, ChainState, CheckpointBeacon, DataRequestInfo, Epoch, Hash, Hashable, NodeStats, PublicKeyHash, SuperBlockVote, SupplyInfo, ValueTransferOutput, }, error::{ChainInfoError, TransactionError::DataRequestNotFound}, + get_protocol_version, + proto::versioning::ProtocolVersion, staking::errors::StakesError, transaction::{ DRTransaction, StakeTransaction, Transaction, UnstakeTransaction, VTTransaction, @@ -72,6 +75,8 @@ pub const SYNCED_BANNER: &str = r" ║ (balance, reputation, proposed blocks, etc.) ║ ╚════════════════════════════════════════════════════╝"; +const MINING_REPLICATION_FACTOR: usize = 4; + //////////////////////////////////////////////////////////////////////////////////////// // ACTOR MESSAGE HANDLERS //////////////////////////////////////////////////////////////////////////////////////// @@ -156,6 +161,7 @@ impl Handler> for ChainManager { match self.chain_state { ChainState { reputation_engine: Some(_), + ref mut stakes, .. } => { if self.epoch_constants.is_none() || self.vrf_ctx.is_none() { @@ -180,6 +186,26 @@ impl Handler> for ChainManager { "There was no valid block candidate to consolidate for epoch {}", previous_epoch ); + if get_protocol_version(Some(previous_epoch)) == ProtocolVersion::V2_0 { + let rank_subset: Vec<_> = stakes + .rank(Capability::Mining, previous_epoch) + .take(MINING_REPLICATION_FACTOR) + .map(|sk| sk) + .collect(); + for (i, (stake_key, _)) in rank_subset.into_iter().enumerate() { + log::warn!( + "Slashed the power of {} as it did not propose a block", + stake_key.validator + ); + let _ = stakes.reset_age( + stake_key.validator, + Capability::Mining, + msg.checkpoint, + // This should never fail + (i + 1).try_into().unwrap(), + ); + } + } } // Send last beacon on block consolidation diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 4b0b0a1e3..3eba273a8 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -1037,7 +1037,7 @@ impl ChainManager { let miner_pkh = block.block_header.proof.proof.pkh(); // Reset the coin age of the miner for all staked coins - let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch); + let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch, 1); // Do not update reputation or stakes when consolidating genesis block if block_hash != chain_info.consensus_constants.genesis_hash { diff --git a/validations/src/eligibility/current.rs b/validations/src/eligibility/current.rs index 3de4be19f..0b7ec22fa 100644 --- a/validations/src/eligibility/current.rs +++ b/validations/src/eligibility/current.rs @@ -121,6 +121,8 @@ where + Display + num_traits::Saturating + Sub + + Add + + Div + From + Sync + Send From 5345b33e80c4e75eb0f3115223e09691bdebed09 Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 14 Jun 2024 18:02:46 +0000 Subject: [PATCH 03/11] fix(validations): fix the node mining eligibility criteria --- node/src/actors/chain_manager/mining.rs | 12 +++++------- validations/src/eligibility/current.rs | 22 +++++----------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/node/src/actors/chain_manager/mining.rs b/node/src/actors/chain_manager/mining.rs index 5b7b1871f..cc136de42 100644 --- a/node/src/actors/chain_manager/mining.rs +++ b/node/src/actors/chain_manager/mining.rs @@ -112,7 +112,11 @@ impl ChainManager { let mut beacon = chain_info.highest_block_checkpoint; let mut vrf_input = chain_info.highest_vrf_output; - if get_protocol_version(self.current_epoch) == ProtocolVersion::V2_0 { + // The highest checkpoint beacon should contain the current epoch + beacon.checkpoint = current_epoch; + vrf_input.checkpoint = current_epoch; + + let target_hash = if get_protocol_version(self.current_epoch) == ProtocolVersion::V2_0 { let eligibility = self .chain_state .stakes @@ -128,13 +132,7 @@ impl ChainManager { return Ok(()); } } - } - // The highest checkpoint beacon should contain the current epoch - beacon.checkpoint = current_epoch; - vrf_input.checkpoint = current_epoch; - - let target_hash = if get_protocol_version(self.current_epoch) == ProtocolVersion::V2_0 { Hash::max() } else { let rep_engine = self.chain_state.reputation_engine.as_ref().unwrap().clone(); diff --git a/validations/src/eligibility/current.rs b/validations/src/eligibility/current.rs index 0b7ec22fa..c3b3e6888 100644 --- a/validations/src/eligibility/current.rs +++ b/validations/src/eligibility/current.rs @@ -135,7 +135,8 @@ where + Mul + Div + From - + Sum, + + Sum + + Display, u64: From + From, { fn mining_eligibility( @@ -157,26 +158,13 @@ where } }; - let mut rank = self.rank(Capability::Mining, epoch); - - // Requirement no. 3 from the WIP: - // "the mining power of the block proposer is greater than `max_power / rf`" - // (This goes before no. 2 because it is cheaper, computation-wise, and we want validations to exit ASAP) - // TODO: verify if defaulting to 0 makes sense - let (_, max_power) = rank.next().unwrap_or_default(); - let threshold = max_power / Power::from(MINING_REPLICATION_FACTOR as u64); - if power <= threshold { - return Ok(IneligibilityReason::InsufficientPower.into()); - } - // Requirement no. 2 from the WIP: // "the mining power of the block proposer is in the `rf / stakers`th quantile among the mining powers of all // the stakers" - let stakers = self.stakes_count(); - let quantile = stakers / MINING_REPLICATION_FACTOR; // TODO: verify if defaulting to 0 makes sense - let (_, threshold) = rank.nth(quantile).unwrap_or_default(); - if power <= threshold { + let mut rank = self.rank(Capability::Mining, epoch); + let (_, threshold) = rank.nth(MINING_REPLICATION_FACTOR - 1).unwrap_or_default(); + if power < threshold { return Ok(IneligibilityReason::InsufficientPower.into()); } From 1da2c4fb9404de814f3bb2bbaebbf9361539278f Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 14 Jun 2024 18:04:27 +0000 Subject: [PATCH 04/11] fix(mining): make sure the age reset only happens when the previous block was part of wit\2 --- node/src/actors/chain_manager/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 3eba273a8..80b91c379 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -1037,7 +1037,9 @@ impl ChainManager { let miner_pkh = block.block_header.proof.proof.pkh(); // Reset the coin age of the miner for all staked coins - let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch, 1); + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch, 1); + } // Do not update reputation or stakes when consolidating genesis block if block_hash != chain_info.consensus_constants.genesis_hash { From 1398bbaf71ddb7987af986e9b1c62a497328c74c Mon Sep 17 00:00:00 2001 From: drcpu Date: Sun, 16 Jun 2024 16:34:15 +0000 Subject: [PATCH 05/11] fix(validations): take change output into account when calculating stake transaction fee --- validations/src/validations.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 320edf76e..343b7f19c 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -128,11 +128,15 @@ pub fn st_transaction_fee( ) -> Result { let in_value = transaction_inputs_sum(&st_tx.body.inputs, utxo_diff, epoch, epoch_constants)?; let out_value = st_tx.body.output.value; + let change_value = match &st_tx.body.change { + Some(change) => change.value, + None => 0, + }; - if out_value > in_value { + if out_value + change_value > in_value { Err(TransactionError::NegativeFee.into()) } else { - Ok(in_value - out_value) + Ok(in_value - out_value - change_value) } } From 061d067f75c12a456eaa1fc253a8282b6122e378 Mon Sep 17 00:00:00 2001 From: drcpu Date: Sun, 16 Jun 2024 16:34:56 +0000 Subject: [PATCH 06/11] feat(jsonrpc): add stake and unstake transaction hashes plus weights to block overview --- node/src/actors/json_rpc/api.rs | 75 ++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/node/src/actors/json_rpc/api.rs b/node/src/actors/json_rpc/api.rs index 463862e9b..c0096c4f3 100644 --- a/node/src/actors/json_rpc/api.rs +++ b/node/src/actors/json_rpc/api.rs @@ -26,7 +26,8 @@ use witnet_data_structures::{ tapi::ActiveWips, Block, DataRequestOutput, Epoch, Hash, Hashable, KeyedSignature, PublicKeyHash, RADType, StakeOutput, StateMachine, SyncStatus, }, - get_environment, + get_environment, get_protocol_version, + proto::versioning::ProtocolVersion, staking::prelude::*, transaction::Transaction, vrf::VrfMessage, @@ -799,14 +800,44 @@ pub async fn get_block(params: Params) -> Result { .map(|txn| txn.hash()) .collect(); - Some(serde_json::json!({ + let mut hashes = Some(serde_json::json!({ "mint" : output.txns.mint.hash(), "value_transfer" : vtt_hashes, "data_request" : drt_hashes, "commit" : ct_hashes, "reveal" : rt_hashes, "tally" : tt_hashes - })) + })); + + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let st_hashes: Vec<_> = output + .txns + .stake_txns + .iter() + .map(|txn| txn.hash()) + .collect(); + if let Some(ref mut hashes) = hashes { + hashes + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("stake".to_string(), serde_json::json!(st_hashes)); + } + + let ut_hashes: Vec<_> = output + .txns + .unstake_txns + .iter() + .map(|txn| txn.hash()) + .collect(); + if let Some(ref mut hashes) = hashes { + hashes + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("unstake".to_string(), serde_json::json!(ut_hashes)); + } + } + + hashes } else { None }; @@ -825,10 +856,40 @@ pub async fn get_block(params: Params) -> Result { .iter() .map(|txn| txn.weight()) .collect(); - Some(serde_json::json!({ - "value_transfer" : vtt_weights, - "data_request" : drt_weights, - })) + + let mut weights = Some(serde_json::json!({ + "value_transfer": vtt_weights, + "data_request": drt_weights, + })); + + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let st_weights: Vec<_> = output + .txns + .stake_txns + .iter() + .map(|txn| txn.weight()) + .collect(); + if let Some(ref mut weights) = weights { + weights + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("stake".to_string(), st_weights.into()); + } + + let ut_weights: Vec<_> = output + .txns + .unstake_txns + .iter() + .map(|txn| txn.weight()) + .collect(); + if let Some(ref mut weights) = weights { + weights + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("unstake".to_string(), ut_weights.into()); + } + } + weights } else { None }; From 46ee415d459698e97f138aff5840db9aa51c3844 Mon Sep 17 00:00:00 2001 From: drcpu Date: Sat, 15 Jun 2024 17:04:25 +0000 Subject: [PATCH 07/11] feat(mining): add functionality to reward validators whom have mined a block --- data_structures/src/staking/helpers.rs | 14 ++++--- data_structures/src/staking/stakes.rs | 32 +++++++++++++++- data_structures/src/wit.rs | 8 ++++ node/src/actors/chain_manager/mod.rs | 51 +++++++++++++++++++++----- validations/src/eligibility/current.rs | 4 +- 5 files changed, 91 insertions(+), 18 deletions(-) diff --git a/data_structures/src/staking/helpers.rs b/data_structures/src/staking/helpers.rs index 9fe633cee..ba2afc8dd 100644 --- a/data_structures/src/staking/helpers.rs +++ b/data_structures/src/staking/helpers.rs @@ -3,7 +3,7 @@ use std::{ fmt::{Debug, Display, Formatter}, iter::Sum, marker::PhantomData, - ops::{Add, Div, Mul, Sub}, + ops::{Add, Div, Mul, Rem, Sub}, rc::Rc, str::FromStr, sync::RwLock, @@ -219,7 +219,7 @@ where impl<'de, Address, Coins, Epoch, Power> Deserialize<'de> for Stakes where - Address: Clone + Debug + Default + DeserializeOwned + Display + Ord + Send + Sync, + Address: Clone + Debug + Default + DeserializeOwned + Display + Ord + Send + Sync + 'static, Coins: Copy + Debug + Default @@ -234,7 +234,9 @@ where + Sub + Sum + Sync - + Zero, + + Zero + + Div + + Rem, Epoch: Copy + Debug + Default @@ -269,7 +271,7 @@ struct StakesVisitor { impl<'de, Address, Coins, Epoch, Power> Visitor<'de> for StakesVisitor where - Address: Clone + Debug + Default + Deserialize<'de> + Display + Ord + Send + Sync, + Address: Clone + Debug + Default + Deserialize<'de> + Display + Ord + Send + Sync + 'static, Coins: Copy + Debug + Default @@ -284,7 +286,9 @@ where + Sub + Sum + Sync - + Zero, + + Zero + + Div + + Rem, Epoch: Copy + Debug + Default diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index 65dea3842..d4423f30d 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -2,7 +2,7 @@ use std::{ collections::{btree_map::Entry, BTreeMap}, fmt::{Debug, Display}, iter::Sum, - ops::{Add, Div, Mul, Sub}, + ops::{Add, Div, Mul, Rem, Sub}, }; use itertools::Itertools; @@ -91,7 +91,7 @@ where impl Stakes where - Address: Clone + Debug + Default + Ord + Send + Sync + Display, + Address: Clone + Debug + Default + Ord + Send + Sync + Display + 'static, Coins: Copy + Default + Ord @@ -107,6 +107,8 @@ where + Sync + Display + Sum + + Div + + Rem + PrecisionLoss, Epoch: Copy + Default @@ -311,6 +313,32 @@ where Ok(()) } + /// Add a reward to the validator's balance + pub fn add_reward( + &mut self, + validator: ISK, + coins: Coins, + current_epoch: Epoch, + ) -> StakesResult<(), Address, Coins, Epoch> + where + ISK: Into
, + { + let validator = validator.into(); + + let stakes = self + .by_validator + .get_mut(&validator) + .ok_or(StakesError::ValidatorNotFound { validator })?; + + let _ = stakes[0] + .value + .write() + .unwrap() + .add_stake(coins, current_epoch, Some(0.into())); + + Ok(()) + } + /// Creates an instance of `Stakes` with a custom minimum stakeable amount. pub fn with_minimum(minimum: Coins) -> Self { Stakes { diff --git a/data_structures/src/wit.rs b/data_structures/src/wit.rs index 74cc12e62..63149e643 100644 --- a/data_structures/src/wit.rs +++ b/data_structures/src/wit.rs @@ -78,6 +78,14 @@ impl Div for Wit { } } +impl Rem for Wit { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + Self::from_nanowits(self.nanowits() % rhs.nanowits()) + } +} + impl Mul for Wit { type Output = Self; diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 80b91c379..4208e13fb 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -80,7 +80,7 @@ use witnet_data_structures::{ visitor::{StatefulVisitor, Visitor}, LastBeacon, }, - utxo_pool::{Diff, OwnUnspentOutputsPool, UnspentOutputsPool, UtxoWriteBatch}, + utxo_pool::{Diff, OwnUnspentOutputsPool, UnspentOutputsPool, UtxoDiff, UtxoWriteBatch}, vrf::VrfCtx, wit::Wit, }; @@ -89,8 +89,9 @@ use witnet_util::timestamp::seconds_to_human_string; use witnet_validations::{ eligibility::legacy::VrfSlots, validations::{ - compare_block_candidates, validate_block, validate_block_transactions, - validate_new_transaction, validate_rad_request, verify_signatures, + compare_block_candidates, dr_transaction_fee, st_transaction_fee, ut_transaction_fee, + validate_block, validate_block_transactions, validate_new_transaction, + validate_rad_request, verify_signatures, vt_transaction_fee, }, }; @@ -1022,6 +1023,43 @@ impl ChainManager { chain_info.highest_block_checkpoint = beacon; chain_info.highest_vrf_output = vrf_input; + let miner_pkh = block.block_header.proof.proof.pkh(); + + // Reset the coin age of the miner for all staked coins + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch, 1); + + let epoch_constants = self.epoch_constants.unwrap(); + let utxo_diff = + UtxoDiff::new(&self.chain_state.unspent_outputs_pool, block_epoch); + + let mut transaction_fees = 0; + for vt_tx in &block.txns.value_transfer_txns { + transaction_fees += + vt_transaction_fee(vt_tx, &utxo_diff, current_epoch, epoch_constants) + .unwrap_or_default(); + } + for dr_tx in &block.txns.data_request_txns { + transaction_fees += + dr_transaction_fee(dr_tx, &utxo_diff, current_epoch, epoch_constants) + .unwrap_or_default(); + } + for st_tx in &block.txns.stake_txns { + transaction_fees += + st_transaction_fee(st_tx, &utxo_diff, current_epoch, epoch_constants) + .unwrap_or_default(); + } + for ut_tx in &block.txns.unstake_txns { + transaction_fees += ut_transaction_fee(ut_tx).unwrap_or_default(); + } + + let _ = stakes.add_reward( + miner_pkh, + Wit::from(50_000_000_000) + Wit::from(transaction_fees), + current_epoch, + ); + } + let rep_info = update_pools( &block, &mut self.chain_state.unspent_outputs_pool, @@ -1034,13 +1072,6 @@ impl ChainManager { self.sm_state, ); - let miner_pkh = block.block_header.proof.proof.pkh(); - - // Reset the coin age of the miner for all staked coins - if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { - let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch, 1); - } - // Do not update reputation or stakes when consolidating genesis block if block_hash != chain_info.consensus_constants.genesis_hash { update_reputation( diff --git a/validations/src/eligibility/current.rs b/validations/src/eligibility/current.rs index c3b3e6888..3819d1626 100644 --- a/validations/src/eligibility/current.rs +++ b/validations/src/eligibility/current.rs @@ -1,7 +1,7 @@ use std::{ fmt::{Debug, Display}, iter::Sum, - ops::{Add, Div, Mul, Sub}, + ops::{Add, Div, Mul, Rem, Sub}, }; use witnet_data_structures::{staking::prelude::*, wit::PrecisionLoss}; @@ -110,6 +110,8 @@ where + Sub + Mul + Mul + + Div + + Rem + PrecisionLoss + Sync + Send From f034c54287578a8d4b4cee9b181883f9bc8aa29a Mon Sep 17 00:00:00 2001 From: drcpu Date: Sun, 16 Jun 2024 05:00:47 +0000 Subject: [PATCH 08/11] feat(mining): disable mint transactions post wit\2 --- data_structures/src/error.rs | 4 +++ node/src/actors/chain_manager/mining.rs | 23 ++++++++----- validations/src/validations.rs | 46 +++++++++++++++---------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/data_structures/src/error.rs b/data_structures/src/error.rs index 8b4c994d5..4e00ba991 100644 --- a/data_structures/src/error.rs +++ b/data_structures/src/error.rs @@ -362,6 +362,10 @@ pub enum BlockError { mint_epoch: Epoch, block_epoch: Epoch, }, + #[fail( + display = "Mint transaction should be set to default after the activation of Witnet 2.0" + )] + InvalidMintTransaction, #[fail(display = "The block has an invalid PoE")] NotValidPoe, #[fail( diff --git a/node/src/actors/chain_manager/mining.rs b/node/src/actors/chain_manager/mining.rs index cc136de42..a62ac174b 100644 --- a/node/src/actors/chain_manager/mining.rs +++ b/node/src/actors/chain_manager/mining.rs @@ -1085,14 +1085,21 @@ pub fn build_block( } // Include Mint Transaction by miner - let reward = block_reward(epoch, initial_block_reward, halving_period) + transaction_fees; - let mint = MintTransaction::with_external_address( - epoch, - reward, - own_pkh, - external_address, - external_percentage, - ); + let mint = if get_protocol_version(Some(epoch)) == ProtocolVersion::V2_0 { + let mut mint = MintTransaction::default(); + mint.epoch = epoch; + + mint + } else { + let reward = block_reward(epoch, initial_block_reward, halving_period) + transaction_fees; + MintTransaction::with_external_address( + epoch, + reward, + own_pkh, + external_address, + external_percentage, + ) + }; // Compute `hash_merkle_root` and build block header let vt_hash_merkle_root = merkle_tree_root(&value_transfer_txns); diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 343b7f19c..98f24ba15 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -308,29 +308,37 @@ pub fn validate_mint_transaction( .into()); } - let mint_value = transaction_outputs_sum(&mint_tx.outputs)?; - let block_reward_value = block_reward(mint_tx.epoch, initial_block_reward, halving_period); - // Mint value must be equal to block_reward + transaction fees - if mint_value != total_fees + block_reward_value { - return Err(BlockError::MismatchedMintValue { - mint_value, - fees_value: total_fees, - reward_value: block_reward_value, + if get_protocol_version(Some(block_epoch)) != ProtocolVersion::V2_0 { + let mint_value = transaction_outputs_sum(&mint_tx.outputs)?; + let block_reward_value = block_reward(mint_tx.epoch, initial_block_reward, halving_period); + // Mint value must be equal to block_reward + transaction fees + if mint_value != total_fees + block_reward_value { + return Err(BlockError::MismatchedMintValue { + mint_value, + fees_value: total_fees, + reward_value: block_reward_value, + } + .into()); } - .into()); - } - if mint_tx.outputs.len() > 2 { - return Err(BlockError::TooSplitMint.into()); - } + if mint_tx.outputs.len() > 2 { + return Err(BlockError::TooSplitMint.into()); + } - for (idx, output) in mint_tx.outputs.iter().enumerate() { - if output.value == 0 { - return Err(TransactionError::ZeroValueOutput { - tx_hash: mint_tx.hash(), - output_id: idx, + for (idx, output) in mint_tx.outputs.iter().enumerate() { + if output.value == 0 { + return Err(TransactionError::ZeroValueOutput { + tx_hash: mint_tx.hash(), + output_id: idx, + } + .into()); } - .into()); + } + } else { + let mut valid_mint_tx = MintTransaction::default(); + valid_mint_tx.epoch = block_epoch; + if *mint_tx != valid_mint_tx { + return Err(BlockError::InvalidMintTransaction.into()); } } From bb4e2266b2bf732a67fcd139690ac842ae66b539 Mon Sep 17 00:00:00 2001 From: drcpu Date: Sun, 16 Jun 2024 19:48:30 +0000 Subject: [PATCH 09/11] feat(validations): validate withdrawer uniqueness for an existing validator --- data_structures/src/staking/errors.rs | 9 +++++ data_structures/src/staking/stakes.rs | 54 +++++++++++++++++++++++++ node/src/actors/chain_manager/mining.rs | 1 - node/src/actors/chain_manager/mod.rs | 12 ++---- validations/src/validations.rs | 14 ++++++- 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/data_structures/src/staking/errors.rs b/data_structures/src/staking/errors.rs index 03ac013a8..ba8313b87 100644 --- a/data_structures/src/staking/errors.rs +++ b/data_structures/src/staking/errors.rs @@ -86,6 +86,15 @@ where /// A withdrawer address. withdrawer: Address, }, + /// Tried to add stake to a validator with a different withdrawer than the one initially set. + #[fail( + display = "Validator {} already has a different withdrawer set", + validator + )] + DifferentWithdrawer { + /// A validator address. + validator: Address, + }, /// Tried to query for a stake entry without providing a validator or a withdrawer address. #[fail( display = "Tried to query a stake entry without providing a validator or a withdrawer address" diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index d4423f30d..13e0b6094 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -413,6 +413,32 @@ where self.by_validator.len() } + /// Query stakes to check for an existing validator / withdrawer pair. + pub fn check_validator_withdrawer( + &self, + validator: ISK, + withdrawer: ISK, + ) -> StakesResult<(), Address, Coins, Epoch> + where + ISK: Into
, + { + let validator = validator.into(); + let withdrawer = withdrawer.into(); + + let valid_staking_pair = if !self.by_validator.contains_key(&validator) { + Ok(()) + } else { + let stake_key = StakeKey::from((validator.clone(), withdrawer)); + if self.by_key.contains_key(&stake_key) { + Ok(()) + } else { + Err(StakesError::DifferentWithdrawer { validator }) + } + }; + + valid_staking_pair + } + /// Query stakes by stake key. #[inline(always)] fn query_by_key( @@ -1091,4 +1117,32 @@ mod tests { assert_eq!(epoch, 789); } + + #[test] + fn test_validator_withdrawer_pair() { + // First, lets create a setup with a few stakers + let mut stakes = Stakes::::with_minimum(5); + let alice = "Alice"; + let bob = "Bob"; + let charlie = "Charlie"; + + // Validator not used yet, so we can stake with any (validator, withdrawer) pair + assert_eq!(stakes.check_validator_withdrawer(alice, bob), Ok(())); + assert_eq!(stakes.check_validator_withdrawer(alice, charlie), Ok(())); + + // Use the validator with a (validator, withdrawer) pair + stakes.add_stake((alice, bob), 10, 0).unwrap(); + + // The validator is used, we can still stake as long as the correct withdrawer is used + assert_eq!(stakes.check_validator_withdrawer(alice, bob), Ok(())); + + // Validator used with another withdrawer address, throw an error + let valid_pair = stakes.check_validator_withdrawer(alice, charlie); + assert_eq!( + valid_pair, + Err(StakesError::DifferentWithdrawer { + validator: alice.into() + }) + ); + } } diff --git a/node/src/actors/chain_manager/mining.rs b/node/src/actors/chain_manager/mining.rs index a62ac174b..4a59f6098 100644 --- a/node/src/actors/chain_manager/mining.rs +++ b/node/src/actors/chain_manager/mining.rs @@ -249,7 +249,6 @@ impl ChainManager { vrf_input, beacon, epoch_constants, - validator_count, ) .map_ok(|_diff, act, _ctx| { // Send AddCandidates message to self diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 4208e13fb..728ce3130 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -702,7 +702,6 @@ impl ChainManager { let mut transaction_visitor = PriorityVisitor::default(); let protocol_version = get_protocol_version(self.current_epoch); - let validator_count = self.chain_state.stakes.validator_count(); let utxo_diff = process_validations( &block, self.current_epoch.unwrap_or_default(), @@ -720,7 +719,6 @@ impl ChainManager { Some(&mut transaction_visitor), protocol_version, &self.chain_state.stakes, - validator_count, )?; // Extract the collected priorities from the internal state of the visitor @@ -756,8 +754,6 @@ impl ChainManager { return; } - let validator_count = self.chain_state.stakes.validator_count(); - let hash_block = block.hash(); // If this candidate has not been seen before, validate it if !self.seen_candidates.contains(&block) { @@ -887,7 +883,6 @@ impl ChainManager { Some(&mut transaction_visitor), protocol_version, &self.chain_state.stakes, - validator_count, ) { Ok(utxo_diff) => { let priorities = transaction_visitor.take_state(); @@ -1570,6 +1565,7 @@ impl ChainManager { required_reward_collateral_ratio, &active_wips, chain_info.consensus_constants.superblock_period, + &self.chain_state.stakes, )) .into_actor(self) .and_then(|fee, act, _ctx| { @@ -2077,7 +2073,6 @@ impl ChainManager { vrf_input: CheckpointVRF, chain_beacon: CheckpointBeacon, epoch_constants: EpochConstants, - validator_count: usize, ) -> ResponseActFuture> { let block_number = self.chain_state.block_number(); let mut signatures_to_verify = vec![]; @@ -2121,7 +2116,7 @@ impl ChainManager { &consensus_constants, &active_wips, None, - Some(validator_count), + &act.chain_state.stakes, ); async { // Short-circuit if validation failed @@ -2899,7 +2894,6 @@ pub fn process_validations( transaction_visitor: Option<&mut dyn Visitor>, protocol_version: ProtocolVersion, stakes: &Stakes, - validator_count: usize, ) -> Result { if !resynchronizing { let mut signatures_to_verify = vec![]; @@ -2931,7 +2925,7 @@ pub fn process_validations( consensus_constants, active_wips, transaction_visitor, - Some(validator_count), + stakes, )?; if !resynchronizing { diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 98f24ba15..5d2961497 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -1248,6 +1248,7 @@ pub fn validate_stake_transaction<'a>( epoch: Epoch, epoch_constants: EpochConstants, signatures_to_verify: &mut Vec, + stakes: &Stakes, ) -> Result, failure::Error> { // Check that the amount of coins to stake is equal or greater than the minimum allowed if st_tx.body.output.value < MIN_STAKE_NANOWITS { @@ -1257,6 +1258,12 @@ pub fn validate_stake_transaction<'a>( })?; } + // A stake transaction can only stake on an existing validator if the withdrawer address is the same + stakes.check_validator_withdrawer( + st_tx.body.output.key.validator, + st_tx.body.output.key.withdrawer, + )?; + validate_transaction_signature( &st_tx.signatures, &st_tx.body.inputs, @@ -1657,7 +1664,7 @@ pub fn validate_block_transactions( consensus_constants: &ConsensusConstants, active_wips: &ActiveWips, mut visitor: Option<&mut dyn Visitor>, - validator_count: Option, + stakes: &Stakes, ) -> Result { let epoch = block.block_header.beacon.checkpoint; let is_genesis = block.is_genesis(&consensus_constants.genesis_hash); @@ -1824,7 +1831,7 @@ pub fn validate_block_transactions( dr_pool, consensus_constants.collateral_minimum, active_wips, - validator_count, + Some(stakes.validator_count()), Some(epoch), )?; @@ -1962,6 +1969,7 @@ pub fn validate_block_transactions( epoch, epoch_constants, signatures_to_verify, + stakes, )?; total_fee += fee; @@ -2209,6 +2217,7 @@ pub fn validate_new_transaction( required_reward_collateral_ratio: u64, active_wips: &ActiveWips, superblock_period: u16, + stakes: &Stakes, ) -> Result { let utxo_diff = UtxoDiff::new(unspent_outputs_pool, block_number); @@ -2261,6 +2270,7 @@ pub fn validate_new_transaction( current_epoch, epoch_constants, signatures_to_verify, + stakes, ) .map(|(_, _, fee, _, _)| fee), _ => Err(TransactionError::NotValidTransaction.into()), From 54e4172eaea1d907360b630139d9e1846492ecc9 Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 21 Jun 2024 08:20:14 +0200 Subject: [PATCH 10/11] fix(mining): do not include stake transactions in block during V1_7 --- node/src/actors/chain_manager/mining.rs | 87 +++++++++++++------------ 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/node/src/actors/chain_manager/mining.rs b/node/src/actors/chain_manager/mining.rs index 4a59f6098..a5774202f 100644 --- a/node/src/actors/chain_manager/mining.rs +++ b/node/src/actors/chain_manager/mining.rs @@ -1038,53 +1038,58 @@ pub fn build_block( } } - let mut included_validators = HashSet::::new(); - for st_tx in transactions_pool.st_iter() { - let validator_pkh = st_tx.body.output.authorization.public_key.pkh(); - if included_validators.contains(&validator_pkh) { - log::debug!( - "Cannot include more than one stake transaction for {} in a single block", - validator_pkh - ); - continue; - } - - let transaction_weight = st_tx.weight(); - let transaction_fee = match st_transaction_fee(st_tx, &utxo_diff, epoch, epoch_constants) { - Ok(x) => x, - Err(e) => { - log::warn!( - "Error when calculating transaction fee for transaction: {}", - e + let protocol_version = get_protocol_version(Some(epoch)); + + if protocol_version != ProtocolVersion::V1_7 { + let mut included_validators = HashSet::::new(); + for st_tx in transactions_pool.st_iter() { + let validator_pkh = st_tx.body.output.authorization.public_key.pkh(); + if included_validators.contains(&validator_pkh) { + log::debug!( + "Cannot include more than one stake transaction for {} in a single block", + validator_pkh ); continue; } - }; - let new_st_weight = st_weight.saturating_add(transaction_weight); - if new_st_weight <= max_st_weight { - update_utxo_diff( - &mut utxo_diff, - st_tx.body.inputs.iter(), - st_tx.body.change.iter(), - st_tx.hash(), - ); - stake_txns.push(st_tx.clone()); - transaction_fees = transaction_fees.saturating_add(transaction_fee); - st_weight = new_st_weight; - } + let transaction_weight = st_tx.weight(); + let transaction_fee = + match st_transaction_fee(st_tx, &utxo_diff, epoch, epoch_constants) { + Ok(x) => x, + Err(e) => { + log::warn!( + "Error when calculating transaction fee for transaction: {}", + e + ); + continue; + } + }; - // The condition to stop is if the free space in the block for VTTransactions - // is less than the minimum stake transaction weight - if st_weight > max_st_weight.saturating_sub(min_st_weight) { - break; - } + let new_st_weight = st_weight.saturating_add(transaction_weight); + if new_st_weight <= max_st_weight { + update_utxo_diff( + &mut utxo_diff, + st_tx.body.inputs.iter(), + st_tx.body.change.iter(), + st_tx.hash(), + ); + stake_txns.push(st_tx.clone()); + transaction_fees = transaction_fees.saturating_add(transaction_fee); + st_weight = new_st_weight; + } + + // The condition to stop is if the free space in the block for VTTransactions + // is less than the minimum stake transaction weight + if st_weight > max_st_weight.saturating_sub(min_st_weight) { + break; + } - included_validators.insert(validator_pkh); + included_validators.insert(validator_pkh); + } } // Include Mint Transaction by miner - let mint = if get_protocol_version(Some(epoch)) == ProtocolVersion::V2_0 { + let mint = if protocol_version == ProtocolVersion::V2_0 { let mut mint = MintTransaction::default(); mint.epoch = epoch; @@ -1107,9 +1112,7 @@ pub fn build_block( let reveal_hash_merkle_root = merkle_tree_root(&reveal_txns); let tally_hash_merkle_root = merkle_tree_root(&tally_txns); - let protocol = get_protocol_version(Some(beacon.checkpoint)); - - let stake_hash_merkle_root = if protocol == ProtocolVersion::V1_7 { + let stake_hash_merkle_root = if protocol_version == ProtocolVersion::V1_7 { log::debug!("Legacy protocol: the default stake hash merkle root will be used"); Hash::default() } else { @@ -1117,7 +1120,7 @@ pub fn build_block( merkle_tree_root(&stake_txns) }; - let unstake_hash_merkle_root = if protocol == ProtocolVersion::V1_7 { + let unstake_hash_merkle_root = if protocol_version == ProtocolVersion::V1_7 { Hash::default() } else { merkle_tree_root(&unstake_txns) From 0a8869edd25d85a1aef153a992a4f1a25b8619b3 Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 2 Jul 2024 17:16:26 +0000 Subject: [PATCH 11/11] feat(staking): discard nanoWits for average epoch computation when adding stake --- data_structures/src/staking/stake.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data_structures/src/staking/stake.rs b/data_structures/src/staking/stake.rs index 321259b64..4652f977f 100644 --- a/data_structures/src/staking/stake.rs +++ b/data_structures/src/staking/stake.rs @@ -81,12 +81,14 @@ where for capability in ALL_CAPABILITIES { let epoch_before = self.epochs.get(capability); - let product_before = coins_before * epoch_before; - let product_added = coins * epoch; + let product_before = coins_before.lose_precision(WIT_DECIMAL_PLACES) * epoch_before; + let product_added = coins.lose_precision(WIT_DECIMAL_PLACES) * epoch; #[allow(clippy::cast_possible_truncation)] let epoch_after = Epoch::from( - (u64::from(product_before + product_added) / u64::from(coins_after)) as u32, + (u64::from(product_before + product_added) + / u64::from(coins_after.lose_precision(WIT_DECIMAL_PLACES))) + as u32, ); self.epochs.update(capability, epoch_after); }