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(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    // one initial pass to reduce any available params;
78    let tx = tx.apply()?;
79
80    // reduce compiler ops
81    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}