darkfi/blockchain/tx_store.rs
1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::collections::HashMap;
20
21use darkfi_sdk::tx::TransactionHash;
22use darkfi_serial::{deserialize, serialize};
23use sled_overlay::sled;
24
25use crate::{tx::Transaction, Error, Result};
26
27use super::{parse_record, parse_u64_key_record, SledDbOverlayPtr};
28
29pub const SLED_TX_TREE: &[u8] = b"_transactions";
30pub const SLED_TX_LOCATION_TREE: &[u8] = b"_transaction_location";
31pub const SLED_PENDING_TX_TREE: &[u8] = b"_pending_transactions";
32pub const SLED_PENDING_TX_ORDER_TREE: &[u8] = b"_pending_transactions_order";
33
34/// The `TxStore` is a structure representing all `sled` trees related
35/// to storing the blockchain's transactions information.
36#[derive(Clone)]
37pub struct TxStore {
38 /// Main `sled` tree, storing all the blockchain's transactions, where
39 /// the key is the transaction hash, and the value is the serialized
40 /// transaction.
41 pub main: sled::Tree,
42 /// The `sled` tree storing the location of the blockchain's transactions
43 /// locations, where the key is the transaction hash, and the value is a
44 /// serialized tuple containing the height and the vector index of the
45 /// block the transaction is included.
46 pub location: sled::Tree,
47 /// The `sled` tree storing all the node pending transactions, where
48 /// the key is the transaction hash, and the value is the serialized
49 /// transaction.
50 pub pending: sled::Tree,
51 /// The `sled` tree storing the order of all the node pending transactions,
52 /// where the key is an incremental value, and the value is the serialized
53 /// transaction.
54 pub pending_order: sled::Tree,
55}
56
57impl TxStore {
58 /// Opens a new or existing `TxStore` on the given sled database.
59 pub fn new(db: &sled::Db) -> Result<Self> {
60 let main = db.open_tree(SLED_TX_TREE)?;
61 let location = db.open_tree(SLED_TX_LOCATION_TREE)?;
62 let pending = db.open_tree(SLED_PENDING_TX_TREE)?;
63 let pending_order = db.open_tree(SLED_PENDING_TX_ORDER_TREE)?;
64 Ok(Self { main, location, pending, pending_order })
65 }
66
67 /// Insert a slice of [`Transaction`] into the store's main tree.
68 pub fn insert(&self, transactions: &[Transaction]) -> Result<Vec<TransactionHash>> {
69 let (batch, ret) = self.insert_batch(transactions);
70 self.main.apply_batch(batch)?;
71 Ok(ret)
72 }
73
74 /// Insert a slice of [`TransactionHash`] into the store's location tree.
75 pub fn insert_location(&self, txs_hashes: &[TransactionHash], block_height: u32) -> Result<()> {
76 let batch = self.insert_batch_location(txs_hashes, block_height);
77 self.location.apply_batch(batch)?;
78 Ok(())
79 }
80
81 /// Insert a slice of [`Transaction`] into the store's pending txs tree.
82 pub fn insert_pending(&self, transactions: &[Transaction]) -> Result<Vec<TransactionHash>> {
83 let (batch, ret) = self.insert_batch_pending(transactions);
84 self.pending.apply_batch(batch)?;
85 Ok(ret)
86 }
87
88 /// Insert a slice of [`TransactionHash`] into the store's pending txs order tree.
89 pub fn insert_pending_order(&self, txs_hashes: &[TransactionHash]) -> Result<()> {
90 let batch = self.insert_batch_pending_order(txs_hashes)?;
91 self.pending_order.apply_batch(batch)?;
92 Ok(())
93 }
94
95 /// Generate the sled batch corresponding to an insert to the main tree,
96 /// so caller can handle the write operation.
97 /// The transactions are hashed with BLAKE3 and this hash is used as
98 /// the key, while the value is the serialized [`Transaction`] itself.
99 /// On success, the function returns the transaction hashes in the same
100 /// order as the input transactions, along with the corresponding operation
101 /// batch.
102 pub fn insert_batch(
103 &self,
104 transactions: &[Transaction],
105 ) -> (sled::Batch, Vec<TransactionHash>) {
106 let mut ret = Vec::with_capacity(transactions.len());
107 let mut batch = sled::Batch::default();
108
109 for tx in transactions {
110 let tx_hash = tx.hash();
111 batch.insert(tx_hash.inner(), serialize(tx));
112 ret.push(tx_hash);
113 }
114
115 (batch, ret)
116 }
117
118 /// Generate the sled batch corresponding to an insert to the location tree,
119 /// so caller can handle the write operation.
120 /// The location tuple is built using the index of each transaction has in
121 /// the slice, along with the provided block height
122 pub fn insert_batch_location(
123 &self,
124 txs_hashes: &[TransactionHash],
125 block_height: u32,
126 ) -> sled::Batch {
127 let mut batch = sled::Batch::default();
128
129 for (index, tx_hash) in txs_hashes.iter().enumerate() {
130 let serialized = serialize(&(block_height, index as u16));
131 batch.insert(tx_hash.inner(), serialized);
132 }
133
134 batch
135 }
136
137 /// Generate the sled batch corresponding to an insert to the pending txs tree,
138 /// so caller can handle the write operation.
139 /// The transactions are hashed with BLAKE3 and this hash is used as
140 /// the key, while the value is the serialized [`Transaction`] itself.
141 /// On success, the function returns the transaction hashes in the same
142 /// order as the input transactions, along with the corresponding operation
143 /// batch.
144 pub fn insert_batch_pending(
145 &self,
146 transactions: &[Transaction],
147 ) -> (sled::Batch, Vec<TransactionHash>) {
148 let mut ret = Vec::with_capacity(transactions.len());
149 let mut batch = sled::Batch::default();
150
151 for tx in transactions {
152 let tx_hash = tx.hash();
153 batch.insert(tx_hash.inner(), serialize(tx));
154 ret.push(tx_hash);
155 }
156
157 (batch, ret)
158 }
159
160 /// Generate the sled batch corresponding to an insert to the pending txs
161 /// order tree, so caller can handle the write operation.
162 pub fn insert_batch_pending_order(&self, tx_hashes: &[TransactionHash]) -> Result<sled::Batch> {
163 let mut batch = sled::Batch::default();
164
165 let mut next_index = match self.pending_order.last()? {
166 Some(n) => {
167 let prev_bytes: [u8; 8] = n.0.as_ref().try_into().unwrap();
168 let prev = u64::from_be_bytes(prev_bytes);
169 prev + 1
170 }
171 None => 0,
172 };
173
174 for tx_hash in tx_hashes {
175 batch.insert(&next_index.to_be_bytes(), tx_hash.inner());
176 next_index += 1;
177 }
178
179 Ok(batch)
180 }
181
182 /// Check if the store's main tree contains a given transaction hash.
183 pub fn contains(&self, tx_hash: &TransactionHash) -> Result<bool> {
184 Ok(self.main.contains_key(tx_hash.inner())?)
185 }
186
187 /// Check if the store's pending txs tree contains a given transaction hash.
188 pub fn contains_pending(&self, tx_hash: &TransactionHash) -> Result<bool> {
189 Ok(self.pending.contains_key(tx_hash.inner())?)
190 }
191
192 /// Fetch given tx hashes from the store's main tree.
193 /// The resulting vector contains `Option`, which is `Some` if the tx
194 /// was found in the txstore, and otherwise it is `None`, if it has not.
195 /// The second parameter is a boolean which tells the function to fail in
196 /// case at least one tx was not found.
197 pub fn get(
198 &self,
199 tx_hashes: &[TransactionHash],
200 strict: bool,
201 ) -> Result<Vec<Option<Transaction>>> {
202 let mut ret = Vec::with_capacity(tx_hashes.len());
203
204 for tx_hash in tx_hashes {
205 if let Some(found) = self.main.get(tx_hash.inner())? {
206 let tx = deserialize(&found)?;
207 ret.push(Some(tx));
208 continue
209 }
210 if strict {
211 return Err(Error::TransactionNotFound(tx_hash.as_string()))
212 }
213 ret.push(None);
214 }
215
216 Ok(ret)
217 }
218
219 /// Fetch given tx hashes locations from the store's location tree.
220 /// The resulting vector contains `Option`, which is `Some` if the tx
221 /// was found in the txstore, and otherwise it is `None`, if it has not.
222 /// The second parameter is a boolean which tells the function to fail in
223 /// case at least one tx was not found.
224 pub fn get_location(
225 &self,
226 tx_hashes: &[TransactionHash],
227 strict: bool,
228 ) -> Result<Vec<Option<(u32, u16)>>> {
229 let mut ret = Vec::with_capacity(tx_hashes.len());
230
231 for tx_hash in tx_hashes {
232 if let Some(found) = self.location.get(tx_hash.inner())? {
233 let location = deserialize(&found)?;
234 ret.push(Some(location));
235 continue
236 }
237 if strict {
238 return Err(Error::TransactionNotFound(tx_hash.as_string()))
239 }
240 ret.push(None);
241 }
242
243 Ok(ret)
244 }
245
246 /// Fetch given tx hashes from the store's pending txs tree.
247 /// The resulting vector contains `Option`, which is `Some` if the tx
248 /// was found in the pending tx store, and otherwise it is `None`, if it has not.
249 /// The second parameter is a boolean which tells the function to fail in
250 /// case at least one tx was not found.
251 pub fn get_pending(
252 &self,
253 tx_hashes: &[TransactionHash],
254 strict: bool,
255 ) -> Result<Vec<Option<Transaction>>> {
256 let mut ret = Vec::with_capacity(tx_hashes.len());
257
258 for tx_hash in tx_hashes {
259 if let Some(found) = self.pending.get(tx_hash.inner())? {
260 let tx = deserialize(&found)?;
261 ret.push(Some(tx));
262 continue
263 }
264 if strict {
265 return Err(Error::TransactionNotFound(tx_hash.as_string()))
266 }
267 ret.push(None);
268 }
269
270 Ok(ret)
271 }
272
273 /// Retrieve all transactions from the store's main tree in the form of
274 /// a tuple (`tx_hash`, `tx`).
275 /// Be careful as this will try to load everything in memory.
276 pub fn get_all(&self) -> Result<Vec<(TransactionHash, Transaction)>> {
277 let mut txs = vec![];
278
279 for tx in self.main.iter() {
280 txs.push(parse_record(tx.unwrap())?);
281 }
282
283 Ok(txs)
284 }
285
286 /// Retrieve all transactions locations from the store's location tree in
287 /// the form of a tuple (`tx_hash`, (`block_height`, `index`)).
288 /// Be careful as this will try to load everything in memory.
289 pub fn get_all_location(&self) -> Result<Vec<(TransactionHash, (u32, u16))>> {
290 let mut locations = vec![];
291
292 for location in self.location.iter() {
293 locations.push(parse_record(location.unwrap())?);
294 }
295
296 Ok(locations)
297 }
298
299 /// Retrieve all transactions from the store's pending txs tree in the
300 /// form of a HashMap with key the transaction hash and value the
301 /// transaction itself.
302 /// Be careful as this will try to load everything in memory.
303 pub fn get_all_pending(&self) -> Result<HashMap<TransactionHash, Transaction>> {
304 let mut txs = HashMap::new();
305
306 for tx in self.pending.iter() {
307 let (key, value) = parse_record(tx.unwrap())?;
308 txs.insert(key, value);
309 }
310
311 Ok(txs)
312 }
313
314 /// Retrieve all transactions from the store's pending txs order tree in
315 /// the form of a tuple (`u64`, `TransactionHash`).
316 /// Be careful as this will try to load everything in memory.
317 pub fn get_all_pending_order(&self) -> Result<Vec<(u64, TransactionHash)>> {
318 let mut txs = vec![];
319
320 for tx in self.pending_order.iter() {
321 txs.push(parse_u64_key_record(tx.unwrap())?);
322 }
323
324 Ok(txs)
325 }
326
327 /// Fetch n transactions after given order([order..order+n)). In the iteration,
328 /// if a transaction order is not found, the iteration stops and the function
329 /// returns what it has found so far in the store's pending order tree.
330 pub fn get_after_pending(&self, order: u64, n: usize) -> Result<(u64, Vec<Transaction>)> {
331 let mut hashes = vec![];
332
333 // First we grab the order itself
334 if let Some(found) = self.pending_order.get(order.to_be_bytes())? {
335 let hash = deserialize(&found)?;
336 hashes.push(hash);
337 }
338
339 // Then whatever comes after it
340 let mut key = order;
341 let mut counter = 0;
342 while counter < n {
343 if let Some(found) = self.pending_order.get_gt(key.to_be_bytes())? {
344 let (order, hash) = parse_u64_key_record(found)?;
345 key = order;
346 hashes.push(hash);
347 counter += 1;
348 continue
349 }
350 break
351 }
352
353 if hashes.is_empty() {
354 return Ok((key, vec![]))
355 }
356
357 let txs = self.get_pending(&hashes, true)?.iter().map(|tx| tx.clone().unwrap()).collect();
358
359 Ok((key, txs))
360 }
361
362 /// Retrieve records count of the store's main tree.
363 pub fn len(&self) -> usize {
364 self.main.len()
365 }
366
367 /// Check if the store's main tree is empty.
368 pub fn is_empty(&self) -> bool {
369 self.main.is_empty()
370 }
371
372 /// Remove a slice of [`TransactionHash`] from the store's pending txs tree.
373 pub fn remove_pending(&self, txs_hashes: &[TransactionHash]) -> Result<()> {
374 let batch = self.remove_batch_pending(txs_hashes);
375 self.pending.apply_batch(batch)?;
376 Ok(())
377 }
378
379 /// Remove a slice of [`u64`] from the store's pending txs order tree.
380 pub fn remove_pending_order(&self, indexes: &[u64]) -> Result<()> {
381 let batch = self.remove_batch_pending_order(indexes);
382 self.pending_order.apply_batch(batch)?;
383 Ok(())
384 }
385
386 /// Generate the sled batch corresponding to a remove from the store's pending
387 /// txs tree, so caller can handle the write operation.
388 pub fn remove_batch_pending(&self, txs_hashes: &[TransactionHash]) -> sled::Batch {
389 let mut batch = sled::Batch::default();
390
391 for tx_hash in txs_hashes {
392 batch.remove(tx_hash.inner());
393 }
394
395 batch
396 }
397
398 /// Generate the sled batch corresponding to a remove from the store's pending
399 /// txs order tree, so caller can handle the write operation.
400 pub fn remove_batch_pending_order(&self, indexes: &[u64]) -> sled::Batch {
401 let mut batch = sled::Batch::default();
402
403 for index in indexes {
404 batch.remove(&index.to_be_bytes());
405 }
406
407 batch
408 }
409}
410
411/// Overlay structure over a [`TxStore`] instance.
412pub struct TxStoreOverlay(SledDbOverlayPtr);
413
414impl TxStoreOverlay {
415 pub fn new(overlay: &SledDbOverlayPtr) -> Result<Self> {
416 overlay.lock().unwrap().open_tree(SLED_TX_TREE, true)?;
417 overlay.lock().unwrap().open_tree(SLED_TX_LOCATION_TREE, true)?;
418 Ok(Self(overlay.clone()))
419 }
420
421 /// Insert a slice of [`Transaction`] into the overlay's main tree.
422 /// The transactions are hashed with BLAKE3 and this hash is used as
423 /// the key, while the value is the serialized [`Transaction`] itself.
424 /// On success, the function returns the transaction hashes in the same
425 /// order as the input transactions.
426 pub fn insert(&self, transactions: &[Transaction]) -> Result<Vec<TransactionHash>> {
427 let mut ret = Vec::with_capacity(transactions.len());
428 let mut lock = self.0.lock().unwrap();
429
430 for tx in transactions {
431 let tx_hash = tx.hash();
432 lock.insert(SLED_TX_TREE, tx_hash.inner(), &serialize(tx))?;
433 ret.push(tx_hash);
434 }
435
436 Ok(ret)
437 }
438
439 /// Insert a slice of [`TransactionHash`] into the overlay's location tree.
440 /// The location tuple is built using the index of each transaction hash
441 /// in the slice, along with the provided block height
442 pub fn insert_location(&self, txs_hashes: &[TransactionHash], block_height: u32) -> Result<()> {
443 let mut lock = self.0.lock().unwrap();
444
445 for (index, tx_hash) in txs_hashes.iter().enumerate() {
446 let serialized = serialize(&(block_height, index as u16));
447 lock.insert(SLED_TX_LOCATION_TREE, tx_hash.inner(), &serialized)?;
448 }
449
450 Ok(())
451 }
452
453 /// Fetch given tx hashes from the overlay's main tree.
454 /// The resulting vector contains `Option`, which is `Some` if the tx
455 /// was found in the overlay, and otherwise it is `None`, if it has not.
456 /// The second parameter is a boolean which tells the function to fail in
457 /// case at least one tx was not found.
458 pub fn get(
459 &self,
460 tx_hashes: &[TransactionHash],
461 strict: bool,
462 ) -> Result<Vec<Option<Transaction>>> {
463 let mut ret = Vec::with_capacity(tx_hashes.len());
464 let lock = self.0.lock().unwrap();
465
466 for tx_hash in tx_hashes {
467 if let Some(found) = lock.get(SLED_TX_TREE, tx_hash.inner())? {
468 let tx = deserialize(&found)?;
469 ret.push(Some(tx));
470 continue
471 }
472 if strict {
473 return Err(Error::TransactionNotFound(tx_hash.as_string()))
474 }
475 ret.push(None);
476 }
477
478 Ok(ret)
479 }
480
481 /// Fetch given tx hash from the overlay's main tree. This function uses
482 /// raw bytes as input and doesn't deserialize the retrieved value.
483 /// The resulting vector contains `Option`, which is `Some` if the tx
484 /// was found in the overlay, and otherwise it is `None`, if it has not.
485 pub fn get_raw(&self, tx_hash: &[u8; 32]) -> Result<Option<Vec<u8>>> {
486 let lock = self.0.lock().unwrap();
487 if let Some(found) = lock.get(SLED_TX_TREE, tx_hash)? {
488 return Ok(Some(found.to_vec()))
489 }
490 Ok(None)
491 }
492
493 /// Fetch given tx hash location from the overlay's location tree.
494 /// This function uses raw bytes as input and doesn't deserialize the
495 /// retrieved value. The resulting vector contains `Option`, which is
496 /// `Some` if the location was found in the overlay, and otherwise it
497 /// is `None`, if it has not.
498 pub fn get_location_raw(&self, tx_hash: &[u8; 32]) -> Result<Option<Vec<u8>>> {
499 let lock = self.0.lock().unwrap();
500 if let Some(found) = lock.get(SLED_TX_LOCATION_TREE, tx_hash)? {
501 return Ok(Some(found.to_vec()))
502 }
503 Ok(None)
504 }
505}