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