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