query.rs 6.47 KB
Newer Older
1
use bitcoin::consensus::encode::deserialize;
Roman Zeyde's avatar
Roman Zeyde committed
2
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
3
use std::sync::{Arc, RwLock};
4

Roman Zeyde's avatar
Roman Zeyde committed
5 6
use crate::app::App;
use crate::errors::*;
kenshin-samourai's avatar
kenshin-samourai committed
7
use crate::index::{TxInRow, TxOutRow, TxRow};
Roman Zeyde's avatar
Roman Zeyde committed
8
use crate::mempool::Tracker;
kenshin-samourai's avatar
kenshin-samourai committed
9
use crate::store::ReadStore;
10
use crate::util::{HashPrefix, HeaderEntry};
kenshin-samourai's avatar
kenshin-samourai committed
11

kenshin-samourai's avatar
kenshin-samourai committed
12 13 14 15 16 17
//
// Output of a Transaction
//
pub struct Txo {
    pub txid: Sha256dHash,
    pub vout: usize,
Roman Zeyde's avatar
Roman Zeyde committed
18 19
}

kenshin-samourai's avatar
kenshin-samourai committed
20 21 22
//
// Input of a Transaction
//
Chiguireitor's avatar
Chiguireitor committed
23
pub type OutPoint = (Sha256dHash, usize); // (txid, vout)
24

Chiguireitor's avatar
Chiguireitor committed
25 26 27
pub struct SpendingInput {
    pub txid: Sha256dHash,
    pub outpoint: OutPoint,
Roman Zeyde's avatar
Roman Zeyde committed
28 29
}

kenshin-samourai's avatar
kenshin-samourai committed
30 31 32 33
//
// Status of an Address
// (vectors of confirmed and unconfirmed outputs and inputs)
//
Roman Zeyde's avatar
Roman Zeyde committed
34
pub struct Status {
kenshin-samourai's avatar
kenshin-samourai committed
35 36
    confirmed: (Vec<Txo>, Vec<SpendingInput>),
    mempool: (Vec<Txo>, Vec<SpendingInput>),
37 38
}

39
impl Status {
Chiguireitor's avatar
Chiguireitor committed
40
    pub fn funding(&self) -> impl Iterator<Item = &Txo> {
41 42 43
        self.confirmed.0.iter().chain(self.mempool.0.iter())
    }

Chiguireitor's avatar
Chiguireitor committed
44
    pub fn spending(&self) -> impl Iterator<Item = &SpendingInput> {
45 46 47
        self.confirmed.1.iter().chain(self.mempool.1.iter())
    }

kenshin-samourai's avatar
kenshin-samourai committed
48 49
    pub fn history(&self) -> Vec<Sha256dHash> {
        let mut txns = vec![];
50
        for f in self.funding() {
kenshin-samourai's avatar
kenshin-samourai committed
51
            txns.push(f.txid);
52
        }
53
        for s in self.spending() {
kenshin-samourai's avatar
kenshin-samourai committed
54
            txns.push(s.txid);
55
        }
56
        txns.sort_unstable();
57
        txns.dedup();
58 59 60 61
        txns
    }
}

kenshin-samourai's avatar
kenshin-samourai committed
62 63 64
//
// QUery tool for the indexer
//
65 66
pub struct Query {
    app: Arc<App>,
67
    tracker: RwLock<Tracker>,
68
    txid_limit: usize,
69 70
}

71
impl Query {
72 73 74 75
    pub fn new(
        app: Arc<App>,
        txid_limit: usize,
    ) -> Arc<Query> {
76
        Arc::new(Query {
77
            app,
78
            tracker: RwLock::new(Tracker::new()),
79
            txid_limit,
80
        })
81 82
    }

kenshin-samourai's avatar
kenshin-samourai committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
    fn get_txrows_by_prefix(
        &self,
        store: &dyn ReadStore,
        prefix: HashPrefix
    ) -> Vec<TxRow> {
        store
            .scan(&TxRow::filter_prefix(prefix))
            .iter()
            .map(|row| TxRow::from_row(row))
            .collect()
    }

    fn get_txoutrows_by_script_hash(
        &self,
        store: &dyn ReadStore,
        script_hash: &[u8]
    ) -> Vec<TxOutRow> {
        store
            .scan(&TxOutRow::filter(script_hash))
            .iter()
            .map(|row| TxOutRow::from_row(row))
            .collect()
    }

    fn get_prefixes_by_funding_txo(
        &self,
        store: &dyn ReadStore,
        txid: &Sha256dHash,
        vout: usize,
    ) -> Vec<HashPrefix> {
        store
            .scan(&TxInRow::filter(&txid, vout))
            .iter()
            .map(|row| TxInRow::from_row(row).txid_prefix)
            .collect()
    }

    fn get_txids_by_prefix(
121
        &self,
122
        store: &dyn ReadStore,
123
        prefixes: Vec<HashPrefix>,
kenshin-samourai's avatar
kenshin-samourai committed
124
    ) -> Result<Vec<Sha256dHash>> {
Roman Zeyde's avatar
Roman Zeyde committed
125
        let mut txns = vec![];
kenshin-samourai's avatar
kenshin-samourai committed
126 127
        for prefix in prefixes {
            for tx_row in self.get_txrows_by_prefix(store, prefix) {
128
                let txid: Sha256dHash = deserialize(&tx_row.key.txid).unwrap();
kenshin-samourai's avatar
kenshin-samourai committed
129
                txns.push(txid)
130 131
            }
        }
132
        Ok(txns)
133 134
    }

135 136
    fn find_spending_input(
        &self,
137
        store: &dyn ReadStore,
kenshin-samourai's avatar
kenshin-samourai committed
138
        txo: &Txo,
139
    ) -> Result<Option<SpendingInput>> {
140

kenshin-samourai's avatar
kenshin-samourai committed
141 142 143
        let mut spendings = vec![];
        let prefixes = self.get_prefixes_by_funding_txo(store, &txo.txid, txo.vout);
        let txids = self.get_txids_by_prefix(store, prefixes)?;
144

kenshin-samourai's avatar
kenshin-samourai committed
145 146 147 148 149
        for txid in &txids {
            spendings.push(SpendingInput {
                txid: *txid,
                outpoint: (txo.txid, txo.vout),
            })
Roman Zeyde's avatar
Roman Zeyde committed
150
        }
151

kenshin-samourai's avatar
kenshin-samourai committed
152
        assert!(spendings.len() <= 1);
kenshin-samourai's avatar
kenshin-samourai committed
153

kenshin-samourai's avatar
kenshin-samourai committed
154 155
        Ok(if spendings.len() == 1 {
            Some(spendings.remove(0))
156 157
        } else {
            None
158
        })
159 160
    }

kenshin-samourai's avatar
kenshin-samourai committed
161 162 163 164
    fn find_funding_outputs(
        &self,
        store: &dyn ReadStore,
        script_hash: &[u8]
kenshin-samourai's avatar
kenshin-samourai committed
165 166
    ) -> Result<Vec<Txo>> {
        let txout_rows = self.get_txoutrows_by_script_hash(store, script_hash);
kenshin-samourai's avatar
kenshin-samourai committed
167

Roman Zeyde's avatar
Roman Zeyde committed
168
        let mut result = vec![];
kenshin-samourai's avatar
kenshin-samourai committed
169 170

        for row in &txout_rows {
kenshin-samourai's avatar
kenshin-samourai committed
171 172 173 174 175
            let txids = self.get_txids_by_prefix(store, vec![row.txid_prefix])?;
            for txid in &txids {
                result.push(Txo {
                    txid: *txid,
                    vout: row.vout as usize,
176 177 178
                })
            }
        }
kenshin-samourai's avatar
kenshin-samourai committed
179 180

        Ok(result)
181 182
    }

183 184 185
    fn confirmed_status(
        &self,
        script_hash: &[u8],
kenshin-samourai's avatar
kenshin-samourai committed
186
    ) -> Result<(Vec<Txo>, Vec<SpendingInput>)> {
187 188
        let mut funding = vec![];
        let mut spending = vec![];
189
        let read_store = self.app.read_store();
kenshin-samourai's avatar
kenshin-samourai committed
190

kenshin-samourai's avatar
kenshin-samourai committed
191 192 193 194 195 196 197 198
        let txos = self.find_funding_outputs(read_store, script_hash)?;
        if self.txid_limit > 0 && txos.len() > self.txid_limit {
            bail!(
                "{}+ transactions found, query may take a long time",
                txos.len()
            );
        }
        funding.extend(txos);
kenshin-samourai's avatar
kenshin-samourai committed
199

kenshin-samourai's avatar
kenshin-samourai committed
200 201
        for txo in &funding {
            if let Some(spent) = self.find_spending_input(read_store, &txo)? {
202
                spending.push(spent);
203 204
            }
        }
kenshin-samourai's avatar
kenshin-samourai committed
205

206
        Ok((funding, spending))
207 208
    }

209 210 211
    fn mempool_status(
        &self,
        script_hash: &[u8],
kenshin-samourai's avatar
kenshin-samourai committed
212 213
        confirmed_funding: &[Txo],
    ) -> Result<(Vec<Txo>, Vec<SpendingInput>)> {
214 215
        let mut funding = vec![];
        let mut spending = vec![];
kenshin-samourai's avatar
kenshin-samourai committed
216

217
        let tracker = self.tracker.read().unwrap();
kenshin-samourai's avatar
kenshin-samourai committed
218

kenshin-samourai's avatar
kenshin-samourai committed
219 220 221 222 223 224 225 226
        let txos = self.find_funding_outputs(tracker.index(), script_hash)?;
        if self.txid_limit > 0 && txos.len() > self.txid_limit {
            bail!(
                "{}+ transactions found, query may take a long time",
                txos.len()
            );
        }
        funding.extend(txos);
kenshin-samourai's avatar
kenshin-samourai committed
227

kenshin-samourai's avatar
kenshin-samourai committed
228 229
        for txo in funding.iter().chain(confirmed_funding.iter()) {
            if let Some(spent) = self.find_spending_input(tracker.index(), &txo)? {
230
                spending.push(spent);
Roman Zeyde's avatar
Roman Zeyde committed
231 232
            }
        }
kenshin-samourai's avatar
kenshin-samourai committed
233

234
        Ok((funding, spending))
235
    }
236

237
    pub fn status(&self, script_hash: &[u8]) -> Result<Status> {
Roman Zeyde's avatar
Roman Zeyde committed
238 239
        let confirmed = self
            .confirmed_status(script_hash)
240
            .chain_err(|| "failed to get confirmed status")?;
241

Roman Zeyde's avatar
Roman Zeyde committed
242 243
        let mempool = self
            .mempool_status(script_hash, &confirmed.0)
244
            .chain_err(|| "failed to get mempool status")?;
245

246
        Ok(Status { confirmed, mempool })
247 248
    }

249 250 251 252 253
    pub fn get_best_header(&self) -> Result<HeaderEntry> {
        let last_header = self.app.index().best_header();
        Ok(last_header.chain_err(|| "no headers indexed")?)
    }

Roman Zeyde's avatar
Roman Zeyde committed
254
    pub fn update_mempool(&self) -> Result<()> {
Roman Zeyde's avatar
Roman Zeyde committed
255
        self.tracker.write().unwrap().update(self.app.daemon())
256
    }
257
}