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("max optimize rounds reached")]
27 MaxOptimizeRoundsReached,
28
29 #[error("can't compile non-constant tir")]
30 CantCompileNonConstantTir,
31
32 #[error("backend error: {0}")]
33 BackendError(#[from] backend::Error),
34}
35
36async fn eval_pass<C: Compiler, S: UtxoStore>(
37 tx: &ir::Tx,
38 compiler: &C,
39 utxos: &S,
40 last_eval: Option<&TxEval>,
41) -> Result<Option<TxEval>, Error> {
42 let attempt = tx.clone();
43
44 let fees = last_eval.as_ref().map(|e| e.fee).unwrap_or(0);
45 let attempt = applying::apply_fees(attempt, fees)?;
46
47 let attempt = applying::reduce(attempt)?;
48
49 let attempt = crate::inputs::resolve(attempt, utxos).await?;
50
51 let attempt = tx3_lang::ProtoTx::from(attempt);
52
53 let attempt = attempt.apply()?;
54
55 if !attempt.as_ref().is_constant() {
56 return Err(Error::CantCompileNonConstantTir);
57 }
58
59 let eval = compiler.compile(attempt.as_ref())?;
60
61 let Some(last_eval) = last_eval else {
62 return Ok(Some(eval));
63 };
64
65 if eval != *last_eval {
66 return Ok(Some(eval));
67 }
68
69 Ok(None)
70}
71
72pub async fn resolve_tx<C: Compiler, S: UtxoStore>(
73 tx: tx3_lang::ProtoTx,
74 compiler: &mut C,
75 utxos: &S,
76 max_optimize_rounds: usize,
77) -> Result<TxEval, Error> {
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 return Err(Error::MaxOptimizeRoundsReached);
93 }
94
95 rounds += 1;
96 }
97
98 Ok(last_eval.unwrap())
99}