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, 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";
32
33/// The `TxStore` is a structure representing all `sled` trees related
34/// to storing the blockchain's transactions information.
35#[derive(Clone)]
36pub struct TxStore {
37    /// Main `sled` tree, storing all the blockchain's transactions, where
38    /// the key is the transaction hash, and the value is the serialized
39    /// transaction.
40    pub main: sled::Tree,
41    /// The `sled` tree storing the location of the blockchain's transactions
42    /// locations, where the key is the transaction hash, and the value is a
43    /// serialized tuple containing the height and the vector index of the
44    /// block the transaction is included.
45    pub location: sled::Tree,
46    /// The `sled` tree storing all the node pending transactions, where
47    /// the key is the transaction hash, and the value is the serialized
48    /// transaction.
49    pub pending: sled::Tree,
50}
51
52impl TxStore {
53    /// Opens a new or existing `TxStore` on the given sled database.
54    pub fn new(db: &sled::Db) -> Result<Self> {
55        let main = db.open_tree(SLED_TX_TREE)?;
56        let location = db.open_tree(SLED_TX_LOCATION_TREE)?;
57        let pending = db.open_tree(SLED_PENDING_TX_TREE)?;
58        Ok(Self { main, location, pending })
59    }
60
61    /// Insert a slice of [`Transaction`] into the store's main tree.
62    pub fn insert(&self, transactions: &[Transaction]) -> Result<Vec<TransactionHash>> {
63        let (batch, ret) = self.insert_batch(transactions);
64        self.main.apply_batch(batch)?;
65        Ok(ret)
66    }
67
68    /// Insert a slice of [`TransactionHash`] into the store's location tree.
69    pub fn insert_location(&self, txs_hashes: &[TransactionHash], block_height: u32) -> Result<()> {
70        let batch = self.insert_batch_location(txs_hashes, block_height);
71        self.location.apply_batch(batch)?;
72        Ok(())
73    }
74
75    /// Insert a slice of [`Transaction`] into the store's pending txs tree.
76    pub fn insert_pending(&self, transactions: &[Transaction]) -> Result<Vec<TransactionHash>> {
77        let (batch, ret) = self.insert_batch_pending(transactions);
78        self.pending.apply_batch(batch)?;
79        Ok(ret)
80    }
81
82    /// Generate the sled batch corresponding to an insert to the main tree,
83    /// so caller can handle the write operation.
84    /// The transactions are hashed with BLAKE3 and this hash is used as
85    /// the key, while the value is the serialized [`Transaction`] itself.
86    /// On success, the function returns the transaction hashes in the same
87    /// order as the input transactions, along with the corresponding operation
88    /// batch.
89    pub fn insert_batch(
90        &self,
91        transactions: &[Transaction],
92    ) -> (sled::Batch, Vec<TransactionHash>) {
93        let mut ret = Vec::with_capacity(transactions.len());
94        let mut batch = sled::Batch::default();
95
96        for tx in transactions {
97            let tx_hash = tx.hash();
98            batch.insert(tx_hash.inner(), serialize(tx));
99            ret.push(tx_hash);
100        }
101
102        (batch, ret)
103    }
104
105    /// Generate the sled batch corresponding to an insert to the location tree,
106    /// so caller can handle the write operation.
107    /// The location tuple is built using the index of each transaction has in
108    /// the slice, along with the provided block height
109    pub fn insert_batch_location(
110        &self,
111        txs_hashes: &[TransactionHash],
112        block_height: u32,
113    ) -> sled::Batch {
114        let mut batch = sled::Batch::default();
115
116        for (index, tx_hash) in txs_hashes.iter().enumerate() {
117            let serialized = serialize(&(block_height, index as u16));
118            batch.insert(tx_hash.inner(), serialized);
119        }
120
121        batch
122    }
123
124    /// Generate the sled batch corresponding to an insert to the pending txs tree,
125    /// so caller can handle the write operation.
126    /// The transactions are hashed with BLAKE3 and this hash is used as
127    /// the key, while the value is the serialized [`Transaction`] itself.
128    /// On success, the function returns the transaction hashes in the same
129    /// order as the input transactions, along with the corresponding operation
130    /// batch.
131    pub fn insert_batch_pending(
132        &self,
133        transactions: &[Transaction],
134    ) -> (sled::Batch, Vec<TransactionHash>) {
135        let mut ret = Vec::with_capacity(transactions.len());
136        let mut batch = sled::Batch::default();
137
138        for tx in transactions {
139            let tx_hash = tx.hash();
140            batch.insert(tx_hash.inner(), serialize(tx));
141            ret.push(tx_hash);
142        }
143
144        (batch, ret)
145    }
146
147    /// Check if the store's main tree contains a given transaction hash.
148    pub fn contains(&self, tx_hash: &TransactionHash) -> Result<bool> {
149        Ok(self.main.contains_key(tx_hash.inner())?)
150    }
151
152    /// Check if the store's pending txs tree contains a given transaction hash.
153    pub fn contains_pending(&self, tx_hash: &TransactionHash) -> Result<bool> {
154        Ok(self.pending.contains_key(tx_hash.inner())?)
155    }
156
157    /// Fetch given tx hashes from the store's main tree.
158    /// The resulting vector contains `Option`, which is `Some` if the tx
159    /// was found in the txstore, and otherwise it is `None`, if it has not.
160    /// The second parameter is a boolean which tells the function to fail in
161    /// case at least one tx was not found.
162    pub fn get(
163        &self,
164        tx_hashes: &[TransactionHash],
165        strict: bool,
166    ) -> Result<Vec<Option<Transaction>>> {
167        let mut ret = Vec::with_capacity(tx_hashes.len());
168
169        for tx_hash in tx_hashes {
170            if let Some(found) = self.main.get(tx_hash.inner())? {
171                let tx = deserialize(&found)?;
172                ret.push(Some(tx));
173                continue
174            }
175            if strict {
176                return Err(Error::TransactionNotFound(tx_hash.as_string()))
177            }
178            ret.push(None);
179        }
180
181        Ok(ret)
182    }
183
184    /// Fetch given tx hashes locations from the store's location tree.
185    /// The resulting vector contains `Option`, which is `Some` if the tx
186    /// was found in the txstore, and otherwise it is `None`, if it has not.
187    /// The second parameter is a boolean which tells the function to fail in
188    /// case at least one tx was not found.
189    pub fn get_location(
190        &self,
191        tx_hashes: &[TransactionHash],
192        strict: bool,
193    ) -> Result<Vec<Option<(u32, u16)>>> {
194        let mut ret = Vec::with_capacity(tx_hashes.len());
195
196        for tx_hash in tx_hashes {
197            if let Some(found) = self.location.get(tx_hash.inner())? {
198                let location = deserialize(&found)?;
199                ret.push(Some(location));
200                continue
201            }
202            if strict {
203                return Err(Error::TransactionNotFound(tx_hash.as_string()))
204            }
205            ret.push(None);
206        }
207
208        Ok(ret)
209    }
210
211    /// Fetch given tx hashes from the store's pending txs tree.
212    /// The resulting vector contains `Option`, which is `Some` if the tx
213    /// was found in the pending tx store, and otherwise it is `None`, if it has not.
214    /// The second parameter is a boolean which tells the function to fail in
215    /// case at least one tx was not found.
216    pub fn get_pending(
217        &self,
218        tx_hashes: &[TransactionHash],
219        strict: bool,
220    ) -> Result<Vec<Option<Transaction>>> {
221        let mut ret = Vec::with_capacity(tx_hashes.len());
222
223        for tx_hash in tx_hashes {
224            if let Some(found) = self.pending.get(tx_hash.inner())? {
225                let tx = deserialize(&found)?;
226                ret.push(Some(tx));
227                continue
228            }
229            if strict {
230                return Err(Error::TransactionNotFound(tx_hash.as_string()))
231            }
232            ret.push(None);
233        }
234
235        Ok(ret)
236    }
237
238    /// Retrieve all transactions from the store's main tree in the form of
239    /// a tuple (`tx_hash`, `tx`).
240    /// Be careful as this will try to load everything in memory.
241    pub fn get_all(&self) -> Result<Vec<(TransactionHash, Transaction)>> {
242        let mut txs = vec![];
243
244        for tx in self.main.iter() {
245            txs.push(parse_record(tx.unwrap())?);
246        }
247
248        Ok(txs)
249    }
250
251    /// Retrieve all transactions locations from the store's location tree in
252    /// the form of a tuple (`tx_hash`, (`block_height`, `index`)).
253    /// Be careful as this will try to load everything in memory.
254    pub fn get_all_location(&self) -> Result<Vec<(TransactionHash, (u32, u16))>> {
255        let mut locations = vec![];
256
257        for location in self.location.iter() {
258            locations.push(parse_record(location.unwrap())?);
259        }
260
261        Ok(locations)
262    }
263
264    /// Retrieve all transactions from the store's pending txs tree in the
265    /// form of a HashMap with key the transaction hash and value the
266    /// transaction itself.
267    /// Be careful as this will try to load everything in memory.
268    pub fn get_all_pending(&self) -> Result<HashMap<TransactionHash, Transaction>> {
269        let mut txs = HashMap::new();
270
271        for tx in self.pending.iter() {
272            let (key, value) = parse_record(tx.unwrap())?;
273            txs.insert(key, value);
274        }
275
276        Ok(txs)
277    }
278
279    /// Fetch n transactions after given transaction hash.
280    pub fn get_after_pending(
281        &self,
282        tx_hash: &TransactionHash,
283        n: usize,
284    ) -> Result<Vec<Transaction>> {
285        let mut txs = vec![];
286        let mut key = tx_hash.inner().into();
287        let mut counter = 0;
288        while counter < n {
289            if let Some((tx_hash, tx)) = self.pending.get_gt(key)? {
290                key = tx_hash;
291                txs.push(deserialize(&tx)?);
292                counter += 1;
293                continue
294            }
295            break
296        }
297
298        Ok(txs)
299    }
300
301    /// Retrieve records count of the store's main tree.
302    pub fn len(&self) -> usize {
303        self.main.len()
304    }
305
306    /// Check if the store's main tree is empty.
307    pub fn is_empty(&self) -> bool {
308        self.main.is_empty()
309    }
310
311    /// Remove a slice of [`TransactionHash`] from the store's pending txs tree.
312    pub fn remove_pending(&self, txs_hashes: &[TransactionHash]) -> Result<()> {
313        let batch = self.remove_batch_pending(txs_hashes);
314        self.pending.apply_batch(batch)?;
315        Ok(())
316    }
317
318    /// Generate the sled batch corresponding to a remove from the store's pending
319    /// txs tree, so caller can handle the write operation.
320    pub fn remove_batch_pending(&self, txs_hashes: &[TransactionHash]) -> sled::Batch {
321        let mut batch = sled::Batch::default();
322
323        for tx_hash in txs_hashes {
324            batch.remove(tx_hash.inner());
325        }
326
327        batch
328    }
329
330    /// Generate the sled batch corresponding to a remove from the store's pending
331    /// txs order tree, so caller can handle the write operation.
332    pub fn remove_batch_pending_order(&self, indexes: &[u64]) -> sled::Batch {
333        let mut batch = sled::Batch::default();
334
335        for index in indexes {
336            batch.remove(&index.to_be_bytes());
337        }
338
339        batch
340    }
341}
342
343/// Overlay structure over a [`TxStore`] instance.
344pub struct TxStoreOverlay(SledDbOverlayPtr);
345
346impl TxStoreOverlay {
347    pub fn new(overlay: &SledDbOverlayPtr) -> Result<Self> {
348        overlay.lock().unwrap().open_tree(SLED_TX_TREE, true)?;
349        overlay.lock().unwrap().open_tree(SLED_TX_LOCATION_TREE, true)?;
350        Ok(Self(overlay.clone()))
351    }
352
353    /// Insert a slice of [`Transaction`] into the overlay's main tree.
354    /// The transactions are hashed with BLAKE3 and this hash is used as
355    /// the key, while the value is the serialized [`Transaction`] itself.
356    /// On success, the function returns the transaction hashes in the same
357    /// order as the input transactions.
358    pub fn insert(&self, transactions: &[Transaction]) -> Result<Vec<TransactionHash>> {
359        let mut ret = Vec::with_capacity(transactions.len());
360        let mut lock = self.0.lock().unwrap();
361
362        for tx in transactions {
363            let tx_hash = tx.hash();
364            lock.insert(SLED_TX_TREE, tx_hash.inner(), &serialize(tx))?;
365            ret.push(tx_hash);
366        }
367
368        Ok(ret)
369    }
370
371    /// Insert a slice of [`TransactionHash`] into the overlay's location tree.
372    /// The location tuple is built using the index of each transaction hash
373    /// in the slice, along with the provided block height
374    pub fn insert_location(&self, txs_hashes: &[TransactionHash], block_height: u32) -> Result<()> {
375        let mut lock = self.0.lock().unwrap();
376
377        for (index, tx_hash) in txs_hashes.iter().enumerate() {
378            let serialized = serialize(&(block_height, index as u16));
379            lock.insert(SLED_TX_LOCATION_TREE, tx_hash.inner(), &serialized)?;
380        }
381
382        Ok(())
383    }
384
385    /// Check if the overlay's main tree contains a given transaction hash.
386    pub fn contains(&self, tx_hash: &TransactionHash) -> Result<bool> {
387        Ok(self.0.lock().unwrap().contains_key(SLED_TX_TREE, tx_hash.inner())?)
388    }
389
390    /// Fetch given tx hashes from the overlay's main tree.
391    /// The resulting vector contains `Option`, which is `Some` if the tx
392    /// was found in the overlay, and otherwise it is `None`, if it has not.
393    /// The second parameter is a boolean which tells the function to fail in
394    /// case at least one tx was not found.
395    pub fn get(
396        &self,
397        tx_hashes: &[TransactionHash],
398        strict: bool,
399    ) -> Result<Vec<Option<Transaction>>> {
400        let mut ret = Vec::with_capacity(tx_hashes.len());
401        let lock = self.0.lock().unwrap();
402
403        for tx_hash in tx_hashes {
404            if let Some(found) = lock.get(SLED_TX_TREE, tx_hash.inner())? {
405                let tx = deserialize(&found)?;
406                ret.push(Some(tx));
407                continue
408            }
409            if strict {
410                return Err(Error::TransactionNotFound(tx_hash.as_string()))
411            }
412            ret.push(None);
413        }
414
415        Ok(ret)
416    }
417
418    /// Fetch given tx hash from the overlay's main tree. This function uses
419    /// raw bytes as input and doesn't deserialize the retrieved value.
420    /// The resulting vector contains `Option`, which is `Some` if the tx
421    /// was found in the overlay, and otherwise it is `None`, if it has not.
422    pub fn get_raw(&self, tx_hash: &[u8; 32]) -> Result<Option<Vec<u8>>> {
423        let lock = self.0.lock().unwrap();
424        if let Some(found) = lock.get(SLED_TX_TREE, tx_hash)? {
425            return Ok(Some(found.to_vec()))
426        }
427        Ok(None)
428    }
429
430    /// Fetch given tx hash location from the overlay's location tree.
431    /// This function uses raw bytes as input and doesn't deserialize the
432    /// retrieved value. The resulting vector contains `Option`, which is
433    /// `Some` if the location was found in the overlay, and otherwise it
434    /// is `None`, if it has not.
435    pub fn get_location_raw(&self, tx_hash: &[u8; 32]) -> Result<Option<Vec<u8>>> {
436        let lock = self.0.lock().unwrap();
437        if let Some(found) = lock.get(SLED_TX_LOCATION_TREE, tx_hash)? {
438            return Ok(Some(found.to_vec()))
439        }
440        Ok(None)
441    }
442}