Skip to main content

ergo_lib_c_core/
transaction.rs

1//! Ergo transaction
2
3use std::convert::{TryFrom, TryInto};
4
5use ergo_lib::{
6    chain,
7    ergo_chain_types::{Base16DecodedBytes, Base16EncodedBytes},
8};
9
10use crate::{
11    collections::{Collection, CollectionPtr, ConstCollectionPtr},
12    data_input::DataInput,
13    ergo_box::{ErgoBox, ErgoBoxCandidate},
14    ergo_state_ctx::ConstErgoStateContextPtr,
15    input::{Input, UnsignedInput},
16    json::{TransactionJsonEip12, UnsignedTransactionJsonEip12},
17    reduced::ConstPropositionsPtr,
18    util::{const_ptr_as_ref, mut_ptr_as_mut, ByteArray},
19    Error,
20};
21
22/// CommitmentHint
23pub struct CommitmentHint(
24    pub(crate) ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::CommitmentHint,
25);
26pub type CommitmentHintPtr = *mut CommitmentHint;
27pub type ConstCommitmentHintPtr = *const CommitmentHint;
28
29/// `HintsBag`
30pub struct HintsBag(
31    pub(crate) ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::HintsBag,
32);
33pub type HintsBagPtr = *mut HintsBag;
34pub type ConstHintsBagPtr = *const HintsBag;
35
36/// Empty HintsBag
37pub unsafe fn hints_bag_empty(hints_bag_out: *mut HintsBagPtr) -> Result<(), Error> {
38    let hints_bag_out = mut_ptr_as_mut(hints_bag_out, "hints_bag_out")?;
39    *hints_bag_out = Box::into_raw(Box::new(HintsBag(
40        ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::HintsBag::empty(),
41    )));
42    Ok(())
43}
44
45/// Add commitment hint to the bag
46pub unsafe fn hints_bag_add_commitment(
47    hints_bag_mut: HintsBagPtr,
48    hint_ptr: ConstCommitmentHintPtr,
49) -> Result<(), Error> {
50    let hints_bag_mut = mut_ptr_as_mut(hints_bag_mut, "hints_bag_mut")?;
51    let hint = const_ptr_as_ref(hint_ptr, "hint_ptr")?;
52    hints_bag_mut.0.add_hint(
53        ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::Hint::CommitmentHint(
54            hint.0.clone(),
55        ),
56    );
57    Ok(())
58}
59
60/// Length of HintsBag
61pub unsafe fn hints_bag_len(hints_bag_ptr: ConstHintsBagPtr) -> Result<usize, Error> {
62    let hints_bag = const_ptr_as_ref(hints_bag_ptr, "hints_bag_ptr")?;
63    Ok(hints_bag.0.hints.len())
64}
65
66/// Get commitment
67pub unsafe fn hints_bag_get(
68    hints_bag_ptr: ConstHintsBagPtr,
69    index: usize,
70    hint_out: *mut CommitmentHintPtr,
71) -> Result<bool, Error> {
72    let hints_bag = const_ptr_as_ref(hints_bag_ptr, "hints_bag_ptr")?;
73    let hint_out = mut_ptr_as_mut(hint_out, "hint_out")?;
74    if let Some(commitment) = hints_bag.0.commitments().get(index) {
75        *hint_out = Box::into_raw(Box::new(CommitmentHint(commitment.clone())));
76        return Ok(true);
77    }
78    Ok(false)
79}
80
81/// TransactionHintsBag
82pub struct TransactionHintsBag(pub(crate) ergo_lib::wallet::multi_sig::TransactionHintsBag);
83pub type TransactionHintsBagPtr = *mut TransactionHintsBag;
84pub type ConstTransactionHintsBagPtr = *const TransactionHintsBag;
85
86/// Empty TransactionHintsBag
87pub unsafe fn transaction_hints_bag_empty(
88    transaction_hints_bag_out: *mut TransactionHintsBagPtr,
89) -> Result<(), Error> {
90    let transaction_hints_bag_out =
91        mut_ptr_as_mut(transaction_hints_bag_out, "transaction_hints_bag_out")?;
92    *transaction_hints_bag_out = Box::into_raw(Box::new(TransactionHintsBag(
93        ergo_lib::wallet::multi_sig::TransactionHintsBag::empty(),
94    )));
95    Ok(())
96}
97
98/// Adding hints for input
99pub unsafe fn transaction_hints_bag_add_hints_for_input(
100    transaction_hints_bag_mut: TransactionHintsBagPtr,
101    index: usize,
102    hints_bag_ptr: ConstHintsBagPtr,
103) -> Result<(), Error> {
104    let transaction_hints_bag_mut =
105        mut_ptr_as_mut(transaction_hints_bag_mut, "transaction_hints_bag_mut")?;
106    let hints_bag = const_ptr_as_ref(hints_bag_ptr, "hints_bag_ptr")?;
107    transaction_hints_bag_mut
108        .0
109        .add_hints_for_input(index, hints_bag.0.clone());
110    Ok(())
111}
112
113/// Get HintsBag corresponding to input index
114pub unsafe fn transaction_hints_bag_all_hints_for_input(
115    transaction_hints_bag_ptr: ConstTransactionHintsBagPtr,
116    index: usize,
117    hints_bag_out: *mut HintsBagPtr,
118) -> Result<(), Error> {
119    let transaction_hints_bag =
120        const_ptr_as_ref(transaction_hints_bag_ptr, "transaction_hints_bag_ptr")?;
121    let hints_bag_out = mut_ptr_as_mut(hints_bag_out, "hints_bag_out")?;
122    *hints_bag_out = Box::into_raw(Box::new(HintsBag(
123        transaction_hints_bag.0.all_hints_for_input(index),
124    )));
125    Ok(())
126}
127
128/// Extract hints from signed transaction
129pub unsafe fn transaction_extract_hints(
130    signed_transaction_ptr: ConstTransactionPtr,
131    state_context_ptr: ConstErgoStateContextPtr,
132    boxes_to_spend_ptr: ConstCollectionPtr<ErgoBox>,
133    data_boxes_ptr: ConstCollectionPtr<ErgoBox>,
134    real_propositions_ptr: ConstPropositionsPtr,
135    simulated_propositions_ptr: ConstPropositionsPtr,
136    transaction_hints_bag_out: *mut TransactionHintsBagPtr,
137) -> Result<(), Error> {
138    let signed_transaction = const_ptr_as_ref(signed_transaction_ptr, "signed_transaction_ptr")?;
139    let state_context = const_ptr_as_ref(state_context_ptr, "state_context_ptr")?;
140    let boxes_to_spend = const_ptr_as_ref(boxes_to_spend_ptr, "boxes_to_spend_ptr")?;
141    let data_boxes = const_ptr_as_ref(data_boxes_ptr, "data_boxes_ptr")?;
142    let real_propositions = const_ptr_as_ref(real_propositions_ptr, "real_propositions_ptr")?;
143    let simulated_propositions =
144        const_ptr_as_ref(simulated_propositions_ptr, "simulated_propositions_ptr")?;
145    let transaction_hints_bag_out =
146        mut_ptr_as_mut(transaction_hints_bag_out, "transaction_hints_bag_out")?;
147
148    let boxes_to_spend = boxes_to_spend.0.clone().into_iter().map(|x| x.0).collect();
149    let data_boxes = data_boxes.0.clone().into_iter().map(|x| x.0).collect();
150    let tx_context = ergo_lib::wallet::signing::TransactionContext::new(
151        signed_transaction.0.clone(),
152        boxes_to_spend,
153        data_boxes,
154    )?;
155
156    *transaction_hints_bag_out = Box::into_raw(Box::new(TransactionHintsBag(
157        ergo_lib::wallet::multi_sig::extract_hints(
158            &tx_context,
159            &state_context.0.clone(),
160            real_propositions.0.clone(),
161            simulated_propositions.0.clone(),
162        )?,
163    )));
164    Ok(())
165}
166
167/// Unsigned (inputs without proofs) transaction
168#[derive(PartialEq, Eq, Debug, Clone)]
169pub struct UnsignedTransaction(pub(crate) chain::transaction::unsigned::UnsignedTransaction);
170pub type UnsignedTransactionPtr = *mut UnsignedTransaction;
171pub type ConstUnsignedTransactionPtr = *const UnsignedTransaction;
172
173/// Get id for transaction
174pub unsafe fn unsigned_tx_id(
175    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
176    tx_id_out: *mut TxIdPtr,
177) -> Result<(), Error> {
178    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
179    let tx_id_out = mut_ptr_as_mut(tx_id_out, "tx_id_out")?;
180    *tx_id_out = Box::into_raw(Box::new(TxId(unsigned_tx.0.id())));
181    Ok(())
182}
183
184/// Inputs for transaction
185pub unsafe fn unsigned_tx_inputs(
186    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
187    unsigned_inputs_out: *mut CollectionPtr<UnsignedInput>,
188) -> Result<(), Error> {
189    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
190    let unsigned_inputs_out = mut_ptr_as_mut(unsigned_inputs_out, "unsigned_inputs_out")?;
191    *unsigned_inputs_out = Box::into_raw(Box::new(Collection(
192        unsigned_tx
193            .0
194            .inputs
195            .as_vec()
196            .clone()
197            .into_iter()
198            .map(UnsignedInput)
199            .collect(),
200    )));
201    Ok(())
202}
203
204/// Data inputs for transaction
205pub unsafe fn unsigned_tx_data_inputs(
206    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
207    data_inputs_out: *mut CollectionPtr<DataInput>,
208) -> Result<(), Error> {
209    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
210    let data_inputs_out = mut_ptr_as_mut(data_inputs_out, "data_inputs_out")?;
211    *data_inputs_out = Box::into_raw(Box::new(Collection(
212        unsigned_tx
213            .0
214            .data_inputs
215            .as_ref()
216            .map(|v| v.as_vec().clone())
217            .unwrap_or_else(Vec::new)
218            .into_iter()
219            .map(DataInput)
220            .collect(),
221    )));
222    Ok(())
223}
224
225/// Output candidates for transaction
226pub unsafe fn unsigned_tx_output_candidates(
227    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
228    ergo_box_candidates_out: *mut CollectionPtr<ErgoBoxCandidate>,
229) -> Result<(), Error> {
230    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
231    let ergo_box_candidates_out =
232        mut_ptr_as_mut(ergo_box_candidates_out, "ergo_box_candidates_out")?;
233    *ergo_box_candidates_out = Box::into_raw(Box::new(Collection(
234        unsigned_tx
235            .0
236            .output_candidates
237            .iter()
238            .map(|ebc| ErgoBoxCandidate(ebc.clone()))
239            .collect(),
240    )));
241    Ok(())
242}
243
244/// Parse from JSON. Supports Ergo Node/Explorer API and box values and token amount encoded as
245/// strings
246pub unsafe fn unsigned_tx_from_json(
247    json: &str,
248    unsigned_tx_out: *mut UnsignedTransactionPtr,
249) -> Result<(), Error> {
250    let unsigned_tx_out = mut_ptr_as_mut(unsigned_tx_out, "unsigned_tx_out")?;
251    let unsigned_tx = serde_json::from_str(json).map(UnsignedTransaction)?;
252    *unsigned_tx_out = Box::into_raw(Box::new(unsigned_tx));
253    Ok(())
254}
255
256/// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
257pub unsafe fn unsigned_tx_to_json(
258    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
259) -> Result<String, Error> {
260    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
261    let s = serde_json::to_string(&unsigned_tx.0)?;
262    Ok(s)
263}
264
265/// JSON representation according to EIP-12 <https://github.com/ergoplatform/eips/pull/23>
266pub unsafe fn unsigned_tx_to_json_eip12(
267    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
268) -> Result<String, Error> {
269    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
270    let tx_dapp: UnsignedTransactionJsonEip12 = unsigned_tx.0.clone().into();
271    let s = serde_json::to_string(&tx_dapp)?;
272    Ok(s)
273}
274
275/// Transaction id
276#[derive(PartialEq, Eq, Debug, Clone)]
277pub struct TxId(pub(crate) chain::transaction::TxId);
278pub type TxIdPtr = *mut TxId;
279pub type ConstTxIdPtr = *const TxId;
280
281/// Convert a hex string into a TxId
282pub unsafe fn tx_id_from_str(str: &str, tx_id_out: *mut TxIdPtr) -> Result<(), Error> {
283    let tx_id_out = mut_ptr_as_mut(tx_id_out, "tx_id_out")?;
284    let bytes = Base16DecodedBytes::try_from(str.to_string())?;
285    let tx_id = bytes
286        .try_into()
287        .map(|digest| TxId(chain::transaction::TxId(digest)))?;
288    *tx_id_out = Box::into_raw(Box::new(tx_id));
289    Ok(())
290}
291
292/// Get the tx id as bytes
293pub unsafe fn tx_id_to_str(tx_id_ptr: ConstTxIdPtr) -> Result<String, Error> {
294    let tx_id = const_ptr_as_ref(tx_id_ptr, "tx_id_ptr")?;
295    let base16_bytes = Base16EncodedBytes::new(tx_id.0 .0 .0.as_ref());
296    Ok(base16_bytes.into())
297}
298
299/**
300 * ErgoTransaction is an atomic state transition operation. It destroys Boxes from the state
301 * and creates new ones. If transaction is spending boxes protected by some non-trivial scripts,
302 * its inputs should also contain proof of spending correctness - context extension (user-defined
303 * key-value map) and data inputs (links to existing boxes in the state) that may be used during
304 * script reduction to crypto, signatures that satisfies the remaining cryptographic protection
305 * of the script.
306 * Transactions are not encrypted, so it is possible to browse and view every transaction ever
307 * collected into a block.
308 */
309pub struct Transaction(pub(crate) chain::transaction::Transaction);
310pub type TransactionPtr = *mut Transaction;
311pub type ConstTransactionPtr = *const Transaction;
312
313/// Create Transaction from UnsignedTransaction and an array of proofs in the same order as
314/// UnsignedTransaction.inputs with empty proof indicated with empty byte array
315pub unsafe fn tx_from_unsigned_tx(
316    unsigned_tx_ptr: ConstUnsignedTransactionPtr,
317    proofs_ptr: ConstCollectionPtr<ByteArray>,
318    tx_out: *mut TransactionPtr,
319) -> Result<(), Error> {
320    let proofs = const_ptr_as_ref(proofs_ptr, "proofs_ptr")?;
321    let unsigned_tx = const_ptr_as_ref(unsigned_tx_ptr, "unsigned_tx_ptr")?;
322    let tx_out = mut_ptr_as_mut(tx_out, "tx_out")?;
323    let tx = chain::transaction::Transaction::from_unsigned_tx(
324        unsigned_tx.0.clone(),
325        proofs
326            .0
327            .iter()
328            .cloned()
329            .map(|bytes| bytes.0.into())
330            .collect(),
331    )
332    .map(Transaction)?;
333    *tx_out = Box::into_raw(Box::new(tx));
334    Ok(())
335}
336
337/// Get id for transaction
338pub unsafe fn tx_id(tx_ptr: ConstTransactionPtr, tx_id_out: *mut TxIdPtr) -> Result<(), Error> {
339    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
340    let tx_id_out = mut_ptr_as_mut(tx_id_out, "tx_id_out")?;
341    *tx_id_out = Box::into_raw(Box::new(TxId(tx.0.id())));
342    Ok(())
343}
344
345/// Parse from JSON. Supports Ergo Node/Explorer API and box values and token amount encoded as
346/// strings
347pub unsafe fn tx_from_json(json: &str, tx_out: *mut TransactionPtr) -> Result<(), Error> {
348    let tx_out = mut_ptr_as_mut(tx_out, "tx_out")?;
349    let tx = serde_json::from_str(json).map(Transaction)?;
350    *tx_out = Box::into_raw(Box::new(tx));
351    Ok(())
352}
353
354/// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
355pub unsafe fn tx_to_json(tx_ptr: ConstTransactionPtr) -> Result<String, Error> {
356    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
357    let s = serde_json::to_string(&tx.0)?;
358    Ok(s)
359}
360
361/// JSON representation according to EIP-12 <https://github.com/ergoplatform/eips/pull/23>
362pub unsafe fn tx_to_json_eip12(tx_ptr: ConstTransactionPtr) -> Result<String, Error> {
363    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
364    let tx_dapp: TransactionJsonEip12 = tx.0.clone().into();
365    let s = serde_json::to_string(&tx_dapp)?;
366    Ok(s)
367}
368
369/// Inputs for transaction
370pub unsafe fn tx_inputs(
371    tx_ptr: ConstTransactionPtr,
372    inputs_out: *mut CollectionPtr<Input>,
373) -> Result<(), Error> {
374    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
375    let inputs_out = mut_ptr_as_mut(inputs_out, "inputs_out")?;
376    *inputs_out = Box::into_raw(Box::new(Collection(
377        tx.0.inputs
378            .as_vec()
379            .clone()
380            .into_iter()
381            .map(Input)
382            .collect(),
383    )));
384    Ok(())
385}
386
387/// Data inputs for transaction
388pub unsafe fn tx_data_inputs(
389    tx_ptr: ConstTransactionPtr,
390    data_inputs_out: *mut CollectionPtr<DataInput>,
391) -> Result<(), Error> {
392    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
393    let data_inputs_out = mut_ptr_as_mut(data_inputs_out, "data_inputs_out")?;
394    *data_inputs_out = Box::into_raw(Box::new(Collection(
395        tx.0.data_inputs
396            .as_ref()
397            .map(|v| v.as_vec().clone())
398            .unwrap_or_else(Vec::new)
399            .into_iter()
400            .map(DataInput)
401            .collect(),
402    )));
403    Ok(())
404}
405
406/// Output candidates for transaction
407pub unsafe fn tx_output_candidates(
408    tx_ptr: ConstTransactionPtr,
409    ergo_box_candidates_out: *mut CollectionPtr<ErgoBoxCandidate>,
410) -> Result<(), Error> {
411    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
412    let ergo_box_candidates_out =
413        mut_ptr_as_mut(ergo_box_candidates_out, "ergo_box_candidates_out")?;
414    *ergo_box_candidates_out = Box::into_raw(Box::new(Collection(
415        tx.0.output_candidates
416            .iter()
417            .map(|ebc| ErgoBoxCandidate(ebc.clone()))
418            .collect(),
419    )));
420    Ok(())
421}
422
423/// Returns ErgoBox's created from ErgoBoxCandidate's with tx id and indices
424pub unsafe fn tx_outputs(
425    tx_ptr: ConstTransactionPtr,
426    ergo_box_out: *mut CollectionPtr<ErgoBox>,
427) -> Result<(), Error> {
428    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
429    let ergo_box_out = mut_ptr_as_mut(ergo_box_out, "ergo_box_candidates_out")?;
430    *ergo_box_out = Box::into_raw(Box::new(Collection(
431        tx.0.outputs
432            .iter()
433            .map(|ebc| ErgoBox(ebc.clone()))
434            .collect(),
435    )));
436    Ok(())
437}
438
439/// Validate a transaction
440pub unsafe fn tx_validate(
441    tx_ptr: ConstTransactionPtr,
442    state_context_ptr: ConstErgoStateContextPtr,
443    boxes_to_spend_ptr: ConstCollectionPtr<ErgoBox>,
444    data_boxes_ptr: ConstCollectionPtr<ErgoBox>,
445) -> Result<(), Error> {
446    let state_context = const_ptr_as_ref(state_context_ptr, "state_context_ptr")?;
447    let tx = const_ptr_as_ref(tx_ptr, "tx_ptr")?;
448    let boxes_to_spend = const_ptr_as_ref(boxes_to_spend_ptr, "boxes_to_spend_ptr")?;
449    let data_boxes = const_ptr_as_ref(data_boxes_ptr, "data_boxes_ptr")?;
450    let boxes_to_spend = boxes_to_spend.0.clone().into_iter().map(|b| b.0).collect();
451    let data_boxes = data_boxes.0.clone().into_iter().map(|b| b.0).collect();
452    let tx_context = ergo_lib::wallet::signing::TransactionContext::new(
453        tx.0.clone(),
454        boxes_to_spend,
455        data_boxes,
456    )?;
457    tx_context.validate(&state_context.0)?;
458    Ok(())
459}