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("input query too broad")]
15 InputQueryTooBroad,
16
17 #[error("input not resolved: {0} {1:?}")]
18 InputNotResolved(String, ir::InputQuery),
19
20 #[error("expected {0}, got {1:?}")]
21 ExpectedData(String, ir::Expression),
22
23 #[error("apply error: {0}")]
24 ApplyError(#[from] applying::Error),
25
26 #[error("can't compile non-constant tir")]
27 CantCompileNonConstantTir,
28
29 #[error("backend error: {0}")]
30 BackendError(#[from] backend::Error),
31}
32
33async fn eval_pass<C: Compiler, S: UtxoStore>(
34 tx: &ir::Tx,
35 compiler: &C,
36 utxos: &S,
37 last_eval: Option<&TxEval>,
38) -> Result<Option<TxEval>, Error> {
39 let attempt = tx.clone();
40
41 let fees = last_eval.as_ref().map(|e| e.fee).unwrap_or(0);
42
43 let attempt = applying::apply_fees(attempt, fees)?;
44
45 let attempt = applying::reduce(attempt)?;
46
47 let attempt = crate::inputs::resolve(attempt, utxos).await?;
48
49 let attempt = tx3_lang::ProtoTx::from(attempt);
50
51 let attempt = attempt.apply()?;
52
53 if !attempt.as_ref().is_constant() {
54 return Err(Error::CantCompileNonConstantTir);
55 }
56
57 let eval = compiler.compile(attempt.as_ref())?;
58
59 let Some(last_eval) = last_eval else {
60 return Ok(Some(eval));
61 };
62
63 if eval != *last_eval {
64 return Ok(Some(eval));
65 }
66
67 Ok(None)
68}
69
70pub async fn resolve_tx<C: Compiler, S: UtxoStore>(
71 tx: tx3_lang::ProtoTx,
72 compiler: &mut C,
73 utxos: &S,
74 max_optimize_rounds: usize,
75) -> Result<TxEval, Error> {
76 let max_optimize_rounds = max_optimize_rounds.max(3);
77
78 let mut last_eval = None;
79 let mut rounds = 0;
80
81 let tx = tx.apply()?;
83
84 let tx = ir::Tx::from(tx);
86 let tx = tx.apply(compiler)?;
87
88 while let Some(better) = eval_pass(&tx, compiler, utxos, last_eval.as_ref()).await? {
89 last_eval = Some(better);
90
91 if rounds > max_optimize_rounds {
92 break;
93 }
94
95 rounds += 1;
96 }
97
98 Ok(last_eval.unwrap())
99}