ergo_lib/chain/transaction/
unsigned.rs

1//! Unsigned (without proofs) transaction
2
3use super::input::UnsignedInput;
4
5use super::DataInput;
6use super::Transaction;
7use super::TxIoVec;
8use super::{distinct_token_ids, TransactionError};
9use bounded_vec::BoundedVec;
10use ergo_chain_types::blake2b256_hash;
11
12use ergotree_ir::chain::ergo_box::ErgoBox;
13use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
14use ergotree_ir::chain::token::TokenId;
15use ergotree_ir::chain::tx_id::TxId;
16use ergotree_ir::serialization::SigmaSerializationError;
17use indexmap::IndexSet;
18use std::convert::TryInto;
19
20/// Unsigned (inputs without proofs) transaction
21#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(
23    feature = "json",
24    serde(
25        try_from = "crate::chain::json::transaction::UnsignedTransactionJson",
26        into = "crate::chain::json::transaction::UnsignedTransactionJson"
27    )
28)]
29#[derive(PartialEq, Eq, Debug, Clone)]
30pub struct UnsignedTransaction {
31    tx_id: TxId,
32    /// unsigned inputs, that will be spent by this transaction.
33    pub inputs: TxIoVec<UnsignedInput>,
34    /// inputs, that are not going to be spent by transaction, but will be reachable from inputs
35    /// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
36    /// included in transaction cost and they do not contain spending proofs.
37    pub data_inputs: Option<TxIoVec<DataInput>>,
38    /// box candidates to be created by this transaction
39    pub output_candidates: TxIoVec<ErgoBoxCandidate>,
40    pub(crate) outputs: TxIoVec<ErgoBox>,
41}
42
43impl UnsignedTransaction {
44    /// Creates new transaction from vectors
45    pub fn new_from_vec(
46        inputs: Vec<UnsignedInput>,
47        data_inputs: Vec<DataInput>,
48        output_candidates: Vec<ErgoBoxCandidate>,
49    ) -> Result<UnsignedTransaction, TransactionError> {
50        Ok(UnsignedTransaction::new(
51            inputs
52                .try_into()
53                .map_err(TransactionError::InvalidInputsCount)?,
54            BoundedVec::opt_empty_vec(data_inputs)
55                .map_err(TransactionError::InvalidDataInputsCount)?,
56            output_candidates
57                .try_into()
58                .map_err(TransactionError::InvalidOutputCandidatesCount)?,
59        )?)
60    }
61
62    /// Creates new transaction
63    pub fn new(
64        inputs: TxIoVec<UnsignedInput>,
65        data_inputs: Option<TxIoVec<DataInput>>,
66        output_candidates: TxIoVec<ErgoBoxCandidate>,
67    ) -> Result<UnsignedTransaction, SigmaSerializationError> {
68        #[allow(clippy::unwrap_used)] // box serialization cannot fail
69        let outputs = output_candidates
70            .iter()
71            .enumerate()
72            .map(|(idx, b)| ErgoBox::from_box_candidate(b, TxId::zero(), idx as u16).unwrap())
73            .collect::<Vec<_>>()
74            .try_into()
75            .unwrap();
76
77        let tx_to_sign = UnsignedTransaction {
78            tx_id: TxId::zero(),
79            inputs,
80            data_inputs,
81            output_candidates,
82            outputs,
83        };
84        let tx_id = tx_to_sign.calc_tx_id()?;
85
86        let outputs = tx_to_sign
87            .output_candidates
88            .clone()
89            .enumerated()
90            .try_mapped_ref(|(idx, bc)| ErgoBox::from_box_candidate(bc, tx_id, *idx as u16))?;
91
92        Ok(UnsignedTransaction {
93            tx_id,
94            outputs,
95            ..tx_to_sign
96        })
97    }
98
99    fn calc_tx_id(&self) -> Result<TxId, SigmaSerializationError> {
100        let bytes = self.bytes_to_sign()?;
101        Ok(TxId(blake2b256_hash(&bytes)))
102    }
103
104    fn to_tx_without_proofs(&self) -> Result<Transaction, SigmaSerializationError> {
105        let empty_proofs_input = self.inputs.mapped_ref(|ui| ui.input_to_sign());
106        Transaction::new(
107            empty_proofs_input,
108            self.data_inputs.clone(),
109            self.output_candidates.clone(),
110        )
111    }
112
113    /// Get transaction id
114    pub fn id(&self) -> TxId {
115        self.tx_id
116    }
117
118    /// message to be signed by the [`ergotree_interpreter::sigma_protocol::prover::Prover`] (serialized tx)
119    pub fn bytes_to_sign(&self) -> Result<Vec<u8>, SigmaSerializationError> {
120        let tx = self.to_tx_without_proofs()?;
121        tx.bytes_to_sign()
122    }
123
124    /// Returns distinct token ids from all output_candidates
125    pub fn distinct_token_ids(&self) -> IndexSet<TokenId> {
126        distinct_token_ids(self.output_candidates.clone())
127    }
128}
129
130/// Arbitrary impl
131#[cfg(feature = "arbitrary")]
132#[allow(clippy::unwrap_used)]
133pub mod arbitrary {
134    use super::*;
135
136    use proptest::prelude::*;
137    use proptest::{arbitrary::Arbitrary, collection::vec};
138
139    impl Arbitrary for UnsignedTransaction {
140        type Parameters = ();
141
142        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
143            (
144                vec(any::<UnsignedInput>(), 1..10),
145                vec(any::<DataInput>(), 0..10),
146                vec(any::<ErgoBoxCandidate>(), 1..10),
147            )
148                .prop_map(|(inputs, data_inputs, outputs)| {
149                    Self::new_from_vec(inputs, data_inputs, outputs).unwrap()
150                })
151                .boxed()
152        }
153        type Strategy = BoxedStrategy<Self>;
154    }
155}
156
157#[cfg(test)]
158#[allow(clippy::unwrap_used, clippy::panic)]
159pub mod tests {
160    use super::*;
161
162    use proptest::prelude::*;
163
164    proptest! {
165
166        #![proptest_config(ProptestConfig::with_cases(16))]
167
168        #[test]
169        fn test_unsigned_tx_bytes_to_sign(v in any::<UnsignedTransaction>()) {
170            prop_assert!(!v.bytes_to_sign().unwrap().is_empty());
171        }
172
173    }
174}