cuprated/txpool/
relay_rules.rs
1use std::cmp::max;
2
3use monero_serai::transaction::Timelock;
4use thiserror::Error;
5
6use cuprate_consensus_context::BlockchainContext;
7use cuprate_consensus_rules::miner_tx::calculate_block_reward;
8use cuprate_helper::cast::usize_to_u64;
9use cuprate_types::TransactionVerificationData;
10
11const MAX_TX_EXTRA_SIZE: usize = 1060;
15
16const DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT: u128 = 3_000;
18
19const FEE_MASK: u64 = 10_u64.pow(4);
21
22#[derive(Debug, Error)]
23pub enum RelayRuleError {
24 #[error("Tx has non-zero timelock.")]
25 NonZeroTimelock,
26 #[error("Tx extra field is too large.")]
27 ExtraFieldTooLarge,
28 #[error("Tx fee too low.")]
29 FeeBelowMinimum,
30}
31
32pub fn check_tx_relay_rules(
36 tx: &TransactionVerificationData,
37 context: &BlockchainContext,
38) -> Result<(), RelayRuleError> {
39 if tx.tx.prefix().additional_timelock != Timelock::None {
40 return Err(RelayRuleError::NonZeroTimelock);
41 }
42
43 if tx.tx.prefix().extra.len() > MAX_TX_EXTRA_SIZE {
44 return Err(RelayRuleError::ExtraFieldTooLarge);
45 }
46
47 check_fee(tx.tx_weight, tx.fee, context)
48}
49
50fn check_fee(
52 tx_weight: usize,
53 fee: u64,
54 context: &BlockchainContext,
55) -> Result<(), RelayRuleError> {
56 let base_reward = calculate_block_reward(
57 1,
58 context.effective_median_weight,
59 context.already_generated_coins,
60 context.current_hf,
61 );
62
63 let fee_per_byte = dynamic_base_fee(base_reward, context.effective_median_weight);
64 let needed_fee = usize_to_u64(tx_weight) * fee_per_byte;
65
66 let needed_fee = needed_fee.div_ceil(FEE_MASK) * FEE_MASK;
67
68 if fee < (needed_fee - needed_fee / 50) {
69 tracing::debug!(fee, needed_fee, "Tx fee is below minimum.");
70 return Err(RelayRuleError::FeeBelowMinimum);
71 }
72
73 Ok(())
74}
75
76fn dynamic_base_fee(base_reward: u64, effective_media_block_weight: usize) -> u64 {
78 let median_block_weight = effective_media_block_weight as u128;
79
80 let fee_per_byte_100 = u128::from(base_reward) * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT
81 / median_block_weight
82 / median_block_weight;
83 let fee_per_byte = fee_per_byte_100 - fee_per_byte_100 / 20;
84
85 #[expect(clippy::cast_possible_truncation)]
86 max(fee_per_byte as u64, 1)
87}