ergo_lib/chain/transaction/
ergo_transaction.rs1use ergotree_interpreter::sigma_protocol::{
3 prover::ContextExtension,
4 verifier::{VerificationResult, VerifierError},
5};
6use ergotree_ir::{
7 chain::{
8 ergo_box::{box_value::BoxValue, BoxId, ErgoBox},
9 token::{TokenAmountError, TokenId},
10 },
11 serialization::SigmaSerializationError,
12};
13use itertools::Itertools;
14use thiserror::Error;
15
16use crate::wallet::tx_context::TransactionContextError;
17
18use super::{unsigned::UnsignedTransaction, DataInput, Transaction};
19
20#[derive(Error, Debug)]
22pub enum TxValidationError {
23 #[error("Sum of ERG in outputs overflowed")]
25 OutputSumOverflow,
27 #[error("Sum of ERG in inputs has overflowed")]
29 InputSumOverflow,
30 #[error("Token amount is not valid, {0}")]
32 TokenAmountError(#[from] TokenAmountError),
33 #[error("Unique inputs: {0}, actual inputs: {1}")]
34 DoubleSpend(usize, usize),
36 #[error("ERG value not preserved, input amount: {0}, output amount: {1}")]
37 ErgPreservationError(u64, u64),
39 #[error("Token preservation error for {token_id:?}, in amount: {in_amount:?}, out_amount: {out_amount:?}, allowed new token id: {new_token_id:?}")]
40 TokenPreservationError {
42 new_token_id: TokenId,
44 token_id: TokenId,
46 in_amount: u64,
48 out_amount: u64,
50 },
51 #[error("Output {0} is dust, amount {1:?} < minimum {2}")]
52 DustOutput(BoxId, BoxValue, u64),
54 #[error("Creation height {0} > preheader height")]
55 InvalidHeightError(u32),
57 #[error("Creation height {0} <= input box max height{1}")]
58 MonotonicHeightError(u32, u32),
60 #[error("Output box's creation height is negative (not allowed after block version 1)")]
61 NegativeHeight,
64 #[error("Output box size {0} > maximum {}", ErgoBox::MAX_BOX_SIZE)]
65 BoxSizeExceeded(usize),
67 #[error("Output box size {0} > maximum {}", ErgoBox::MAX_SCRIPT_SIZE)]
68 ScriptSizeExceeded(usize),
70 #[error("TX context error: {0}")]
71 TransactionContextError(#[from] TransactionContextError),
73 #[error("Input {0} reduced to false during verification: {1:?}")]
75 ReducedToFalse(usize, VerificationResult),
76 #[error("Sigma serialization error: {0}")]
78 SigmaSerializationError(#[from] SigmaSerializationError),
79 #[error("Verifier error on input {0}: {1}")]
81 VerifierError(usize, VerifierError),
82}
83
84pub trait ErgoTransaction {
86 fn inputs_ids(&self) -> impl ExactSizeIterator<Item = BoxId>;
88 fn data_inputs(&self) -> Option<&[DataInput]>;
90 fn outputs(&self) -> &[ErgoBox];
92 fn context_extension(&self, input_index: usize) -> Option<ContextExtension>;
94
95 fn validate_stateless(&self) -> Result<(), TxValidationError> {
98 let inputs = self.inputs_ids();
100 let outputs = self.outputs();
101
102 outputs
103 .iter()
104 .try_fold(0i64, |a, b| a.checked_add(b.value.as_i64()))
105 .ok_or(TxValidationError::OutputSumOverflow)?;
106
107 let len = inputs.len();
109 let unique_count = inputs.unique().count();
110 if unique_count != len {
111 return Err(TxValidationError::DoubleSpend(unique_count, len));
112 }
113 Ok(())
114 }
115}
116
117impl ErgoTransaction for UnsignedTransaction {
118 fn inputs_ids(&self) -> impl ExactSizeIterator<Item = BoxId> {
119 self.inputs.iter().map(|input| input.box_id)
120 }
121
122 fn data_inputs(&self) -> Option<&[DataInput]> {
123 self.data_inputs.as_ref().map(|di| di.as_slice())
124 }
125
126 fn outputs(&self) -> &[ErgoBox] {
127 self.outputs.as_slice()
128 }
129
130 fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
131 self.inputs
132 .get(input_index)
133 .map(|input| input.extension.clone())
134 }
135}
136
137impl ErgoTransaction for Transaction {
138 fn inputs_ids(&self) -> impl ExactSizeIterator<Item = BoxId> {
139 self.inputs.iter().map(|input| input.box_id)
140 }
141
142 fn data_inputs(&self) -> Option<&[DataInput]> {
143 self.data_inputs.as_ref().map(|di| di.as_slice())
144 }
145
146 fn outputs(&self) -> &[ErgoBox] {
147 self.outputs.as_slice()
148 }
149
150 fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
151 self.inputs
152 .get(input_index)
153 .map(|input| input.spending_proof.extension.clone())
154 }
155}