ergo_lib/chain/transaction/
unsigned.rs1use 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#[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 pub inputs: TxIoVec<UnsignedInput>,
34 pub data_inputs: Option<TxIoVec<DataInput>>,
38 pub output_candidates: TxIoVec<ErgoBoxCandidate>,
40 pub(crate) outputs: TxIoVec<ErgoBox>,
41}
42
43impl UnsignedTransaction {
44 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 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)] 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 pub fn id(&self) -> TxId {
115 self.tx_id
116 }
117
118 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 pub fn distinct_token_ids(&self) -> IndexSet<TokenId> {
126 distinct_token_ids(self.output_candidates.clone())
127 }
128}
129
130#[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}