Skip to content

Commit

Permalink
Implement updated KIP9 mempool rules (excluding TN11) (#448)
Browse files Browse the repository at this point in the history
* apply the updated kip9 rules wherever possible (excluding TN11)

* temp

* bump version to 0.13.6
  • Loading branch information
michaelsutton authored Apr 8, 2024
1 parent 6fd7029 commit 292d6cc
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 129 deletions.
106 changes: 53 additions & 53 deletions Cargo.lock

Large diffs are not rendered by default.

106 changes: 53 additions & 53 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ members = [

[workspace.package]
rust-version = "1.77.0"
version = "0.13.5"
version = "0.13.6"
authors = ["Kaspa developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/kaspanet/rusty-kaspa"
Expand All @@ -75,58 +75,58 @@ include = [
]

[workspace.dependencies]
# kaspa-testing-integration = { version = "0.13.5", path = "testing/integration" }
kaspa-addresses = { version = "0.13.5", path = "crypto/addresses" }
kaspa-addressmanager = { version = "0.13.5", path = "components/addressmanager" }
kaspa-bip32 = { version = "0.13.5", path = "wallet/bip32" }
kaspa-cli = { version = "0.13.5", path = "cli" }
kaspa-connectionmanager = { version = "0.13.5", path = "components/connectionmanager" }
kaspa-consensus = { version = "0.13.5", path = "consensus" }
kaspa-consensus-core = { version = "0.13.5", path = "consensus/core" }
kaspa-consensus-notify = { version = "0.13.5", path = "consensus/notify" }
kaspa-consensus-wasm = { version = "0.13.5", path = "consensus/wasm" }
kaspa-consensusmanager = { version = "0.13.5", path = "components/consensusmanager" }
kaspa-core = { version = "0.13.5", path = "core" }
kaspa-daemon = { version = "0.13.5", path = "daemon" }
kaspa-database = { version = "0.13.5", path = "database" }
kaspa-grpc-client = { version = "0.13.5", path = "rpc/grpc/client" }
kaspa-grpc-core = { version = "0.13.5", path = "rpc/grpc/core" }
kaspa-grpc-server = { version = "0.13.5", path = "rpc/grpc/server" }
kaspa-hashes = { version = "0.13.5", path = "crypto/hashes" }
kaspa-index-core = { version = "0.13.5", path = "indexes/core" }
kaspa-index-processor = { version = "0.13.5", path = "indexes/processor" }
kaspa-math = { version = "0.13.5", path = "math" }
kaspa-merkle = { version = "0.13.5", path = "crypto/merkle" }
kaspa-metrics-core = { version = "0.13.5", path = "metrics/core" }
kaspa-mining = { version = "0.13.5", path = "mining" }
kaspa-mining-errors = { version = "0.13.5", path = "mining/errors" }
kaspa-muhash = { version = "0.13.5", path = "crypto/muhash" }
kaspa-notify = { version = "0.13.5", path = "notify" }
kaspa-os = { version = "0.13.5", path = "kaspa-os" }
kaspa-p2p-flows = { version = "0.13.5", path = "protocol/flows" }
kaspa-p2p-lib = { version = "0.13.5", path = "protocol/p2p" }
kaspa-perf-monitor = { version = "0.13.5", path = "metrics/perf_monitor" }
kaspa-pow = { version = "0.13.5", path = "consensus/pow" }
kaspa-rpc-core = { version = "0.13.5", path = "rpc/core" }
kaspa-rpc-macros = { version = "0.13.5", path = "rpc/macros" }
kaspa-rpc-service = { version = "0.13.5", path = "rpc/service" }
kaspa-txscript = { version = "0.13.5", path = "crypto/txscript" }
kaspa-txscript-errors = { version = "0.13.5", path = "crypto/txscript/errors" }
kaspa-utils = { version = "0.13.5", path = "utils" }
kaspa-utils-tower = { version = "0.13.5", path = "utils/tower" }
kaspa-utxoindex = { version = "0.13.5", path = "indexes/utxoindex" }
kaspa-wallet = { version = "0.13.5", path = "wallet/native" }
kaspa-wallet-cli-wasm = { version = "0.13.5", path = "wallet/wasm" }
kaspa-wallet-core = { version = "0.13.5", path = "wallet/core" }
kaspa-wallet-macros = { version = "0.13.5", path = "wallet/macros" }
kaspa-wasm = { version = "0.13.5", path = "wasm" }
kaspa-wrpc-client = { version = "0.13.5", path = "rpc/wrpc/client" }
kaspa-wrpc-core = { version = "0.13.5", path = "rpc/wrpc/core" }
kaspa-wrpc-proxy = { version = "0.13.5", path = "rpc/wrpc/proxy" }
kaspa-wrpc-server = { version = "0.13.5", path = "rpc/wrpc/server" }
kaspa-wrpc-wasm = { version = "0.13.5", path = "rpc/wrpc/wasm" }
kaspad = { version = "0.13.5", path = "kaspad" }
kaspa-alloc = { version = "0.13.5", path = "utils/alloc" }
# kaspa-testing-integration = { version = "0.13.6", path = "testing/integration" }
kaspa-addresses = { version = "0.13.6", path = "crypto/addresses" }
kaspa-addressmanager = { version = "0.13.6", path = "components/addressmanager" }
kaspa-bip32 = { version = "0.13.6", path = "wallet/bip32" }
kaspa-cli = { version = "0.13.6", path = "cli" }
kaspa-connectionmanager = { version = "0.13.6", path = "components/connectionmanager" }
kaspa-consensus = { version = "0.13.6", path = "consensus" }
kaspa-consensus-core = { version = "0.13.6", path = "consensus/core" }
kaspa-consensus-notify = { version = "0.13.6", path = "consensus/notify" }
kaspa-consensus-wasm = { version = "0.13.6", path = "consensus/wasm" }
kaspa-consensusmanager = { version = "0.13.6", path = "components/consensusmanager" }
kaspa-core = { version = "0.13.6", path = "core" }
kaspa-daemon = { version = "0.13.6", path = "daemon" }
kaspa-database = { version = "0.13.6", path = "database" }
kaspa-grpc-client = { version = "0.13.6", path = "rpc/grpc/client" }
kaspa-grpc-core = { version = "0.13.6", path = "rpc/grpc/core" }
kaspa-grpc-server = { version = "0.13.6", path = "rpc/grpc/server" }
kaspa-hashes = { version = "0.13.6", path = "crypto/hashes" }
kaspa-index-core = { version = "0.13.6", path = "indexes/core" }
kaspa-index-processor = { version = "0.13.6", path = "indexes/processor" }
kaspa-math = { version = "0.13.6", path = "math" }
kaspa-merkle = { version = "0.13.6", path = "crypto/merkle" }
kaspa-metrics-core = { version = "0.13.6", path = "metrics/core" }
kaspa-mining = { version = "0.13.6", path = "mining" }
kaspa-mining-errors = { version = "0.13.6", path = "mining/errors" }
kaspa-muhash = { version = "0.13.6", path = "crypto/muhash" }
kaspa-notify = { version = "0.13.6", path = "notify" }
kaspa-os = { version = "0.13.6", path = "kaspa-os" }
kaspa-p2p-flows = { version = "0.13.6", path = "protocol/flows" }
kaspa-p2p-lib = { version = "0.13.6", path = "protocol/p2p" }
kaspa-perf-monitor = { version = "0.13.6", path = "metrics/perf_monitor" }
kaspa-pow = { version = "0.13.6", path = "consensus/pow" }
kaspa-rpc-core = { version = "0.13.6", path = "rpc/core" }
kaspa-rpc-macros = { version = "0.13.6", path = "rpc/macros" }
kaspa-rpc-service = { version = "0.13.6", path = "rpc/service" }
kaspa-txscript = { version = "0.13.6", path = "crypto/txscript" }
kaspa-txscript-errors = { version = "0.13.6", path = "crypto/txscript/errors" }
kaspa-utils = { version = "0.13.6", path = "utils" }
kaspa-utils-tower = { version = "0.13.6", path = "utils/tower" }
kaspa-utxoindex = { version = "0.13.6", path = "indexes/utxoindex" }
kaspa-wallet = { version = "0.13.6", path = "wallet/native" }
kaspa-wallet-cli-wasm = { version = "0.13.6", path = "wallet/wasm" }
kaspa-wallet-core = { version = "0.13.6", path = "wallet/core" }
kaspa-wallet-macros = { version = "0.13.6", path = "wallet/macros" }
kaspa-wasm = { version = "0.13.6", path = "wasm" }
kaspa-wrpc-client = { version = "0.13.6", path = "rpc/wrpc/client" }
kaspa-wrpc-core = { version = "0.13.6", path = "rpc/wrpc/core" }
kaspa-wrpc-proxy = { version = "0.13.6", path = "rpc/wrpc/proxy" }
kaspa-wrpc-server = { version = "0.13.6", path = "rpc/wrpc/server" }
kaspa-wrpc-wasm = { version = "0.13.6", path = "rpc/wrpc/wasm" }
kaspad = { version = "0.13.6", path = "kaspad" }
kaspa-alloc = { version = "0.13.6", path = "utils/alloc" }


# external
Expand Down
2 changes: 1 addition & 1 deletion consensus/core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub const MAX_SCRIPT_PUBLIC_KEY_VERSION: u16 = 0;
/// SompiPerKaspa is the number of sompi in one kaspa (1 KAS).
pub const SOMPI_PER_KASPA: u64 = 100_000_000;

/// The parameter for scaling inverse KAS value to mass units (unpublished KIP-0009)
/// The parameter for scaling inverse KAS value to mass units (KIP-0009)
pub const STORAGE_MASS_PARAMETER: u64 = SOMPI_PER_KASPA * 10_000;

/// MaxSompi is the maximum transaction amount allowed in sompi.
Expand Down
2 changes: 1 addition & 1 deletion consensus/core/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ pub struct MutableTransaction<T: AsRef<Transaction> = std::sync::Arc<Transaction
pub entries: Vec<Option<UtxoEntry>>,
/// Populated fee
pub calculated_fee: Option<u64>,
/// Populated mass (does not include the storage mass)
/// Populated compute mass (does not include the storage mass)
pub calculated_compute_mass: Option<u64>,
}

Expand Down
5 changes: 3 additions & 2 deletions consensus/src/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,9 @@ impl ConsensusApi for Consensus {
self.services.mass_calculator.calc_tx_compute_mass(transaction)
}

fn calculate_transaction_storage_mass(&self, transaction: &MutableTransaction) -> Option<u64> {
self.services.mass_calculator.calc_tx_storage_mass(&transaction.as_verifiable())
fn calculate_transaction_storage_mass(&self, _transaction: &MutableTransaction) -> Option<u64> {
// self.services.mass_calculator.calc_tx_storage_mass(&transaction.as_verifiable())
unimplemented!("unsupported at the API level until KIP9 is finalized")
}

fn get_stats(&self) -> ConsensusStats {
Expand Down
16 changes: 11 additions & 5 deletions consensus/src/pipeline/virtual_processor/utxo_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ use crate::{
RuleError::{BadAcceptedIDMerkleRoot, BadCoinbaseTransaction, BadUTXOCommitment, InvalidTransactionsInUtxoContext},
},
model::stores::{block_transactions::BlockTransactionsStoreReader, daa::DaaStoreReader, ghostdag::GhostdagData},
processes::transaction_validator::{
errors::{TxResult, TxRuleError},
transaction_validator_populated::TxValidationFlags,
processes::{
mass::Kip9Version,
transaction_validator::{
errors::{TxResult, TxRuleError},
transaction_validator_populated::TxValidationFlags,
},
},
};
use kaspa_consensus_core::{
Expand Down Expand Up @@ -290,12 +293,15 @@ impl VirtualStateProcessor {
) -> TxResult<()> {
self.populate_mempool_transaction_in_utxo_context(mutable_tx, utxo_view)?;

// For non-activated nets (mainnet, TN10) we can update mempool rules to KIP9 beta asap. For
// TN11 we need to hard-fork consensus first (since the new beta rules are more relaxed)
let kip9_version = if self.storage_mass_activation_daa_score == u64::MAX { Kip9Version::Beta } else { Kip9Version::Alpha };

// Calc the full contextual mass including storage mass
let contextual_mass = self
.transaction_validator
.mass_calculator
.calc_tx_storage_mass(&mutable_tx.as_verifiable())
.and_then(|m| m.checked_add(mutable_tx.calculated_compute_mass.unwrap()))
.calc_tx_overall_mass(&mutable_tx.as_verifiable(), mutable_tx.calculated_compute_mass, kip9_version)
.ok_or(TxRuleError::MassIncomputable)?;

// Set the inner mass field
Expand Down
68 changes: 60 additions & 8 deletions consensus/src/processes/mass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ use kaspa_consensus_core::{
tx::{Transaction, VerifiableTransaction},
};

/// Temp enum for the transition phases of KIP9
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Kip9Version {
/// Initial KIP9 mass calculation, w/o the relaxed formula and summing storage mass and compute mass
Alpha,

/// Currently proposed KIP9 mass calculation, with the relaxed formula (for the cases `|O| = 1 OR |O| <= |I| <= 2`),
/// and using a maximum operator over storage and compute mass
Beta,
}

// TODO (aspect) - review and potentially merge this with the new MassCalculator currently located in the wallet core
// (i.e. migrate mass calculator from wallet core here or to consensus core)
#[derive(Clone)]
Expand Down Expand Up @@ -46,7 +57,7 @@ impl MassCalculator {
/// 2. At least one input (unless coinbase)
///
/// Otherwise this function should never fail.
pub fn calc_tx_storage_mass(&self, tx: &impl VerifiableTransaction) -> Option<u64> {
pub fn calc_tx_storage_mass(&self, tx: &impl VerifiableTransaction, version: Kip9Version) -> Option<u64> {
if tx.is_coinbase() {
return Some(0);
}
Expand All @@ -58,24 +69,41 @@ impl MassCalculator {
input values, H(S) := |S|/sum_{s in S} 1 / s is the harmonic mean over the set S and
A(S) := sum_{s in S} / |S| is the arithmetic mean.
See the (to date unpublished) KIP-0009 for more details
See KIP-0009 for more details
*/

// Since we are doing integer division, we perform the multiplication with C over the inner
// fractions, otherwise we'll get a sum of zeros or ones.
//
// If sum of fractions overflowed (nearly impossible, requires 10^7 outputs for C = 10^12),
// we return `None` indicating mass is incomputable
//
// Note: in theory this can be tighten by subtracting input mass in the process (possibly avoiding the overflow),
// however the overflow case is so unpractical with current mass limits so we avoid the hassle
let harmonic_outs = tx
.tx()
.outputs
.iter()
.map(|out| self.storage_mass_parameter / out.value)
.try_fold(0u64, |total, current| total.checked_add(current))?; // C·|O|/H(O)

let outs_len = tx.tx().outputs.len() as u64;
let ins_len = tx.tx().inputs.len() as u64;

/*
KIP-0009 relaxed formula for the cases |O| = 1 OR |O| <= |I| <= 2:
max( 0 , C·( |O|/H(O) - |I|/H(I) ) )
*/
if version == Kip9Version::Beta && (outs_len == 1 || (outs_len <= ins_len && ins_len <= 2)) {
let harmonic_ins = tx
.populated_inputs()
.map(|(_, entry)| self.storage_mass_parameter / entry.amount)
.fold(0u64, |total, current| total.saturating_add(current)); // C·|I|/H(I)
return Some(harmonic_outs.saturating_sub(harmonic_ins)); // max( 0 , C·( |O|/H(O) - |I|/H(I) ) );
}

// Total supply is bounded, so a sum of existing UTXO entries cannot overflow (nor can it be zero)
let sum_ins = tx.populated_inputs().map(|(_, entry)| entry.amount).sum::<u64>(); // |I|·A(I)
let ins_len = tx.tx().inputs.len() as u64;
let mean_ins = sum_ins / ins_len;

// Inner fraction must be with C and over the mean value, in order to maximize precision.
Expand All @@ -84,6 +112,24 @@ impl MassCalculator {

Some(harmonic_outs.saturating_sub(arithmetic_ins)) // max( 0 , C·( |O|/H(O) - |I|/A(I) ) )
}

/// Calculates the overall mass of this transaction, combining both compute and storage masses.
/// The combination strategy depends on the version passed.
pub fn calc_tx_overall_mass(
&self,
tx: &impl VerifiableTransaction,
cached_compute_mass: Option<u64>,
version: Kip9Version,
) -> Option<u64> {
match version {
Kip9Version::Alpha => self
.calc_tx_storage_mass(tx, version)
.and_then(|mass| mass.checked_add(cached_compute_mass.unwrap_or_else(|| self.calc_tx_compute_mass(tx.tx())))),
Kip9Version::Beta => self
.calc_tx_storage_mass(tx, version)
.map(|mass| mass.max(cached_compute_mass.unwrap_or_else(|| self.calc_tx_compute_mass(tx.tx())))),
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -155,17 +201,20 @@ mod tests {
},
];
let mut tx = MutableTransaction::with_entries(tx, entries);
let test_version = Kip9Version::Alpha;

// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/A(I) ) )

let storage_mass = MassCalculator::new(0, 0, 0, 10u64.pow(12)).calc_tx_storage_mass(&tx.as_verifiable()).unwrap();
let storage_mass =
MassCalculator::new(0, 0, 0, 10u64.pow(12)).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
assert_eq!(storage_mass, 0); // Compounds from 3 to 2, with symmetric outputs and no fee, should be zero

// Create asymmetry
tx.tx.outputs[0].value = 50;
tx.tx.outputs[1].value = 550;
let storage_mass_parameter = 10u64.pow(12);
let storage_mass = MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable()).unwrap();
let storage_mass =
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
assert_eq!(storage_mass, storage_mass_parameter / 50 + storage_mass_parameter / 550 - 3 * (storage_mass_parameter / 200));

// Create a tx with more outs than ins
Expand Down Expand Up @@ -238,20 +287,23 @@ mod tests {
let mut tx = MutableTransaction::with_entries(tx, entries);

let storage_mass_parameter = STORAGE_MASS_PARAMETER;
let storage_mass = MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable()).unwrap();
let storage_mass =
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
assert_eq!(storage_mass, 4); // Inputs are above C so they don't contribute negative mass, 4 outputs exactly equal C each charge 1

let mut tx2 = tx.clone();
tx2.tx.outputs[0].value = 10 * SOMPI_PER_KASPA;
let storage_mass = MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx2.as_verifiable()).unwrap();
let storage_mass =
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx2.as_verifiable(), test_version).unwrap();
assert_eq!(storage_mass, 1003);

// Increase values over the lim
for out in tx.tx.outputs.iter_mut() {
out.value += 1
}
tx.entries[0].as_mut().unwrap().amount += tx.tx.outputs.len() as u64;
let storage_mass = MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable()).unwrap();
let storage_mass =
MassCalculator::new(0, 0, 0, storage_mass_parameter).calc_tx_storage_mass(&tx.as_verifiable(), test_version).unwrap();
assert_eq!(storage_mass, 0);

drop(script_pub_key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ impl TransactionValidator {
fn check_mass_commitment(&self, tx: &impl VerifiableTransaction) -> TxResult<()> {
let calculated_contextual_mass = self
.mass_calculator
.calc_tx_storage_mass(tx)
.and_then(|m| m.checked_add(self.mass_calculator.calc_tx_compute_mass(tx.tx())))
.calc_tx_overall_mass(tx, None, crate::processes::mass::Kip9Version::Alpha)
.ok_or(TxRuleError::MassIncomputable)?;
let committed_contextual_mass = tx.tx().mass();
if committed_contextual_mass != calculated_contextual_mass {
Expand Down
11 changes: 11 additions & 0 deletions mining/src/mempool/validate_and_insert_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ impl Mempool {
}

fn validate_transaction_in_context(&self, transaction: &MutableTransaction) -> RuleResult<()> {
// TEMP: apply parts of go-kaspad mempool dust prevention patch
let has_coinbase_input = transaction.entries.iter().any(|e| e.as_ref().unwrap().is_coinbase);
let num_extra_outs = transaction.tx.outputs.len() as i64 - transaction.tx.inputs.len() as i64;
if !has_coinbase_input
&& num_extra_outs > 2
&& transaction.calculated_fee.unwrap() < num_extra_outs as u64 * kaspa_consensus_core::constants::SOMPI_PER_KASPA
{
kaspa_core::trace!("Rejected spam tx {} from mempool ({} outputs)", transaction.id(), transaction.tx.outputs.len());
return Err(RuleError::RejectSpamTransaction(transaction.id()));
}

if !self.config.accept_non_standard {
self.check_transaction_standard_in_context(transaction)?;
}
Expand Down
5 changes: 3 additions & 2 deletions protocol/flows/src/v5/txrelay/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,11 @@ impl RelayTransactionsFlow {
// TODO: discuss a banning process
return Err(ProtocolError::MisbehavingPeer(format!("rejected invalid transaction {}", transaction_id)));
}
Err(MiningManagerError::MempoolError(RuleError::RejectSpamTransaction(_))) => {
Err(MiningManagerError::MempoolError(RuleError::RejectSpamTransaction(_)))
| Err(MiningManagerError::MempoolError(RuleError::RejectNonStandard(..))) => {
self.spam_counter += 1;
if self.spam_counter % 100 == 0 {
kaspa_core::warn!("Peer {} has shared {} spam txs", self.router, self.spam_counter);
kaspa_core::warn!("Peer {} has shared {} spam/non-standard txs ({:?})", self.router, self.spam_counter, res);
}
}
Err(_) => {}
Expand Down
Loading

0 comments on commit 292d6cc

Please sign in to comment.