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}