use ergotree_interpreter::sigma_protocol::{
prover::ContextExtension,
verifier::{VerificationResult, VerifierError},
};
use ergotree_ir::{
chain::{
ergo_box::{box_value::BoxValue, BoxId, ErgoBox},
token::{TokenAmountError, TokenId},
},
serialization::SigmaSerializationError,
};
use itertools::Itertools;
use thiserror::Error;
use crate::wallet::tx_context::TransactionContextError;
use super::{unsigned::UnsignedTransaction, DataInput, Transaction};
#[derive(Error, Debug)]
pub enum TxValidationError {
#[error("Sum of ERG in outputs overflowed")]
OutputSumOverflow,
#[error("Sum of ERG in inputs has overflowed")]
InputSumOverflow,
#[error("Token amount is not valid, {0}")]
TokenAmountError(#[from] TokenAmountError),
#[error("Unique inputs: {0}, actual inputs: {1}")]
DoubleSpend(usize, usize),
#[error("ERG value not preserved, input amount: {0}, output amount: {1}")]
ErgPreservationError(u64, u64),
#[error("Token preservation error for {token_id:?}, in amount: {in_amount:?}, out_amount: {out_amount:?}, allowed new token id: {new_token_id:?}")]
TokenPreservationError {
new_token_id: TokenId,
token_id: TokenId,
in_amount: u64,
out_amount: u64,
},
#[error("Output {0} is dust, amount {1:?} < minimum {2}")]
DustOutput(BoxId, BoxValue, u64),
#[error("Creation height {0} > preheader height")]
InvalidHeightError(u32),
#[error("Creation height {0} <= input box max height{1}")]
MonotonicHeightError(u32, u32),
#[error("Output box's creation height is negative (not allowed after block version 1)")]
NegativeHeight,
#[error("Output box size {0} > maximum {}", ErgoBox::MAX_BOX_SIZE)]
BoxSizeExceeded(usize),
#[error("Output box size {0} > maximum {}", ErgoBox::MAX_SCRIPT_SIZE)]
ScriptSizeExceeded(usize),
#[error("TX context error: {0}")]
TransactionContextError(#[from] TransactionContextError),
#[error("Input {0} reduced to false during verification: {1:?}")]
ReducedToFalse(usize, VerificationResult),
#[error("Sigma serialization error: {0}")]
SigmaSerializationError(#[from] SigmaSerializationError),
#[error("Verifier error on input {0}: {1}")]
VerifierError(usize, VerifierError),
}
pub trait ErgoTransaction {
fn inputs_ids(&self) -> impl ExactSizeIterator<Item = BoxId>;
fn data_inputs(&self) -> Option<&[DataInput]>;
fn outputs(&self) -> &[ErgoBox];
fn context_extension(&self, input_index: usize) -> Option<ContextExtension>;
fn validate_stateless(&self) -> Result<(), TxValidationError> {
let inputs = self.inputs_ids();
let outputs = self.outputs();
outputs
.iter()
.try_fold(0i64, |a, b| a.checked_add(b.value.as_i64()))
.ok_or(TxValidationError::OutputSumOverflow)?;
let len = inputs.len();
let unique_count = inputs.unique().count();
if unique_count != len {
return Err(TxValidationError::DoubleSpend(unique_count, len));
}
Ok(())
}
}
impl ErgoTransaction for UnsignedTransaction {
fn inputs_ids(&self) -> impl ExactSizeIterator<Item = BoxId> {
self.inputs.iter().map(|input| input.box_id)
}
fn data_inputs(&self) -> Option<&[DataInput]> {
self.data_inputs.as_ref().map(|di| di.as_slice())
}
fn outputs(&self) -> &[ErgoBox] {
self.outputs.as_slice()
}
fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
self.inputs
.get(input_index)
.map(|input| input.extension.clone())
}
}
impl ErgoTransaction for Transaction {
fn inputs_ids(&self) -> impl ExactSizeIterator<Item = BoxId> {
self.inputs.iter().map(|input| input.box_id)
}
fn data_inputs(&self) -> Option<&[DataInput]> {
self.data_inputs.as_ref().map(|di| di.as_slice())
}
fn outputs(&self) -> &[ErgoBox] {
self.outputs.as_slice()
}
fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
self.inputs
.get(input_index)
.map(|input| input.spending_proof.extension.clone())
}
}