use crate::error::Reject;
use crate::pool::TxPool;
use ckb_chain_spec::consensus::Consensus;
use ckb_dao::DaoCalculator;
use ckb_script::ChunkCommand;
use ckb_snapshot::Snapshot;
use ckb_store::ChainStore;
use ckb_store::data_loader_wrapper::AsDataLoader;
use ckb_types::core::{
Capacity, Cycle, TransactionView, cell::ResolvedTransaction, tx_pool::TRANSACTION_SIZE_LIMIT,
};
use ckb_verification::{
ContextualTransactionVerifier, DaoScriptSizeVerifier, NonContextualTransactionVerifier,
TimeRelativeTransactionVerifier, TxVerifyEnv,
cache::{CacheEntry, Completed},
};
use std::sync::Arc;
use tokio::{sync::watch, task::block_in_place};
pub(crate) fn check_txid_collision(tx_pool: &TxPool, tx: &TransactionView) -> Result<(), Reject> {
let short_id = tx.proposal_short_id();
if tx_pool.contains_proposal_id(&short_id) {
return Err(Reject::Duplicated(tx.hash()));
}
Ok(())
}
pub(crate) fn check_tx_fee(
tx_pool: &TxPool,
snapshot: &Snapshot,
rtx: &ResolvedTransaction,
tx_size: usize,
) -> Result<Capacity, Reject> {
let fee = DaoCalculator::new(snapshot.consensus(), &snapshot.borrow_as_data_loader())
.transaction_fee(rtx)
.map_err(|err| {
Reject::Malformed(
format!("{err}"),
"expect (outputs capacity) <= (inputs capacity)".to_owned(),
)
})?;
let min_fee = tx_pool.config.min_fee_rate.fee(tx_size as u64);
if fee < min_fee {
let reject =
Reject::LowFeeRate(tx_pool.config.min_fee_rate, min_fee.as_u64(), fee.as_u64());
ckb_logger::debug!("Reject tx {}", reject);
return Err(reject);
}
Ok(fee)
}
pub(crate) fn non_contextual_verify(
consensus: &Consensus,
tx: &TransactionView,
) -> Result<(), Reject> {
NonContextualTransactionVerifier::new(tx, consensus)
.verify()
.map_err(Reject::Verification)?;
let tx_size = tx.data().serialized_size_in_block() as u64;
if tx_size > TRANSACTION_SIZE_LIMIT {
return Err(Reject::ExceededTransactionSizeLimit(
tx_size,
TRANSACTION_SIZE_LIMIT,
));
}
if tx.is_cellbase() {
return Err(Reject::Malformed(
"cellbase like".to_owned(),
Default::default(),
));
}
Ok(())
}
pub(crate) async fn verify_rtx(
snapshot: Arc<Snapshot>,
rtx: Arc<ResolvedTransaction>,
tx_env: Arc<TxVerifyEnv>,
cache_entry: &Option<CacheEntry>,
max_tx_verify_cycles: Cycle,
command_rx: Option<&mut watch::Receiver<ChunkCommand>>,
) -> Result<Completed, Reject> {
let consensus = snapshot.cloned_consensus();
let data_loader = snapshot.as_data_loader();
if let Some(completed) = cache_entry {
TimeRelativeTransactionVerifier::new(rtx, consensus, data_loader, tx_env)
.verify()
.map(|_| *completed)
.map_err(Reject::Verification)
} else if let Some(command_rx) = command_rx {
ContextualTransactionVerifier::new(
Arc::clone(&rtx),
consensus,
data_loader,
Arc::clone(&tx_env),
)
.verify_with_pause(max_tx_verify_cycles, command_rx)
.await
.and_then(|result| {
DaoScriptSizeVerifier::new(rtx, snapshot.cloned_consensus(), snapshot.as_data_loader())
.verify()?;
Ok(result)
})
.map_err(Reject::Verification)
} else {
block_in_place(|| {
ContextualTransactionVerifier::new(Arc::clone(&rtx), consensus, data_loader, tx_env)
.verify(max_tx_verify_cycles, false)
.and_then(|result| {
DaoScriptSizeVerifier::new(
rtx,
snapshot.cloned_consensus(),
snapshot.as_data_loader(),
)
.verify()?;
Ok(result)
})
.map_err(Reject::Verification)
})
}
}
pub(crate) fn time_relative_verify(
snapshot: Arc<Snapshot>,
rtx: Arc<ResolvedTransaction>,
tx_env: TxVerifyEnv,
) -> Result<(), Reject> {
let consensus = snapshot.cloned_consensus();
TimeRelativeTransactionVerifier::new(
rtx,
consensus,
snapshot.as_data_loader(),
Arc::new(tx_env),
)
.verify()
.map_err(Reject::Verification)
}
pub(crate) fn is_missing_input(reject: &Reject) -> bool {
matches!(reject, Reject::Resolve(out_point_err) if out_point_err.is_unknown())
}
#[macro_export]
macro_rules! try_or_return_with_snapshot {
($expr:expr, $snapshot:expr) => {
match $expr {
core::result::Result::Ok(val) => val,
core::result::Result::Err(err) => {
return Some((
core::result::Result::Err(core::convert::From::from(err)),
$snapshot,
));
}
}
};
}