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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the snarkOS library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![forbid(unsafe_code)]

#[macro_use]
extern crate tracing;

mod memory_pool;
pub use memory_pool::*;

#[cfg(test)]
mod tests;

use snarkvm::prelude::*;

use anyhow::Result;

#[derive(Clone)]
pub struct Consensus<N: Network, C: ConsensusStorage<N>> {
    /// The ledger.
    ledger: Ledger<N, C>,
    /// The memory pool.
    memory_pool: MemoryPool<N>,
    /// The boolean flag for the development mode.
    #[allow(dead_code)]
    is_dev: bool,
}

impl<N: Network, C: ConsensusStorage<N>> Consensus<N, C> {
    /// Initializes a new instance of consensus.
    pub fn new(ledger: Ledger<N, C>, is_dev: bool) -> Result<Self> {
        Ok(Self { ledger, memory_pool: Default::default(), is_dev })
    }

    /// Returns the ledger.
    pub const fn ledger(&self) -> &Ledger<N, C> {
        &self.ledger
    }

    /// Returns the coinbase puzzle.
    pub const fn coinbase_puzzle(&self) -> &CoinbasePuzzle<N> {
        self.ledger.coinbase_puzzle()
    }

    /// Returns the memory pool.
    pub const fn memory_pool(&self) -> &MemoryPool<N> {
        &self.memory_pool
    }

    /// Checks the given transaction is well-formed and unique.
    pub fn check_transaction_basic(&self, transaction: &Transaction<N>, rejected_id: Option<Field<N>>) -> Result<()> {
        self.ledger.check_transaction_basic(transaction, rejected_id)
    }

    /// Checks the given block is valid next block.
    pub fn check_next_block(&self, block: &Block<N>) -> Result<()> {
        self.ledger.check_next_block(block)
    }

    /// Adds the given unconfirmed transaction to the memory pool.
    pub fn add_unconfirmed_transaction(&self, transaction: Transaction<N>) -> Result<()> {
        // Ensure the transaction is not already in the memory pool.
        if self.memory_pool.contains_unconfirmed_transaction(transaction.id()) {
            bail!("Transaction is already in the memory pool.");
        }
        // Check that the transaction is well-formed and unique.
        self.check_transaction_basic(&transaction, None)?;
        // Insert the transaction to the memory pool.
        self.memory_pool.add_unconfirmed_transaction(&transaction);

        Ok(())
    }

    /// Adds the given unconfirmed solution to the memory pool.
    pub fn add_unconfirmed_solution(&self, solution: &ProverSolution<N>) -> Result<()> {
        // Ensure the prover solution is not already in the memory pool.
        if self.memory_pool.contains_unconfirmed_solution(solution.commitment()) {
            bail!("Prover solution is already in the memory pool.");
        }
        // Ensure the prover solution is not already in the ledger.
        if self.ledger.contains_puzzle_commitment(&solution.commitment())? {
            bail!("Prover solution is already in the ledger.");
        }

        // Compute the current epoch challenge.
        let epoch_challenge = self.ledger.latest_epoch_challenge()?;
        // Retrieve the current proof target.
        let proof_target = self.ledger.latest_proof_target();

        // Ensure that the prover solution is valid for the given epoch.
        if !solution.verify(self.coinbase_puzzle().coinbase_verifying_key(), &epoch_challenge, proof_target)? {
            bail!("Invalid prover solution '{}' for the current epoch.", solution.commitment());
        }

        // Insert the solution to the memory pool.
        self.memory_pool.add_unconfirmed_solution(solution)?;

        Ok(())
    }

    /// Returns `true` if the coinbase target is met.
    pub fn is_coinbase_target_met(&self) -> Result<bool> {
        // Retrieve the latest proof target.
        let latest_proof_target = self.ledger.latest_proof_target();
        // Compute the candidate coinbase target.
        let cumulative_proof_target = self.memory_pool.candidate_coinbase_target(latest_proof_target)?;
        // Retrieve the latest coinbase target.
        let latest_coinbase_target = self.ledger.latest_coinbase_target();
        // Check if the coinbase target is met.
        Ok(cumulative_proof_target >= latest_coinbase_target as u128)
    }

    /// Returns a candidate for the next block in the ledger.
    pub fn propose_next_block<R: Rng + CryptoRng>(&self, private_key: &PrivateKey<N>, rng: &mut R) -> Result<Block<N>> {
        // Retrieve the latest block.
        let latest_block = self.ledger.latest_block();
        // Retrieve the latest height.
        let latest_height = latest_block.height();
        // Retrieve the latest proof target.
        let latest_proof_target = latest_block.proof_target();
        // Retrieve the latest coinbase target.
        let latest_coinbase_target = latest_block.coinbase_target();

        // Select the transactions from the memory pool.
        let transactions = self.memory_pool.candidate_transactions(self);
        // Select the prover solutions from the memory pool.
        let prover_solutions =
            self.memory_pool.candidate_solutions(self, latest_height, latest_proof_target, latest_coinbase_target)?;

        // Prepare the next block.
        self.ledger.prepare_advance_to_next_block(private_key, transactions, prover_solutions, rng)
    }

    /// Advances the ledger to the next block.
    pub fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
        // Adds the next block to the ledger.
        self.ledger.advance_to_next_block(block)?;

        // Clear the memory pool of unconfirmed transactions that are now invalid.
        self.memory_pool.clear_invalid_transactions(self);

        // If this starts a new epoch, clear all unconfirmed solutions from the memory pool.
        if block.epoch_number() > self.ledger.latest_epoch_number() {
            self.memory_pool.clear_all_unconfirmed_solutions();
        }
        // Otherwise, if a new coinbase was produced, clear the memory pool of unconfirmed solutions that are now invalid.
        else if block.coinbase().is_some() {
            self.memory_pool.clear_invalid_solutions(self);
        }

        info!("Advanced to block {}", block.height());
        Ok(())
    }

    /// Clears the memory pool of invalid solutions and transactions.
    pub fn refresh_memory_pool(&self) -> Result<()> {
        // Clear the memory pool of unconfirmed solutions that are now invalid.
        self.memory_pool.clear_invalid_solutions(self);
        // Clear the memory pool of unconfirmed transactions that are now invalid.
        self.memory_pool.clear_invalid_transactions(self);
        Ok(())
    }

    /// Clears the memory pool of all solutions and transactions.
    pub fn clear_memory_pool(&self) -> Result<()> {
        // Clear the memory pool of unconfirmed solutions that are now invalid.
        self.memory_pool.clear_all_unconfirmed_solutions();
        // Clear the memory pool of unconfirmed transactions that are now invalid.
        self.memory_pool.clear_unconfirmed_transactions();
        Ok(())
    }
}