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 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 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 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 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 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 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 let reorged_block = fixtures::regtest_reorged_block();
242 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}