1use tx3_lang::{
2 applying::{self, Apply as _},
3 backend::{self, Compiler, TxEval, UtxoStore},
4 ir::{self, Node},
5};
6
7pub mod inputs;
8
9#[cfg(test)]
10pub mod mock;
11
12#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error(transparent)]
15 InputsError(#[from] inputs::Error),
16
17 #[error("can't compile non-constant tir")]
18 CantCompileNonConstantTir,
19
20 #[error("backend error: {0}")]
21 BackendError(#[from] backend::Error),
22
23 #[error("apply error: {0}")]
24 ApplyError(#[from] applying::Error),
25}
26
27async fn eval_pass<C: Compiler, S: UtxoStore>(
28 tx: &ir::Tx,
29 compiler: &mut C,
30 utxos: &S,
31 last_eval: Option<&TxEval>,
32) -> Result<Option<TxEval>, Error> {
33 let attempt = tx.clone();
34
35 let fees = last_eval.as_ref().map(|e| e.fee).unwrap_or(0);
36
37 let attempt = applying::apply_fees(attempt, fees)?;
38
39 let attempt = attempt.apply(compiler)?;
40
41 let attempt = applying::reduce(attempt)?;
42
43 let attempt = crate::inputs::resolve(attempt, utxos).await?;
44
45 let attempt = tx3_lang::ProtoTx::from(attempt);
46
47 let attempt = attempt.apply()?;
48
49 if !attempt.as_ref().is_constant() {
50 return Err(Error::CantCompileNonConstantTir);
51 }
52
53 let eval = compiler.compile(attempt.as_ref())?;
54
55 let Some(last_eval) = last_eval else {
56 return Ok(Some(eval));
57 };
58
59 if eval != *last_eval {
60 return Ok(Some(eval));
61 }
62
63 Ok(None)
64}
65
66pub async fn resolve_tx<C: Compiler, S: UtxoStore>(
67 tx: tx3_lang::ProtoTx,
68 compiler: &mut C,
69 utxos: &S,
70 max_optimize_rounds: usize,
71) -> Result<TxEval, Error> {
72 let max_optimize_rounds = max_optimize_rounds.max(3);
73
74 let mut last_eval = None;
75 let mut rounds = 0;
76
77 let tx = tx.apply()?;
79
80 let tx = ir::Tx::from(tx);
82
83 while let Some(better) = eval_pass(&tx, compiler, utxos, last_eval.as_ref()).await? {
84 last_eval = Some(better);
85
86 if rounds > max_optimize_rounds {
87 break;
88 }
89
90 rounds += 1;
91 }
92
93 Ok(last_eval.unwrap())
94}