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}