darkfi/validator/
consensus.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 std::collections::{BTreeSet, HashMap, HashSet};
20
21use darkfi_sdk::{crypto::MerkleTree, tx::TransactionHash};
22use darkfi_serial::{async_trait, deserialize, SerialDecodable, SerialEncodable};
23use num_bigint::BigUint;
24use sled_overlay::{database::SledDbOverlayStateDiff, sled::IVec};
25use tracing::{debug, info, warn};
26
27use crate::{
28    blockchain::{
29        block_store::{BlockDifficulty, BlockRanks},
30        BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr, Header, HeaderHash,
31    },
32    runtime::vm_runtime::GAS_LIMIT,
33    tx::{Transaction, MAX_TX_CALLS},
34    validator::{
35        pow::{PoWModule, RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT},
36        utils::{best_fork_index, block_rank, find_extended_fork_index, worst_fork_index},
37        verification::{verify_proposal, verify_transaction},
38    },
39    zk::VerifyingKey,
40    Error, Result,
41};
42
43/// Gas limit for total block transactions(50 full transactions).
44pub const BLOCK_GAS_LIMIT: u64 = GAS_LIMIT * MAX_TX_CALLS as u64 * 50;
45
46/// This struct represents the information required by the consensus
47/// algorithm.
48pub struct Consensus {
49    /// Canonical (confirmed) blockchain
50    pub blockchain: Blockchain,
51    /// Fork size(length) after which it can be confirmed
52    pub confirmation_threshold: usize,
53    /// Fork chains containing block proposals
54    pub forks: Vec<Fork>,
55    /// Max in-memory forks to maintain.
56    max_forks: usize,
57    /// Canonical blockchain PoW module state
58    pub module: PoWModule,
59}
60
61impl Consensus {
62    /// Generate a new Consensus state.
63    pub fn new(
64        blockchain: Blockchain,
65        confirmation_threshold: usize,
66        max_forks: usize,
67        pow_target: u32,
68        pow_fixed_difficulty: Option<BigUint>,
69    ) -> Result<Self> {
70        let max_forks = if max_forks == 0 { 1 } else { max_forks };
71        let module = PoWModule::new(blockchain.clone(), pow_target, pow_fixed_difficulty, None)?;
72
73        Ok(Self { blockchain, confirmation_threshold, forks: vec![], max_forks, module })
74    }
75
76    /// Try to generate a new empty fork. If the forks bound has been
77    /// reached, try to replace the worst ranking one with the new
78    /// empty fork.
79    pub async fn generate_empty_fork(&mut self) -> Result<()> {
80        debug!(target: "validator::consensus::generate_empty_fork", "Generating new empty fork...");
81        // Check if we already have an empty fork
82        for fork in &self.forks {
83            if fork.proposals.is_empty() {
84                debug!(target: "validator::consensus::generate_empty_fork", "An empty fork already exists.");
85                return Ok(())
86            }
87        }
88        let fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
89        self.push_fork(fork);
90        debug!(target: "validator::consensus::generate_empty_fork", "Fork generated!");
91
92        Ok(())
93    }
94
95    /// Auxiliary function to push a fork into the forks vector
96    /// respecting the bounding confirguration. The fork will be
97    /// inserted iff the bound has not be reached or it ranks higher
98    /// than the lowest ranking existing fork.
99    fn push_fork(&mut self, fork: Fork) {
100        // Check if we have reached the bound
101        if self.forks.len() < self.max_forks {
102            self.forks.push(fork);
103            return
104        }
105
106        // Grab worst fork. We don't care about competing forks since
107        // any of them can be replaced. It's safe to unwrap here since
108        // we already checked forks length. `best_fork_index` returns
109        // an error iff we pass an empty forks vector.
110        let index = worst_fork_index(&self.forks).unwrap();
111
112        // Check if the provided one ranks lower
113        if fork.targets_rank < self.forks[index].targets_rank {
114            return
115        }
116
117        // Break tie using their hash distances rank
118        if fork.targets_rank == self.forks[index].targets_rank &&
119            fork.hashes_rank <= self.forks[index].hashes_rank
120        {
121            return
122        }
123
124        // Replace the current worst fork with the provided one
125        self.forks[index] = fork;
126    }
127
128    /// Given a proposal, the node verifys it and finds which fork it
129    /// extends. If the proposal extends the canonical blockchain, a
130    /// new fork chain is created.
131    pub async fn append_proposal(
132        &mut self,
133        proposal: &Proposal,
134        is_new: bool,
135        verify_fees: bool,
136    ) -> Result<()> {
137        debug!(target: "validator::consensus::append_proposal", "Appending proposal {}", proposal.hash);
138
139        // Check if proposal already exists
140        for fork in &self.forks {
141            for p in fork.proposals.iter().rev() {
142                if p == &proposal.hash {
143                    debug!(target: "validator::consensus::append_proposal", "Proposal {} already exists", proposal.hash);
144                    return Err(Error::ProposalAlreadyExists)
145                }
146            }
147        }
148        // Check if proposal is canonical
149        if let Ok(canonical_headers) =
150            self.blockchain.blocks.get_order(&[proposal.block.header.height], true)
151        {
152            if canonical_headers[0].unwrap() == proposal.hash {
153                debug!(target: "validator::consensus::append_proposal", "Proposal {} already exists", proposal.hash);
154                return Err(Error::ProposalAlreadyExists)
155            }
156        }
157
158        // Verify proposal and grab corresponding fork
159        let (mut fork, index) = verify_proposal(self, proposal, is_new, verify_fees).await?;
160
161        // Append proposal to the fork
162        fork.append_proposal(proposal).await?;
163
164        // If a fork index was found, replace fork with the mutated
165        // one, otherwise try to push the new fork.
166        match index {
167            Some(i) => {
168                if i < self.forks.len() &&
169                    self.forks[i].proposals == fork.proposals[..fork.proposals.len() - 1]
170                {
171                    self.forks[i] = fork;
172                } else {
173                    self.push_fork(fork);
174                }
175            }
176            None => {
177                self.push_fork(fork);
178            }
179        }
180
181        info!(target: "validator::consensus::append_proposal", "Appended proposal {} - {}", proposal.hash, proposal.block.header.height);
182
183        Ok(())
184    }
185
186    /// Given a proposal, find the fork chain it extends, and return
187    /// its full clone. If the proposal extends the fork not on its
188    /// tail, a new fork is created and we re-apply the proposals up to
189    /// the extending one. If proposal extends canonical, a new fork is
190    /// created. Additionally, we return the fork index if a new fork
191    /// was not created, so caller can replace the fork.
192    pub async fn find_extended_fork(&self, proposal: &Proposal) -> Result<(Fork, Option<usize>)> {
193        // Check if proposal extends any fork
194        let found = find_extended_fork_index(&self.forks, proposal);
195        if found.is_err() {
196            if let Err(Error::ProposalAlreadyExists) = found {
197                return Err(Error::ProposalAlreadyExists)
198            }
199
200            // Check if proposal extends canonical
201            let (last_height, last_block) = self.blockchain.last()?;
202            if proposal.block.header.previous != last_block ||
203                proposal.block.header.height <= last_height
204            {
205                return Err(Error::ExtendedChainIndexNotFound)
206            }
207
208            // Check if we have an empty fork to use
209            for (f_index, fork) in self.forks.iter().enumerate() {
210                if fork.proposals.is_empty() {
211                    return Ok((self.forks[f_index].full_clone()?, Some(f_index)))
212                }
213            }
214
215            // Generate a new fork extending canonical
216            let fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
217            return Ok((fork, None))
218        }
219
220        let (f_index, p_index) = found.unwrap();
221        let original_fork = &self.forks[f_index];
222        // Check if proposal extends fork at last proposal
223        if p_index == (original_fork.proposals.len() - 1) {
224            return Ok((original_fork.full_clone()?, Some(f_index)))
225        }
226
227        // Rebuild fork
228        let mut fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
229        fork.proposals = original_fork.proposals[..p_index + 1].to_vec();
230        fork.diffs = original_fork.diffs[..p_index + 1].to_vec();
231
232        // Retrieve proposals blocks from original fork
233        let blocks = &original_fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
234        for (index, block) in blocks.iter().enumerate() {
235            // Apply block diffs
236            fork.overlay.lock().unwrap().overlay.lock().unwrap().add_diff(&fork.diffs[index])?;
237
238            // Grab next mine target and difficulty
239            let (next_target, next_difficulty) = fork.module.next_mine_target_and_difficulty()?;
240
241            // Calculate block rank
242            let (target_distance_sq, hash_distance_sq) = block_rank(block, &next_target)?;
243
244            // Update PoW module
245            fork.module.append(&block.header, &next_difficulty)?;
246
247            // Update fork ranks
248            fork.targets_rank += target_distance_sq;
249            fork.hashes_rank += hash_distance_sq;
250        }
251
252        Ok((fork, None))
253    }
254
255    /// Check if best fork proposals can be confirmed.
256    /// Consensus confirmation logic:
257    /// - If the current best fork has reached greater length than the
258    ///   security threshold, and no other fork exist with same rank,
259    ///   first proposal(s) in that fork can be appended to
260    ///   canonical/confirmed blockchain.
261    ///
262    /// When best fork can be confirmed, first block(s) should be
263    /// appended to canonical, and forks should be rebuilt.
264    pub async fn confirmation(&self) -> Result<Option<usize>> {
265        debug!(target: "validator::consensus::confirmation", "Started confirmation check");
266
267        // Grab best fork index
268        let index = best_fork_index(&self.forks)?;
269
270        // Check its length
271        if self.forks[index].proposals.len() < self.confirmation_threshold {
272            debug!(target: "validator::consensus::confirmation", "Nothing to confirm yet, best fork size: {}", self.forks[index].proposals.len());
273            return Ok(None)
274        }
275
276        // Ensure no other fork exists with same rank
277        for (f_index, fork) in self.forks.iter().enumerate() {
278            // Skip best fork
279            if f_index == index {
280                continue
281            }
282
283            // Skip lower ranking forks
284            if fork.targets_rank != self.forks[index].targets_rank {
285                continue
286            }
287
288            // Check hash distances rank
289            if fork.hashes_rank == self.forks[index].hashes_rank {
290                debug!(target: "validator::consensus::confirmation", "Competing best forks found");
291                return Ok(None)
292            }
293        }
294
295        Ok(Some(index))
296    }
297
298    /// Auxiliary function to find the index of a fork containing the
299    /// provided header hash in its proposals.
300    fn find_fork_by_header(&self, fork_header: &HeaderHash) -> Option<usize> {
301        for (index, fork) in self.forks.iter().enumerate() {
302            for p in fork.proposals.iter().rev() {
303                if p == fork_header {
304                    return Some(index)
305                }
306            }
307        }
308        None
309    }
310
311    /// Auxiliary function to retrieve the fork header hash of provided
312    /// height. The fork is identified by the provided header hash.
313    pub async fn get_fork_header_hash(
314        &self,
315        height: u32,
316        fork_header: &HeaderHash,
317    ) -> Result<Option<HeaderHash>> {
318        // Find the fork containing the provided header
319        let Some(index) = self.find_fork_by_header(fork_header) else { return Ok(None) };
320
321        // Grab header if it exists
322        let header =
323            self.forks[index].overlay.lock().unwrap().blocks.get_order(&[height], false)?[0];
324
325        Ok(header)
326    }
327
328    /// Auxiliary function to retrieve the fork headers of provided
329    /// hashes. The fork is identified by the provided header hash. If
330    /// fork doesn't exists, an empty vector is returned.
331    pub async fn get_fork_headers(
332        &self,
333        headers: &[HeaderHash],
334        fork_header: &HeaderHash,
335    ) -> Result<Vec<Header>> {
336        // Find the fork containing the provided header
337        let Some(index) = self.find_fork_by_header(fork_header) else { return Ok(vec![]) };
338
339        // Grab headers
340        let headers = self.forks[index].overlay.lock().unwrap().get_headers_by_hash(headers)?;
341
342        Ok(headers)
343    }
344
345    /// Auxiliary function to retrieve the fork proposals of provided
346    /// hashes. The fork is identified by the provided header hash. If
347    /// fork doesn't exists, an empty vector is returned.
348    pub async fn get_fork_proposals(
349        &self,
350        headers: &[HeaderHash],
351        fork_header: &HeaderHash,
352    ) -> Result<Vec<Proposal>> {
353        // Find the fork containing the provided header
354        let Some(index) = self.find_fork_by_header(fork_header) else { return Ok(vec![]) };
355
356        // Grab proposals
357        let blocks = self.forks[index].overlay.lock().unwrap().get_blocks_by_hash(headers)?;
358        let mut proposals = Vec::with_capacity(blocks.len());
359        for block in blocks {
360            proposals.push(Proposal::new(block));
361        }
362
363        Ok(proposals)
364    }
365
366    /// Auxiliary function to retrieve a fork proposals, starting from
367    /// provided tip. If provided tip is too far behind, unknown, or
368    /// fork doesn't exists, an empty vector is returned. The fork is
369    /// identified by the optional provided header hash. If its `None`,
370    /// we use our best fork.
371    pub async fn get_fork_proposals_after(
372        &self,
373        tip: HeaderHash,
374        fork_tip: Option<HeaderHash>,
375        limit: u32,
376    ) -> Result<Vec<Proposal>> {
377        // Create return vector
378        let mut proposals = vec![];
379
380        // Grab fork index to use
381        let index = match fork_tip {
382            Some(fork_tip) => {
383                let Some(found) = self.find_fork_by_header(&fork_tip) else { return Ok(proposals) };
384                found
385            }
386            None => best_fork_index(&self.forks)?,
387        };
388
389        // Check tip exists
390        let Ok(existing_tips) =
391            self.forks[index].overlay.lock().unwrap().get_blocks_by_hash(&[tip])
392        else {
393            return Ok(proposals)
394        };
395
396        // Check tip is not far behind
397        let last_block_height = self.forks[index].overlay.lock().unwrap().last()?.0;
398        if last_block_height.saturating_sub(existing_tips[0].header.height) >= limit {
399            return Ok(proposals)
400        }
401
402        // Retrieve all proposals after requested one
403        let headers = self.blockchain.blocks.get_all_after(existing_tips[0].header.height)?;
404        let blocks = self.blockchain.get_blocks_by_hash(&headers)?;
405        for block in blocks {
406            proposals.push(Proposal::new(block));
407        }
408        let blocks = self.forks[index]
409            .overlay
410            .lock()
411            .unwrap()
412            .get_blocks_by_hash(&self.forks[index].proposals)?;
413        for block in blocks {
414            // Add fork proposals after the requested one height
415            if block.header.height > existing_tips[0].header.height {
416                proposals.push(Proposal::new(block));
417            }
418        }
419
420        Ok(proposals)
421    }
422
423    /// Auxiliary function to grab current mining RandomX key,
424    /// based on next block height.
425    /// If no forks exist, returns the canonical key.
426    pub async fn current_mining_randomx_key(&self) -> Result<HeaderHash> {
427        // Grab next block height and current keys.
428        // If no forks exist, use canonical keys
429        let (next_block_height, rx_keys) = if self.forks.is_empty() {
430            let (next_block_height, _) = self.blockchain.last()?;
431            (next_block_height + 1, self.module.darkfi_rx_keys)
432        } else {
433            // Grab best fork and its last proposal
434            let index = best_fork_index(&self.forks)?;
435            let fork = &self.forks[index];
436            let last = fork.last_proposal()?;
437            (last.block.header.height + 1, fork.module.darkfi_rx_keys)
438        };
439
440        // We only use the next key when the next block is the
441        // height changing one.
442        if next_block_height > RANDOMX_KEY_CHANGING_HEIGHT &&
443            next_block_height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
444        {
445            Ok(rx_keys.1.ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?)
446        } else {
447            Ok(rx_keys.0)
448        }
449    }
450
451    /// Auxiliary function to grab best current fork full clone.
452    pub async fn best_current_fork(&self) -> Result<Fork> {
453        let index = best_fork_index(&self.forks)?;
454        self.forks[index].full_clone()
455    }
456
457    /// Auxiliary function to retrieve current best fork last header.
458    /// If no forks exist, grab the last header from canonical.
459    pub async fn best_fork_last_header(&self) -> Result<(u32, HeaderHash)> {
460        // Check if node has any forks
461        if self.forks.is_empty() {
462            return self.blockchain.last()
463        }
464
465        // Grab best fork
466        let index = best_fork_index(&self.forks)?;
467        let fork = &self.forks[index];
468
469        // Grab its last header
470        let last = fork.last_proposal()?;
471        Ok((last.block.header.height, last.hash))
472    }
473
474    /// Auxiliary function to purge current forks and reset the ones
475    /// starting with the provided prefix, excluding provided confirmed
476    /// fork. Additionally, remove confirmed transactions from the
477    /// forks mempools. This function assumes that the prefix blocks
478    /// have already been appended to canonical chain from the
479    /// confirmed fork.
480    ///
481    /// Note: Always remember to purge new trees from the database if
482    /// not needed.
483    pub async fn reset_forks(
484        &mut self,
485        prefix: &[HeaderHash],
486        confirmed_fork_index: &usize,
487        confirmed_txs: &[Transaction],
488    ) -> Result<()> {
489        // Find all the forks that start with the provided prefix,
490        // excluding confirmed fork index, and remove their prefixed
491        // proposals, and their corresponding diffs. If the fork is not
492        // starting with the provided prefix, drop it.
493        let excess = prefix.len();
494        let prefix_last_index = excess - 1;
495        let prefix_last = prefix.last().unwrap();
496        let mut keep = vec![true; self.forks.len()];
497        let confirmed_txs_hashes: Vec<TransactionHash> =
498            confirmed_txs.iter().map(|tx| tx.hash()).collect();
499        for (index, fork) in self.forks.iter_mut().enumerate() {
500            if &index == confirmed_fork_index {
501                // Remove confirmed proposals txs from fork's mempool
502                fork.mempool.retain(|tx| !confirmed_txs_hashes.contains(tx));
503                continue
504            }
505
506            // If a fork is empty, has less proposals than the prefix
507            // or it doesn't start with the provided prefix we mark it
508            // for removal. It's sufficient to check the prefix last
509            // as the hashes sequence matching is enforced by it, since
510            // it contains all previous ones.
511            if fork.proposals.is_empty() ||
512                prefix_last_index >= fork.proposals.len() ||
513                &fork.proposals[prefix_last_index] != prefix_last
514            {
515                keep[index] = false;
516                continue
517            }
518
519            // Remove confirmed proposals txs from fork's mempool
520            fork.mempool.retain(|tx| !confirmed_txs_hashes.contains(tx));
521
522            // Remove the commited differences
523            let rest_proposals = fork.proposals.split_off(excess);
524            let rest_diffs = fork.diffs.split_off(excess);
525            let mut diffs = fork.diffs.clone();
526            fork.proposals = rest_proposals;
527            fork.diffs = rest_diffs;
528            for diff in diffs.iter_mut() {
529                fork.overlay.lock().unwrap().overlay.lock().unwrap().remove_diff(diff);
530            }
531        }
532
533        // Drop invalid forks
534        let mut iter = keep.iter();
535        self.forks.retain(|_| *iter.next().unwrap());
536
537        // Remove confirmed proposals txs from the unporposed txs sled
538        // tree.
539        self.blockchain.remove_pending_txs_hashes(&confirmed_txs_hashes)?;
540
541        Ok(())
542    }
543
544    /// Auxiliary function to fully purge current forks and leave only
545    /// a new empty fork.
546    pub async fn purge_forks(&mut self) -> Result<()> {
547        debug!(target: "validator::consensus::purge_forks", "Purging current forks...");
548        self.forks = vec![Fork::new(self.blockchain.clone(), self.module.clone()).await?];
549        debug!(target: "validator::consensus::purge_forks", "Forks purged!");
550
551        Ok(())
552    }
553
554    /// Auxiliary function to reset PoW module.
555    pub async fn reset_pow_module(&mut self) -> Result<()> {
556        debug!(target: "validator::consensus::reset_pow_module", "Resetting PoW module...");
557        self.module = PoWModule::new(
558            self.blockchain.clone(),
559            self.module.target,
560            self.module.fixed_difficulty.clone(),
561            None,
562        )?;
563        debug!(target: "validator::consensus::reset_pow_module", "PoW module reset successfully!");
564
565        Ok(())
566    }
567
568    /// Auxiliary function to check current contracts states
569    /// Monotree(SMT) validity in all active forks and canonical.
570    pub async fn healthcheck(&self) -> Result<()> {
571        // Grab current canonical contracts states monotree root
572        let state_root = self.blockchain.contracts.get_state_monotree_root()?;
573
574        // Check that the root matches last block header state root
575        let last_block_state_root = self.blockchain.last_header()?.state_root;
576        if state_root != last_block_state_root {
577            return Err(Error::ContractsStatesRootError(
578                blake3::Hash::from_bytes(state_root).to_string(),
579                blake3::Hash::from_bytes(last_block_state_root).to_string(),
580            ));
581        }
582
583        // Check each fork health
584        for fork in &self.forks {
585            fork.healthcheck()?;
586        }
587
588        Ok(())
589    }
590
591    /// Auxiliary function to purge all unreferenced contract trees
592    /// from the database.
593    pub async fn purge_unreferenced_trees(
594        &self,
595        referenced_trees: &mut BTreeSet<IVec>,
596    ) -> Result<()> {
597        // Check if we have forks
598        if self.forks.is_empty() {
599            // If no forks exist, build a new one so we retrieve the
600            // native/protected trees references.
601            let fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
602            fork.referenced_trees(referenced_trees);
603        } else {
604            // Iterate over current forks to retrieve referenced trees
605            for fork in &self.forks {
606                fork.referenced_trees(referenced_trees);
607            }
608        }
609
610        // Retrieve current database trees
611        let current_trees = self.blockchain.sled_db.tree_names();
612
613        // Iterate over current database trees and drop unreferenced
614        // contracts ones.
615        for tree in current_trees {
616            // Check if its referenced
617            if referenced_trees.contains(&tree) {
618                continue
619            }
620
621            // Check if its a contract tree pointer
622            let Ok(tree) = deserialize::<[u8; 32]>(&tree) else { continue };
623
624            // Drop it
625            debug!(target: "validator::consensus::purge_unreferenced_trees", "Dropping unreferenced tree: {}", blake3::Hash::from(tree));
626            self.blockchain.sled_db.drop_tree(tree)?;
627        }
628
629        Ok(())
630    }
631
632    /// Auxiliary function to purge all unproposed pending
633    /// transactions from the database.
634    pub async fn purge_unproposed_pending_txs(
635        &mut self,
636        mut proposed_txs: HashSet<TransactionHash>,
637    ) -> Result<()> {
638        // Iterate over all forks to find proposed txs
639        for fork in &self.forks {
640            // Grab all current proposals transactions hashes
641            let proposals_txs =
642                fork.overlay.lock().unwrap().get_blocks_txs_hashes(&fork.proposals)?;
643            for tx in proposals_txs {
644                proposed_txs.insert(tx);
645            }
646        }
647
648        // Iterate over all forks again to remove unproposed txs from
649        // their mempools.
650        for fork in self.forks.iter_mut() {
651            fork.mempool.retain(|tx| proposed_txs.contains(tx));
652        }
653
654        // Remove unproposed txs from the pending store
655        let proposed_txs: Vec<TransactionHash> = proposed_txs.into_iter().collect();
656        self.blockchain.reset_pending_txs(&proposed_txs)?;
657
658        Ok(())
659    }
660}
661
662/// This struct represents a block proposal, used for consensus.
663#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
664pub struct Proposal {
665    /// Block hash
666    pub hash: HeaderHash,
667    /// Block data
668    pub block: BlockInfo,
669}
670
671impl Proposal {
672    pub fn new(block: BlockInfo) -> Self {
673        let hash = block.hash();
674        Self { hash, block }
675    }
676}
677
678impl From<Proposal> for BlockInfo {
679    fn from(proposal: Proposal) -> BlockInfo {
680        proposal.block
681    }
682}
683
684/// Struct representing a forked blockchain state.
685///
686/// An overlay over the original blockchain is used, containing all
687/// pending to-write records. Additionally, each fork keeps a vector of
688/// valid pending transactions hashes, in order of receival, and the
689/// proposals hashes sequence, for validations.
690#[derive(Clone)]
691pub struct Fork {
692    /// Canonical (confirmed) blockchain
693    pub blockchain: Blockchain,
694    /// Overlay cache over canonical Blockchain
695    pub overlay: BlockchainOverlayPtr,
696    /// Current PoW module state
697    pub module: PoWModule,
698    /// Fork proposal hashes sequence
699    pub proposals: Vec<HeaderHash>,
700    /// Fork proposal overlay diffs sequence
701    pub diffs: Vec<SledDbOverlayStateDiff>,
702    /// Valid pending transaction hashes
703    pub mempool: Vec<TransactionHash>,
704    /// Current fork mining targets rank, cached for better performance
705    pub targets_rank: BigUint,
706    /// Current fork hashes rank, cached for better performance
707    pub hashes_rank: BigUint,
708}
709
710impl Fork {
711    pub async fn new(blockchain: Blockchain, module: PoWModule) -> Result<Self> {
712        let mempool = blockchain.get_pending_txs()?.iter().map(|tx| tx.hash()).collect();
713        let overlay = BlockchainOverlay::new(&blockchain)?;
714        // Retrieve last block difficulty to access current ranks
715        let last_difficulty = blockchain.last_block_difficulty()?;
716        let targets_rank = last_difficulty.ranks.targets_rank;
717        let hashes_rank = last_difficulty.ranks.hashes_rank;
718        Ok(Self {
719            blockchain,
720            overlay,
721            module,
722            proposals: vec![],
723            diffs: vec![],
724            mempool,
725            targets_rank,
726            hashes_rank,
727        })
728    }
729
730    /// Auxiliary function to append a proposal and update current fork
731    /// rank.
732    pub async fn append_proposal(&mut self, proposal: &Proposal) -> Result<()> {
733        // Grab next mine target and difficulty
734        let (next_target, next_difficulty) = self.module.next_mine_target_and_difficulty()?;
735
736        // Calculate block rank
737        let (target_distance_sq, hash_distance_sq) = block_rank(&proposal.block, &next_target)?;
738
739        // Update fork ranks
740        self.targets_rank += target_distance_sq.clone();
741        self.hashes_rank += hash_distance_sq.clone();
742
743        // Generate block difficulty and update PoW module
744        let cumulative_difficulty =
745            self.module.cumulative_difficulty.clone() + next_difficulty.clone();
746        let ranks = BlockRanks::new(
747            target_distance_sq,
748            self.targets_rank.clone(),
749            hash_distance_sq,
750            self.hashes_rank.clone(),
751        );
752        let block_difficulty = BlockDifficulty::new(
753            proposal.block.header.height,
754            proposal.block.header.timestamp,
755            next_difficulty,
756            cumulative_difficulty,
757            ranks,
758        );
759        self.module.append_difficulty(&self.overlay, &proposal.block.header, block_difficulty)?;
760
761        // Push proposal's hash
762        self.proposals.push(proposal.hash);
763
764        // Push proposal overlay diff
765        self.diffs.push(self.overlay.lock().unwrap().overlay.lock().unwrap().diff(&self.diffs)?);
766
767        Ok(())
768    }
769
770    /// Auxiliary function to retrieve last proposal.
771    pub fn last_proposal(&self) -> Result<Proposal> {
772        let block = if let Some(last) = self.proposals.last() {
773            self.overlay.lock().unwrap().get_blocks_by_hash(&[*last])?[0].clone()
774        } else {
775            self.overlay.lock().unwrap().last_block()?
776        };
777
778        Ok(Proposal::new(block))
779    }
780
781    /// Auxiliary function to compute forks' next block height.
782    pub fn get_next_block_height(&self) -> Result<u32> {
783        let proposal = self.last_proposal()?;
784        Ok(proposal.block.header.height + 1)
785    }
786
787    /// Auxiliary function to retrieve unproposed valid transactions,
788    /// along with their total gas used and total paid fees.
789    ///
790    /// Note: Always remember to purge new trees from the database if
791    /// not needed.
792    pub async fn unproposed_txs(
793        &mut self,
794        verifying_block_height: u32,
795        verify_fees: bool,
796    ) -> Result<(Vec<Transaction>, u64, u64)> {
797        // Check if our mempool is not empty
798        if self.mempool.is_empty() {
799            return Ok((vec![], 0, 0))
800        }
801
802        // Transactions Merkle tree
803        let mut tree = MerkleTree::new(1);
804
805        // Total gas accumulators
806        let mut total_gas_used = 0_u64;
807        let mut total_gas_paid = 0_u64;
808
809        // Map of ZK proof verifying keys for the current transaction
810        // batch.
811        let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
812
813        // Grab all current proposals transactions hashes
814        let proposals_txs = self.overlay.lock().unwrap().get_blocks_txs_hashes(&self.proposals)?;
815
816        // Iterate through all pending transactions in the forks'
817        // mempool.
818        let mut unproposed_txs = vec![];
819        let mut erroneous_txs = vec![];
820        for tx in &self.mempool {
821            // If the hash is contained in the proposals transactions
822            // vec, skip it.
823            if proposals_txs.contains(tx) {
824                continue
825            }
826
827            // Retrieve the actual unproposed transaction
828            let unproposed_tx = match self.blockchain.transactions.get_pending(&[*tx], true) {
829                Ok(txs) => txs[0].clone().unwrap(),
830                Err(e) => {
831                    debug!(target: "validator::consensus::unproposed_txs", "Transaction retrieval failed: {e}");
832                    erroneous_txs.push(*tx);
833                    continue
834                }
835            };
836
837            // Update the verifying keys map
838            for call in &unproposed_tx.calls {
839                vks.entry(call.data.contract_id.to_bytes()).or_default();
840            }
841
842            // Verify the transaction against current state
843            self.overlay.lock().unwrap().checkpoint();
844            let gas_data = match verify_transaction(
845                &self.overlay,
846                verifying_block_height,
847                self.module.target,
848                &unproposed_tx,
849                &mut tree,
850                &mut vks,
851                verify_fees,
852            )
853            .await
854            {
855                Ok(gas_values) => gas_values,
856                Err(e) => {
857                    debug!(target: "validator::consensus::unproposed_txs", "Transaction verification failed: {e}");
858                    self.overlay.lock().unwrap().revert_to_checkpoint();
859                    erroneous_txs.push(*tx);
860                    continue
861                }
862            };
863
864            // Store the gas used by the verified transaction
865            let tx_gas_used = gas_data.total_gas_used();
866
867            // Calculate current accumulated gas usage
868            let accumulated_gas_usage = total_gas_used.saturating_add(tx_gas_used);
869
870            // Check gas limit - if accumulated gas used exceeds it,
871            // break out of loop.
872            if accumulated_gas_usage > BLOCK_GAS_LIMIT {
873                warn!(
874                    target: "validator::consensus::unproposed_txs",
875                    "Retrieving transaction {tx} would exceed configured unproposed transaction gas limit: {accumulated_gas_usage} - {BLOCK_GAS_LIMIT}"
876                );
877                self.overlay.lock().unwrap().revert_to_checkpoint();
878                break
879            }
880
881            // Update accumulated total gas
882            total_gas_used = total_gas_used.saturating_add(tx_gas_used);
883            total_gas_paid = total_gas_paid.saturating_add(gas_data.paid);
884
885            // Push the tx hash into the unproposed transactions vector
886            unproposed_txs.push(unproposed_tx);
887        }
888
889        // Remove erroneous transactions txs from fork's mempool
890        self.mempool.retain(|tx| !erroneous_txs.contains(tx));
891
892        Ok((unproposed_txs, total_gas_used, total_gas_paid))
893    }
894
895    /// Auxiliary function to create a full clone using
896    /// BlockchainOverlay::full_clone. Changes to this copy don't
897    /// affect original fork overlay records, since underlying overlay
898    /// pointer have been updated to the cloned one.
899    pub fn full_clone(&self) -> Result<Self> {
900        let blockchain = self.blockchain.clone();
901        let overlay = self.overlay.lock().unwrap().full_clone()?;
902        let module = self.module.clone();
903        let proposals = self.proposals.clone();
904        let diffs = self.diffs.clone();
905        let mempool = self.mempool.clone();
906        let targets_rank = self.targets_rank.clone();
907        let hashes_rank = self.hashes_rank.clone();
908
909        Ok(Self {
910            blockchain,
911            overlay,
912            module,
913            proposals,
914            diffs,
915            mempool,
916            targets_rank,
917            hashes_rank,
918        })
919    }
920
921    /// Auxiliary function to check current contracts states
922    /// Monotree(SMT) validity.
923    ///
924    /// Note: This should be executed on fresh forks and/or when
925    ///       a fork doesn't contain changes over the last appended
926    //        proposal.
927    pub fn healthcheck(&self) -> Result<()> {
928        // Grab current contracts states monotree root
929        let state_root = self.overlay.lock().unwrap().contracts.get_state_monotree_root()?;
930
931        // Check that the root matches last block header state root
932        let last_block_state_root = self.last_proposal()?.block.header.state_root;
933        if state_root != last_block_state_root {
934            return Err(Error::ContractsStatesRootError(
935                blake3::Hash::from_bytes(state_root).to_string(),
936                blake3::Hash::from_bytes(last_block_state_root).to_string(),
937            ));
938        }
939
940        Ok(())
941    }
942
943    /// Auxiliary function to retrieve all referenced trees from the
944    /// fork overlay and insert them to provided `BTreeSet`.
945    pub fn referenced_trees(&self, trees: &mut BTreeSet<IVec>) {
946        // Grab its current overlay
947        let fork_overlay = self.overlay.lock().unwrap();
948        let overlay = fork_overlay.overlay.lock().unwrap();
949
950        // Retrieve its initial trees
951        for initial_tree in &overlay.state.initial_tree_names {
952            trees.insert(initial_tree.clone());
953        }
954
955        // Retrieve its new trees
956        for new_tree in &overlay.state.new_tree_names {
957            trees.insert(new_tree.clone());
958        }
959
960        // Retrieve its dropped trees
961        for dropped_tree in overlay.state.dropped_trees.keys() {
962            trees.insert(dropped_tree.clone());
963        }
964
965        // Retrieve its protected trees
966        for protected_tree in &overlay.state.protected_tree_names {
967            trees.insert(protected_tree.clone());
968        }
969    }
970}