query.rs 9.65 KB
Newer Older
1
use bitcoin::blockdata::transaction::Transaction;
2
use bitcoin::consensus::encode::deserialize;
Roman Zeyde's avatar
Roman Zeyde committed
3 4
use bitcoin_hashes::hex::ToHex;
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
5 6
use crypto::digest::Digest;
use crypto::sha2::Sha256;
Roman Zeyde's avatar
Roman Zeyde committed
7
use serde_json::Value;
8
use std::collections::HashMap;
9
use std::sync::{Arc, RwLock};
10

Roman Zeyde's avatar
Roman Zeyde committed
11
use crate::app::App;
12
use crate::cache::TransactionCache;
Roman Zeyde's avatar
Roman Zeyde committed
13 14 15 16 17
use crate::errors::*;
use crate::index::{compute_script_hash, TxInRow, TxOutRow, TxRow};
use crate::mempool::Tracker;
use crate::store::{ReadStore, Row};
use crate::util::{FullHash, HashPrefix, HeaderEntry};
Roman Zeyde's avatar
Roman Zeyde committed
18

19 20 21 22
pub struct FundingOutput {
    pub txn_id: Sha256dHash,
    pub height: u32,
    pub output_index: usize,
Roman Zeyde's avatar
Roman Zeyde committed
23 24
}

25 26
type OutPoint = (Sha256dHash, usize); // (txid, output_index)

27 28
struct SpendingInput {
    txn_id: Sha256dHash,
29
    height: u32,
30
    funding_output: OutPoint,
Roman Zeyde's avatar
Roman Zeyde committed
31 32 33
}

pub struct Status {
34 35 36 37
    confirmed: (Vec<FundingOutput>, Vec<SpendingInput>),
    mempool: (Vec<FundingOutput>, Vec<SpendingInput>),
}

38
impl Status {
39 40 41 42 43 44 45 46
    fn funding(&self) -> impl Iterator<Item = &FundingOutput> {
        self.confirmed.0.iter().chain(self.mempool.0.iter())
    }

    fn spending(&self) -> impl Iterator<Item = &SpendingInput> {
        self.confirmed.1.iter().chain(self.mempool.1.iter())
    }

47 48
    pub fn history(&self) -> Vec<(i32, Sha256dHash)> {
        let mut txns_map = HashMap::<Sha256dHash, i32>::new();
49
        for f in self.funding() {
50
            txns_map.insert(f.txn_id, f.height as i32);
51
        }
52
        for s in self.spending() {
53
            txns_map.insert(s.txn_id, s.height as i32);
54 55 56
        }
        let mut txns: Vec<(i32, Sha256dHash)> =
            txns_map.into_iter().map(|item| (item.1, item.0)).collect();
57
        txns.sort_unstable();
58 59 60 61 62 63 64 65 66 67 68
        txns
    }

    pub fn hash(&self) -> Option<FullHash> {
        let txns = self.history();
        if txns.is_empty() {
            None
        } else {
            let mut hash = FullHash::default();
            let mut sha2 = Sha256::new();
            for (height, txn_id) in txns {
Roman Zeyde's avatar
Roman Zeyde committed
69
                let part = format!("{}:{}:", txn_id.to_hex(), height);
70 71 72 73 74 75 76 77
                sha2.input(part.as_bytes());
            }
            sha2.result(&mut hash);
            Some(hash)
        }
    }
}

Roman Zeyde's avatar
Roman Zeyde committed
78 79
struct TxnHeight {
    txn: Transaction,
80
    height: u32,
Roman Zeyde's avatar
Roman Zeyde committed
81 82
}

83
// TODO: the functions below can be part of ReadStore.
84
fn txrow_by_txid(store: &dyn ReadStore, txid: &Sha256dHash) -> Option<TxRow> {
85 86 87 88 89
    let key = TxRow::filter_full(&txid);
    let value = store.get(&key)?;
    Some(TxRow::from_row(&Row { key, value }))
}

90
fn txrows_by_prefix(store: &dyn ReadStore, txid_prefix: HashPrefix) -> Vec<TxRow> {
91
    store
Roman Zeyde's avatar
Roman Zeyde committed
92
        .scan(&TxRow::filter_prefix(txid_prefix))
93 94 95 96
        .iter()
        .map(|row| TxRow::from_row(row))
        .collect()
}
97

98
fn txids_by_script_hash(store: &dyn ReadStore, script_hash: &[u8]) -> Vec<HashPrefix> {
99 100 101 102 103 104
    store
        .scan(&TxOutRow::filter(script_hash))
        .iter()
        .map(|row| TxOutRow::from_row(row).txid_prefix)
        .collect()
}
105

106
fn txids_by_funding_output(
107
    store: &dyn ReadStore,
108 109 110 111 112 113 114 115
    txn_id: &Sha256dHash,
    output_index: usize,
) -> Vec<HashPrefix> {
    store
        .scan(&TxInRow::filter(&txn_id, output_index))
        .iter()
        .map(|row| TxInRow::from_row(row).txid_prefix)
        .collect()
116 117
}

118 119
pub struct Query {
    app: Arc<App>,
120
    tracker: RwLock<Tracker>,
121
    tx_cache: TransactionCache,
122
    txid_limit: usize,
123 124
}

125
impl Query {
126 127 128 129 130
    pub fn new(
        app: Arc<App>,
        tx_cache: TransactionCache,
        txid_limit: usize,
    ) -> Arc<Query> {
131
        Arc::new(Query {
132
            app,
133
            tracker: RwLock::new(Tracker::new()),
134
            tx_cache,
135
            txid_limit,
136
        })
137 138
    }

139 140
    fn load_txns_by_prefix(
        &self,
141
        store: &dyn ReadStore,
142 143
        prefixes: Vec<HashPrefix>,
    ) -> Result<Vec<TxnHeight>> {
Roman Zeyde's avatar
Roman Zeyde committed
144
        let mut txns = vec![];
145
        for txid_prefix in prefixes {
Roman Zeyde's avatar
Roman Zeyde committed
146
            for tx_row in txrows_by_prefix(store, txid_prefix) {
147
                let txid: Sha256dHash = deserialize(&tx_row.key.txid).unwrap();
148
                let txn = self.load_txn(&txid, Some(tx_row.height))?;
149 150
                txns.push(TxnHeight {
                    txn,
151
                    height: tx_row.height,
152
                })
153 154
            }
        }
155
        Ok(txns)
156 157
    }

158 159
    fn find_spending_input(
        &self,
160
        store: &dyn ReadStore,
161
        funding: &FundingOutput,
162
    ) -> Result<Option<SpendingInput>> {
163
        let spending_txns: Vec<TxnHeight> = self.load_txns_by_prefix(
164
            store,
165
            txids_by_funding_output(store, &funding.txn_id, funding.output_index),
166
        )?;
167

Roman Zeyde's avatar
Roman Zeyde committed
168
        let mut spending_inputs = vec![];
169

Roman Zeyde's avatar
Roman Zeyde committed
170
        for t in &spending_txns {
171
            for input in t.txn.input.iter() {
172 173
                if input.previous_output.txid == funding.txn_id
                    && input.previous_output.vout == funding.output_index as u32
Roman Zeyde's avatar
Roman Zeyde committed
174 175 176 177
                {
                    spending_inputs.push(SpendingInput {
                        txn_id: t.txn.txid(),
                        height: t.height,
178
                        funding_output: (funding.txn_id, funding.output_index),
Roman Zeyde's avatar
Roman Zeyde committed
179 180 181 182
                    })
                }
            }
        }
183

Roman Zeyde's avatar
Roman Zeyde committed
184
        assert!(spending_inputs.len() <= 1);
185
        Ok(if spending_inputs.len() == 1 {
Roman Zeyde's avatar
Roman Zeyde committed
186
            Some(spending_inputs.remove(0))
187 188
        } else {
            None
189
        })
190 191
    }

192
    fn find_funding_outputs(&self, t: &TxnHeight, script_hash: &[u8]) -> Vec<FundingOutput> {
Roman Zeyde's avatar
Roman Zeyde committed
193
        let mut result = vec![];
194
        let txn_id = t.txn.txid();
Roman Zeyde's avatar
Roman Zeyde committed
195
        for (index, output) in t.txn.output.iter().enumerate() {
196 197
            if compute_script_hash(&output.script_pubkey[..]) == script_hash {
                result.push(FundingOutput {
Roman Zeyde's avatar
Roman Zeyde committed
198
                    txn_id,
199 200 201 202 203 204 205 206
                    height: t.height,
                    output_index: index,
                })
            }
        }
        result
    }

207 208 209 210
    fn confirmed_status(
        &self,
        script_hash: &[u8],
    ) -> Result<(Vec<FundingOutput>, Vec<SpendingInput>)> {
211 212
        let mut funding = vec![];
        let mut spending = vec![];
213 214
        let read_store = self.app.read_store();
        let txid_prefixes = txids_by_script_hash(read_store, script_hash);
215
        // if the limit is enabled
Roman Zeyde's avatar
Roman Zeyde committed
216 217 218 219 220
        if self.txid_limit > 0 && txid_prefixes.len() > self.txid_limit {
            bail!(
                "{}+ transactions found, query may take a long time",
                txid_prefixes.len()
            );
221
        }
222
        for t in self.load_txns_by_prefix(read_store, txid_prefixes)? {
223
            funding.extend(self.find_funding_outputs(&t, script_hash));
224
        }
225
        for funding_output in &funding {
226
            if let Some(spent) = self.find_spending_input(read_store, &funding_output)? {
227
                spending.push(spent);
228 229
            }
        }
230
        Ok((funding, spending))
231 232
    }

233 234 235 236
    fn mempool_status(
        &self,
        script_hash: &[u8],
        confirmed_funding: &[FundingOutput],
237
    ) -> Result<(Vec<FundingOutput>, Vec<SpendingInput>)> {
238 239
        let mut funding = vec![];
        let mut spending = vec![];
240
        let tracker = self.tracker.read().unwrap();
241 242
        let txid_prefixes = txids_by_script_hash(tracker.index(), script_hash);
        for t in self.load_txns_by_prefix(tracker.index(), txid_prefixes)? {
243
            funding.extend(self.find_funding_outputs(&t, script_hash));
244
        }
245
        // // TODO: dedup outputs (somehow) both confirmed and in mempool (e.g. reorg?)
246
        for funding_output in funding.iter().chain(confirmed_funding.iter()) {
247
            if let Some(spent) = self.find_spending_input(tracker.index(), &funding_output)? {
248
                spending.push(spent);
Roman Zeyde's avatar
Roman Zeyde committed
249 250
            }
        }
251
        Ok((funding, spending))
252
    }
253

254
    pub fn status(&self, script_hash: &[u8]) -> Result<Status> {
Roman Zeyde's avatar
Roman Zeyde committed
255 256
        let confirmed = self
            .confirmed_status(script_hash)
257
            .chain_err(|| "failed to get confirmed status")?;
258

Roman Zeyde's avatar
Roman Zeyde committed
259 260
        let mempool = self
            .mempool_status(script_hash, &confirmed.0)
261
            .chain_err(|| "failed to get mempool status")?;
262

263
        Ok(Status { confirmed, mempool })
264 265
    }

266
    fn lookup_confirmed_blockhash(
267 268 269
        &self,
        tx_hash: &Sha256dHash,
        block_height: Option<u32>,
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    ) -> Result<Option<Sha256dHash>> {
        let blockhash = if self.tracker.read().unwrap().get_txn(&tx_hash).is_some() {
            None // found in mempool (as unconfirmed transaction)
        } else {
            // Lookup in confirmed transactions' index
            let height = match block_height {
                Some(height) => height,
                None => {
                    txrow_by_txid(self.app.read_store(), &tx_hash)
                        .chain_err(|| format!("not indexed tx {}", tx_hash))?
                        .height
                }
            };
            let header = self
                .app
                .index()
                .get_header(height as usize)
                .chain_err(|| format!("missing header at height {}", height))?;
            Some(*header.hash())
289
        };
290 291 292 293
        Ok(blockhash)
    }

    // Internal API for transaction retrieval
294 295 296
    fn load_txn(&self, txid: &Sha256dHash, block_height: Option<u32>) -> Result<Transaction> {
        self.tx_cache.get_or_else(&txid, || {
            let blockhash = self.lookup_confirmed_blockhash(txid, block_height)?;
297 298 299 300 301 302
            let value: Value = self
                .app
                .daemon()
                .gettransaction_raw(txid, blockhash, /*verbose*/ false)?;
            let value_hex: &str = value.as_str().chain_err(|| "non-string tx")?;
            hex::decode(&value_hex).chain_err(|| "non-hex tx")
303
        })
304 305
    }

Roman Zeyde's avatar
Roman Zeyde committed
306
    pub fn update_mempool(&self) -> Result<()> {
Roman Zeyde's avatar
Roman Zeyde committed
307
        self.tracker.write().unwrap().update(self.app.daemon())
308
    }
309
}