Takes &Network as an argument in `transparent_coinbase_spend()` and skips coinbase maturity check on regtest

This commit is contained in:
ar 2024-07-26 17:13:31 -04:00
parent 2ac84f4728
commit 7db0de5e0c
6 changed files with 63 additions and 27 deletions

View File

@ -369,6 +369,7 @@ pub fn allow_all_transparent_coinbase_spends(
_: transparent::OutPoint,
_: transparent::CoinbaseSpendRestriction,
_: &transparent::Utxo,
_: &Network,
) -> Result<(), ()> {
Ok(())
}
@ -396,6 +397,7 @@ impl Block {
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
&transparent::Utxo,
&Network,
) -> Result<(), E>
+ Copy
+ 'static,
@ -564,6 +566,7 @@ where
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
&transparent::Utxo,
&Network,
) -> Result<(), E>
+ Copy
+ 'static,
@ -645,6 +648,7 @@ where
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
&transparent::Utxo,
&Network,
) -> Result<(), E>
+ Copy
+ 'static,
@ -668,6 +672,7 @@ where
*candidate_outpoint,
*spend_restriction,
candidate_utxo.as_ref(),
&Network::Mainnet,
)
.is_ok()
{
@ -677,6 +682,7 @@ where
*candidate_outpoint,
delete_transparent_outputs,
candidate_utxo.as_ref(),
&Network::Mainnet,
)
.is_ok()
{

View File

@ -376,7 +376,7 @@ where
// WONTFIX: Return an error for Request::Block as well to replace this check in
// the state once #2336 has been implemented?
if req.is_mempool() {
Self::check_maturity_height(&req, &spent_utxos)?;
Self::check_maturity_height(&req, &spent_utxos, &network)?;
}
let cached_ffi_transaction =
@ -586,12 +586,14 @@ where
pub fn check_maturity_height(
request: &Request,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
network: &Network,
) -> Result<(), TransactionError> {
check::tx_transparent_coinbase_spends_maturity(
request.transaction(),
request.height(),
request.known_utxos(),
spent_utxos,
network,
)
}

View File

@ -480,6 +480,7 @@ pub fn tx_transparent_coinbase_spends_maturity(
height: Height,
block_new_outputs: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
network: &Network,
) -> Result<(), TransactionError> {
for spend in tx.spent_outpoints() {
let utxo = block_new_outputs
@ -490,7 +491,7 @@ pub fn tx_transparent_coinbase_spends_maturity(
let spend_restriction = tx.coinbase_spend_restriction(height);
zebra_state::check::transparent_coinbase_spend(spend, spend_restriction, &utxo)?;
zebra_state::check::transparent_coinbase_spend(spend, spend_restriction, &utxo, network)?;
}
Ok(())

View File

@ -634,11 +634,15 @@ async fn mempool_request_with_immature_spend_is_rejected() {
})
.expect("known_utxos should contain the outpoint");
let expected_error =
zebra_state::check::transparent_coinbase_spend(input_outpoint, spend_restriction, &utxo)
.map_err(Box::new)
.map_err(TransactionError::ValidateContextError)
.expect_err("check should fail");
let expected_error = zebra_state::check::transparent_coinbase_spend(
input_outpoint,
spend_restriction,
&utxo,
&Network::Mainnet,
)
.map_err(Box::new)
.map_err(TransactionError::ValidateContextError)
.expect_err("check should fail");
let mock_state_responses = || {
let mut state = state.clone();

View File

@ -8,6 +8,7 @@ use zebra_chain::{
amount::Amount,
block::{Block, Height},
fmt::TypeNameToDebug,
parameters::Network,
serialization::ZcashDeserializeInto,
transaction::{self, LockTime, Transaction},
transparent,
@ -52,8 +53,12 @@ fn accept_shielded_mature_coinbase_utxo_spend() {
spend_height: min_spend_height,
};
let result =
check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref());
let result = check::utxo::transparent_coinbase_spend(
outpoint,
spend_restriction,
ordered_utxo.as_ref(),
&Network::Mainnet,
);
assert_eq!(
result,
@ -80,8 +85,12 @@ fn reject_unshielded_coinbase_utxo_spend() {
let spend_restriction = transparent::CoinbaseSpendRestriction::SomeTransparentOutputs;
let result =
check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref());
let result = check::utxo::transparent_coinbase_spend(
outpoint,
spend_restriction,
ordered_utxo.as_ref(),
&Network::Mainnet,
);
assert_eq!(result, Err(UnshieldedTransparentCoinbaseSpend { outpoint }));
}
@ -106,8 +115,12 @@ fn reject_immature_coinbase_utxo_spend() {
let spend_restriction =
transparent::CoinbaseSpendRestriction::OnlyShieldedOutputs { spend_height };
let result =
check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref());
let result = check::utxo::transparent_coinbase_spend(
outpoint,
spend_restriction,
ordered_utxo.as_ref(),
&Network::Mainnet,
);
assert_eq!(
result,
Err(ImmatureTransparentCoinbaseSpend {
@ -122,7 +135,7 @@ fn reject_immature_coinbase_utxo_spend() {
outpoint,
spend_restriction,
ordered_utxo.as_ref(),
&Network::new_regtest(),
&Network::new_regtest(None, None),
)
.expect("should pass check on Regtest");
}

View File

@ -4,6 +4,7 @@ use std::collections::{HashMap, HashSet};
use zebra_chain::{
amount,
parameters::Network,
transparent::{self, utxos_from_ordered_utxos, CoinbaseSpendRestriction::*},
};
@ -53,6 +54,18 @@ pub fn transparent_spend(
.iter()
.filter_map(transparent::Input::outpoint);
// The state service returns UTXOs from pending blocks,
// which can be rejected by later contextual checks.
// This is a particular issue for v5 transactions,
// because their authorizing data is only bound to the block data
// during contextual validation (#2336).
//
// We don't want to use UTXOs from invalid pending blocks,
// so we check transparent coinbase maturity and shielding
// using known valid UTXOs during non-finalized chain validation.
let spend_restriction =
transaction.coinbase_spend_restriction(semantically_verified.height);
for spend in spends {
let utxo = transparent_spend_chain_order(
spend,
@ -63,18 +76,12 @@ pub fn transparent_spend(
finalized_state,
)?;
// The state service returns UTXOs from pending blocks,
// which can be rejected by later contextual checks.
// This is a particular issue for v5 transactions,
// because their authorizing data is only bound to the block data
// during contextual validation (#2336).
//
// We don't want to use UTXOs from invalid pending blocks,
// so we check transparent coinbase maturity and shielding
// using known valid UTXOs during non-finalized chain validation.
let spend_restriction =
transaction.coinbase_spend_restriction(semantically_verified.height);
transparent_coinbase_spend(spend, spend_restriction, utxo.as_ref())?;
transparent_coinbase_spend(
spend,
spend_restriction,
utxo.as_ref(),
&finalized_state.network(),
)?;
// We don't delete the UTXOs until the block is committed,
// so we need to check for duplicate spends within the same block.
@ -185,10 +192,13 @@ fn transparent_spend_chain_order(
/// > Founders Reward outputs and transparent funding stream outputs.
///
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
///
/// Coinbase outputs _can_ be spent immediately on Regtest.
pub fn transparent_coinbase_spend(
outpoint: transparent::OutPoint,
spend_restriction: transparent::CoinbaseSpendRestriction,
utxo: &transparent::Utxo,
network: &Network,
) -> Result<(), ValidateContextError> {
if !utxo.from_coinbase {
return Ok(());
@ -199,7 +209,7 @@ pub fn transparent_coinbase_spend(
let min_spend_height = utxo.height + MIN_TRANSPARENT_COINBASE_MATURITY.into();
let min_spend_height =
min_spend_height.expect("valid UTXOs have coinbase heights far below Height::MAX");
if spend_height >= min_spend_height {
if spend_height >= min_spend_height || network.is_regtest() {
Ok(())
} else {
Err(ImmatureTransparentCoinbaseSpend {