Skip to content

Commit

Permalink
Merge of #6665
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored May 16, 2023
2 parents 185d138 + 1219724 commit 0ab795a
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 3 deletions.
103 changes: 102 additions & 1 deletion zebra-consensus/src/transaction/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use zebra_chain::{
transparent::{self, CoinbaseData},
};

use zebra_state::ValidateContextError;
use zebra_test::mock_service::MockService;

use crate::error::TransactionError;
Expand Down Expand Up @@ -592,7 +593,7 @@ async fn mempool_request_with_past_lock_time_is_accepted() {
/// Tests that calls to the transaction verifier with a mempool request that spends
/// immature coinbase outputs will return an error.
#[tokio::test]
async fn mempool_request_with_immature_spent_is_rejected() {
async fn mempool_request_with_immature_spend_is_rejected() {
let _init_guard = zebra_test::init();

let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests();
Expand Down Expand Up @@ -692,6 +693,106 @@ async fn mempool_request_with_immature_spent_is_rejected() {
);
}

/// Tests that errors from the read state service are correctly converted into
/// transaction verifier errors.
#[tokio::test]
async fn state_error_converted_correctly() {
use zebra_state::DuplicateNullifierError;

let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests();
let verifier = Verifier::new(Network::Mainnet, state.clone());

let height = NetworkUpgrade::Canopy
.activation_height(Network::Mainnet)
.expect("Canopy activation height is specified");
let fund_height = (height - 1).expect("fake source fund block height is too small");
let (input, output, known_utxos) = mock_transparent_transfer(
fund_height,
true,
0,
Amount::try_from(10001).expect("invalid value"),
);

// Create a non-coinbase V4 tx with the last valid expiry height.
let tx = Transaction::V4 {
inputs: vec![input],
outputs: vec![output],
lock_time: LockTime::unlocked(),
expiry_height: height,
joinsplit_data: None,
sapling_shielded_data: None,
};

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

let make_validate_context_error =
|| sprout::Nullifier([0; 32].into()).duplicate_nullifier_error(true);

tokio::spawn(async move {
state
.expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint))
.await
.expect("verifier should call mock state service with correct request")
.respond(zebra_state::Response::UnspentBestChainUtxo(
known_utxos
.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 with correct request")
.respond(Err::<zebra_state::Response, zebra_state::BoxError>(
make_validate_context_error().into(),
));
});

let verifier_response = verifier
.oneshot(Request::Mempool {
transaction: tx.into(),
height,
})
.await;

let transaction_error =
verifier_response.expect_err("expected failed verification, got: {verifier_response:?}");

assert_eq!(
TransactionError::from(make_validate_context_error()),
transaction_error,
"expected matching state and transaction errors"
);

let state_error = zebra_state::BoxError::from(make_validate_context_error())
.downcast::<ValidateContextError>()
.map(|boxed| TransactionError::from(*boxed))
.expect("downcast should succeed");

assert_eq!(
state_error, transaction_error,
"expected matching state and transaction errors"
);

let TransactionError::ValidateContextError(propagated_validate_context_error) = transaction_error else {
panic!("should be a ValidateContextError variant");
};

assert_eq!(
*propagated_validate_context_error,
make_validate_context_error(),
"expected matching state and transaction errors"
);
}

#[test]
fn v5_transaction_with_no_outputs_fails_validation() {
let transaction = fake_v5_transactions_for_network(
Expand Down
2 changes: 1 addition & 1 deletion zebra-state/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ pub enum ValidateContextError {
}

/// Trait for creating the corresponding duplicate nullifier error from a nullifier.
pub(crate) trait DuplicateNullifierError {
pub trait DuplicateNullifierError {
/// Returns the corresponding duplicate nullifier error for `self`.
fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError;
}
Expand Down
4 changes: 3 additions & 1 deletion zebra-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ mod tests;

pub use config::{check_and_delete_old_databases, Config};
pub use constants::MAX_BLOCK_REORG_HEIGHT;
pub use error::{BoxError, CloneError, CommitBlockError, ValidateContextError};
pub use error::{
BoxError, CloneError, CommitBlockError, DuplicateNullifierError, ValidateContextError,
};
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, ReadRequest, Request};
pub use response::{KnownBlock, MinedTx, ReadResponse, Response};
pub use service::{
Expand Down

0 comments on commit 0ab795a

Please sign in to comment.