use tari_common_types::{epoch::VnEpoch, types::HashOutput};
use tari_node_components::blocks::{BlockHeaderValidationError, BlockValidationError};
use tari_sidechain::SidechainProofValidationError;
use tari_transaction_components::{
BanPeriod,
BanReason,
tari_proof_of_work::{DifficultyError, PowError},
transaction_components::{OutputType, TransactionError, covenants::CovenantError},
validation::AggregatedBodyValidationError,
};
use tari_utilities::ByteArrayError;
use thiserror::Error;
use crate::{
chain_storage::ChainStorageError,
proof_of_work::{cuckaroo_pow::CuckarooVerificationError, monero_rx::MergeMineError},
};
#[derive(Debug, Error)]
pub enum ValidationError {
#[error("Serialization failed: {0}")]
SerializationError(String),
#[error("Block header validation failed: {0}")]
BlockHeaderError(#[from] BlockHeaderValidationError),
#[error("Block validation error: {0}")]
BlockError(#[from] BlockValidationError),
#[error("Contains kernels or inputs that are not yet spendable")]
MaturityError,
#[error("The block weight ({actual_weight}) is above the maximum ({max_weight})")]
BlockTooLarge { actual_weight: u64, max_weight: u64 },
#[error("Contains {} unknown inputs", .0.len())]
UnknownInputs(Vec<HashOutput>),
#[error("Contains an unknown input")]
UnknownInput,
#[error("The transaction is invalid: {0}")]
TransactionError(#[from] TransactionError),
#[error("Fatal storage error during validation: {0}")]
FatalStorageError(String),
#[error(
"The total expected supply plus the total accumulated (offset) excess does not equal the sum of all UTXO \
commitments."
)]
InvalidAccountingBalance,
#[error("Transaction contains already spent inputs")]
ContainsSTxO,
#[error("Transaction contains an output commitment that already exists")]
ContainsDuplicateUtxoCommitment,
#[error("Final state validation failed: The UTXO set did not balance with the expected emission at height {0}")]
ChainBalanceValidationFailed(u64),
#[error("The total value + fees of the block exceeds the maximum allowance on chain")]
CoinbaseExceedsMaxLimit,
#[error("Proof of work error: {0}")]
ProofOfWorkError(#[from] PowError),
#[error("Attempted to validate genesis block")]
ValidatingGenesis,
#[error("Duplicate or unsorted input found in block body")]
UnsortedOrDuplicateInput,
#[error("Duplicate or unsorted output found in block body")]
UnsortedOrDuplicateOutput,
#[error("Error in merge mine data:{0}")]
MergeMineError(#[from] MergeMineError),
#[error("Maximum transaction weight exceeded")]
MaxTransactionWeightExceeded,
#[error("Expected block height to be {expected}, but was {block_height}")]
IncorrectHeight { expected: u64, block_height: u64 },
#[error("Expected block previous hash to be {expected}, but was {block_hash}")]
IncorrectPreviousHash { expected: String, block_hash: String },
#[error("Bad block with hash '{hash}' and reason '{reason}' found")]
BadBlockFound { hash: String, reason: String },
#[error("Consensus Error: {0}")]
ConsensusError(String),
#[error("Duplicate kernel error: {0}")]
DuplicateKernelError(String),
#[error("Missing kernel error: {0}")]
MissingKernelError(String),
#[error("Header height mismatch error: {0}")]
HeaderHeightMismatch(String),
#[error("Header hash mismatch error: {0}")]
HeaderHashMismatch(String),
#[error("Missing output error: {0}")]
MissingOutputError(String),
#[error("Input spent before mined error: {0}")]
InputSpentBeforeMined(String),
#[error("Covenant failed to validate: {0}")]
CovenantError(#[from] CovenantError),
#[error("Invalid or unsupported blockchain version {version}")]
InvalidBlockchainVersion { version: u16 },
#[error("Contains Invalid Burn: {0}")]
InvalidBurnError(String),
#[error("Validator node registration signature failed verification")]
IncorrectNumberOfTimestampsProvided { expected: u64, actual: u64 },
#[error("Invalid difficulty: {0}")]
DifficultyError(#[from] DifficultyError),
#[error("Invalid Serialized Public key: {0}")]
InvalidSerializedPublicKey(String),
#[error("Sidechain proof invalid: `{0}`")]
SidechainProofInvalid(#[from] SidechainProofValidationError),
#[error("Sidechain eviction proof submitted for unregistered validator {validator_pk}")]
SidechainEvictionProofValidatorNotFound { validator_pk: String },
#[error(
"Sidechain eviction proof invalid: given epoch {epoch} is greater than the epoch at tip height {tip_height}"
)]
SidechainEvictionProofInvalidEpoch { epoch: VnEpoch, tip_height: u64 },
#[error("Validator node already registered: {public_key}")]
ValidatorNodeAlreadyRegistered { public_key: String },
#[error("Validator node {public_key} not registered: {details}")]
ValidatorNodeNotRegistered { public_key: String, details: String },
#[error("Validator registration {public_key} invalid: max epoch {max_epoch} < current epoch {current_epoch}")]
ValidatorNodeRegistrationMaxEpoch {
public_key: String,
current_epoch: VnEpoch,
max_epoch: VnEpoch,
},
#[error("{output_type} output rule disallows the spend: {details}")]
OutputSpendRuleDisallow { output_type: OutputType, details: String },
#[error("Output type '{output_type}' does not match sidechain data")]
OutputTypeNotMatchSidechainData { output_type: OutputType, details: String },
#[error("Validation error: {0}")]
AggregatedBodyValidationError(#[from] AggregatedBodyValidationError),
#[error("Cuckaroo POW error: {0}")]
CuckarooPowError(#[from] CuckarooVerificationError),
}
impl From<ChainStorageError> for ValidationError {
fn from(err: ChainStorageError) -> Self {
Self::FatalStorageError(err.to_string())
}
}
impl From<ByteArrayError> for ValidationError {
fn from(err: ByteArrayError) -> Self {
Self::InvalidSerializedPublicKey(err.to_string())
}
}
impl ValidationError {
pub fn get_ban_reason(&self) -> Option<BanReason> {
match self {
ValidationError::ProofOfWorkError(e) => e.get_ban_reason(),
err @ ValidationError::SerializationError(_) |
err @ ValidationError::BlockHeaderError(_) |
err @ ValidationError::BlockError(_) |
err @ ValidationError::MaturityError |
err @ ValidationError::BlockTooLarge { .. } |
err @ ValidationError::UnknownInputs(_) |
err @ ValidationError::UnknownInput |
err @ ValidationError::TransactionError(_) |
err @ ValidationError::InvalidAccountingBalance |
err @ ValidationError::ContainsSTxO |
err @ ValidationError::ContainsDuplicateUtxoCommitment |
err @ ValidationError::ChainBalanceValidationFailed(_) |
err @ ValidationError::ValidatingGenesis |
err @ ValidationError::UnsortedOrDuplicateInput |
err @ ValidationError::UnsortedOrDuplicateOutput |
err @ ValidationError::MaxTransactionWeightExceeded |
err @ ValidationError::IncorrectHeight { .. } |
err @ ValidationError::IncorrectPreviousHash { .. } |
err @ ValidationError::BadBlockFound { .. } |
err @ ValidationError::ConsensusError(_) |
err @ ValidationError::DuplicateKernelError(_) |
err @ ValidationError::CovenantError(_) |
err @ ValidationError::InvalidBlockchainVersion { .. } |
err @ ValidationError::InvalidBurnError(_) |
err @ ValidationError::DifficultyError(_) |
err @ ValidationError::CoinbaseExceedsMaxLimit |
err @ ValidationError::InvalidSerializedPublicKey(_) |
err @ ValidationError::SidechainEvictionProofValidatorNotFound { .. } |
err @ ValidationError::SidechainProofInvalid(_) |
err @ ValidationError::SidechainEvictionProofInvalidEpoch { .. } |
err @ ValidationError::ValidatorNodeAlreadyRegistered { .. } |
err @ ValidationError::ValidatorNodeNotRegistered { .. } |
err @ ValidationError::ValidatorNodeRegistrationMaxEpoch { .. } |
err @ ValidationError::OutputTypeNotMatchSidechainData { .. } |
err @ ValidationError::AggregatedBodyValidationError(_) |
err @ ValidationError::CuckarooPowError(_) |
err @ ValidationError::OutputSpendRuleDisallow { .. } => Some(BanReason {
reason: err.to_string(),
ban_duration: BanPeriod::Long,
}),
ValidationError::MergeMineError(e) => e.get_ban_reason(),
ValidationError::FatalStorageError(_) |
ValidationError::IncorrectNumberOfTimestampsProvided { .. } |
ValidationError::MissingKernelError(_) |
ValidationError::MissingOutputError(_) |
ValidationError::InputSpentBeforeMined(_) |
ValidationError::HeaderHashMismatch(_) |
ValidationError::HeaderHeightMismatch(_) => None,
}
}
}