use crate::mempool::{
Mempool,
errors::{RuleError, RuleResult},
};
use kaspa_consensus_core::{mass::NonContextualMasses, tx::MutableTransaction};
impl Mempool {
pub(crate) fn validate_transaction_limits_in_isolation(&self, transaction: &MutableTransaction) -> RuleResult<()> {
if transaction.tx.gas > self.config.block_lane_limits.gas_per_lane {
return Err(RuleError::RejectGas(transaction.id(), transaction.tx.gas, self.config.block_lane_limits.gas_per_lane));
}
let limits = self.config.mempool_block_mass_limits;
let NonContextualMasses { compute_mass, transient_mass } = transaction.calculated_non_contextual_masses.unwrap();
if compute_mass > limits.compute {
return Err(RuleError::RejectComputeMass(transaction.id(), compute_mass, limits.compute));
}
if transient_mass > limits.transient {
return Err(RuleError::RejectTransientMass(transaction.id(), transient_mass, limits.transient));
}
Ok(())
}
pub(crate) fn validate_transaction_limits_in_context(&self, transaction: &MutableTransaction) -> RuleResult<()> {
let limits = self.config.mempool_block_mass_limits;
let storage_mass = transaction.tx.storage_mass();
if storage_mass > limits.storage {
return Err(RuleError::RejectStorageMass(transaction.id(), storage_mass, limits.storage));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{MiningCounters, mempool::config::Config};
use kaspa_consensus_core::{
config::constants::consensus::DEFAULT_LANES_PER_BLOCK_LIMIT,
constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASPA, TX_VERSION},
mass::{BlockLaneLimits, BlockMassLimits},
subnets::SUBNETWORK_ID_NATIVE,
tx::{ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, scriptvec},
};
use kaspa_hashes::Hash;
use std::sync::Arc;
const LIMITS: BlockMassLimits = BlockMassLimits { compute: 100, storage: 200, transient: 300 };
const GAS_PER_LANE: u64 = 7;
enum Expected {
Ok,
RejectGas(u64, u64),
RejectComputeMass(u64, u64),
RejectTransientMass(u64, u64),
RejectStorageMass(u64, u64),
}
struct Test {
name: &'static str,
gas: u64,
compute_mass: u64,
transient_mass: u64,
storage_mass: u64,
expected: Expected,
}
fn mempool() -> Mempool {
let config = Config::build_default(
100,
true,
LIMITS,
BlockLaneLimits { lanes_per_block: DEFAULT_LANES_PER_BLOCK_LIMIT, gas_per_lane: GAS_PER_LANE },
);
Mempool::new(Arc::new(config), Arc::new(MiningCounters::default()))
}
fn transaction(gas: u64, compute_mass: u64, transient_mass: u64, storage_mass: u64) -> MutableTransaction {
let script_public_key = ScriptPublicKey::new(0, scriptvec![0x51]);
let outpoint = TransactionOutpoint::new(Hash::from_u64_word(1), 0);
let input = TransactionInput::new(outpoint, vec![], MAX_TX_IN_SEQUENCE_NUM, 0);
let output = TransactionOutput::new(SOMPI_PER_KASPA, script_public_key.clone());
let tx = Transaction::new(TX_VERSION, vec![input], vec![output], 0, SUBNETWORK_ID_NATIVE, gas, vec![]);
tx.set_storage_mass(storage_mass);
let entry = UtxoEntry::new(SOMPI_PER_KASPA, script_public_key, 0, false, None);
let mut tx = MutableTransaction::with_entries(tx.into(), vec![entry]);
tx.calculated_non_contextual_masses = Some(NonContextualMasses::new(compute_mass, transient_mass));
tx
}
fn assert_expected(name: &str, result: RuleResult<()>, tx: &MutableTransaction, expected: Expected) {
let expected = match expected {
Expected::Ok => Ok(()),
Expected::RejectGas(gas, limit) => Err(RuleError::RejectGas(tx.id(), gas, limit)),
Expected::RejectComputeMass(mass, limit) => Err(RuleError::RejectComputeMass(tx.id(), mass, limit)),
Expected::RejectTransientMass(mass, limit) => Err(RuleError::RejectTransientMass(tx.id(), mass, limit)),
Expected::RejectStorageMass(mass, limit) => Err(RuleError::RejectStorageMass(tx.id(), mass, limit)),
};
assert_eq!(result, expected, "failed for test '{name}'");
}
#[test]
fn test_validate_transaction_limits_in_isolation() {
let tests = [
Test {
name: "non-contextual values at limits",
gas: GAS_PER_LANE,
compute_mass: LIMITS.compute,
transient_mass: LIMITS.transient,
storage_mass: 0,
expected: Expected::Ok,
},
Test {
name: "transaction gas exceeds the per-lane limit",
gas: GAS_PER_LANE + 1,
compute_mass: 1,
transient_mass: 1,
storage_mass: 0,
expected: Expected::RejectGas(GAS_PER_LANE + 1, GAS_PER_LANE),
},
Test {
name: "transaction compute mass exceeds the block limit",
gas: 0,
compute_mass: LIMITS.compute + 1,
transient_mass: 1,
storage_mass: 0,
expected: Expected::RejectComputeMass(LIMITS.compute + 1, LIMITS.compute),
},
Test {
name: "transaction transient byte size exceeds the block limit",
gas: 0,
compute_mass: 1,
transient_mass: LIMITS.transient + 1,
storage_mass: 0,
expected: Expected::RejectTransientMass(LIMITS.transient + 1, LIMITS.transient),
},
];
let mempool = mempool();
for test in tests {
let tx = transaction(test.gas, test.compute_mass, test.transient_mass, test.storage_mass);
let result = mempool.validate_transaction_limits_in_isolation(&tx);
assert_expected(test.name, result, &tx, test.expected);
}
}
#[test]
fn test_validate_transaction_limits_in_context() {
let tests = [
Test {
name: "contextual storage value at limit",
gas: 0,
compute_mass: 1,
transient_mass: 1,
storage_mass: LIMITS.storage,
expected: Expected::Ok,
},
Test {
name: "transaction storage mass exceeds the block limit",
gas: 0,
compute_mass: 1,
transient_mass: 1,
storage_mass: LIMITS.storage + 1,
expected: Expected::RejectStorageMass(LIMITS.storage + 1, LIMITS.storage),
},
];
let mempool = mempool();
for test in tests {
let tx = transaction(test.gas, test.compute_mass, test.transient_mass, test.storage_mass);
let result = mempool.validate_transaction_limits_in_context(&tx);
assert_expected(test.name, result, &tx, test.expected);
}
}
}