1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* This file is part of DarkFi (https://dark.fi)
 *
 * Copyright (C) 2020-2024 Dyne.org foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use darkfi::{error::TxVerifyFailed, validator::verification::verify_transactions, Error, Result};
use darkfi_sdk::crypto::MerkleTree;
use log::{debug, error, info};

use crate::DarkfiNodePtr;

/// Async task used for purging erroneous pending transactions from the nodes mempool.
pub async fn garbage_collect_task(node: DarkfiNodePtr) -> Result<()> {
    info!(target: "darkfid::task::garbage_collect_task", "Starting garbage collection task...");

    // Grab all current unproposed transactions.  We verify them in batches,
    // to not load them all in memory.
    let (mut last_checked, mut txs) =
        match node.validator.blockchain.transactions.get_after_pending(0, node.txs_batch_size) {
            Ok(pair) => pair,
            Err(e) => {
                error!(
                    target: "darkfid::task::garbage_collect_task",
                    "Uproposed transactions retrieval failed: {e}"
                );
                return Ok(())
            }
        };

    while !txs.is_empty() {
        // Verify each one against current forks
        for tx in txs {
            let tx_hash = tx.hash();
            let tx_vec = [tx.clone()];
            let mut valid = false;

            // Grab a lock over current consensus forks state
            let mut forks = node.validator.consensus.forks.write().await;

            // Iterate over them to verify transaction validity in their overlays
            for fork in forks.iter_mut() {
                // Clone forks' overlay
                let overlay = match fork.overlay.lock().unwrap().full_clone() {
                    Ok(o) => o,
                    Err(e) => {
                        error!(
                            target: "darkfid::task::garbage_collect_task",
                            "Overlay full clone creation failed: {e}"
                        );
                        return Err(e)
                    }
                };

                // Grab all current proposals transactions hashes
                let proposals_txs =
                    match overlay.lock().unwrap().get_blocks_txs_hashes(&fork.proposals) {
                        Ok(txs) => txs,
                        Err(e) => {
                            error!(
                                target: "darkfid::task::garbage_collect_task",
                                "Proposal transactions retrieval failed: {e}"
                            );
                            return Err(e)
                        }
                    };

                // If the hash is contained in the proposals transactions vec, skip it
                if proposals_txs.contains(&tx_hash) {
                    continue
                }

                // Grab forks' next block height
                let next_block_height = match fork.get_next_block_height() {
                    Ok(h) => h,
                    Err(e) => {
                        error!(
                            target: "darkfid::task::garbage_collect_task",
                            "Next fork block height retrieval failed: {e}"
                        );
                        return Err(e)
                    }
                };

                // Verify transaction
                match verify_transactions(
                    &overlay,
                    next_block_height,
                    node.validator.consensus.module.read().await.target,
                    &tx_vec,
                    &mut MerkleTree::new(1),
                    false,
                )
                .await
                {
                    Ok(_) => valid = true,
                    Err(Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => {
                        // Remove transaction from fork's mempool
                        fork.mempool.retain(|tx| *tx != tx_hash);
                    }
                    Err(e) => {
                        error!(
                            target: "darkfid::task::garbage_collect_task",
                            "Verifying transaction {tx_hash} failed: {e}"
                        );
                        return Err(e)
                    }
                }
            }

            // Drop forks lock
            drop(forks);

            // Remove transaction if its invalid for all the forks
            if !valid {
                debug!(target: "darkfid::task::garbage_collect_task", "Removing invalid transaction: {tx_hash}");
                if let Err(e) = node.validator.blockchain.remove_pending_txs_hashes(&[tx_hash]) {
                    error!(
                        target: "darkfid::task::garbage_collect_task",
                        "Removing invalid transaction {tx_hash} failed: {e}"
                    );
                };
            }
        }

        // Grab next batch
        (last_checked, txs) = match node
            .validator
            .blockchain
            .transactions
            .get_after_pending(last_checked + node.txs_batch_size as u64, node.txs_batch_size)
        {
            Ok(pair) => pair,
            Err(e) => {
                error!(
                    target: "darkfid::task::garbage_collect_task",
                    "Uproposed transactions next batch retrieval failed: {e}"
                );
                break
            }
        };
    }

    info!(target: "darkfid::task::garbage_collect_task", "Garbage collection finished successfully!");
    Ok(())
}