• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ergoplatform / sigma-rust / 8953175335

04 May 2024 08:51PM UTC coverage: 80.473% (+0.1%) from 80.331%
8953175335

Pull #736

github

web-flow
Merge 0fdf2d258 into 57a105462
Pull Request #736: Transaction Validation

165 of 228 new or added lines in 15 files covered. (72.37%)

8 existing lines in 2 files now uncovered.

10723 of 13325 relevant lines covered (80.47%)

3.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.55
/ergo-lib/src/chain/transaction/ergo_transaction.rs
1
//! Exposes common properties for signed and unsigned transactions
2
use ergotree_interpreter::{
3
    eval::context::TxIoVec,
4
    sigma_protocol::{
5
        prover::ContextExtension,
6
        verifier::{VerificationResult, VerifierError},
7
    },
8
};
9
use ergotree_ir::{
10
    chain::{
11
        ergo_box::{box_value::BoxValue, BoxId, ErgoBox},
12
        token::{TokenAmountError, TokenId},
13
    },
14
    serialization::SigmaSerializationError,
15
};
16
use itertools::Itertools;
17
use thiserror::Error;
18

19
use crate::wallet::tx_context::TransactionContextError;
20

21
use super::{unsigned::UnsignedTransaction, DataInput, Transaction};
22

23
/// Errors when validating transaction
24
#[derive(Error, Debug)]
25
pub enum TxValidationError {
26
    /// Transaction has more than [`i16::MAX`] inputs
27
    #[error("Sum of ERG in outputs overflowed")]
28
    /// Sum of ERG in outputs has overflowed
29
    OutputSumOverflow,
30
    /// Sum of ERG in inputs has overflowed
31
    #[error("Sum of ERG in inputs has overflowed")]
32
    InputSumOverflow,
33
    /// Token Amount Error
34
    #[error("Token amount is not valid, {0}")]
35
    TokenAmountError(#[from] TokenAmountError),
36
    #[error("Unique inputs: {0}, actual inputs: {1}")]
37
    /// The transaction is attempting to spend the same [`BoxId`] twice
38
    DoubleSpend(usize, usize),
39
    #[error("ERG value not preserved, input amount: {0}, output amount: {1}")]
40
    /// The amount of Ergo in inputs must be equal to the amount of ergo in output (cannot be burned)
41
    ErgPreservationError(u64, u64),
42
    #[error("Token preservation error for {token_id:?}, in amount: {in_amount:?}, out_amount: {out_amount:?}, allowed new token id: {new_token_id:?}")]
43
    /// Transaction is creating more tokens than exists in inputs. This is only allowed when minting a new token
44
    TokenPreservationError {
45
        /// If the transaction is minting a new token, then it must have this token id
46
        new_token_id: TokenId,
47
        /// The token id whose amount was not preserved
48
        token_id: TokenId,
49
        /// Total amount of token in inputs
50
        in_amount: u64,
51
        /// Total amount of token in outputs
52
        out_amount: u64,
53
    },
54
    #[error("Output {0} is dust, amount {1:?} < minimum {2}")]
55
    /// Transaction was creating a dust output. The value of a box should be >= than box size * [Parameters::min_value_per_byte](crate::chain::parameters::Parameters::min_value_per_byte())
56
    DustOutput(BoxId, BoxValue, u64),
57
    #[error("Creation height {0} > preheader height")]
58
    /// The output's height is greater than the current block height
59
    InvalidHeightError(u32),
60
    #[error("Creation height {0} <= input box max height{1}")]
61
    /// After Block V3, all output boxes height must be >= max(inputs.height). See <https://github.com/ergoplatform/eips/blob/master/eip-0039.md> for more information
62
    MonotonicHeightError(u32, u32),
63
    #[error("Output box's creation height is negative (not allowed after block version 1)")]
64
    /// Negative heights are not allowed after block v1.
65
    /// When using sigma-rust where heights are always unsigned, this error may be because creation height was set to be >= 2147483648
66
    NegativeHeight,
67
    #[error("Output box size {0} > maximum {}", ErgoBox::MAX_BOX_SIZE)]
68
    /// Box size is > [ErgoBox::MAX_BOX_SIZE]
69
    BoxSizeExceeded(usize),
70
    #[error("Output box size {0} > maximum {}", ErgoBox::MAX_SCRIPT_SIZE)]
71
    /// Script size is > [ErgoBox::MAX_SCRIPT_SIZE]
72
    ScriptSizeExceeded(usize),
73
    #[error("TX context error: {0}")]
74
    /// Transaction Context Error
75
    TransactionContextError(#[from] TransactionContextError),
76
    /// Input's proposition reduced to false. This means the proof provided for the input was most likely invalid
77
    #[error("Input {0} reduced to false during verification: {1:?}")]
78
    ReducedToFalse(usize, VerificationResult),
79
    /// Serialization error
80
    #[error("Sigma serialization error: {0}")]
81
    SigmaSerializationError(#[from] SigmaSerializationError),
82
    /// Verifying input script failed
83
    #[error("Verifier error on input {0}: {1}")]
84
    VerifierError(usize, VerifierError),
85
}
86

87
/// Exposes common properties for signed and unsigned transactions
88
pub trait ErgoTransaction {
89
    /// input boxes ids
90
    fn inputs_ids(&self) -> TxIoVec<BoxId>;
91
    /// data input boxes
92
    fn data_inputs(&self) -> Option<TxIoVec<DataInput>>;
93
    /// output boxes
94
    fn outputs(&self) -> TxIoVec<ErgoBox>;
95
    /// ContextExtension for the given input index
96
    fn context_extension(&self, input_index: usize) -> Option<ContextExtension>;
97

98
    /// Stateless transaction validation (no blockchain context) for a transaction
99
    /// Returns [`Ok(())`] if validation has succeeded or returns [`TxValidationError`]
100
    fn validate_stateless(&self) -> Result<(), TxValidationError> {
1✔
101
        // Note that we don't need to check if inputs/data inputs/outputs are >= 1 <= 32767 here since BoundedVec takes care of that
102
        let inputs = self.inputs_ids();
1✔
103
        let outputs = self.outputs();
1✔
104

105
        outputs
3✔
106
            .iter()
107
            .try_fold(0i64, |a, b| a.checked_add(b.value.as_i64()))
2✔
108
            .ok_or(TxValidationError::OutputSumOverflow)?;
1✔
109

110
        // Check if there are no double-spends in input (one BoxId being spent more than once)
111
        let unique_count = inputs.iter().unique().count();
2✔
112
        if unique_count != inputs.len() {
1✔
NEW
113
            return Err(TxValidationError::DoubleSpend(unique_count, inputs.len()));
×
114
        }
115
        Ok(())
1✔
116
    }
117
}
118

119
impl ErgoTransaction for UnsignedTransaction {
120
    fn inputs_ids(&self) -> TxIoVec<BoxId> {
1✔
121
        self.inputs.clone().mapped(|input| input.box_id)
3✔
122
    }
123

124
    fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
1✔
125
        self.data_inputs.clone()
1✔
126
    }
127

128
    fn outputs(&self) -> TxIoVec<ErgoBox> {
1✔
129
        #[allow(clippy::unwrap_used)] // box serialization cannot fail?
130
        self.output_candidates
2✔
131
            .clone()
132
            .enumerated()
133
            .try_mapped(|(idx, b)| ErgoBox::from_box_candidate(&b, self.id(), idx as u16))
3✔
134
            .unwrap()
135
    }
136

137
    fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
1✔
138
        self.inputs
2✔
139
            .get(input_index)
140
            .map(|input| input.extension.clone())
4✔
141
    }
142
}
143

144
impl ErgoTransaction for Transaction {
145
    fn inputs_ids(&self) -> TxIoVec<BoxId> {
1✔
146
        self.inputs.clone().mapped(|input| input.box_id)
3✔
147
    }
148

149
    fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
1✔
150
        self.data_inputs.clone()
1✔
151
    }
152

153
    fn outputs(&self) -> TxIoVec<ErgoBox> {
1✔
154
        self.outputs.clone()
1✔
155
    }
156

157
    fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
1✔
158
        self.inputs
1✔
159
            .get(input_index)
160
            .map(|input| input.spending_proof.extension.clone())
2✔
161
    }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc