diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index 68bede648..816f0704e 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -122,6 +122,8 @@ pub struct Params { /// - OpTxOutputSpk (0xc3): Get output script public key pub kip10_activation: ForkActivation, + pub transient_storage_activation: ForkActivation, + /// DAA score after which the pre-deflationary period switches to the deflationary period pub deflationary_phase_daa_score: u64, @@ -396,6 +398,7 @@ pub const MAINNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), kip10_activation: ForkActivation::never(), + transient_storage_activation: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: @@ -462,6 +465,8 @@ pub const TESTNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), kip10_activation: ForkActivation::never(), + transient_storage_activation: ForkActivation::never(), + // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: // We define a year as 365.25 days @@ -536,6 +541,8 @@ pub const TESTNET11_PARAMS: Params = Params { // Roughly at Dec 3, 2024 1800 UTC kip10_activation: ForkActivation::new(287238000), payload_activation: ForkActivation::new(287238000), + // Activation TBD + transient_storage_activation: ForkActivation::never(), skip_proof_of_work: false, max_block_level: 250, @@ -590,6 +597,7 @@ pub const SIMNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::always(), kip10_activation: ForkActivation::never(), + transient_storage_activation: ForkActivation::always(), skip_proof_of_work: true, // For simnet only, PoW can be simulated by default max_block_level: 250, @@ -639,6 +647,7 @@ pub const DEVNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), kip10_activation: ForkActivation::never(), + transient_storage_activation: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: diff --git a/consensus/core/src/constants.rs b/consensus/core/src/constants.rs index 450c12f67..5230f2a61 100644 --- a/consensus/core/src/constants.rs +++ b/consensus/core/src/constants.rs @@ -15,6 +15,11 @@ pub const SOMPI_PER_KASPA: u64 = 100_000_000; /// The parameter for scaling inverse KAS value to mass units (KIP-0009) pub const STORAGE_MASS_PARAMETER: u64 = SOMPI_PER_KASPA * 10_000; +/// TRANSIENT_BYTE_TO_MASS_FACTOR is how much mass per byte to charge for when calculating +/// transient storage mass. Since normally the block mass limit is 500_000, this limits +/// block byte size to 125_000. +pub const TRANSIENT_BYTE_TO_MASS_FACTOR: u64 = 4; + /// MaxSompi is the maximum transaction amount allowed in sompi. pub const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; diff --git a/consensus/core/src/mass/mod.rs b/consensus/core/src/mass/mod.rs index 67bcc63ae..8b826f548 100644 --- a/consensus/core/src/mass/mod.rs +++ b/consensus/core/src/mass/mod.rs @@ -120,8 +120,19 @@ impl MassCalculator { } /// Calculates the overall mass of this transaction, combining both compute and storage masses. - pub fn calc_tx_overall_mass(&self, tx: &impl VerifiableTransaction, cached_compute_mass: Option) -> Option { - self.calc_tx_storage_mass(tx).map(|mass| mass.max(cached_compute_mass.unwrap_or_else(|| self.calc_tx_compute_mass(tx.tx())))) + pub fn calc_tx_overall_mass( + &self, + tx: &impl VerifiableTransaction, + cached_compute_mass: Option, + is_transient_storage_activated: bool, + ) -> Option { + self.calc_tx_storage_mass(tx).map(|mass| { + if is_transient_storage_activated { + mass + } else { + mass.max(cached_compute_mass.unwrap_or_else(|| self.calc_tx_compute_mass(tx.tx()))) + } + }) } } @@ -260,6 +271,22 @@ mod tests { assert_eq!(storage_mass, 5000000000); } + #[test] + fn test_calc_tx_overall_mass() { + let tx = generate_tx_from_amounts(&[100, 200], &[50, 250]); + let storage_mass_parameter = 10u64.pow(12); + let mass_calculator = MassCalculator::new(1, 10, 1000, storage_mass_parameter); + let storage_mass = mass_calculator.calc_tx_storage_mass(&tx.as_verifiable()).unwrap(); + let compute_mass = mass_calculator.calc_tx_compute_mass(&tx.tx); + + // Pre-HF behavior + let overall_mass = storage_mass.max(compute_mass); + assert_eq!(overall_mass, mass_calculator.calc_tx_overall_mass(&tx.as_verifiable(), None, false).unwrap()); + + // Post-HF behavior + assert_eq!(storage_mass, mass_calculator.calc_tx_overall_mass(&tx.as_verifiable(), Some(compute_mass), true).unwrap()); + } + fn generate_tx_from_amounts(ins: &[u64], outs: &[u64]) -> MutableTransaction { let script_pub_key = ScriptVec::from_slice(&[]); let prev_tx_id = TransactionId::from_str("880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3").unwrap(); diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index 769d29452..6101e9df6 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -400,12 +400,20 @@ pub struct MutableTransaction = std::sync::Arc, /// Populated compute mass (does not include the storage mass) pub calculated_compute_mass: Option, + /// Populated transient storage mass + pub calculated_transient_storage_mass: Option, } impl> MutableTransaction { pub fn new(tx: T) -> Self { let num_inputs = tx.as_ref().inputs.len(); - Self { tx, entries: vec![None; num_inputs], calculated_fee: None, calculated_compute_mass: None } + Self { + tx, + entries: vec![None; num_inputs], + calculated_fee: None, + calculated_compute_mass: None, + calculated_transient_storage_mass: None, + } } pub fn id(&self) -> TransactionId { @@ -414,7 +422,13 @@ impl> MutableTransaction { pub fn with_entries(tx: T, entries: Vec) -> Self { assert_eq!(tx.as_ref().inputs.len(), entries.len()); - Self { tx, entries: entries.into_iter().map(Some).collect(), calculated_fee: None, calculated_compute_mass: None } + Self { + tx, + entries: entries.into_iter().map(Some).collect(), + calculated_fee: None, + calculated_compute_mass: None, + calculated_transient_storage_mass: None, + } } /// Returns the tx wrapped as a [`VerifiableTransaction`]. Note that this function @@ -430,7 +444,10 @@ impl> MutableTransaction { } pub fn is_fully_populated(&self) -> bool { - self.is_verifiable() && self.calculated_fee.is_some() && self.calculated_compute_mass.is_some() + self.is_verifiable() + && self.calculated_fee.is_some() + && self.calculated_compute_mass.is_some() + && self.calculated_transient_storage_mass.is_some() } pub fn missing_outpoints(&self) -> impl Iterator + '_ { @@ -455,7 +472,7 @@ impl> MutableTransaction { /// function returns a value when calculated fee exists and the contextual mass is greater /// than zero, otherwise `None` is returned. pub fn calculated_feerate(&self) -> Option { - let contextual_mass = self.tx.as_ref().mass(); + let contextual_mass = self.calculated_max_overall_mass(); if contextual_mass > 0 { self.calculated_fee.map(|fee| fee as f64 / contextual_mass as f64) } else { @@ -463,6 +480,16 @@ impl> MutableTransaction { } } + /// Returns the maximum overall mass of this transaction if all values are set + /// else returns 0 + pub fn calculated_max_overall_mass(&self) -> u64 { + if self.calculated_compute_mass.is_some() && self.calculated_transient_storage_mass.is_some() { + self.tx.as_ref().mass().max(self.calculated_compute_mass.unwrap()).max(self.calculated_transient_storage_mass.unwrap()) + } else { + 0 + } + } + /// A function for estimating the amount of memory bytes used by this transaction (dedicated to mempool usage). /// We need consistency between estimation calls so only this function should be used for this purpose since /// `estimate_mem_bytes` is sensitive to pointer wrappers such as Arc diff --git a/consensus/src/consensus/services.rs b/consensus/src/consensus/services.rs index 06abb4e0b..162864680 100644 --- a/consensus/src/consensus/services.rs +++ b/consensus/src/consensus/services.rs @@ -148,6 +148,7 @@ impl ConsensusServices { params.storage_mass_activation, params.kip10_activation, params.payload_activation, + params.transient_storage_activation, ); let pruning_point_manager = PruningPointManager::new( diff --git a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs index 4c6139846..5b4593d72 100644 --- a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs +++ b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs @@ -2,17 +2,49 @@ use std::{collections::HashSet, sync::Arc}; use super::BlockBodyProcessor; use crate::errors::{BlockProcessResult, RuleError}; -use kaspa_consensus_core::{block::Block, merkle::calc_hash_merkle_root, tx::TransactionOutpoint}; +use kaspa_consensus_core::{ + block::Block, constants::TRANSIENT_BYTE_TO_MASS_FACTOR, mass::transaction_estimated_serialized_size, + merkle::calc_hash_merkle_root, tx::TransactionOutpoint, +}; + +struct TrackedMasses { + compute_mass: u64, + storage_mass: u64, + transient_storage_mass: u64, +} + +impl TrackedMasses { + fn new() -> Self { + Self { compute_mass: 0, storage_mass: 0, transient_storage_mass: 0 } + } + + fn is_any_above_threshold(&self, threshold: u64) -> bool { + self.compute_mass > threshold || self.storage_mass > threshold || self.transient_storage_mass > threshold + } + + fn add_compute_mass(&mut self, mass: u64) { + self.compute_mass = self.compute_mass.saturating_add(mass); + } + + fn add_storage_mass(&mut self, mass: u64) { + self.storage_mass = self.storage_mass.saturating_add(mass); + } + + fn add_transient_storage_mass(&mut self, mass: u64) { + self.transient_storage_mass = self.transient_storage_mass.saturating_add(mass); + } +} impl BlockBodyProcessor { pub fn validate_body_in_isolation(self: &Arc, block: &Block) -> BlockProcessResult { let storage_mass_activated = self.storage_mass_activation.is_active(block.header.daa_score); + let transient_storage_activated = self.transient_storage_activation.is_active(block.header.daa_score); Self::check_has_transactions(block)?; Self::check_hash_merkle_root(block, storage_mass_activated)?; Self::check_only_one_coinbase(block)?; self.check_transactions_in_isolation(block)?; - let mass = self.check_block_mass(block, storage_mass_activated)?; + let mass = self.check_block_mass(block, storage_mass_activated, transient_storage_activated)?; self.check_duplicate_transactions(block)?; self.check_block_double_spends(block)?; self.check_no_chained_transactions(block)?; @@ -57,22 +89,47 @@ impl BlockBodyProcessor { Ok(()) } - fn check_block_mass(self: &Arc, block: &Block, storage_mass_activated: bool) -> BlockProcessResult { + fn check_block_mass( + self: &Arc, + block: &Block, + storage_mass_activated: bool, + transient_storage_activated: bool, + ) -> BlockProcessResult { let mut total_mass: u64 = 0; if storage_mass_activated { - for tx in block.transactions.iter() { - // This is only the compute part of the mass, the storage part cannot be computed here - let calculated_tx_compute_mass = self.mass_calculator.calc_tx_compute_mass(tx); - let committed_contextual_mass = tx.mass(); - // We only check the lower-bound here, a precise check of the mass commitment - // is done when validating the tx in context - if committed_contextual_mass < calculated_tx_compute_mass { - return Err(RuleError::MassFieldTooLow(tx.id(), committed_contextual_mass, calculated_tx_compute_mass)); + if transient_storage_activated { + let mut tracked_masses = TrackedMasses::new(); + + for tx in block.transactions.iter() { + // This is only the compute part of the mass, the storage part cannot be computed here + let calculated_tx_compute_mass = self.mass_calculator.calc_tx_compute_mass(tx); + let transient_storage_mass = transaction_estimated_serialized_size(tx) * TRANSIENT_BYTE_TO_MASS_FACTOR; + let committed_contextual_mass = tx.mass(); + + // Sum over the committed masses + tracked_masses.add_compute_mass(calculated_tx_compute_mass); + tracked_masses.add_storage_mass(committed_contextual_mass); + tracked_masses.add_transient_storage_mass(transient_storage_mass); + + if tracked_masses.is_any_above_threshold(self.max_block_mass) { + return Err(RuleError::ExceedsMassLimit(self.max_block_mass)); + } } - // Sum over the committed masses - total_mass = total_mass.saturating_add(committed_contextual_mass); - if total_mass > self.max_block_mass { - return Err(RuleError::ExceedsMassLimit(self.max_block_mass)); + } else { + for tx in block.transactions.iter() { + // This is only the compute part of the mass, the storage part cannot be computed here + let calculated_tx_compute_mass = self.mass_calculator.calc_tx_compute_mass(tx); + let committed_contextual_mass = tx.mass(); + // We only check the lower-bound here, a precise check of the mass commitment + // is done when validating the tx in context + if committed_contextual_mass < calculated_tx_compute_mass { + return Err(RuleError::MassFieldTooLow(tx.id(), committed_contextual_mass, calculated_tx_compute_mass)); + } + // Sum over the committed masses + total_mass = total_mass.saturating_add(committed_contextual_mass); + if total_mass > self.max_block_mass { + return Err(RuleError::ExceedsMassLimit(self.max_block_mass)); + } } } } else { diff --git a/consensus/src/pipeline/body_processor/processor.rs b/consensus/src/pipeline/body_processor/processor.rs index 7bad12ce3..0fde2d2d3 100644 --- a/consensus/src/pipeline/body_processor/processor.rs +++ b/consensus/src/pipeline/body_processor/processor.rs @@ -88,6 +88,9 @@ pub struct BlockBodyProcessor { /// Storage mass hardfork DAA score pub(crate) storage_mass_activation: ForkActivation, + + /// Temp storage mass HF + pub(crate) transient_storage_activation: ForkActivation, } impl BlockBodyProcessor { @@ -131,6 +134,7 @@ impl BlockBodyProcessor { notification_root, counters, storage_mass_activation: params.storage_mass_activation, + transient_storage_activation: params.transient_storage_activation, } } diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index 914e0a327..1c9ed5d39 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -167,6 +167,7 @@ pub struct VirtualStateProcessor { // Storage mass hardfork DAA score pub(crate) storage_mass_activation: ForkActivation, pub(crate) kip10_activation: ForkActivation, + pub(crate) transient_storage_activation: ForkActivation, } impl VirtualStateProcessor { @@ -232,6 +233,7 @@ impl VirtualStateProcessor { counters, storage_mass_activation: params.storage_mass_activation, kip10_activation: params.kip10_activation, + transient_storage_activation: params.transient_storage_activation, } } diff --git a/consensus/src/pipeline/virtual_processor/utxo_validation.rs b/consensus/src/pipeline/virtual_processor/utxo_validation.rs index f0da0535e..246d519c5 100644 --- a/consensus/src/pipeline/virtual_processor/utxo_validation.rs +++ b/consensus/src/pipeline/virtual_processor/utxo_validation.rs @@ -329,7 +329,11 @@ impl VirtualStateProcessor { let contextual_mass = self .transaction_validator .mass_calculator - .calc_tx_overall_mass(&mutable_tx.as_verifiable(), mutable_tx.calculated_compute_mass) + .calc_tx_overall_mass( + &mutable_tx.as_verifiable(), + mutable_tx.calculated_compute_mass, + self.transient_storage_activation.is_active(pov_daa_score), + ) .ok_or(TxRuleError::MassIncomputable)?; // Set the inner mass field diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index b4a946c2f..fee01cb79 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -31,6 +31,8 @@ pub struct TransactionValidator { /// KIP-10 hardfork DAA score kip10_activation: ForkActivation, payload_activation: ForkActivation, + /// Temporary storage activation + transient_storage_activation: ForkActivation, } impl TransactionValidator { @@ -48,6 +50,7 @@ impl TransactionValidator { storage_mass_activation: ForkActivation, kip10_activation: ForkActivation, payload_activation: ForkActivation, + transient_storage_activation: ForkActivation, ) -> Self { Self { max_tx_inputs, @@ -62,6 +65,7 @@ impl TransactionValidator { storage_mass_activation, kip10_activation, payload_activation, + transient_storage_activation, } } @@ -88,6 +92,7 @@ impl TransactionValidator { storage_mass_activation: ForkActivation::never(), kip10_activation: ForkActivation::never(), payload_activation: ForkActivation::never(), + transient_storage_activation: ForkActivation::never(), } } } diff --git a/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs b/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs index 854c46de0..c4fc74cd9 100644 --- a/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs +++ b/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs @@ -45,7 +45,7 @@ impl TransactionValidator { let fee = total_in - total_out; if flags != TxValidationFlags::SkipMassCheck && self.storage_mass_activation.is_active(pov_daa_score) { // Storage mass hardfork was activated - self.check_mass_commitment(tx)?; + self.check_mass_commitment(tx, pov_daa_score)?; if self.storage_mass_activation.is_within_range_from_activation(pov_daa_score, 10) { warn!("--------- Storage mass hardfork was activated successfully!!! --------- (DAA score: {})", pov_daa_score); @@ -124,8 +124,11 @@ impl TransactionValidator { Ok(total_out) } - fn check_mass_commitment(&self, tx: &impl VerifiableTransaction) -> TxResult<()> { - let calculated_contextual_mass = self.mass_calculator.calc_tx_overall_mass(tx, None).ok_or(TxRuleError::MassIncomputable)?; + fn check_mass_commitment(&self, tx: &impl VerifiableTransaction, pov_daa_score: u64) -> TxResult<()> { + let calculated_contextual_mass = self + .mass_calculator + .calc_tx_overall_mass(tx, None, self.transient_storage_activation.is_active(pov_daa_score)) + .ok_or(TxRuleError::MassIncomputable)?; let committed_contextual_mass = tx.tx().mass(); if committed_contextual_mass != calculated_contextual_mass { return Err(TxRuleError::WrongMass(calculated_contextual_mass, committed_contextual_mass)); diff --git a/mining/src/manager_tests.rs b/mining/src/manager_tests.rs index 6ddc86e45..dac3f22a9 100644 --- a/mining/src/manager_tests.rs +++ b/mining/src/manager_tests.rs @@ -20,7 +20,7 @@ mod tests { api::ConsensusApi, block::TemplateBuildMode, coinbase::MinerData, - constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASPA, TX_VERSION}, + constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASPA, TRANSIENT_BYTE_TO_MASS_FACTOR, TX_VERSION}, errors::tx::TxRuleError, mass::transaction_estimated_serialized_size, subnets::SUBNETWORK_ID_NATIVE, @@ -116,6 +116,14 @@ mod tests { tx_to_insert.calculated_compute_mass.unwrap(), tx.calculated_compute_mass.unwrap() ); + assert_eq!( + tx_to_insert.calculated_transient_storage_mass.unwrap(), + tx.calculated_transient_storage_mass.unwrap(), + "({priority:?}, {orphan:?}, {rbf_policy:?}) wrong transient mass in transaction {}: expected: {}, got: {}", + tx.id(), + tx_to_insert.calculated_transient_storage_mass.unwrap(), + tx.calculated_transient_storage_mass.unwrap() + ); } assert!( found_exact_match, @@ -1349,6 +1357,8 @@ mod tests { mutable_tx.calculated_fee = Some(DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE); // Please note: this is the ConsensusMock version of the calculated_mass which differs from Consensus mutable_tx.calculated_compute_mass = Some(transaction_estimated_serialized_size(&mutable_tx.tx)); + mutable_tx.calculated_transient_storage_mass = + Some(transaction_estimated_serialized_size(&mutable_tx.tx) * TRANSIENT_BYTE_TO_MASS_FACTOR); mutable_tx.entries[0] = Some(entry); mutable_tx diff --git a/mining/src/mempool/check_transaction_standard.rs b/mining/src/mempool/check_transaction_standard.rs index ef4d3fb9e..aae17b83d 100644 --- a/mining/src/mempool/check_transaction_standard.rs +++ b/mining/src/mempool/check_transaction_standard.rs @@ -61,12 +61,9 @@ impl Mempool { // almost as much to process as the sender fees, limit the maximum // size of a transaction. This also helps mitigate CPU exhaustion // attacks. - if transaction.calculated_compute_mass.unwrap() > MAXIMUM_STANDARD_TRANSACTION_MASS { - return Err(NonStandardError::RejectMass( - transaction_id, - transaction.calculated_compute_mass.unwrap(), - MAXIMUM_STANDARD_TRANSACTION_MASS, - )); + let max_tx_mass = transaction.calculated_compute_mass.unwrap().max(transaction.calculated_transient_storage_mass.unwrap()); + if max_tx_mass > MAXIMUM_STANDARD_TRANSACTION_MASS { + return Err(NonStandardError::RejectMass(transaction_id, max_tx_mass, MAXIMUM_STANDARD_TRANSACTION_MASS)); } for (i, input) in transaction.tx.inputs.iter().enumerate() { @@ -171,7 +168,7 @@ impl Mempool { /// into the mempool and relay. pub(crate) fn check_transaction_standard_in_context(&self, transaction: &MutableTransaction) -> NonStandardResult<()> { let transaction_id = transaction.id(); - let contextual_mass = transaction.tx.mass(); + let contextual_mass = transaction.calculated_max_overall_mass(); assert!(contextual_mass > 0, "expected to be set by consensus"); if contextual_mass > MAXIMUM_STANDARD_TRANSACTION_MASS { return Err(NonStandardError::RejectContextualMass(transaction_id, contextual_mass, MAXIMUM_STANDARD_TRANSACTION_MASS)); @@ -239,7 +236,7 @@ mod tests { use kaspa_addresses::{Address, Prefix, Version}; use kaspa_consensus_core::{ config::params::Params, - constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASPA, TX_VERSION}, + constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASPA, TRANSIENT_BYTE_TO_MASS_FACTOR, TX_VERSION}, network::NetworkType, subnets::SUBNETWORK_ID_NATIVE, tx::{ScriptPublicKey, ScriptVec, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput}, @@ -248,6 +245,7 @@ mod tests { opcodes::codes::{OpReturn, OpTrue}, script_builder::ScriptBuilder, }; + use mass::transaction_estimated_serialized_size; use smallvec::smallvec; use std::sync::Arc; @@ -412,6 +410,8 @@ mod tests { fn new_mtx(tx: Transaction, mass: u64) -> MutableTransaction { let mut mtx = MutableTransaction::from_tx(tx); mtx.calculated_compute_mass = Some(mass); + mtx.calculated_transient_storage_mass = + Some(transaction_estimated_serialized_size(&mtx.tx) * TRANSIENT_BYTE_TO_MASS_FACTOR); mtx } diff --git a/mining/src/mempool/model/frontier/feerate_key.rs b/mining/src/mempool/model/frontier/feerate_key.rs index 843ef0ff1..aabcc4957 100644 --- a/mining/src/mempool/model/frontier/feerate_key.rs +++ b/mining/src/mempool/model/frontier/feerate_key.rs @@ -77,7 +77,15 @@ impl Ord for FeerateTransactionKey { impl From<&MempoolTransaction> for FeerateTransactionKey { fn from(tx: &MempoolTransaction) -> Self { - let mass = tx.mtx.tx.mass(); + // Should be calculated by pre_validate_and_populate_transaction + let calc_tx_mass = tx.mtx.calculated_compute_mass.expect("compute mass was expected to be calculated prior"); + let calc_transient_mass = + tx.mtx.calculated_transient_storage_mass.expect("transient storage mass was expected to be calculated prior"); + // Pre-Transient Storage HF, tx.mass() will contain both compute and storage masses. + // Post-Transient Storage HF, tx.mass() will only be the storage mass. Use the max with calc_tx_max to preserve + // original FeerateTransactionKey behavior when that happens + // In addition, on both pre and post HF, we'll now also consider the transient storage mass + let mass = tx.mtx.tx.mass().max(calc_tx_mass).max(calc_transient_mass); let fee = tx.mtx.calculated_fee.expect("fee is expected to be populated"); assert_ne!(mass, 0, "mass field is expected to be set when inserting to the mempool"); Self::new(fee, mass, tx.mtx.tx.clone()) diff --git a/mining/src/mempool/model/orphan_pool.rs b/mining/src/mempool/model/orphan_pool.rs index f813e1a56..093b79f70 100644 --- a/mining/src/mempool/model/orphan_pool.rs +++ b/mining/src/mempool/model/orphan_pool.rs @@ -99,11 +99,9 @@ impl OrphanPool { } fn check_orphan_mass(&self, transaction: &MutableTransaction) -> RuleResult<()> { - if transaction.calculated_compute_mass.unwrap() > self.config.maximum_orphan_transaction_mass { - return Err(RuleError::RejectBadOrphanMass( - transaction.calculated_compute_mass.unwrap(), - self.config.maximum_orphan_transaction_mass, - )); + let max_tx_mass = transaction.calculated_compute_mass.unwrap().max(transaction.calculated_transient_storage_mass.unwrap()); + if max_tx_mass > self.config.maximum_orphan_transaction_mass { + return Err(RuleError::RejectBadOrphanMass(max_tx_mass, self.config.maximum_orphan_transaction_mass)); } Ok(()) } diff --git a/mining/src/mempool/model/tx.rs b/mining/src/mempool/model/tx.rs index 27bb87d09..286fd7fa1 100644 --- a/mining/src/mempool/model/tx.rs +++ b/mining/src/mempool/model/tx.rs @@ -23,9 +23,7 @@ impl MempoolTransaction { } pub(crate) fn fee_rate(&self) -> f64 { - let contextual_mass = self.mtx.tx.mass(); - assert!(contextual_mass > 0, "expected to be called for validated txs only"); - self.mtx.calculated_fee.unwrap() as f64 / contextual_mass as f64 + self.mtx.calculated_feerate().expect("expected to be called for validated txs only") } } diff --git a/mining/src/mempool/validate_and_insert_transaction.rs b/mining/src/mempool/validate_and_insert_transaction.rs index 69e08019b..3660b5714 100644 --- a/mining/src/mempool/validate_and_insert_transaction.rs +++ b/mining/src/mempool/validate_and_insert_transaction.rs @@ -11,7 +11,8 @@ use crate::mempool::{ }; use kaspa_consensus_core::{ api::ConsensusApi, - constants::UNACCEPTED_DAA_SCORE, + constants::{TRANSIENT_BYTE_TO_MASS_FACTOR, UNACCEPTED_DAA_SCORE}, + mass::transaction_estimated_serialized_size, tx::{MutableTransaction, Transaction, TransactionId, TransactionOutpoint, UtxoEntry}, }; use kaspa_core::{debug, info}; @@ -26,6 +27,8 @@ impl Mempool { self.validate_transaction_unacceptance(&transaction)?; // Populate mass and estimated_size in the beginning, it will be used in multiple places throughout the validation and insertion. transaction.calculated_compute_mass = Some(consensus.calculate_transaction_compute_mass(&transaction.tx)); + transaction.calculated_transient_storage_mass = + Some(transaction_estimated_serialized_size(&transaction.tx) * TRANSIENT_BYTE_TO_MASS_FACTOR); self.validate_transaction_in_isolation(&transaction)?; let feerate_threshold = self.get_replace_by_fee_constraint(&transaction, rbf_policy)?; self.populate_mempool_entries(&mut transaction); diff --git a/mining/src/testutils/consensus_mock.rs b/mining/src/testutils/consensus_mock.rs index 28d3f5897..4f2c3c8fc 100644 --- a/mining/src/testutils/consensus_mock.rs +++ b/mining/src/testutils/consensus_mock.rs @@ -133,9 +133,7 @@ impl ConsensusApi for ConsensusMock { // At this point we know all UTXO entries are populated, so we can safely calculate the fee let total_in: u64 = mutable_tx.entries.iter().map(|x| x.as_ref().unwrap().amount).sum(); let total_out: u64 = mutable_tx.tx.outputs.iter().map(|x| x.value).sum(); - mutable_tx - .tx - .set_mass(self.calculate_transaction_storage_mass(mutable_tx).unwrap() + mutable_tx.calculated_compute_mass.unwrap()); + mutable_tx.tx.set_mass(self.calculate_transaction_storage_mass(mutable_tx).unwrap()); if mutable_tx.calculated_fee.is_none() { let calculated_fee = total_in - total_out; diff --git a/simpa/src/main.rs b/simpa/src/main.rs index c35c0c640..f78d0b746 100644 --- a/simpa/src/main.rs +++ b/simpa/src/main.rs @@ -192,6 +192,7 @@ fn main_impl(mut args: Args) { args.bps = if args.testnet11 { Testnet11Bps::bps() as f64 } else { args.bps }; let mut params = if args.testnet11 { TESTNET11_PARAMS } else { DEVNET_PARAMS }; params.storage_mass_activation = ForkActivation::new(400); + params.transient_storage_activation = ForkActivation::new(400); params.storage_mass_parameter = 10_000; params.payload_activation = ForkActivation::always(); let mut builder = ConfigBuilder::new(params) diff --git a/simpa/src/simulator/miner.rs b/simpa/src/simulator/miner.rs index 958b4799e..fc9107c0b 100644 --- a/simpa/src/simulator/miner.rs +++ b/simpa/src/simulator/miner.rs @@ -157,7 +157,14 @@ impl Miner { .into_par_iter() .map(|mutable_tx| { let signed_tx = sign(mutable_tx, schnorr_key); - let mass = self.mass_calculator.calc_tx_overall_mass(&signed_tx.as_verifiable(), None).unwrap(); + let mass = self + .mass_calculator + .calc_tx_overall_mass( + &signed_tx.as_verifiable(), + None, + self.params.transient_storage_activation.is_active(virtual_state.daa_score), + ) + .unwrap(); signed_tx.tx.set_mass(mass); let mut signed_tx = signed_tx.tx; signed_tx.finalize(); diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index 52d9b7986..25a5b3b0b 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -845,6 +845,7 @@ impl KaspadGoParams { max_block_level: self.MaxBlockLevel, pruning_proof_m: self.PruningProofM, payload_activation: ForkActivation::never(), + transient_storage_activation: ForkActivation::never(), } } }