Skip to content

Commit fe8639e

Browse files
upbqdnconradoplg
andauthored
fix(consensus): check ZIP-317 before expensive verifications (#11053)
Co-authored-by: Conrado Gouvea <conradoplg@gmail.com>
1 parent 9502e78 commit fe8639e

2 files changed

Lines changed: 23 additions & 36 deletions

File tree

zebra-consensus/src/transaction.rs

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,12 @@ where
484484

485485
tracing::trace!(?tx_id, "finished async checks");
486486

487-
let (miner_fee, sigops) = Self::compute_fee_and_sigops(tx.as_ref(), &spent_utxos)?;
487+
let miner_fee = if tx.is_coinbase() {
488+
None
489+
} else {
490+
Some(Self::miner_fee(tx.as_ref(), &spent_utxos)?)
491+
};
492+
let sigops = tx.sigops().map_err(zebra_script::Error::from)?;
488493

489494
Ok(Response::Block {
490495
tx_id,
@@ -577,6 +582,11 @@ where
577582
// the script interpreter.
578583
check::mempool_standard_input_scripts(tx.as_ref(), &spent_outputs)?;
579584

585+
// Apply ZIP-317 policy before expensive cryptographic verification.
586+
let miner_fee = Self::miner_fee(tx.as_ref(), &spent_utxos)?;
587+
let unpaid_actions = transaction::zip317::unpaid_actions(&unmined_tx, miner_fee);
588+
transaction::zip317::mempool_checks(unpaid_actions, miner_fee, unmined_tx.size)?;
589+
580590
let cached_ffi_transaction =
581591
Arc::new(CachedFfiTransaction::new(tx.clone(), Arc::new(spent_outputs), nu).map_err(|_| TransactionError::UnsupportedByNetworkUpgrade(tx.version(), nu))?);
582592

@@ -611,7 +621,7 @@ where
611621

612622
tracing::trace!(?tx_id, "finished async checks");
613623

614-
let (miner_fee, sigops) = Self::compute_fee_and_sigops(tx.as_ref(), &spent_utxos)?;
624+
let sigops = tx.sigops().map_err(zebra_script::Error::from)?;
615625

616626
// TODO: `spent_outputs` may not align with `tx.inputs()` when a transaction
617627
// spends both chain and mempool UTXOs (mempool outputs are appended last by
@@ -622,7 +632,7 @@ where
622632

623633
let transaction = VerifiedUnminedTx::new(
624634
unmined_tx,
625-
miner_fee.expect("fee should have been checked earlier"),
635+
miner_fee,
626636
sigops,
627637
cached_ffi_transaction.p2sh_sigops(),
628638
spent_outputs.into(),
@@ -1478,32 +1488,17 @@ where
14781488
async_checks
14791489
}
14801490

1481-
/// Computes the miner fee and transaction sigop count for `tx`.
1482-
///
1483-
/// Returns `None` for coinbase transaction fees.
1484-
fn compute_fee_and_sigops(
1491+
/// Calculate the miner fee from the transaction's value balance.
1492+
fn miner_fee(
14851493
tx: &Transaction,
14861494
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1487-
) -> Result<(Option<Amount<NonNegative>>, u32), TransactionError> {
1488-
// Get the `value_balance` to calculate the transaction fee.
1489-
let value_balance = tx.value_balance(spent_utxos);
1490-
1491-
// Calculate the fee only for non-coinbase transactions.
1492-
let mut miner_fee = None;
1493-
if !tx.is_coinbase() {
1494-
// TODO: deduplicate this code with remaining_transaction_value()?
1495-
miner_fee = match value_balance {
1496-
Ok(vb) => match vb.remaining_transaction_value() {
1497-
Ok(tx_rtv) => Some(tx_rtv),
1498-
Err(_) => return Err(TransactionError::IncorrectFee),
1499-
},
1500-
Err(_) => return Err(TransactionError::IncorrectFee),
1501-
};
1495+
) -> Result<Amount<NonNegative>, TransactionError> {
1496+
match tx.value_balance(spent_utxos) {
1497+
Ok(value_balance) => value_balance
1498+
.remaining_transaction_value()
1499+
.map_err(|_| TransactionError::IncorrectFee),
1500+
Err(_) => Err(TransactionError::IncorrectFee),
15021501
}
1503-
1504-
let sigops = tx.sigops().map_err(zebra_script::Error::from)?;
1505-
1506-
Ok((miner_fee, sigops))
15071502
}
15081503
}
15091504

zebra-consensus/src/transaction/tests.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,16 +4169,8 @@ async fn mempool_zip317_error() {
41694169
.map(|utxo| utxo.utxo.clone()),
41704170
));
41714171

4172-
state
4173-
.expect_request_that(|req| {
4174-
matches!(
4175-
req,
4176-
zebra_state::Request::CheckBestChainTipNullifiersAndAnchors(_)
4177-
)
4178-
})
4179-
.await
4180-
.expect("verifier should call mock state service with correct request")
4181-
.respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors);
4172+
// ZIP-317 policy is checked before expensive cryptographic verification, so the
4173+
// verifier never reaches the anchor/nullifier check for this transaction.
41824174
});
41834175

41844176
let verifier_response = verifier

0 commit comments

Comments
 (0)