use std::sync::atomic::Ordering;
use crate::mempool::{
errors::{RuleError, RuleResult},
model::{
pool::Pool,
tx::{MempoolTransaction, TransactionPostValidation, TransactionPreValidation, TxRemovalReason},
},
tx::{Orphan, Priority, RbfPolicy},
Mempool,
};
use kaspa_consensus_core::{
api::ConsensusApi,
constants::UNACCEPTED_DAA_SCORE,
tx::{MutableTransaction, Transaction, TransactionId, TransactionOutpoint, UtxoEntry},
};
use kaspa_core::{debug, info};
impl Mempool {
pub(crate) fn pre_validate_and_populate_transaction(
&self,
consensus: &dyn ConsensusApi,
mut transaction: MutableTransaction,
rbf_policy: RbfPolicy,
) -> RuleResult<TransactionPreValidation> {
self.validate_transaction_unacceptance(&transaction)?;
transaction.calculated_compute_mass = Some(consensus.calculate_transaction_compute_mass(&transaction.tx));
self.validate_transaction_in_isolation(&transaction)?;
let feerate_threshold = self.get_replace_by_fee_constraint(&transaction, rbf_policy)?;
self.populate_mempool_entries(&mut transaction);
Ok(TransactionPreValidation { transaction, feerate_threshold })
}
pub(crate) fn post_validate_and_insert_transaction(
&mut self,
consensus: &dyn ConsensusApi,
validation_result: RuleResult<()>,
transaction: MutableTransaction,
priority: Priority,
orphan: Orphan,
rbf_policy: RbfPolicy,
) -> RuleResult<TransactionPostValidation> {
let transaction_id = transaction.id();
if self.transaction_pool.has(&transaction_id) {
debug!("Transaction {0} is not post validated since already in the mempool", transaction_id);
return Err(RuleError::RejectDuplicate(transaction_id));
}
self.validate_transaction_unacceptance(&transaction)?;
match validation_result {
Ok(_) => {}
Err(RuleError::RejectMissingOutpoint) => {
if orphan == Orphan::Forbidden {
return Err(RuleError::RejectDisallowedOrphan(transaction_id));
}
let _ = self.get_replace_by_fee_constraint(&transaction, rbf_policy)?;
self.orphan_pool.try_add_orphan(consensus.get_virtual_daa_score(), transaction, priority)?;
return Ok(TransactionPostValidation::default());
}
Err(err) => {
return Err(err);
}
}
self.validate_transaction_in_context(&transaction)?;
let removed_transaction = self.execute_replace_by_fee(&transaction, rbf_policy)?;
let transaction_size = transaction.mempool_estimated_bytes();
let txs_to_remove = self.transaction_pool.limit_transaction_count(&transaction, transaction_size)?;
if !txs_to_remove.is_empty() {
let transaction_pool_len_before = self.transaction_pool.len();
for x in txs_to_remove.iter() {
self.remove_transaction(x, true, TxRemovalReason::MakingRoom, format!(" for {}", transaction_id).as_str())?;
if self.transaction_pool.len() < self.config.maximum_transaction_count
&& self.transaction_pool.get_estimated_size() + transaction_size <= self.config.mempool_size_limit
{
break;
}
}
self.counters
.tx_evicted_counts
.fetch_add(transaction_pool_len_before.saturating_sub(self.transaction_pool.len()) as u64, Ordering::Relaxed);
}
assert!(
self.transaction_pool.len() < self.config.maximum_transaction_count
&& self.transaction_pool.get_estimated_size() + transaction_size <= self.config.mempool_size_limit,
"Transactions in mempool: {}, max: {}, mempool bytes size: {}, max: {}",
self.transaction_pool.len() + 1,
self.config.maximum_transaction_count,
self.transaction_pool.get_estimated_size() + transaction_size,
self.config.mempool_size_limit,
);
let accepted_transaction = self
.transaction_pool
.add_transaction(transaction, consensus.get_virtual_daa_score(), priority, transaction_size)?
.mtx
.tx
.clone();
Ok(TransactionPostValidation { removed: removed_transaction, accepted: Some(accepted_transaction) })
}
fn validate_transaction_unacceptance(&self, transaction: &MutableTransaction) -> RuleResult<()> {
let transaction_id = transaction.id();
match self.accepted_transactions.has(&transaction_id) {
true => Err(RuleError::RejectAlreadyAccepted(transaction_id)),
false => Ok(()),
}
}
fn validate_transaction_in_isolation(&self, transaction: &MutableTransaction) -> RuleResult<()> {
let transaction_id = transaction.id();
if self.transaction_pool.has(&transaction_id) {
return Err(RuleError::RejectDuplicate(transaction_id));
}
if !self.config.accept_non_standard {
self.check_transaction_standard_in_isolation(transaction)?;
}
Ok(())
}
fn validate_transaction_in_context(&self, transaction: &MutableTransaction) -> RuleResult<()> {
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)?;
}
Ok(())
}
pub(crate) fn get_unorphaned_transactions_after_accepted_transaction(
&mut self,
transaction: &Transaction,
) -> Vec<MempoolTransaction> {
let mut unorphaned_transactions = Vec::new();
let transaction_id = transaction.id();
let mut outpoint = TransactionOutpoint::new(transaction_id, 0);
for (i, output) in transaction.outputs.iter().enumerate() {
outpoint.index = i as u32;
let mut orphan_id = None;
if let Some(orphan) = self.orphan_pool.outpoint_orphan_mut(&outpoint) {
for (i, input) in orphan.mtx.tx.inputs.iter().enumerate() {
if input.previous_outpoint == outpoint {
if orphan.mtx.entries[i].is_none() {
let entry = UtxoEntry::new(output.value, output.script_public_key.clone(), UNACCEPTED_DAA_SCORE, false);
orphan.mtx.entries[i] = Some(entry);
if orphan.mtx.is_verifiable() {
orphan_id = Some(orphan.id());
}
}
break;
}
}
} else {
continue;
}
if let Some(orphan_id) = orphan_id {
match self.unorphan_transaction(&orphan_id) {
Ok(unorphaned_tx) => {
unorphaned_transactions.push(unorphaned_tx);
debug!("Transaction {0} unorphaned", transaction_id);
}
Err(RuleError::RejectAlreadyAccepted(transaction_id)) => {
debug!("Ignoring already accepted transaction {}", transaction_id);
}
Err(err) => {
info!("Failed to unorphan transaction {0} due to rule error: {1}", orphan_id, err.to_string());
}
}
}
}
unorphaned_transactions
}
fn unorphan_transaction(&mut self, transaction_id: &TransactionId) -> RuleResult<MempoolTransaction> {
let mut transactions = self.orphan_pool.remove_orphan(transaction_id, false, TxRemovalReason::Unorphaned, "")?;
assert_eq!(transactions.len(), 1, "the list returned by remove_orphan is expected to contain exactly one transaction");
let transaction = transactions.pop().unwrap();
let rbf_policy = Self::get_orphan_transaction_rbf_policy(transaction.priority);
self.validate_transaction_unacceptance(&transaction.mtx)?;
let _ = self.get_replace_by_fee_constraint(&transaction.mtx, rbf_policy)?;
Ok(transaction)
}
pub(crate) fn get_orphan_transaction_rbf_policy(priority: Priority) -> RbfPolicy {
match priority {
Priority::High => RbfPolicy::Forbidden,
Priority::Low => RbfPolicy::Allowed,
}
}
}