use std::rc::Rc;
use ergotree_interpreter::eval::env::Env;
use ergotree_interpreter::eval::reduce_to_crypto;
use ergotree_interpreter::eval::ReductionResult;
use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
use ergotree_interpreter::sigma_protocol::prover::ProverError;
use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
use ergotree_ir::serialization::SigmaParsingError;
use ergotree_ir::serialization::SigmaSerializable;
use ergotree_ir::serialization::SigmaSerializeResult;
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
use crate::chain::ergo_state_context::ErgoStateContext;
use crate::chain::transaction::Transaction;
use crate::chain::transaction::UnsignedInput;
use crate::wallet::signing::make_context;
use crate::wallet::signing::TransactionContext;
use crate::wallet::signing::TxSigningError;
use super::unsigned::UnsignedTransaction;
use super::TxIoVec;
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ReducedInput {
pub reduction_result: ReductionResult,
pub extension: ContextExtension,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ReducedTransaction {
pub unsigned_tx: UnsignedTransaction,
tx_cost: u32,
reduced_inputs: TxIoVec<ReducedInput>,
}
impl ReducedTransaction {
pub fn reduced_inputs(&self) -> TxIoVec<ReducedInput> {
self.reduced_inputs.clone()
}
}
pub fn reduce_tx(
tx_context: TransactionContext<UnsignedTransaction>,
state_context: &ErgoStateContext,
) -> Result<ReducedTransaction, TxSigningError> {
let tx = &tx_context.spending_tx;
let reduced_inputs = tx
.inputs
.clone()
.enumerated()
.try_mapped::<_, _, TxSigningError>(|(idx, input)| {
let input_box = tx_context
.get_input_box(&input.box_id)
.ok_or(TxSigningError::InputBoxNotFound(idx))?;
let ctx = Rc::new(make_context(state_context, &tx_context, idx)?);
let expr = input_box
.ergo_tree
.proposition()
.map_err(ProverError::ErgoTreeError)
.map_err(|e| TxSigningError::ProverError(e, idx))?;
let reduction_result = reduce_to_crypto(&expr, &Env::empty(), ctx)
.map_err(ProverError::EvalError)
.map_err(|e| TxSigningError::ProverError(e, idx))?;
Ok(ReducedInput {
reduction_result,
extension: input.extension,
})
})?;
Ok(ReducedTransaction {
unsigned_tx: tx.clone(),
reduced_inputs,
tx_cost: 0,
})
}
impl SigmaSerializable for ReducedTransaction {
fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
let msg = self.unsigned_tx.bytes_to_sign()?;
w.put_usize_as_u32_unwrapped(msg.len())?;
w.write_all(&msg)?;
self.reduced_inputs.as_vec().iter().try_for_each(|red_in| {
red_in.reduction_result.sigma_prop.sigma_serialize(w)?;
w.put_u64(red_in.reduction_result.cost)?;
SigmaSerializeResult::Ok(())
})?;
w.put_u32(self.tx_cost)?;
Ok(())
}
fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
let bytes_len = r.get_u32()?;
let mut buf = vec![0u8; bytes_len as usize];
r.read_exact(buf.as_mut_slice())?;
let tx = Transaction::sigma_parse_bytes(&buf)?;
let input_pairs: TxIoVec<(ReducedInput, UnsignedInput)> =
tx.inputs.try_mapped::<_, _, SigmaParsingError>(|input| {
let sigma_prop = SigmaBoolean::sigma_parse(r)?;
let cost = r.get_u64()?;
let extension = input.spending_proof.extension;
let reduced_input = ReducedInput {
reduction_result: ReductionResult { sigma_prop, cost },
extension: extension.clone(),
};
let unsigned_input = UnsignedInput {
box_id: input.box_id,
extension,
};
Ok((reduced_input, unsigned_input))
})?;
let reduced_inputs = input_pairs.clone().mapped(|p| p.0);
let unsigned_inputs = input_pairs.mapped(|p| p.1);
let unsigned_tx =
UnsignedTransaction::new(unsigned_inputs, tx.data_inputs, tx.output_candidates)?;
let tx_cost = r.get_u32()?;
Ok(ReducedTransaction {
unsigned_tx,
reduced_inputs,
tx_cost,
})
}
}
#[cfg(feature = "arbitrary")]
pub mod arbitrary {
use super::*;
use proptest::prelude::*;
impl Arbitrary for ReducedTransaction {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<UnsignedTransaction>(),
any::<SigmaBoolean>(),
any::<u32>(),
)
.prop_map(|(unsigned_tx, sb, tx_cost)| Self {
unsigned_tx: unsigned_tx.clone(),
reduced_inputs: unsigned_tx.inputs.mapped(|unsigned_input| ReducedInput {
reduction_result: ReductionResult {
sigma_prop: sb.clone(),
cost: 0,
},
extension: unsigned_input.extension,
}),
tx_cost,
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
use ergotree_ir::serialization::sigma_serialize_roundtrip;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn ser_roundtrip(v in any::<ReducedTransaction>()) {
prop_assert_eq![sigma_serialize_roundtrip(&v), v];
}
}
}