Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge queue: embarking main (2e434b0) and [#6510 + #6530 + #6527] together #6533

Closed
wants to merge 13 commits into from
14 changes: 7 additions & 7 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,9 @@ checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"

[[package]]
name = "bech32"
version = "0.8.1"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b"
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"

[[package]]
name = "bellman"
Expand Down Expand Up @@ -4780,9 +4780,9 @@ dependencies = [

[[package]]
name = "tonic"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bd8e87955eb13c1986671838177d6792cdc52af9bffced0d2c8a9a7f741ab3"
checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
dependencies = [
"async-trait",
"axum",
Expand Down Expand Up @@ -5557,9 +5557,9 @@ dependencies = [

[[package]]
name = "zcash_address"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804268e702b664fc09d3e2ce82786d0addf4ae57ba6976469be63e09066bf9f7"
checksum = "52be35a205369d480378646bff9c9fedafd8efe8af1e0e54bb858f405883f2b2"
dependencies = [
"bech32",
"bs58",
Expand Down Expand Up @@ -6045,7 +6045,7 @@ dependencies = [
"tokio",
"tokio-stream",
"toml 0.7.3",
"tonic 0.9.1",
"tonic 0.9.2",
"tonic-build",
"tower",
"tracing",
Expand Down
2 changes: 1 addition & 1 deletion zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ redjubjub = "0.5.0"
reddsa = "0.5.0"

# Experimental feature getblocktemplate-rpcs
zcash_address = { version = "0.2.0", optional = true }
zcash_address = { version = "0.2.1", optional = true }

# Optional testing dependencies
proptest = { version = "1.1.0", optional = true }
Expand Down
24 changes: 12 additions & 12 deletions zebra-chain/src/block/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ impl Arbitrary for Block {
pub fn allow_all_transparent_coinbase_spends(
_: transparent::OutPoint,
_: transparent::CoinbaseSpendRestriction,
_: transparent::OrderedUtxo,
_: &transparent::Utxo,
) -> Result<(), ()> {
Ok(())
}
Expand All @@ -390,7 +390,7 @@ impl Block {
/// `generate_valid_commitments` specifies if the generated blocks
/// should have valid commitments. This makes it much slower so it's better
/// to enable only when needed.
pub fn partial_chain_strategy<F, T, E>(
pub fn partial_chain_strategy<F, E>(
mut current: LedgerState,
count: usize,
check_transparent_coinbase_spend: F,
Expand All @@ -400,8 +400,8 @@ impl Block {
F: Fn(
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
transparent::OrderedUtxo,
) -> Result<T, E>
&transparent::Utxo,
) -> Result<(), E>
+ Copy
+ 'static,
{
Expand Down Expand Up @@ -558,7 +558,7 @@ impl Block {
/// Spends [`transparent::OutPoint`]s from `utxos`, and adds newly created outputs.
///
/// If the transaction can't be fixed, returns `None`.
pub fn fix_generated_transaction<F, T, E>(
pub fn fix_generated_transaction<F, E>(
mut transaction: Transaction,
tx_index_in_block: usize,
height: Height,
Expand All @@ -570,8 +570,8 @@ where
F: Fn(
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
transparent::OrderedUtxo,
) -> Result<T, E>
&transparent::Utxo,
) -> Result<(), E>
+ Copy
+ 'static,
{
Expand Down Expand Up @@ -640,7 +640,7 @@ where
/// Modifies `transaction` and updates `spend_restriction` if needed.
///
/// If there is no valid output, or many search attempts have failed, returns `None`.
pub fn find_valid_utxo_for_spend<F, T, E>(
pub fn find_valid_utxo_for_spend<F, E>(
transaction: &mut Transaction,
spend_restriction: &mut CoinbaseSpendRestriction,
spend_height: Height,
Expand All @@ -651,8 +651,8 @@ where
F: Fn(
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
transparent::OrderedUtxo,
) -> Result<T, E>
&transparent::Utxo,
) -> Result<(), E>
+ Copy
+ 'static,
{
Expand All @@ -674,7 +674,7 @@ where
if check_transparent_coinbase_spend(
*candidate_outpoint,
*spend_restriction,
candidate_utxo.clone(),
candidate_utxo.as_ref(),
)
.is_ok()
{
Expand All @@ -683,7 +683,7 @@ where
&& check_transparent_coinbase_spend(
*candidate_outpoint,
delete_transparent_outputs,
candidate_utxo.clone(),
candidate_utxo.as_ref(),
)
.is_ok()
{
Expand Down
6 changes: 6 additions & 0 deletions zebra-chain/src/transparent/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ pub struct OrderedUtxo {
pub tx_index_in_block: usize,
}

impl AsRef<Utxo> for OrderedUtxo {
fn as_ref(&self) -> &Utxo {
&self.utxo
}
}

impl Utxo {
/// Create a new UTXO from its fields.
pub fn new(output: transparent::Output, height: block::Height, from_coinbase: bool) -> Utxo {
Expand Down
34 changes: 31 additions & 3 deletions zebra-consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
use chrono::{DateTime, Utc};
use thiserror::Error;

use zebra_chain::{amount, block, orchard, sapling, sprout, transparent};
use zebra_chain::{
amount, block, orchard, sapling, sprout,
transparent::{self, MIN_TRANSPARENT_COINBASE_MATURITY},
};
use zebra_state::ValidateContextError;

use crate::{block::MAX_BLOCK_SIGOPS, BoxError};
Expand Down Expand Up @@ -187,17 +190,42 @@ pub enum TransactionError {
#[error("could not validate nullifiers and anchors on best chain")]
#[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
// This error variant is at least 128 bytes
ValidateNullifiersAndAnchorsError(Box<ValidateContextError>),
ValidateContextError(Box<ValidateContextError>),

#[error("could not validate mempool transaction lock time on best chain")]
#[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
// TODO: turn this into a typed error
ValidateMempoolLockTimeError(String),

#[error(
"immature transparent coinbase spend: \
attempt to spend {outpoint:?} at {spend_height:?}, \
but spends are invalid before {min_spend_height:?}, \
which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \
after it was created at {created_height:?}"
)]
#[non_exhaustive]
ImmatureTransparentCoinbaseSpend {
outpoint: transparent::OutPoint,
spend_height: block::Height,
min_spend_height: block::Height,
created_height: block::Height,
},

#[error(
"unshielded transparent coinbase spend: {outpoint:?} \
must be spent in a transaction which only has shielded outputs"
)]
#[non_exhaustive]
UnshieldedTransparentCoinbaseSpend {
outpoint: transparent::OutPoint,
min_spend_height: block::Height,
},
}

impl From<ValidateContextError> for TransactionError {
fn from(err: ValidateContextError) -> Self {
TransactionError::ValidateNullifiersAndAnchorsError(Box::new(err))
TransactionError::ValidateContextError(Box::new(err))
}
}

Expand Down
38 changes: 33 additions & 5 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,7 @@ impl Request {

/// Returns true if the request is a mempool request.
pub fn is_mempool(&self) -> bool {
match self {
Request::Block { .. } => false,
Request::Mempool { .. } => true,
}
matches!(self, Request::Mempool { .. })
}
}

Expand Down Expand Up @@ -377,6 +374,12 @@ where
Self::spent_utxos(tx.clone(), req.known_utxos(), req.is_mempool(), state.clone());
let (spent_utxos, spent_outputs) = load_spent_utxos_fut.await?;

// 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)?;
}

let cached_ffi_transaction =
Arc::new(CachedFfiTransaction::new(tx.clone(), spent_outputs));

Expand Down Expand Up @@ -420,7 +423,10 @@ where
unmined_tx,
))
.map(|res| {
assert!(res? == zs::Response::ValidBestChainTipNullifiersAndAnchors, "unexpected response to CheckBestChainTipNullifiersAndAnchors request");
assert!(
res? == zs::Response::ValidBestChainTipNullifiersAndAnchors,
"unexpected response to CheckBestChainTipNullifiersAndAnchors request"
);
Ok(())
}
);
Expand Down Expand Up @@ -567,6 +573,28 @@ where
Ok((spent_utxos, spent_outputs))
}

/// Accepts `request`, a transaction verifier [`&Request`](Request),
/// and `spent_utxos`, a HashMap of UTXOs in the chain that are spent by this transaction.
///
/// Gets the `transaction`, `height`, and `known_utxos` for the request and checks calls
/// [`check::tx_transparent_coinbase_spends_maturity`] to verify that every transparent
/// coinbase output spent by the transaction will have matured by `height`.
///
/// Returns `Ok(())` if every transparent coinbase output spent by the transaction is
/// mature and valid for the request height, or a [`TransactionError`] if the transaction
/// spends transparent coinbase outputs that are immature and invalid for the request height.
pub fn check_maturity_height(
request: &Request,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
) -> Result<(), TransactionError> {
check::tx_transparent_coinbase_spends_maturity(
request.transaction(),
request.height(),
request.known_utxos(),
spent_utxos,
)
}

/// Verify a V4 transaction.
///
/// Returns a set of asynchronous checks that must all succeed for the transaction to be
Expand Down
34 changes: 33 additions & 1 deletion zebra-consensus/src/transaction/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
//!
//! Code in this file can freely assume that no pre-V4 transactions are present.
use std::{borrow::Cow, collections::HashSet, convert::TryFrom, hash::Hash};
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
hash::Hash,
sync::Arc,
};

use chrono::{DateTime, Utc};

Expand All @@ -13,6 +18,7 @@ use zebra_chain::{
parameters::{Network, NetworkUpgrade},
primitives::zcash_note_encryption,
transaction::{LockTime, Transaction},
transparent,
};

use crate::error::TransactionError;
Expand Down Expand Up @@ -462,3 +468,29 @@ fn validate_expiry_height_mined(

Ok(())
}

/// Accepts a transaction, block height, block UTXOs, and
/// the transaction's spent UTXOs from the chain.
///
/// Returns `Ok(())` if spent transparent coinbase outputs are
/// valid for the block height, or a [`Err(TransactionError)`](TransactionError)
pub fn tx_transparent_coinbase_spends_maturity(
tx: Arc<Transaction>,
height: Height,
block_new_outputs: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
) -> Result<(), TransactionError> {
for spend in tx.spent_outpoints() {
let utxo = block_new_outputs
.get(&spend)
.map(|ordered_utxo| ordered_utxo.utxo.clone())
.or_else(|| spent_utxos.get(&spend).cloned())
.expect("load_spent_utxos_fut.await should return an error if a utxo is missing");

let spend_restriction = tx.coinbase_spend_restriction(height);

zebra_state::check::transparent_coinbase_spend(spend, spend_restriction, &utxo)?;
}

Ok(())
}
Loading