Skip to content

Commit

Permalink
Merge of #6556
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] committed May 1, 2023
2 parents 7c9a1d6 + 00b03ad commit 92b977d
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 71 deletions.
4 changes: 3 additions & 1 deletion zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ pub use serialize::{
MIN_TRANSPARENT_TX_V5_SIZE,
};
pub use sighash::{HashType, SigHash};
pub use unmined::{UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD};
pub use unmined::{
zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
};

use crate::{
amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
Expand Down
10 changes: 6 additions & 4 deletions zebra-chain/src/transaction/unmined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use proptest_derive::Arbitrary;
#[allow(unused_imports)]
use crate::block::MAX_BLOCK_BYTES;

mod zip317;
pub mod zip317;

/// The minimum cost value for a transaction in the mempool.
///
Expand Down Expand Up @@ -353,17 +353,19 @@ impl VerifiedUnminedTx {
transaction: UnminedTx,
miner_fee: Amount<NonNegative>,
legacy_sigop_count: u64,
) -> Self {
) -> Result<Self, zip317::Error> {
let fee_weight_ratio = zip317::conventional_fee_weight_ratio(&transaction, miner_fee);
let unpaid_actions = zip317::unpaid_actions(&transaction, miner_fee);

Self {
zip317::mempool_checks(unpaid_actions, miner_fee, transaction.size)?;

Ok(Self {
transaction,
miner_fee,
legacy_sigop_count,
fee_weight_ratio,
unpaid_actions,
}
})
}

/// Returns `true` if the transaction pays at least the [ZIP-317] conventional fee.
Expand Down
94 changes: 94 additions & 0 deletions zebra-chain/src/transaction/unmined/zip317.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::cmp::max;

use num_integer::div_ceil;
use thiserror::Error;

use crate::{
amount::{Amount, NonNegative},
Expand All @@ -13,6 +14,9 @@ use crate::{
transaction::{Transaction, UnminedTx},
};

#[cfg(test)]
mod tests;

/// The marginal fee for the ZIP-317 fee calculation, in zatoshis per logical action.
//
// TODO: allow Amount<NonNegative> in constants
Expand All @@ -37,6 +41,27 @@ const BLOCK_PRODUCTION_WEIGHT_RATIO_CAP: f32 = 4.0;
/// This avoids special handling for transactions with zero weight.
const MIN_BLOCK_PRODUCTION_SUBSTITUTE_FEE: i64 = 1;

/// The ZIP-317 recommended limit on the number of unpaid actions per block.
/// `block_unpaid_action_limit` in ZIP-317.
pub const BLOCK_PRODUCTION_UNPAID_ACTION_LIMIT: u32 = 50;

/// The minimum fee per kilobyte for Zebra mempool transactions.
/// Also used as the minimum fee for a mempool transaction.
///
/// Based on `DEFAULT_MIN_RELAY_TX_FEE` in `zcashd`:
/// <https://github.com/zcash/zcash/blob/f512291ff20098291442e83713de89bcddc07546/src/main.h#L71-L72>
///
/// This is a `usize` to simplify transaction size-based calculation code.
pub const MIN_MEMPOOL_TX_FEE_RATE: usize = 100;

/// The fee cap for [`MIN_MEMPOOL_TX_FEE_RATE`] minimum required mempool fees.
///
/// Based on `LEGACY_DEFAULT_FEE` in `zcashd`:
/// <https://github.com/zcash/zcash/blob/9e856cfc5b81aa2607a16a23ff5584ea10014de6/src/amount.h#L35-L36>
///
/// This is a `usize` to simplify transaction size-based calculation code.
pub const MEMPOOL_TX_FEE_REQUIREMENT_CAP: usize = 1000;

/// Returns the conventional fee for `transaction`, as defined by [ZIP-317].
///
/// [ZIP-317]: https://zips.z.cash/zip-0317#fee-calculation
Expand Down Expand Up @@ -139,3 +164,72 @@ fn conventional_actions(transaction: &Transaction) -> u32 {

max(GRACE_ACTIONS, logical_actions)
}

/// Make ZIP-317 checks before inserting a transaction into the mempool.
pub fn mempool_checks(
unpaid_actions: u32,
miner_fee: Amount<NonNegative>,
transaction_size: usize,
) -> Result<(), Error> {
// # Standard Rule
//
// > If a transaction has more than `block_unpaid_action_limit` "unpaid actions" as defined by the
// > Recommended algorithm for block template construction, it will never be mined by that algorithm.
// > Nodes MAY drop these transactions.
//
// <https://zips.z.cash/zip-0317#transaction-relaying>
if unpaid_actions > BLOCK_PRODUCTION_UNPAID_ACTION_LIMIT {
return Err(Error::UnpaidActions);
}

// # Standard Rule
//
// > Nodes that normally relay transactions are expected to do so for transactions that pay at least the
// > conventional fee as specified in this ZIP.
//
// <https://zips.z.cash/zip-0317#transaction-relaying>
//
// In Zebra, we use a similar minimum fee rate to `zcashd` v5.5.0 and later.
// Transactions must pay a fee of at least 100 zatoshis per 1000 bytes of serialized size,
// with a maximum fee of 1000 zatoshis.
//
// <https://github.com/zcash/zcash/blob/9e856cfc5b81aa2607a16a23ff5584ea10014de6/src/amount.cpp#L24-L37>
//
// In zcashd this is `DEFAULT_MIN_RELAY_TX_FEE` and `LEGACY_DEFAULT_FEE`:
// <https://github.com/zcash/zcash/blob/f512291ff20098291442e83713de89bcddc07546/src/main.h#L71-L72>
// <https://github.com/zcash/zcash/blob/9e856cfc5b81aa2607a16a23ff5584ea10014de6/src/amount.h#L35-L36>

const KILOBYTE: usize = 1000;

// This calculation can't overflow, because transactions are limited to 2 MB,
// and usize is at least 4 GB.
assert!(
MIN_MEMPOOL_TX_FEE_RATE
< usize::MAX / usize::try_from(MAX_BLOCK_BYTES).expect("constant fits in usize"),
"the fee rate multiplication must never overflow",
);

let min_fee = (MIN_MEMPOOL_TX_FEE_RATE * transaction_size / KILOBYTE)
.clamp(MIN_MEMPOOL_TX_FEE_RATE, MEMPOOL_TX_FEE_REQUIREMENT_CAP);
let min_fee: u64 = min_fee
.try_into()
.expect("clamped value always fits in u64");
let min_fee: Amount<NonNegative> = min_fee.try_into().expect("clamped value is positive");

if miner_fee < min_fee {
return Err(Error::FeeBelowMinimumRate);
}

Ok(())
}

/// Errors related to ZIP-317.
#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Error {
#[error("Unpaid actions is higher than the limit")]
UnpaidActions,

#[error("Transaction fee is below the minimum fee rate")]
FeeBelowMinimumRate,
}
18 changes: 18 additions & 0 deletions zebra-chain/src/transaction/unmined/zip317/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! ZIP-317 tests.

use super::{mempool_checks, Amount, Error};
#[test]
fn zip317_unpaid_actions_err() {
let check = mempool_checks(51, Amount::try_from(1).unwrap(), 1);

assert!(check.is_err());
assert_eq!(check.err(), Some(Error::UnpaidActions));
}

#[test]
fn zip317_minimum_rate_fee_err() {
let check = mempool_checks(50, Amount::try_from(1).unwrap(), 1000);

assert!(check.is_err());
assert_eq!(check.err(), Some(Error::FeeBelowMinimumRate));
}
4 changes: 4 additions & 0 deletions zebra-consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ pub enum TransactionError {
outpoint: transparent::OutPoint,
min_spend_height: block::Height,
},

#[error("failed to verify ZIP-317 transaction rules, transaction was not inserted to mempool")]
#[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
Zip317(#[from] zebra_chain::transaction::zip317::Error),
}

impl From<ValidateContextError> for TransactionError {
Expand Down
7 changes: 4 additions & 3 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,14 +466,15 @@ where
miner_fee,
legacy_sigop_count,
},
Request::Mempool { transaction, .. } => Response::Mempool {
transaction: VerifiedUnminedTx::new(
Request::Mempool { transaction, .. } => {
let transaction = VerifiedUnminedTx::new(
transaction,
miner_fee.expect(
"unexpected mempool coinbase transaction: should have already rejected",
),
legacy_sigop_count,
),
)?;
Response::Mempool { transaction }
},
};

Expand Down
Loading

0 comments on commit 92b977d

Please sign in to comment.