query.rs 6.51 KB
Newer Older
1
use bincode;
Roman Zeyde's avatar
Roman Zeyde committed
2
use bitcoin::blockdata::block::{Block, BlockHeader};
3
use bitcoin::blockdata::transaction::Transaction;
Roman Zeyde's avatar
Roman Zeyde committed
4
use bitcoin::network::serialize::deserialize;
5 6 7
use bitcoin::util::hash::Sha256dHash;
use itertools::enumerate;

Roman Zeyde's avatar
Roman Zeyde committed
8
use daemon::Daemon;
Roman Zeyde's avatar
Roman Zeyde committed
9
use index::{compute_script_hash, HeaderEntry, Index, TxInKey, TxInRow, TxKey, TxOutRow};
10
use store::Store;
Roman Zeyde's avatar
Roman Zeyde committed
11
use types::{hash_prefix, HashPrefix, HASH_PREFIX_LEN};
12 13 14 15

pub struct Query<'a> {
    store: &'a Store,
    daemon: &'a Daemon,
16
    index: &'a Index,
17 18
}

Roman Zeyde's avatar
Roman Zeyde committed
19 20
pub struct FundingOutput {
    pub txn_id: Sha256dHash,
21
    pub height: i32,
Roman Zeyde's avatar
Roman Zeyde committed
22 23 24 25 26 27
    pub output_index: usize,
    pub value: u64,
}

pub struct SpendingInput {
    pub txn_id: Sha256dHash,
28
    pub height: i32,
Roman Zeyde's avatar
Roman Zeyde committed
29 30 31 32 33 34 35 36 37 38 39
    pub input_index: usize,
}

pub struct Status {
    pub balance: u64,
    pub funding: Vec<FundingOutput>,
    pub spending: Vec<SpendingInput>,
}

struct TxnHeight {
    txn: Transaction,
40
    height: i32,
Roman Zeyde's avatar
Roman Zeyde committed
41 42
}

Roman Zeyde's avatar
Roman Zeyde committed
43 44 45 46 47
fn merklize(left: Sha256dHash, right: Sha256dHash) -> Sha256dHash {
    let data = [&left[..], &right[..]].concat();
    Sha256dHash::from_data(&data)
}

48
// TODO: return errors instead of panics
49
impl<'a> Query<'a> {
50 51 52 53 54 55
    pub fn new(store: &'a Store, daemon: &'a Daemon, index: &'a Index) -> Query<'a> {
        Query {
            store,
            daemon,
            index,
        }
56 57
    }

Roman Zeyde's avatar
Roman Zeyde committed
58
    fn load_txns(&self, prefixes: Vec<HashPrefix>) -> Vec<TxnHeight> {
59 60 61 62 63
        let mut txns = Vec::new();
        for txid_prefix in prefixes {
            for row in self.store.scan(&[b"T", &txid_prefix[..]].concat()) {
                let key: TxKey = bincode::deserialize(&row.key).unwrap();
                let txid: Sha256dHash = deserialize(&key.txid).unwrap();
64
                let txn: Transaction = self.get_tx(&txid);
Roman Zeyde's avatar
Roman Zeyde committed
65
                let height: u32 = bincode::deserialize(&row.value).unwrap();
66 67 68 69
                txns.push(TxnHeight {
                    txn,
                    height: height as i32,
                })
70 71 72 73 74
            }
        }
        txns
    }

Roman Zeyde's avatar
Roman Zeyde committed
75
    fn find_spending_input(&self, funding: &FundingOutput) -> Option<SpendingInput> {
76 77
        let spend_key = bincode::serialize(&TxInKey {
            code: b'I',
Roman Zeyde's avatar
Roman Zeyde committed
78 79
            prev_hash_prefix: hash_prefix(&funding.txn_id[..]),
            prev_index: funding.output_index as u16,
80
        }).unwrap();
Roman Zeyde's avatar
Roman Zeyde committed
81
        let spending_txns: Vec<TxnHeight> = self.load_txns(
82 83 84 85 86 87 88 89 90 91
            self.store
                .scan(&spend_key)
                .iter()
                .map(|row| {
                    bincode::deserialize::<TxInRow>(&row.key)
                        .unwrap()
                        .txid_prefix
                })
                .collect(),
        );
Roman Zeyde's avatar
Roman Zeyde committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
        let mut spending_inputs = Vec::new();
        for t in &spending_txns {
            for (index, input) in enumerate(&t.txn.input) {
                if input.prev_hash == funding.txn_id
                    && input.prev_index == funding.output_index as u32
                {
                    spending_inputs.push(SpendingInput {
                        txn_id: t.txn.txid(),
                        height: t.height,
                        input_index: index,
                    })
                }
            }
        }
        assert!(spending_inputs.len() <= 1);
        if spending_inputs.len() == 1 {
            Some(spending_inputs.remove(0))
109 110 111 112 113
        } else {
            None
        }
    }

Roman Zeyde's avatar
Roman Zeyde committed
114 115 116 117 118 119 120 121
    pub fn status(&self, script_hash: &[u8]) -> Status {
        let mut status = Status {
            balance: 0,
            funding: vec![],
            spending: vec![],
        };

        let funding_txns = self.load_txns(
122 123 124 125 126 127 128 129 130 131
            self.store
                .scan(&[b"O", &script_hash[..HASH_PREFIX_LEN]].concat())
                .iter()
                .map(|row| {
                    bincode::deserialize::<TxOutRow>(&row.key)
                        .unwrap()
                        .txid_prefix
                })
                .collect(),
        );
Roman Zeyde's avatar
Roman Zeyde committed
132 133 134 135 136 137 138 139 140 141
        for t in funding_txns {
            let txn_id = t.txn.txid();
            for (index, output) in enumerate(&t.txn.output) {
                if compute_script_hash(&output.script_pubkey[..]) == script_hash {
                    status.funding.push(FundingOutput {
                        txn_id: txn_id,
                        height: t.height,
                        output_index: index,
                        value: output.value,
                    })
142 143 144
                }
            }
        }
Roman Zeyde's avatar
Roman Zeyde committed
145 146 147 148 149 150 151 152
        for funding_output in &status.funding {
            if let Some(spent) = self.find_spending_input(&funding_output) {
                status.spending.push(spent);
            } else {
                status.balance += funding_output.value;
            }
        }
        status
153
    }
154

155
    pub fn get_tx(&self, tx_hash: &Sha256dHash) -> Transaction {
156
        self.daemon
157 158
            .gettransaction(tx_hash)
            .expect(&format!("failed to load tx {}", tx_hash))
159
    }
160

Roman Zeyde's avatar
Roman Zeyde committed
161
    pub fn get_headers(&self, heights: &[usize]) -> Vec<BlockHeader> {
162 163 164
        let headers_list = self.index.headers_list();
        let headers = headers_list.headers();
        let mut result = Vec::new();
Roman Zeyde's avatar
Roman Zeyde committed
165 166 167 168 169
        for height in heights {
            let header: &BlockHeader = match headers.get(*height) {
                Some(header) => header.header(),
                None => break,
            };
Roman Zeyde's avatar
Roman Zeyde committed
170
            result.push(*header);
171 172 173
        }
        result
    }
Roman Zeyde's avatar
Roman Zeyde committed
174 175 176 177 178

    pub fn get_best_header(&self) -> Option<HeaderEntry> {
        let header_list = self.index.headers_list();
        Some(header_list.headers().last()?.clone())
    }
Roman Zeyde's avatar
Roman Zeyde committed
179 180 181 182 183 184 185 186 187

    // TODO: add error-handling logic
    pub fn get_merkle_proof(
        &self,
        tx_hash: &Sha256dHash,
        height: usize,
    ) -> Option<(Vec<Sha256dHash>, usize)> {
        let header_list = self.index.headers_list();
        let blockhash = header_list.headers().get(height)?.hash();
188
        let block: Block = self.daemon.getblock(&blockhash).unwrap();
Roman Zeyde's avatar
Roman Zeyde committed
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        let mut txids: Vec<Sha256dHash> = block.txdata.iter().map(|tx| tx.txid()).collect();
        let pos = txids.iter().position(|txid| txid == tx_hash)?;
        let mut merkle = Vec::new();
        let mut index = pos;
        while txids.len() > 1 {
            if txids.len() % 2 != 0 {
                let last = txids.last().unwrap().clone();
                txids.push(last);
            }
            index = if index % 2 == 0 { index + 1 } else { index - 1 };
            merkle.push(txids[index]);
            index = index / 2;
            txids = txids
                .chunks(2)
                .map(|pair| merklize(pair[0], pair[1]))
                .collect()
        }
        Some((merkle, pos))
    }
208
}