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