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 (035fad5) and [#5733 + #5716] together #5746

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5011db0
updates comments
arya2 Nov 22, 2022
430ed61
adds check nullifier no dup fns for transactions
arya2 Nov 24, 2022
0789aec
Adds:
arya2 Nov 24, 2022
72acc4d
updates check::anchors fns to use transactions
arya2 Nov 24, 2022
ad30194
conditions new state req call on is_mempool
arya2 Nov 24, 2022
616e826
fix doc link / lint error
arya2 Nov 24, 2022
1b12334
checks for duplicate nullifiers with closures
arya2 Nov 24, 2022
6295a8e
Update zebra-state/src/service/check/nullifier.rs
arya2 Nov 24, 2022
4cad214
documents find_duplicate_nullifier params
arya2 Nov 24, 2022
2e48738
renames new state req/res
arya2 Nov 24, 2022
03eba19
asserts correct response variant in tx verifier
arya2 Nov 24, 2022
be723fe
adds CheckBestChainTipShieldedSpends call in tx verifier to async checks
arya2 Nov 24, 2022
65f39ba
re-adds tracing instrumentation to check::anchors fn
arya2 Nov 24, 2022
00c2a4b
adds block/tx wrapper fns for anchors checks
arya2 Nov 25, 2022
55232d1
uses UnminedTx instead of transaction.hash()
arya2 Nov 25, 2022
3f5efe3
updates new state req/res name
arya2 Nov 25, 2022
c02a5bf
updates tests and uses par_iter for anchors checks
arya2 Nov 25, 2022
5e1f103
Updates check::anchors pub fn docs.
arya2 Nov 25, 2022
115cd6a
Adds:
arya2 Nov 26, 2022
c5c88a0
Apply suggestions from code review
arya2 Nov 28, 2022
42352a5
moves downcast to From impl
arya2 Nov 28, 2022
5a064c0
moves the ValidateContextError into an Arc
arya2 Nov 28, 2022
84aa68f
leaves par_iter for another PR
arya2 Nov 28, 2022
11bd49c
Log zebrad config path as well as config values
teor2345 Nov 28, 2022
295e169
Remove duplicate config log
teor2345 Nov 28, 2022
d095e90
Only log config path and data for server commands
teor2345 Nov 28, 2022
5329498
Use config ref instead
teor2345 Nov 28, 2022
6796fa1
puts io::Error in an Arc
arya2 Nov 28, 2022
1b0d532
updates anchors tests to call tx_anchors check
arya2 Nov 28, 2022
513b811
updates tests to call tx_no_duplicates_in_chain
arya2 Nov 29, 2022
827ac57
Update zebra-consensus/src/error.rs
arya2 Nov 29, 2022
a01ab07
moves Arc from HistoryError to ValidateContextError
arya2 Nov 29, 2022
5b91ec4
Merge of #5733
mergify[bot] Nov 30, 2022
396fbc6
Merge of #5716
mergify[bot] Nov 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zebra-chain/src/block/commitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ impl FromHex for ChainHistoryBlockTxAuthCommitmentHash {
/// implement, and ensures that we don't reject blocks or transactions
/// for a non-enumerated reason.
#[allow(missing_docs)]
#[derive(Error, Debug, PartialEq, Eq)]
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum CommitmentError {
#[error(
"invalid final sapling root: expected {:?}, actual: {:?}",
Expand Down
10 changes: 10 additions & 0 deletions zebra-consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use chrono::{DateTime, Utc};
use thiserror::Error;

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

use crate::{block::MAX_BLOCK_SIGOPS, BoxError};

Expand Down Expand Up @@ -180,6 +181,10 @@ pub enum TransactionError {

#[error("could not find a mempool transaction input UTXO in the best chain")]
TransparentInputNotFound,

#[error("could not validate nullifiers and anchors on best chain")]
#[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
ValidateNullifiersAndAnchorsError(#[from] ValidateContextError),
}

impl From<BoxError> for TransactionError {
Expand All @@ -190,6 +195,11 @@ impl From<BoxError> for TransactionError {
Err(e) => err = e,
}

match err.downcast::<ValidateContextError>() {
Ok(e) => return (*e).into(),
Err(e) => err = e,
}

// buffered transaction verifier service error
match err.downcast::<TransactionError>() {
Ok(e) => return *e,
Expand Down
26 changes: 21 additions & 5 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ impl Request {
}

/// The unverified mempool transaction, if this is a mempool request.
pub fn into_mempool_transaction(self) -> Option<UnminedTx> {
pub fn mempool_transaction(&self) -> Option<UnminedTx> {
match self {
Request::Block { .. } => None,
Request::Mempool { transaction, .. } => Some(transaction),
Request::Mempool { transaction, .. } => Some(transaction.clone()),
}
}

Expand Down Expand Up @@ -357,15 +357,16 @@ where

// Load spent UTXOs from state.
// TODO: Make this a method of `Request` and replace `tx.clone()` with `self.transaction()`?
let (spent_utxos, spent_outputs) =
Self::spent_utxos(tx.clone(), req.known_utxos(), req.is_mempool(), state).await?;
let load_spent_utxos_fut =
Self::spent_utxos(tx.clone(), req.known_utxos(), req.is_mempool(), state.clone());
let (spent_utxos, spent_outputs) = load_spent_utxos_fut.await?;

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

tracing::trace!(?tx_id, "got state UTXOs");

let async_checks = match tx.as_ref() {
let mut async_checks = match tx.as_ref() {
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
tracing::debug!(?tx, "got transaction with wrong version");
return Err(TransactionError::WrongVersion);
Expand Down Expand Up @@ -396,6 +397,21 @@ where
)?,
};

if let Some(unmined_tx) = req.mempool_transaction() {
let check_anchors_and_revealed_nullifiers_query = state
.clone()
.oneshot(zs::Request::CheckBestChainTipNullifiersAndAnchors(
unmined_tx,
))
.map(|res| {
assert!(res? == zs::Response::ValidBestChainTipNullifiersAndAnchors, "unexpected response to CheckBestChainTipNullifiersAndAnchors request");
Ok(())
}
);

async_checks.push(check_anchors_and_revealed_nullifiers_query);
}

tracing::trace!(?tx_id, "awaiting async checks...");

// If the Groth16 parameter download hangs,
Expand Down
28 changes: 25 additions & 3 deletions zebra-consensus/src/transaction/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,28 @@ async fn mempool_request_with_missing_input_is_rejected() {
.find(|(_, tx)| !(tx.is_coinbase() || tx.inputs().is_empty()))
.expect("At least one non-coinbase transaction with transparent inputs in test vectors");

let expected_state_request = zebra_state::Request::UnspentBestChainUtxo(match tx.inputs()[0] {
let input_outpoint = match tx.inputs()[0] {
transparent::Input::PrevOut { outpoint, .. } => outpoint,
transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"),
});
};

tokio::spawn(async move {
state
.expect_request(expected_state_request)
.expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint))
.await
.expect("verifier should call mock state service")
.respond(zebra_state::Response::UnspentBestChainUtxo(None));

state
.expect_request_that(|req| {
matches!(
req,
zebra_state::Request::CheckBestChainTipNullifiersAndAnchors(_)
)
})
.await
.expect("verifier should call mock state service")
.respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors);
});

let verifier_response = verifier
Expand Down Expand Up @@ -251,6 +262,17 @@ async fn mempool_request_with_present_input_is_accepted() {
.get(&input_outpoint)
.map(|utxo| utxo.utxo.clone()),
));

state
.expect_request_that(|req| {
matches!(
req,
zebra_state::Request::CheckBestChainTipNullifiersAndAnchors(_)
)
})
.await
.expect("verifier should call mock state service")
.respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors);
});

let verifier_response = verifier
Expand Down
16 changes: 8 additions & 8 deletions zebra-state/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
pub struct CommitBlockError(#[from] ValidateContextError);

/// An error describing why a block failed contextual validation.
#[derive(Debug, Error, PartialEq, Eq)]
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ValidateContextError {
Expand Down Expand Up @@ -224,7 +224,7 @@ pub enum ValidateContextError {
NoteCommitmentTreeError(#[from] zebra_chain::parallel::tree::NoteCommitmentTreeError),

#[error("error building the history tree")]
HistoryTreeError(#[from] HistoryTreeError),
HistoryTreeError(#[from] Arc<HistoryTreeError>),

#[error("block contains an invalid commitment")]
InvalidBlockCommitment(#[from] block::CommitmentError),
Expand All @@ -236,8 +236,8 @@ pub enum ValidateContextError {
#[non_exhaustive]
UnknownSproutAnchor {
anchor: sprout::tree::Root,
height: block::Height,
tx_index_in_block: usize,
height: Option<block::Height>,
tx_index_in_block: Option<usize>,
transaction_hash: transaction::Hash,
},

Expand All @@ -248,8 +248,8 @@ pub enum ValidateContextError {
#[non_exhaustive]
UnknownSaplingAnchor {
anchor: sapling::tree::Root,
height: block::Height,
tx_index_in_block: usize,
height: Option<block::Height>,
tx_index_in_block: Option<usize>,
transaction_hash: transaction::Hash,
},

Expand All @@ -260,8 +260,8 @@ pub enum ValidateContextError {
#[non_exhaustive]
UnknownOrchardAnchor {
anchor: orchard::tree::Root,
height: block::Height,
tx_index_in_block: usize,
height: Option<block::Height>,
tx_index_in_block: Option<usize>,
transaction_hash: transaction::Hash,
},
}
Expand Down
23 changes: 22 additions & 1 deletion zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use zebra_chain::{
parallel::tree::NoteCommitmentTrees,
sapling,
serialization::SerializationError,
sprout, transaction,
sprout,
transaction::{self, UnminedTx},
transparent::{self, utxos_from_ordered_utxos},
value_balance::{ValueBalance, ValueBalanceError},
};
Expand Down Expand Up @@ -539,6 +540,11 @@ pub enum Request {
/// Optionally, the hash of the last header to request.
stop: Option<block::Hash>,
},

/// Contextually validates anchors and nullifiers of a transaction on the best chain
///
/// Returns [`Response::ValidBestChainTipNullifiersAndAnchors`]
CheckBestChainTipNullifiersAndAnchors(UnminedTx),
}

impl Request {
Expand All @@ -555,6 +561,9 @@ impl Request {
Request::Block(_) => "block",
Request::FindBlockHashes { .. } => "find_block_hashes",
Request::FindBlockHeaders { .. } => "find_block_headers",
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
"best_chain_tip_nullifiers_anchors"
}
}
}

Expand Down Expand Up @@ -736,6 +745,11 @@ pub enum ReadRequest {
/// Returns a type with found utxos and transaction information.
UtxosByAddresses(HashSet<transparent::Address>),

/// Contextually validates anchors and nullifiers of a transaction on the best chain
///
/// Returns [`ReadResponse::ValidBestChainTipNullifiersAndAnchors`].
CheckBestChainTipNullifiersAndAnchors(UnminedTx),

#[cfg(feature = "getblocktemplate-rpcs")]
/// Looks up a block hash by height in the current best chain.
///
Expand Down Expand Up @@ -772,6 +786,9 @@ impl ReadRequest {
ReadRequest::AddressBalance { .. } => "address_balance",
ReadRequest::TransactionIdsByAddresses { .. } => "transaction_ids_by_addesses",
ReadRequest::UtxosByAddresses(_) => "utxos_by_addesses",
ReadRequest::CheckBestChainTipNullifiersAndAnchors(_) => {
"best_chain_tip_nullifiers_anchors"
}
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::BestChainBlockHash(_) => "best_chain_block_hash",
#[cfg(feature = "getblocktemplate-rpcs")]
Expand Down Expand Up @@ -815,6 +832,10 @@ impl TryFrom<Request> for ReadRequest {
Ok(ReadRequest::FindBlockHeaders { known_blocks, stop })
}

Request::CheckBestChainTipNullifiersAndAnchors(tx) => {
Ok(ReadRequest::CheckBestChainTipNullifiersAndAnchors(tx))
}

Request::CommitBlock(_) | Request::CommitFinalizedBlock(_) => {
Err("ReadService does not write blocks")
}
Expand Down
12 changes: 12 additions & 0 deletions zebra-state/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ pub enum Response {

/// The response to a `FindBlockHeaders` request.
BlockHeaders(Vec<block::CountedHeader>),

/// Response to [`Request::CheckBestChainTipNullifiersAndAnchors`].
///
/// Does not check transparent UTXO inputs
ValidBestChainTipNullifiersAndAnchors,
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -114,6 +119,11 @@ pub enum ReadResponse {
/// Response to [`ReadRequest::UtxosByAddresses`] with found utxos and transaction data.
AddressUtxos(AddressUtxos),

/// Response to [`ReadRequest::CheckBestChainTipNullifiersAndAnchors`].
///
/// Does not check transparent UTXO inputs
ValidBestChainTipNullifiersAndAnchors,

#[cfg(feature = "getblocktemplate-rpcs")]
/// Response to [`ReadRequest::BestChainBlockHash`](crate::ReadRequest::BestChainBlockHash) with the
/// specified block hash.
Expand Down Expand Up @@ -171,6 +181,8 @@ impl TryFrom<ReadResponse> for Response {
ReadResponse::BlockHashes(hashes) => Ok(Response::BlockHashes(hashes)),
ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)),

ReadResponse::ValidBestChainTipNullifiersAndAnchors => Ok(Response::ValidBestChainTipNullifiersAndAnchors),

ReadResponse::TransactionIdsForBlock(_)
| ReadResponse::SaplingTree(_)
| ReadResponse::OrchardTree(_)
Expand Down
37 changes: 35 additions & 2 deletions zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,8 @@ impl Service<Request> for StateService {
| Request::UnspentBestChainUtxo(_)
| Request::Block(_)
| Request::FindBlockHashes { .. }
| Request::FindBlockHeaders { .. } => {
| Request::FindBlockHeaders { .. }
| Request::CheckBestChainTipNullifiersAndAnchors(_) => {
// Redirect the request to the concurrent ReadStateService
let read_service = self.read_service.clone();

Expand Down Expand Up @@ -1217,7 +1218,6 @@ impl Service<ReadRequest> for ReadStateService {
.boxed()
}

// Currently unused.
ReadRequest::UnspentBestChainUtxo(outpoint) => {
let timer = CodeTimer::start();

Expand Down Expand Up @@ -1519,6 +1519,39 @@ impl Service<ReadRequest> for ReadStateService {
.boxed()
}

ReadRequest::CheckBestChainTipNullifiersAndAnchors(unmined_tx) => {
let timer = CodeTimer::start();

let state = self.clone();

let span = Span::current();
tokio::task::spawn_blocking(move || {
span.in_scope(move || {
let latest_non_finalized_best_chain =
state.latest_non_finalized_state().best_chain().cloned();

check::nullifier::tx_no_duplicates_in_chain(
&state.db,
latest_non_finalized_best_chain.as_ref(),
&unmined_tx.transaction,
)?;

check::anchors::tx_anchors_refer_to_final_treestates(
&state.db,
latest_non_finalized_best_chain.as_ref(),
&unmined_tx,
)?;

// The work is done in the future.
timer.finish(module_path!(), line!(), "ReadRequest::UnspentBestChainUtxo");

Ok(ReadResponse::ValidBestChainTipNullifiersAndAnchors)
})
})
.map(|join_result| join_result.expect("panic in ReadRequest::UnspentBestChainUtxo"))
.boxed()
}

// Used by get_block_hash RPC.
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::BestChainBlockHash(height) => {
Expand Down
Loading