Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temp storage handling #13

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
9 changes: 9 additions & 0 deletions consensus/core/src/config/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions consensus/core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
31 changes: 29 additions & 2 deletions consensus/core/src/mass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>) -> Option<u64> {
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<u64>,
is_transient_storage_activated: bool,
) -> Option<u64> {
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())))
}
})
}
}

Expand Down Expand Up @@ -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<Transaction> {
let script_pub_key = ScriptVec::from_slice(&[]);
let prev_tx_id = TransactionId::from_str("880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3").unwrap();
Expand Down
35 changes: 31 additions & 4 deletions consensus/core/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,20 @@ pub struct MutableTransaction<T: AsRef<Transaction> = std::sync::Arc<Transaction
pub calculated_fee: Option<u64>,
/// Populated compute mass (does not include the storage mass)
pub calculated_compute_mass: Option<u64>,
/// Populated transient storage mass
pub calculated_transient_storage_mass: Option<u64>,
}

impl<T: AsRef<Transaction>> MutableTransaction<T> {
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 {
Expand All @@ -414,7 +422,13 @@ impl<T: AsRef<Transaction>> MutableTransaction<T> {

pub fn with_entries(tx: T, entries: Vec<UtxoEntry>) -> 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
Expand All @@ -430,7 +444,10 @@ impl<T: AsRef<Transaction>> MutableTransaction<T> {
}

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<Item = TransactionOutpoint> + '_ {
Expand All @@ -455,14 +472,24 @@ impl<T: AsRef<Transaction>> MutableTransaction<T> {
/// 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<f64> {
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 {
None
}
}

/// 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
Expand Down
1 change: 1 addition & 0 deletions consensus/src/consensus/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>, block: &Block) -> BlockProcessResult<u64> {
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)?;
Expand Down Expand Up @@ -57,22 +89,47 @@ impl BlockBodyProcessor {
Ok(())
}

fn check_block_mass(self: &Arc<Self>, block: &Block, storage_mass_activated: bool) -> BlockProcessResult<u64> {
fn check_block_mass(
self: &Arc<Self>,
block: &Block,
storage_mass_activated: bool,
transient_storage_activated: bool,
) -> BlockProcessResult<u64> {
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 {
Expand Down
4 changes: 4 additions & 0 deletions consensus/src/pipeline/body_processor/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -131,6 +134,7 @@ impl BlockBodyProcessor {
notification_root,
counters,
storage_mass_activation: params.storage_mass_activation,
transient_storage_activation: params.transient_storage_activation,
}
}

Expand Down
2 changes: 2 additions & 0 deletions consensus/src/pipeline/virtual_processor/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}

Expand Down
6 changes: 5 additions & 1 deletion consensus/src/pipeline/virtual_processor/utxo_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions consensus/src/processes/transaction_validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -62,6 +65,7 @@ impl TransactionValidator {
storage_mass_activation,
kip10_activation,
payload_activation,
transient_storage_activation,
}
}

Expand All @@ -88,6 +92,7 @@ impl TransactionValidator {
storage_mass_activation: ForkActivation::never(),
kip10_activation: ForkActivation::never(),
payload_activation: ForkActivation::never(),
transient_storage_activation: ForkActivation::never(),
}
}
}
Loading
Loading