Skip to main content

tx3_resolver/inputs/
mod.rs

1//! Tx input selection algorithms
2
3use std::collections::HashSet;
4
5use tx3_tir::encoding::AnyTir;
6use tx3_tir::model::core::UtxoSet;
7use tx3_tir::model::v1beta0 as tir;
8use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef};
9
10use crate::{Error, UtxoStore};
11
12mod narrow;
13mod select;
14
15macro_rules! data_or_bail {
16    ($expr:expr, bytes) => {
17        $expr
18            .as_bytes()
19            .ok_or(Error::ExpectedData("bytes".to_string(), $expr.clone()))
20    };
21
22    ($expr:expr, number) => {
23        $expr
24            .as_number()
25            .ok_or(Error::ExpectedData("number".to_string(), $expr.clone()))?
26    };
27
28    ($expr:expr, assets) => {
29        $expr
30            .as_assets()
31            .ok_or(Error::ExpectedData("assets".to_string(), $expr.clone()))
32    };
33
34    ($expr:expr, utxo_refs) => {
35        $expr
36            .as_utxo_refs()
37            .ok_or(Error::ExpectedData("utxo refs".to_string(), $expr.clone()))
38    };
39}
40
41pub struct Diagnostic {
42    pub query: tir::InputQuery,
43    pub utxos: UtxoSet,
44    pub selected: UtxoSet,
45}
46
47const MAX_SEARCH_SPACE_SIZE: usize = 50;
48
49#[derive(Debug, Clone)]
50pub struct CanonicalQuery {
51    pub address: Option<Vec<u8>>,
52    pub min_amount: Option<CanonicalAssets>,
53    pub refs: HashSet<UtxoRef>,
54    pub support_many: bool,
55    pub collateral: bool,
56}
57
58impl std::fmt::Display for CanonicalQuery {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "CanonicalQuery {{")?;
61
62        if let Some(address) = &self.address {
63            write!(f, "address: {}", hex::encode(address))?;
64        }
65
66        if let Some(min_amount) = &self.min_amount {
67            write!(f, "min_amount: {}", min_amount)?;
68        }
69
70        for (i, ref_) in self.refs.iter().enumerate() {
71            write!(f, "ref[{}]:{}#{}", i, hex::encode(&ref_.txid), ref_.index)?;
72        }
73
74        write!(f, "support_many: {:?}", self.support_many)?;
75        write!(f, "for_collateral: {:?}", self.collateral)?;
76        write!(f, "}}")
77    }
78}
79
80impl TryFrom<tir::InputQuery> for CanonicalQuery {
81    type Error = Error;
82
83    fn try_from(query: tir::InputQuery) -> Result<Self, Self::Error> {
84        let address = query
85            .address
86            .as_option()
87            .map(|x| data_or_bail!(x, bytes))
88            .transpose()?
89            .map(Vec::from);
90
91        let min_amount = query
92            .min_amount
93            .as_option()
94            .map(|x| data_or_bail!(x, assets))
95            .transpose()?
96            .map(|x| CanonicalAssets::from(Vec::from(x)));
97
98        let refs = query
99            .r#ref
100            .as_option()
101            .map(|x| data_or_bail!(x, utxo_refs))
102            .transpose()?
103            .map(|x| HashSet::from_iter(x.iter().cloned()))
104            .unwrap_or_default();
105
106        Ok(Self {
107            address,
108            min_amount,
109            refs,
110            support_many: query.many,
111            collateral: query.collateral,
112        })
113    }
114}
115
116pub async fn resolve<T: UtxoStore>(tx: AnyTir, utxos: &T) -> Result<AnyTir, Error> {
117    let mut selector = select::InputSelector::new(utxos);
118
119    for (name, query) in tx3_tir::reduce::find_queries(&tx) {
120        let query = CanonicalQuery::try_from(query)?;
121        selector.add(name, query).await?;
122    }
123
124    let all_inputs = selector.select_all()?;
125
126    let out = tx3_tir::reduce::apply_inputs(tx, &all_inputs)?;
127
128    Ok(out)
129}