chainseeker_server/db/
tx.rs

1use bitcoin::{Transaction, Txid, TxOut, Block};
2use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
3
4use crate::*;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct TxDBKey {
8    txid: Txid,
9}
10
11impl Serialize for TxDBKey {
12    fn serialize(&self) -> Vec<u8> {
13        consensus_encode(&self.txid)
14    }
15}
16
17impl Deserialize for TxDBKey {
18    fn deserialize(buf: &[u8]) -> Self {
19        Self {
20            txid: consensus_decode(buf)
21        }
22    }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct TxDBValue {
27    pub confirmed_height: Option<u32>,
28    pub tx: Transaction,
29    pub previous_txouts: Vec<TxOut>,
30}
31
32impl TxDBValue {
33    pub fn deserialize_as_rawtx(buf: &[u8]) -> (Option<u32>, Vec<u8>, Vec<TxOut>) {
34        let confirmed_height = bytes_to_i32(&buf[0..4]);
35        let confirmed_height = if confirmed_height >= 0 {
36            Some(confirmed_height as u32)
37        } else {
38            None
39        };
40        let tx_len = bytes_to_u32(&buf[4..8]) as usize;
41        let tx = buf[8..tx_len+8].to_vec();
42        let mut offset: usize = tx_len + 8;
43        let mut previous_txouts = Vec::new();
44        while offset < buf.len() {
45            let txout_len = bytes_to_u32(&buf[offset..offset+4]) as usize;
46            offset += 4;
47            let txout = consensus_decode(&buf[offset..txout_len+offset]);
48            offset += txout_len;
49            previous_txouts.push(txout);
50        }
51        (confirmed_height, tx, previous_txouts)
52    }
53}
54
55impl Serialize for TxDBValue {
56    fn serialize(&self) -> Vec<u8> {
57        let mut ret = Vec::new();
58        let confirmed_height = self.confirmed_height.map_or_else(|| -1i32, |confirmed_height| confirmed_height as i32);
59        ret.push(confirmed_height.to_le_bytes().to_vec());
60        let tx = consensus_encode(&self.tx);
61        let tx_len = tx.len() as u32;
62        ret.push(tx_len.to_le_bytes().to_vec());
63        ret.push(tx);
64        for txout in self.previous_txouts.iter() {
65            let txout = consensus_encode(txout);
66            let txout_len = txout.len() as u32;
67            ret.push(txout_len.to_le_bytes().to_vec());
68            ret.push(txout);
69        }
70        ret.concat()
71    }
72}
73
74impl Deserialize for TxDBValue {
75    fn deserialize(buf: &[u8]) -> Self {
76        let (confirmed_height, tx, previous_txouts) = Self::deserialize_as_rawtx(buf);
77        Self {
78            confirmed_height,
79            tx: consensus_decode(&tx),
80            previous_txouts,
81        }
82    }
83}
84
85#[derive(Debug)]
86pub struct TxDB {
87    db: RocksDB<TxDBKey, TxDBValue>,
88}
89
90impl TxDB {
91    pub fn path(coin: &str) -> String {
92        format!("{}/{}/tx", data_dir(), coin)
93    }
94    pub fn new(coin: &str, temporary: bool) -> Self {
95        let path = Self::path(coin);
96        Self {
97            db: RocksDB::new(&path, temporary),
98        }
99    }
100    pub fn put(&self, txid: &Txid, value: &TxDBValue) {
101        self.db.put(&TxDBKey { txid: *txid }, value);
102    }
103    pub fn put_tx(&self, tx: &Transaction, confirmed_height: Option<u32>) -> Result<TxDBValue, Txid> {
104        let mut previous_txouts = Vec::new();
105        for vin in tx.input.iter() {
106            if !vin.previous_output.is_null() {
107                let previous_txid = &vin.previous_output.txid;
108                match self.get(previous_txid) {
109                    Some(previous_tx) => previous_txouts.push(previous_tx.tx.output[vin.previous_output.vout as usize].clone()),
110                    None => return Err(*previous_txid),
111                }
112            }
113        }
114        let value = TxDBValue {
115            confirmed_height,
116            tx: (*tx).clone(),
117            previous_txouts,
118        };
119        self.put(&tx.txid(), &value);
120        Ok(value)
121    }
122    pub fn get(&self, txid: &Txid) -> Option<TxDBValue> {
123        self.db.get(&TxDBKey { txid: *txid })
124    }
125    pub fn get_as_rest(&self, txid: &Txid, config: &Config) -> Option<RestTx> {
126        //let begin_get = std::time::Instant::now();
127        let buf = self.db.get_raw(&TxDBKey { txid: *txid });
128        //println!("Transaction got in {}us.", begin_get.elapsed().as_micros());
129        buf.map_or_else(|| None, |buf| {
130            //let begin_convert = std::time::Instant::now();
131            let (confirmed_height, rawtx, previous_txouts) = TxDBValue::deserialize_as_rawtx(&buf);
132            let tx: Transaction = consensus_decode(&rawtx);
133            let mut input_value = 0;
134            let mut vin = Vec::new();
135            let mut previous_txout_index = 0;
136            for input in tx.input.iter() {
137                if input.previous_output.is_null() {
138                    vin.push(RestVin::new(input, &None, config));
139                } else {
140                    input_value += previous_txouts[previous_txout_index].value;
141                    vin.push(RestVin::new(input, &Some(previous_txouts[previous_txout_index].clone()), config));
142                    previous_txout_index += 1;
143                }
144            }
145            let output_value: u64 = tx.output.iter().map(|output| output.value).sum();
146            let tx = RestTx {
147                confirmed_height,
148                hex: hex::encode(&rawtx),
149                txid: tx.txid().to_string(),
150                hash: tx.wtxid().to_string(),
151                size: tx.get_size(),
152                // TODO: waiting for upstream merge.
153                //vsize: tx.get_vsize(),
154                vsize: (tx.get_weight() + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR,
155                weight: tx.get_weight(),
156                version: tx.version,
157                locktime: tx.lock_time,
158                vin,
159                vout: tx.output.iter().enumerate().map(|(n, vout)| RestVout::new(vout, n, config)).collect(),
160                // TODO: compute for coinbase transactions!
161                fee: (input_value as i64) - (output_value as i64),
162            };
163            //println!("Transaction converted in {}us.", begin_convert.elapsed().as_micros());
164            Some(tx)
165        })
166    }
167    pub fn multi_get<I: IntoIterator<Item = Txid>>(&self, txids: I) -> Vec<Option<TxDBValue>> {
168        let txids: Vec<TxDBKey> = txids.into_iter().map(|txid| TxDBKey { txid }).collect();
169        self.db.multi_get(txids)
170    }
171    pub fn process_block(&self, confirmed_height: u32, block: &Block, previous_utxos: &[UtxoEntry]) {
172        let mut previous_utxo_index = 0;
173        for tx in block.txdata.iter() {
174            // Process vins.
175            let mut previous_txouts = Vec::new();
176            for vin in tx.input.iter() {
177                if !vin.previous_output.is_null() {
178                    let txout = TxOut {
179                        value: previous_utxos[previous_utxo_index].value,
180                        script_pubkey: previous_utxos[previous_utxo_index].script_pubkey.clone(),
181                    };
182                    previous_txouts.push(txout);
183                    previous_utxo_index += 1;
184                }
185            }
186            let value = TxDBValue {
187                confirmed_height: Some(confirmed_height),
188                tx: (*tx).clone(),
189                previous_txouts,
190            };
191            self.put(&tx.txid(), &value);
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use std::str::FromStr;
199    use super::*;
200    const TXID: &str = "503e4e9824282eb06f1a328484e2b367b5f4f93a405d6e7b97261bafabfb53d5";
201    #[test]
202    fn key_deserialize() {
203        assert_eq!(
204            TxDBKey {
205                txid: Txid::from_str(TXID).unwrap(),
206            },
207            TxDBKey::deserialize(&Txid::from_hex(TXID).unwrap()),
208        );
209    }
210    #[test]
211    fn put_unconfirmed() {
212        let tx = &fixtures::regtest_blocks()[0].txdata[0];
213        let tx_db = TxDB::new("test/tx/unconfirmed", true);
214        tx_db.put_tx(&tx, None).unwrap();
215        assert_eq!(
216            tx_db.get(&tx.txid()).unwrap(),
217            TxDBValue {
218                confirmed_height: None,
219                tx: (*tx).clone(),
220                previous_txouts: Vec::new(),
221            },
222        );
223    }
224    #[test]
225    fn put_confirmed() {
226        let blocks = fixtures::regtest_blocks();
227        let mut utxo_db = UtxoDB::new("test/tx/confirmed", true);
228        let tx_db = TxDB::new("test/tx/confirmed", true);
229        let mut previous_utxos_vec = Vec::new();
230        for (height, block) in blocks.iter().enumerate() {
231            let previous_utxos = utxo_db.process_block(&block, true);
232            tx_db.process_block(height as u32, &block, &previous_utxos);
233            previous_utxos_vec.push(previous_utxos);
234        }
235        // txid = fe6c48bbfdc025670f4db0340650ba5a50f9307b091d9aaa19aa44291961c69f.
236        assert_eq!(
237            tx_db.put_tx(&consensus_decode(&hex::decode("01000000000101d553fbabaf1b26977b6e5d403af9f4b567b3e28484321a6fb02e2824984e3e5000000000171600142b2296c588ec413cebd19c3cbc04ea830ead6e78ffffffff01be1611020000000017a91487e4e5a7ff7bf78b8a8972a49381c8a673917f3e870247304402205f39ccbab38b644acea0776d18cb63ce3e37428cbac06dc23b59c61607aef69102206b8610827e9cb853ea0ba38983662034bd3575cc1ab118fb66d6a98066fa0bed01210304c01563d46e38264283b99bb352b46e69bf132431f102d4bd9a9d8dab075e7f00000000").unwrap()), Some(500_000)),
238            Result::<TxDBValue, Txid>::Err(Txid::from_str(TXID).unwrap()),
239        );
240        for (height, block) in blocks.iter().enumerate() {
241            let mut previous_utxo_index = 0;
242            for tx in block.txdata.iter() {
243                let mut previous_txout_index = 0;
244                let value = tx_db.get(&tx.txid()).unwrap();
245                assert_eq!(value.confirmed_height, Some(height as u32));
246                assert_eq!(value.tx, *tx);
247                for vin in tx.input.iter() {
248                    if !vin.previous_output.is_null() {
249                        let txout = TxOut {
250                            value: previous_utxos_vec[height][previous_utxo_index].value,
251                            script_pubkey: previous_utxos_vec[height][previous_utxo_index].script_pubkey.clone(),
252                        };
253                        assert_eq!(value.previous_txouts[previous_txout_index], txout);
254                        previous_utxo_index += 1;
255                        previous_txout_index += 1;
256                    }
257                }
258            }
259        }
260    }
261}