tx3_resolver/
lib.rs

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    // one initial pass to reduce any available params;
82    let tx = tx.apply()?;
83
84    // reduce compiler ops
85    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}