darkfid/task/garbage_collect.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 darkfi::{error::TxVerifyFailed, validator::verification::verify_transactions, Error, Result};
20use darkfi_sdk::crypto::MerkleTree;
21use tracing::{debug, error, info};
22
23use crate::DarkfiNodePtr;
24
25/// Async task used for purging erroneous pending transactions from the nodes mempool.
26pub async fn garbage_collect_task(node: DarkfiNodePtr) -> Result<()> {
27 info!(target: "darkfid::task::garbage_collect_task", "Starting garbage collection task...");
28
29 // Grab all current unproposed transactions. We verify them in batches,
30 // to not load them all in memory.
31 let (mut last_checked, mut txs) =
32 match node.validator.blockchain.transactions.get_after_pending(0, node.txs_batch_size) {
33 Ok(pair) => pair,
34 Err(e) => {
35 error!(
36 target: "darkfid::task::garbage_collect_task",
37 "Uproposed transactions retrieval failed: {e}"
38 );
39 return Ok(())
40 }
41 };
42
43 while !txs.is_empty() {
44 // Verify each one against current forks
45 for tx in txs {
46 let tx_hash = tx.hash();
47 let tx_vec = [tx.clone()];
48 let mut valid = false;
49
50 // Grab a lock over current consensus forks state
51 let mut forks = node.validator.consensus.forks.write().await;
52
53 // Iterate over them to verify transaction validity in their overlays
54 for fork in forks.iter_mut() {
55 // Clone forks' overlay
56 let overlay = match fork.overlay.lock().unwrap().full_clone() {
57 Ok(o) => o,
58 Err(e) => {
59 error!(
60 target: "darkfid::task::garbage_collect_task",
61 "Overlay full clone creation failed: {e}"
62 );
63 return Err(e)
64 }
65 };
66
67 // Grab all current proposals transactions hashes
68 let proposals_txs =
69 match overlay.lock().unwrap().get_blocks_txs_hashes(&fork.proposals) {
70 Ok(txs) => txs,
71 Err(e) => {
72 error!(
73 target: "darkfid::task::garbage_collect_task",
74 "Proposal transactions retrieval failed: {e}"
75 );
76 return Err(e)
77 }
78 };
79
80 // If the hash is contained in the proposals transactions vec, skip it
81 if proposals_txs.contains(&tx_hash) {
82 continue
83 }
84
85 // Grab forks' next block height
86 let next_block_height = match fork.get_next_block_height() {
87 Ok(h) => h,
88 Err(e) => {
89 error!(
90 target: "darkfid::task::garbage_collect_task",
91 "Next fork block height retrieval failed: {e}"
92 );
93 return Err(e)
94 }
95 };
96
97 // Verify transaction
98 let result = verify_transactions(
99 &overlay,
100 next_block_height,
101 node.validator.consensus.module.read().await.target,
102 &tx_vec,
103 &mut MerkleTree::new(1),
104 false,
105 )
106 .await;
107
108 // Drop new trees opened by the forks' overlay
109 overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
110
111 // Check result
112 match result {
113 Ok(_) => valid = true,
114 Err(Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => {
115 // Remove transaction from fork's mempool
116 fork.mempool.retain(|tx| *tx != tx_hash);
117 }
118 Err(e) => {
119 error!(
120 target: "darkfid::task::garbage_collect_task",
121 "Verifying transaction {tx_hash} failed: {e}"
122 );
123 return Err(e)
124 }
125 }
126 }
127
128 // Drop forks lock
129 drop(forks);
130
131 // Remove transaction if its invalid for all the forks
132 if !valid {
133 debug!(target: "darkfid::task::garbage_collect_task", "Removing invalid transaction: {tx_hash}");
134 if let Err(e) = node.validator.blockchain.remove_pending_txs_hashes(&[tx_hash]) {
135 error!(
136 target: "darkfid::task::garbage_collect_task",
137 "Removing invalid transaction {tx_hash} failed: {e}"
138 );
139 };
140 }
141 }
142
143 // Grab next batch
144 (last_checked, txs) = match node
145 .validator
146 .blockchain
147 .transactions
148 .get_after_pending(last_checked + node.txs_batch_size as u64, node.txs_batch_size)
149 {
150 Ok(pair) => pair,
151 Err(e) => {
152 error!(
153 target: "darkfid::task::garbage_collect_task",
154 "Uproposed transactions next batch retrieval failed: {e}"
155 );
156 break
157 }
158 };
159 }
160
161 info!(target: "darkfid::task::garbage_collect_task", "Garbage collection finished successfully!");
162 Ok(())
163}