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}