zebra/zebra-state/src/error.rs

189 lines
6.4 KiB
Rust
Raw Normal View History

use std::sync::Arc;
use chrono::{DateTime, Utc};
use thiserror::Error;
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
use zebra_chain::{
block, orchard, sapling, sprout, transparent, work::difficulty::CompactDifficulty,
};
use crate::constants::MIN_TRANSPARENT_COINBASE_MATURITY;
/// A wrapper for type erased errors that is itself clonable and implements the
/// Error trait
#[derive(Debug, Error, Clone)]
#[error(transparent)]
pub struct CloneError {
source: Arc<dyn std::error::Error + Send + Sync + 'static>,
}
impl From<CommitBlockError> for CloneError {
fn from(source: CommitBlockError) -> Self {
let source = Arc::new(source);
Self { source }
}
}
impl From<BoxError> for CloneError {
fn from(source: BoxError) -> Self {
let source = Arc::from(source);
Self { source }
}
}
/// A boxed [`std::error::Error`].
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
/// An error describing the reason a block could not be committed to the state.
#[derive(Debug, Error, PartialEq, Eq)]
#[error("block is not contextually valid")]
pub struct CommitBlockError(#[from] ValidateContextError);
/// An error describing why a block failed contextual validation.
#[derive(Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ValidateContextError {
#[error("block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}")]
#[non_exhaustive]
OrphanedBlock {
candidate_height: block::Height,
finalized_tip_height: block::Height,
},
#[error("block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}")]
#[non_exhaustive]
NonSequentialBlock {
candidate_height: block::Height,
parent_height: block::Height,
},
#[error("block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}")]
#[non_exhaustive]
TimeTooEarly {
candidate_time: DateTime<Utc>,
median_time_past: DateTime<Utc>,
},
#[error("block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}")]
#[non_exhaustive]
TimeTooLate {
candidate_time: DateTime<Utc>,
block_time_max: DateTime<Utc>,
},
#[error("block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}")]
#[non_exhaustive]
InvalidDifficultyThreshold {
difficulty_threshold: CompactDifficulty,
expected_difficulty: CompactDifficulty,
},
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
#[error("transparent double-spend: {outpoint:?} is spent twice in {location:?}")]
#[non_exhaustive]
DuplicateTransparentSpend {
outpoint: transparent::OutPoint,
location: &'static str,
},
#[error("missing transparent output: possible double-spend of {outpoint:?} in {location:?}")]
#[non_exhaustive]
MissingTransparentOutput {
outpoint: transparent::OutPoint,
location: &'static str,
},
#[error("out-of-order transparent spend: {outpoint:?} is created by a later transaction in the same block")]
#[non_exhaustive]
EarlyTransparentSpend { outpoint: transparent::OutPoint },
#[error(
"unshielded transparent coinbase spend: {outpoint:?} \
must be spent in a transaction which only has shielded outputs"
)]
#[non_exhaustive]
UnshieldedTransparentCoinbaseSpend { outpoint: transparent::OutPoint },
#[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("sprout double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
#[non_exhaustive]
DuplicateSproutNullifier {
nullifier: sprout::Nullifier,
in_finalized_state: bool,
},
Reject duplicate Sapling and Orchard nullifiers (#2497) * Add sapling and orchard duplicate nullifier errors * Reject duplicate finalized sapling and orchard nullifiers Reject duplicate sapling and orchard nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is already in the finalized state. * Reject duplicate non-finalized sapling and orchard nullifiers Reject duplicate sapling and orchard nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is in: * the same shielded data, * the same transaction, * the same block, or * an earlier block in the non-finalized chain. * Refactor sprout nullifier tests to remove common code * Add sapling nullifier tests Test that the state rejects duplicate sapling nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is in: * the same shielded data, * the same transaction, * the same block, * an earlier block in the non-finalized chain, or * the finalized state. * Add orchard nullifier tests Test that the state rejects duplicate orchard nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is in: * the same shielded data, * the same transaction, * the same block, * an earlier block in the non-finalized chain, or * the finalized state. * Check for specific nullifiers in the state in tests * Replace slices with vectors in arguments * Remove redundant code and variables * Simplify sapling TransferData tests Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Remove an extra : * Remove redundant vec! Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-19 17:39:05 -07:00
#[error("sapling double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
#[non_exhaustive]
DuplicateSaplingNullifier {
nullifier: sapling::Nullifier,
in_finalized_state: bool,
},
#[error("orchard double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
#[non_exhaustive]
DuplicateOrchardNullifier {
nullifier: orchard::Nullifier,
in_finalized_state: bool,
},
#[error("remaining value in the transparent transaction value pool MUST be nonnegative: {transaction_hash:?}, in finalized state: {in_finalized_state:?}")]
#[non_exhaustive]
InvalidRemainingTransparentValue {
transaction_hash: zebra_chain::transaction::Hash,
in_finalized_state: bool,
},
Track anchors and note commitment trees in zebra-state (#2458) * Tidy chain Cargo.toml * Organize imports * Add method to get note commitments from all Actions in Orchard shielded data * Add method to get note commitments from all JoinSplits in Sprout JoinSplitData * Add Request and Response variants for awaiting anchors * Add anchors and note commitment trees to finalized state db * Add (From|Into)Disk impls for tree::Roots and stubs for NoteCommitmentTrees * Track anchors and note commitment trees in Chain Append note commitments to their trees when doing update_chain_state_with, then use the resulting Sapling and Orchard roots to pass to history_tree, and add new roots to the anchor sets. * Handle errors when appending to note commitment trees * Add comments explaining why note commitment are not removed from the tree in revert_chain_state_with * Implementing note commitments in finalized state * Finish serialization of Orchard tree; remove old tree when updating finalize state * Add serialization and finalized state updates for Sprout and Sapling trees * Partially handle trees in non-finalized state. Use Option for trees in Chain * Rebuild trees when forking; change finalized state tree getters to not require height * Pass empty trees to tests; use empty trees by default in Chain * Also rebuild anchor sets when forking * Use empty tree as default in finalized state tree getters (for now) * Use HashMultiSet for anchors in order to make pop_root() work correctly * Reduce DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES and MAX_PARTIAL_CHAIN_BLOCKS * Reduce DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES and MAX_PARTIAL_CHAIN_BLOCKS even more * Apply suggestions from code review * Add comments about order of note commitments and related methods/fields * Don't use Option for trees * Set DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES=1 and restore MAX_PARTIAL_CHAIN_BLOCKS * Remove unneeded anchor set rebuilding in fork() * Improve proptest formatting * Add missing comparisons to eq_internal_state * Renamed sprout::tree::NoteCommitmentTree::hash() to root() * Improve comments * Add asserts, add issues to TODOs * Remove impl Default for Chain since it was only used by tests * Improve documentation and assertions; add tree serialization tests * Remove Sprout code, which will be moved to another branch * Add todo! in Sprout tree append() * Remove stub request, response *Anchor* handling for now * Add test for validating Sapling note commitment tree using test blocks * Increase database version (new columns added for note commitment trees and anchors) * Update test to make sure the order of sapling_note_commitments() is being tested * Improve comments and structure of the test * Improve variable names again * Rustfmt Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> Co-authored-by: Conrado P. L. Gouvea <conradoplg@gmail.com> Co-authored-by: Conrado Gouvea <conrado@zfnd.org> Co-authored-by: teor <teor@riseup.net>
2021-07-29 06:37:18 -07:00
#[error("error in Sapling note commitment tree")]
SaplingNoteCommitmentTreeError(#[from] zebra_chain::sapling::tree::NoteCommitmentTreeError),
#[error("error in Orchard note commitment tree")]
OrchardNoteCommitmentTreeError(#[from] zebra_chain::orchard::tree::NoteCommitmentTreeError),
}
/// Trait for creating the corresponding duplicate nullifier error from a nullifier.
pub(crate) trait DuplicateNullifierError {
/// Returns the corresponding duplicate nullifier error for `self`.
fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError;
}
impl DuplicateNullifierError for sprout::Nullifier {
fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
ValidateContextError::DuplicateSproutNullifier {
nullifier: *self,
in_finalized_state,
}
}
}
Reject duplicate Sapling and Orchard nullifiers (#2497) * Add sapling and orchard duplicate nullifier errors * Reject duplicate finalized sapling and orchard nullifiers Reject duplicate sapling and orchard nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is already in the finalized state. * Reject duplicate non-finalized sapling and orchard nullifiers Reject duplicate sapling and orchard nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is in: * the same shielded data, * the same transaction, * the same block, or * an earlier block in the non-finalized chain. * Refactor sprout nullifier tests to remove common code * Add sapling nullifier tests Test that the state rejects duplicate sapling nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is in: * the same shielded data, * the same transaction, * the same block, * an earlier block in the non-finalized chain, or * the finalized state. * Add orchard nullifier tests Test that the state rejects duplicate orchard nullifiers in a new block, when the block is added to a non-finalized chain, and the duplicate nullifier is in: * the same shielded data, * the same transaction, * the same block, * an earlier block in the non-finalized chain, or * the finalized state. * Check for specific nullifiers in the state in tests * Replace slices with vectors in arguments * Remove redundant code and variables * Simplify sapling TransferData tests Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Remove an extra : * Remove redundant vec! Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-19 17:39:05 -07:00
impl DuplicateNullifierError for sapling::Nullifier {
fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
ValidateContextError::DuplicateSaplingNullifier {
nullifier: *self,
in_finalized_state,
}
}
}
impl DuplicateNullifierError for orchard::Nullifier {
fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
ValidateContextError::DuplicateOrchardNullifier {
nullifier: *self,
in_finalized_state,
}
}
}