chainseeker_server/db/
utxo.rs

1use bitcoin::{Block, Txid, Script, Transaction};
2
3use crate::*;
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
6pub struct UtxoEntry {
7    pub script_pubkey: Script,
8    pub txid: Txid,
9    pub vout: u32,
10    pub value: u64,
11}
12
13#[derive(Debug, Clone)]
14pub struct UtxoDBKey {
15    txid: Txid,
16    vout: u32,
17}
18
19impl Serialize for UtxoDBKey {
20    fn serialize(&self) -> Vec<u8> {
21        [self.txid.to_vec(), self.vout.to_le_bytes().to_vec()].concat()
22    }
23}
24
25impl Deserialize for UtxoDBKey {
26    fn deserialize(buf: &[u8]) -> Self {
27        let txid = consensus_decode(&buf[0..32]);
28        let vout = bytes_to_u32(&buf[32..36]);
29        Self {
30            txid,
31            vout,
32        }
33    }
34}
35
36#[derive(Debug, Clone)]
37pub struct UtxoDBValue {
38    script_pubkey: Script,
39    value: u64,
40}
41
42impl Serialize for UtxoDBValue {
43    fn serialize(&self) -> Vec<u8> {
44        [consensus_encode(&self.script_pubkey), self.value.to_le_bytes().to_vec()].concat()
45    }
46}
47
48impl Deserialize for UtxoDBValue {
49    fn deserialize(buf: &[u8]) -> Self {
50        let script_pubkey_len = buf.len() - 8;
51        let script_pubkey = consensus_decode(&buf[0..script_pubkey_len]);
52        let value = bytes_to_u64(&buf[script_pubkey_len..]);
53        Self {
54            script_pubkey,
55            value,
56        }
57    }
58}
59
60impl From<(UtxoDBKey, UtxoDBValue)> for UtxoEntry {
61    fn from(data: (UtxoDBKey, UtxoDBValue)) -> Self {
62        UtxoEntry {
63            script_pubkey: data.1.script_pubkey,
64            txid: data.0.txid,
65            vout: data.0.vout,
66            value: data.1.value,
67        }
68    }
69}
70
71pub struct UtxoDBIterator<'a> {
72    iter: RocksDBIterator<'a, UtxoDBKey, UtxoDBValue>,
73}
74
75impl<'a> Iterator for UtxoDBIterator<'a> {
76    type Item = UtxoEntry;
77    fn next(&mut self) -> Option<Self::Item> {
78        let next = self.iter.next();
79        match next {
80            Some((key, value)) => {
81                let utxo: UtxoEntry = (key, value).into();
82                Some(utxo)
83            },
84            None => None,
85        }
86    }
87}
88
89pub struct UtxoDB {
90    /// Stores:
91    ///     key   = txid || vout
92    ///     value = script_pubkey || value
93    pub db: RocksDB<UtxoDBKey, UtxoDBValue>,
94}
95
96impl UtxoDB {
97    pub fn new(coin: &str, temporary: bool) -> Self {
98        let path = Self::get_path(coin);
99        Self {
100            db: RocksDB::new(&path, temporary),
101        }
102    }
103    fn get_path(coin: &str) -> String {
104        format!("{}/{}/utxo", data_dir(), coin)
105    }
106    pub fn iter(&self) -> UtxoDBIterator {
107        UtxoDBIterator {
108            iter: self.db.iter(),
109        }
110    }
111    pub fn process_block(&mut self, block: &Block, no_panic: bool) -> Vec<UtxoEntry> {
112        // Process vouts.
113        for tx in block.txdata.iter() {
114            let txid = tx.txid();
115            for (vout, output) in tx.output.iter().enumerate() {
116                self.db.put(
117                    &UtxoDBKey {
118                        txid,
119                        vout: vout as u32,
120                    },
121                    &UtxoDBValue {
122                        script_pubkey: output.script_pubkey.clone(),
123                        value: output.value,
124                    },
125                );
126            }
127        }
128        // Process vins.
129        let mut previous_utxos = Vec::new();
130        for tx in block.txdata.iter() {
131            for vin in tx.input.iter() {
132                if !vin.previous_output.is_null() {
133                    let txid = vin.previous_output.txid;
134                    let vout = vin.previous_output.vout;
135                    let key = UtxoDBKey {
136                        txid,
137                        vout,
138                    };
139                    match self.db.get(&key) {
140                        Some(value) => {
141                            self.db.delete(&key);
142                            let utxo = UtxoEntry {
143                                script_pubkey: value.script_pubkey,
144                                txid: key.txid,
145                                vout: key.vout,
146                                value: value.value,
147                            };
148                            previous_utxos.push(utxo);
149                        },
150                        None => {
151                            if !no_panic {
152                                panic!("Failed to find UTXO entry.");
153                            }
154                        },
155                    }
156                }
157            }
158        }
159        previous_utxos
160    }
161    pub fn reorg_block(&mut self, block: &Block, prev_txs: &[Transaction]) {
162        // Process vins.
163        let mut prev_tx_offset = 0;
164        for tx in block.txdata.iter() {
165            for vin in tx.input.iter() {
166                if vin.previous_output.is_null() {
167                    continue;
168                }
169                let txid = &vin.previous_output.txid;
170                let vout = vin.previous_output.vout;
171                let key = UtxoDBKey {
172                    txid: *txid,
173                    vout,
174                };
175                let prev_tx = &prev_txs[prev_tx_offset];
176                prev_tx_offset += 1;
177                let prev_out = &prev_tx.output[vout as usize];
178                let script_pubkey = &prev_out.script_pubkey;
179                let value = prev_out.value;
180                let value = UtxoDBValue {
181                    script_pubkey: (*script_pubkey).clone(),
182                    value,
183                };
184                self.db.put(&key, &value);
185            }
186        }
187        // Process vouts.
188        for tx in block.txdata.iter() {
189            let txid = tx.txid();
190            for vout in 0..tx.output.len() {
191                let key = UtxoDBKey {
192                    txid,
193                    vout: vout as u32,
194                };
195                self.db.delete(&key);
196            }
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    #[allow(dead_code)]
205    fn print_utxo_db(utxo_db: &UtxoDB) {
206        let mut utxos = utxo_db.iter().collect::<Vec<UtxoEntry>>();
207        utxos.sort();
208        for utxo in utxos.iter() {
209            println!("        UtxoEntry {{ script_pubkey: consensus_decode(&Vec::from_hex(\"{}\").unwrap()), txid: consensus_decode(&Vec::from_hex(\"{}\").unwrap()), vout: {}, value: {}u64, }},",
210            hex::encode(consensus_encode(&utxo.script_pubkey)),
211            hex::encode(consensus_encode(&utxo.txid)),
212            utxo.vout,
213            utxo.value);
214        }
215    }
216    fn find_tx(blocks: &[Block], txid: &Txid) -> Transaction {
217        for block in blocks.iter() {
218            for tx in block.txdata.iter() {
219                if tx.txid() == *txid {
220                    return (*tx).clone();
221                }
222            }
223        }
224        panic!("Failed to find the transaction with txid = {}.", txid);
225    }
226    #[test]
227    fn utxo_db() {
228        let blocks = fixtures::regtest_blocks();
229        let mut utxo_db = UtxoDB::new("test/utxo", true);
230        for block in blocks.iter() {
231            utxo_db.process_block(&block, false);
232        }
233        println!("BEFORE");
234        print_utxo_db(&utxo_db);
235        // Test UTXO database BEFORE reorg.
236        let mut utxos_test = utxo_db.iter().collect::<Vec<UtxoEntry>>();
237        utxos_test.sort();
238        let utxos = fixtures::utxos_before_reorg();
239        assert_eq!(utxos_test, utxos);
240        // Test UTXO database AFTER reorg.
241        let reorged_block = fixtures::regtest_reorged_block();
242        // Find previous transactions.
243        let mut prev_txs = Vec::new();
244        for tx in blocks.last().unwrap().txdata.iter() {
245            for vin in tx.input.iter() {
246                if vin.previous_output.is_null() {
247                    continue;
248                }
249                let txid = &vin.previous_output.txid;
250                let prev_tx = find_tx(&blocks, txid);
251                prev_txs.push(prev_tx);
252            }
253        }
254        utxo_db.reorg_block(&blocks.last().unwrap(), &prev_txs);
255        utxo_db.process_block(&reorged_block, false);
256        println!("AFTER");
257        print_utxo_db(&utxo_db);
258        let mut utxos_test = utxo_db.iter().collect::<Vec<UtxoEntry>>();
259        utxos_test.sort();
260        let utxos = fixtures::utxos_after_reorg();
261        assert_eq!(utxos_test, utxos);
262    }
263}