Ver código fonte

validator: changed ranking logic

skoupidi 2 anos atrás
pai
commit
7010aae22e

+ 10 - 10
bin/darkfid/src/task/miner.rs

@@ -24,7 +24,7 @@ use darkfi::{
     util::encoding::base64,
     util::encoding::base64,
     validator::{
     validator::{
         consensus::{Fork, Proposal},
         consensus::{Fork, Proposal},
-        utils::best_forks_indexes,
+        utils::best_fork_index,
     },
     },
     zk::{empty_witnesses, ProvingKey, ZkCircuit},
     zk::{empty_witnesses, ProvingKey, ZkCircuit},
     zkas::ZkBinary,
     zkas::ZkBinary,
@@ -111,7 +111,7 @@ pub async fn miner_task(node: &Darkfid, recipient: &PublicKey, skip_sync: bool)
     loop {
     loop {
         // Grab best current fork
         // Grab best current fork
         let forks = node.validator.consensus.forks.read().await;
         let forks = node.validator.consensus.forks.read().await;
-        let extended_fork = forks[best_forks_indexes(&forks)?[0]].full_clone()?;
+        let extended_fork = forks[best_fork_index(&forks)?].full_clone()?;
         drop(forks);
         drop(forks);
 
 
         // Start listenning for network proposals and mining next block for best fork.
         // Start listenning for network proposals and mining next block for best fork.
@@ -148,16 +148,16 @@ async fn listen_to_network(
         // Grab a lock over node forks
         // Grab a lock over node forks
         let forks = node.validator.consensus.forks.read().await;
         let forks = node.validator.consensus.forks.read().await;
 
 
-        // Grab best current fork indexes
-        let fork_indexes = best_forks_indexes(&forks)?;
+        // Grab best current fork index
+        let index = best_fork_index(&forks)?;
 
 
-        // Iterate to verify if proposals sequence has changed
-        for index in fork_indexes {
-            if forks[index].last_proposal()?.hash != last_proposal_hash {
-                drop(forks);
-                return Ok(())
-            }
+        // Verify if proposals sequence has changed
+        if forks[index].last_proposal()?.hash != last_proposal_hash {
+            drop(forks);
+            return Ok(())
         }
         }
+
+        drop(forks);
     }
     }
 }
 }
 
 

+ 2 - 3
bin/darkfid/src/tests/mod.rs

@@ -18,7 +18,7 @@
 
 
 use std::sync::Arc;
 use std::sync::Arc;
 
 
-use darkfi::{net::Settings, validator::utils::best_forks_indexes, Result};
+use darkfi::{net::Settings, validator::utils::best_fork_index, Result};
 use darkfi_contract_test_harness::init_logger;
 use darkfi_contract_test_harness::init_logger;
 use darkfi_sdk::num_traits::One;
 use darkfi_sdk::num_traits::One;
 use num_bigint::BigUint;
 use num_bigint::BigUint;
@@ -86,8 +86,7 @@ async fn sync_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
     assert_eq!(alice.blockchain.len(), charlie.blockchain.len());
     assert_eq!(alice.blockchain.len(), charlie.blockchain.len());
     // Node must have just the best fork
     // Node must have just the best fork
     let forks = alice.consensus.forks.read().await;
     let forks = alice.consensus.forks.read().await;
-    let best_fork_index = best_forks_indexes(&forks)?[0];
-    let best_fork = &forks[best_fork_index];
+    let best_fork = &forks[best_fork_index(&forks)?];
     let charlie_forks = charlie.consensus.forks.read().await;
     let charlie_forks = charlie.consensus.forks.read().await;
     assert_eq!(charlie_forks.len(), 1);
     assert_eq!(charlie_forks.len(), 1);
     assert_eq!(charlie_forks[0].proposals.len(), best_fork.proposals.len());
     assert_eq!(charlie_forks[0].proposals.len(), best_fork.proposals.len());

+ 37 - 48
doc/src/arch/consensus.md

@@ -15,10 +15,11 @@ blockchain achieve consensus.
 | P2P network            | Peer-to-peer network on which nodes communicate with each other                        |
 | P2P network            | Peer-to-peer network on which nodes communicate with each other                        |
 | Finalization           | State achieved when a block and its contents are appended to the canonical blockchain  |
 | Finalization           | State achieved when a block and its contents are appended to the canonical blockchain  |
 | Fork                   | Chain of block proposals that begins with the last block of the canonical blockchain   |
 | Fork                   | Chain of block proposals that begins with the last block of the canonical blockchain   |
+| MAX_INT                | The maximum 32 bytes (256 bits) integer 2^256 − 1                                      |
 
 
 ## Miner main loop
 ## Miner main loop
 
 
-DarkFi uses a Proof of Work RandomX algorithm paired with delayed finality.
+DarkFi uses RandomX Proof of Work algorithm with enforced finality.
 Therefore, block production involves the following steps:
 Therefore, block production involves the following steps:
 
 
 * First, a miner grabs its current best ranking fork and extends it with a
 * First, a miner grabs its current best ranking fork and extends it with a
@@ -64,32 +65,26 @@ new best fork.
 
 
 ## Ranking
 ## Ranking
 
 
-Block producers create a reward transaction containing a `ECVRF` proof (`VRF`)
-that contributes to ranking logic. The `VRF` is built using the `pallas::Base`
-of the $(n-1)$-block proposal's nonce, the $(n-2)$-block proposal's hash, and
-the `pallas::Base` of the block proposal's block height. The `VRF`'s purpose
-is to eliminate long range attacks by predicting a high-ranking future block
-that we can produce in advance.
-
-Each block proposal is ranked based on the modulus of the $(n-2)$-block
-proposal's `VRF` proof (attached to the block producer's reward transaction)
-and the big-integer from the big endian output of its hash.
-
-The rank of the genesis block is 0. The rank of the following 2 blocks is equal
-to their hash output, since there is no $(n-2)$-block producer or `VRF` attached to the
-reward transaction.
-
-For all other blocks, the rank is computed as follows:
-
-1. Obtain a big-integer from the big endian output of the blocks hash
-2. Grab the `VRF` proof from the reward transaction of the $(n-2)$-block proposal
-3. Obtain a big-integer from the big endian output of the `VRF`
-4. Compute the rank: `vrf.output` % `hash_output` (If `hash_output` is 0, rank is equal to `vrf.output`)
-
-To calculate each fork rank, we simply multiply the sum of every block
-proposal's rank in the fork by the fork's length. We use the length
-multiplier to give a preference to longer forks (i.e. longer forks are
-likely to have a higher ranking).
+Each block proposal is ranked based on how hard it is to produce. To measure
+that, we compute the squared distance of its height target from `MAX_INT`.
+For two honest nodes that mine the next block height of the highest ranking
+fork, their block will have the same rank. To mitigate this tie scenario,
+we also compute the squared distance of the blocks `RandomX` hash from
+`MAX_INT`, allowing us to always chose the actual higher ranking block for
+that height, in case of ties. The complete block rank is a tuple containing
+both squared distances.
+
+Proof of Work algorithm lowers the difficulty target as hashpower grows.
+This means that blocks will have to be mined for a lower target, therefore
+rank higher, as they go further away from `MAX_INT`.
+
+Similar to blocks, forks rank is a tuple, with the first part being the
+sum of its block's squared target distances, and the second being the sum of
+their squared hash distances Squared distances are used to disproportionately
+favors smaller targets, with the idea being that it will be harder to trigger
+a longer reorg between forks. When we compare forks, we first check the first
+sum, and if its tied, we use the second as the tie breaker, since we know it
+will be statistically unique for each sequence.
 
 
 The ranking of a fork is always increasing as new blocks are appended.
 The ranking of a fork is always increasing as new blocks are appended.
 To see this, let $F = (M₁ ⋯  Mₙ)$ be a fork with a finite sequence of blocks $(Mᵢ)$
 To see this, let $F = (M₁ ⋯  Mₙ)$ be a fork with a finite sequence of blocks $(Mᵢ)$
@@ -167,24 +162,19 @@ Extending the canonical blockchain with a new block proposal:
 
 
 ## Finalization
 ## Finalization
 
 
-When the finalization check kicks in, each node will grab its best fork.
-
+Based on the rank properties, each node will diverge to the highest ranking
+fork, and new fork wil emerge extending that at its tips.
 A security threshold is set, which refers to the height where the probability
 A security threshold is set, which refers to the height where the probability
 to produce a fork, able to reorg the current best ranking fork reaches zero,
 to produce a fork, able to reorg the current best ranking fork reaches zero,
 similar to the # of block confirmation used by other PoW based protocols.
 similar to the # of block confirmation used by other PoW based protocols.
 
 
-If more than one fork exists with same rank, the node will not finalize any
-block proposals. If the fork's length exceeds the security threshold, the node
-will push (finalize) its first proposal to the canonical blockchain. The fork
-acts as a queue (buffer) for to-be-finalized proposals.
-
-Once a finalization occurs, all the remaining fork chains are removed from the
-node's memory pool, keeping just the best ranking one, along with its remaining
-proposals.
+When the finalization check kicks in, each node will grab its best fork.
+If the fork's length exceeds the security threshold, the node will push (finalize)
+its first proposal to the canonical blockchain. The fork acts as a queue (buffer)
+for the to-be-finalized proposals.
 
 
-Because of this design, finalization cannot occur while there are competing
-fork chains of the same rank whose length exceeds the security threshold. In
-this case, finalization will occur when a single highest ranking fork emerges.
+Once a finalization occurs, all the fork chains not starting with the finalized
+block(s) are removed from the node's memory pool.
 
 
 We continue Case 3 from the previous section to visualize this logic.
 We continue Case 3 from the previous section to visualize this logic.
 
 
@@ -199,8 +189,6 @@ proposals. One extends the F0 fork and the other extends the F2 fork:
                    |
                    |
                    |--[M4]              <-- F3
                    |--[M4]              <-- F3
 
 
-The two competing fork chains also have the same rank, therefore finalization
-cannot occur.
 
 
 Later, the node only observes 1 proposal, extending the F2 fork:
 Later, the node only observes 1 proposal, extending the F2 fork:
 
 
@@ -212,10 +200,10 @@ Later, the node only observes 1 proposal, extending the F2 fork:
                    |
                    |
                    |--[M4]                    <-- F3
                    |--[M4]                    <-- F3
 
 
-When the finalization sync period starts, the node finalizes fork F2 and all
-other forks get dropped:
+When the finalization sync period starts, the node finalizes block M0 and
+keeps the forks that extend that:
 
 
-                   |/--[M0]--[M2]--[M5]      <-- F0
+                   |--[M0]--[M2]--[M5]       <-- F0
     [C]--...--[C]--|
     [C]--...--[C]--|
                    |/--[M1]                  <-- F1
                    |/--[M1]                  <-- F1
                    |
                    |
@@ -223,10 +211,11 @@ other forks get dropped:
                    |
                    |
                    |/--[M4]                  <-- F3
                    |/--[M4]                  <-- F3
 
 
-The canonical blockchain now contains blocks M0, M3, M6 from fork F2. The
-current state is:
+The canonical blockchain now contains blocks M0 and the current state is:
 
 
-    [C]--...--[C]--|--[M7] <-- F2
+                   |--[M2]--[M5]       <-- F0
+    [C]--...--[C]--|
+                   |--[M3]--[M6]--[M7] <-- F2
 
 
 # Appendix: Data Structures
 # Appendix: Data Structures
 
 

+ 90 - 97
src/validator/consensus.rs

@@ -28,7 +28,7 @@ use crate::{
     util::time::Timestamp,
     util::time::Timestamp,
     validator::{
     validator::{
         pow::PoWModule,
         pow::PoWModule,
-        utils::{best_forks_indexes, block_rank, find_extended_fork_index},
+        utils::{best_fork_index, block_rank, find_extended_fork_index},
         verify_block, verify_proposal, verify_transactions, TxVerifyFailed,
         verify_block, verify_proposal, verify_transactions, TxVerifyFailed,
     },
     },
     Error, Result,
     Error, Result,
@@ -99,10 +99,10 @@ impl Consensus {
         let (mut fork, index) = verify_proposal(self, proposal).await?;
         let (mut fork, index) = verify_proposal(self, proposal).await?;
 
 
         // Append proposal to the fork
         // Append proposal to the fork
-        fork.append_proposal(proposal.hash).await?;
+        fork.append_proposal(proposal).await?;
 
 
-        // Update PoW module
-        fork.module.append(proposal.block.header.timestamp.0, &fork.module.next_difficulty()?);
+        // TODO: to keep memory usage low, we should only append forks that
+        // are higher ranking than our current best one
 
 
         // If a fork index was found, replace forks with the mutated one,
         // If a fork index was found, replace forks with the mutated one,
         // otherwise push the new fork.
         // otherwise push the new fork.
@@ -208,33 +208,27 @@ impl Consensus {
     ///   and no other fork exist with same rank, first proposal(s) in that fork can be
     ///   and no other fork exist with same rank, first proposal(s) in that fork can be
     ///   appended to canonical blockchain (finalize).
     ///   appended to canonical blockchain (finalize).
     /// When best fork can be finalized, first block(s) should be appended to canonical,
     /// When best fork can be finalized, first block(s) should be appended to canonical,
-    /// and fork should be rebuilt.
+    /// and forks should be rebuilt.
     pub async fn finalization(&self) -> Result<Option<usize>> {
     pub async fn finalization(&self) -> Result<Option<usize>> {
         debug!(target: "validator::consensus::finalization", "Started finalization check");
         debug!(target: "validator::consensus::finalization", "Started finalization check");
 
 
-        // Grab best forks
+        // Grab best fork
         let forks = self.forks.read().await;
         let forks = self.forks.read().await;
-        let forks_indexes = best_forks_indexes(&forks)?;
-        // Check if multiple forks with same rank were found
-        if forks_indexes.len() > 1 {
-            debug!(target: "validator::consensus::finalization", "Multiple best ranked forks were found");
-            return Ok(None)
-        }
-
-        // Grag the actual best fork
-        let fork = &forks[forks_indexes[0]];
+        let index = best_fork_index(&forks)?;
+        let fork = &forks[index];
 
 
         // Check its length
         // Check its length
         let length = fork.proposals.len();
         let length = fork.proposals.len();
         if length < self.finalization_threshold {
         if length < self.finalization_threshold {
             debug!(target: "validator::consensus::finalization", "Nothing to finalize yet, best fork size: {}", length);
             debug!(target: "validator::consensus::finalization", "Nothing to finalize yet, best fork size: {}", length);
+            drop(forks);
             return Ok(None)
             return Ok(None)
         }
         }
 
 
         // Drop forks lock
         // Drop forks lock
         drop(forks);
         drop(forks);
 
 
-        Ok(Some(forks_indexes[0]))
+        Ok(Some(index))
     }
     }
 
 
     /// Auxilliary function to retrieve a fork proposals.
     /// Auxilliary function to retrieve a fork proposals.
@@ -297,8 +291,7 @@ impl Consensus {
         }
         }
 
 
         // Grab best fork
         // Grab best fork
-        let forks_indexes = best_forks_indexes(&forks)?;
-        let fork = &forks[forks_indexes[0]];
+        let fork = &forks[best_fork_index(&forks)?];
 
 
         // Grab its proposals
         // Grab its proposals
         let blocks = fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
         let blocks = fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
@@ -310,54 +303,69 @@ impl Consensus {
         Ok(ret)
         Ok(ret)
     }
     }
 
 
-    /// Auxilliary function to purge current forks and build one from the
-    /// provided proposals sequence.
-    pub async fn rebuild_best_fork(&self, proposals: &[BlockInfo]) -> Result<()> {
+    /// Auxilliary function to purge current forks and rebuild the ones starting
+    /// with the provided prefix. This function assumes that the prefix blocks have
+    /// already been appended to canonical chain.
+    pub async fn rebuild_forks(&self, prefix: &[BlockInfo]) -> Result<()> {
         // Grab a lock over current forks
         // Grab a lock over current forks
         let mut forks = self.forks.write().await;
         let mut forks = self.forks.write().await;
 
 
+        // Find all the forks that start with the provided prefix,
+        // and grab their proposals
+        let suffix_start_index = prefix.len();
+        let prefix_last_index = suffix_start_index - 1;
+        let prefix_last = prefix.last().unwrap().hash()?;
+        let mut forks_proposals: Vec<Vec<BlockInfo>> = vec![];
+        for fork in forks.iter() {
+            if fork.proposals.is_empty() ||
+                prefix_last_index >= fork.proposals.len() ||
+                fork.proposals[prefix_last_index] != prefix_last
+            {
+                continue
+            }
+            let suffix_proposals = fork
+                .overlay
+                .lock()
+                .unwrap()
+                .get_blocks_by_hash(&fork.proposals[suffix_start_index..])?;
+            // TODO add a stale forks purging logic, aka forks that
+            // we keep should be close to buffer size, for lower
+            // memory consumption
+            forks_proposals.push(suffix_proposals);
+        }
+
         // Purge existing forks;
         // Purge existing forks;
         *forks = vec![];
         *forks = vec![];
 
 
-        // Create a new fork extending canonical
-        let mut fork = Fork::new(self.blockchain.clone(), self.module.read().await.clone()).await?;
+        // Rebuild forks
+        for proposals in forks_proposals {
+            // Create a new fork extending canonical
+            let mut fork =
+                Fork::new(self.blockchain.clone(), self.module.read().await.clone()).await?;
+
+            // Grab overlay last block
+            let mut previous = &fork.overlay.lock().unwrap().last_block()?;
+
+            // Append all proposals
+            for proposal in &proposals {
+                if verify_block(&fork.overlay, &fork.module, proposal, previous).await.is_err() {
+                    error!(target: "validator::consensus::rebuild_best_fork", "Erroneous proposal block found");
+                    fork.overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
+                    drop(forks);
+                    return Err(Error::BlockIsInvalid(proposal.hash()?.to_string()))
+                };
+
+                // Append proposal to the fork
+                fork.append_proposal(&Proposal::new(proposal.clone())?).await?;
+
+                // Set proposals as previous
+                previous = proposal;
+            }
 
 
-        // Check if we got any proposals to append
-        if proposals.is_empty() {
             // Push the fork
             // Push the fork
             forks.push(fork);
             forks.push(fork);
-
-            // Drop forks lock
-            drop(forks);
-
-            return Ok(())
         }
         }
 
 
-        // Grab overlay last block
-        let mut previous = &fork.overlay.lock().unwrap().last_block()?;
-
-        // Append all proposals
-        for proposal in proposals {
-            let proposal_hash = proposal.hash()?;
-            if verify_block(&fork.overlay, &fork.module, proposal, previous).await.is_err() {
-                error!(target: "validator::consensus::rebuild_best_fork", "Erroneous proposal block found");
-                fork.overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
-                return Err(Error::BlockIsInvalid(proposal_hash.to_string()))
-            };
-
-            // Append proposal to the fork
-            fork.append_proposal(proposal_hash).await?;
-
-            // Update PoW module
-            fork.module.append(proposal.header.timestamp.0, &fork.module.next_difficulty()?);
-
-            // Set proposals as previous
-            previous = proposal;
-        }
-
-        // Push the fork
-        forks.push(fork);
-
         // Drop forks lock
         // Drop forks lock
         drop(forks);
         drop(forks);
 
 
@@ -403,8 +411,10 @@ pub struct Fork {
     pub proposals: Vec<blake3::Hash>,
     pub proposals: Vec<blake3::Hash>,
     /// Valid pending transaction hashes
     /// Valid pending transaction hashes
     pub mempool: Vec<blake3::Hash>,
     pub mempool: Vec<blake3::Hash>,
-    /// Current fork rank, cached for better performance
-    pub rank: BigUint,
+    /// Current fork mining targets rank, cached for better performance
+    pub targets_rank: BigUint,
+    /// Current fork hashes rank, cached for better performance
+    pub hashes_rank: BigUint,
 }
 }
 
 
 impl Fork {
 impl Fork {
@@ -418,7 +428,8 @@ impl Fork {
             module,
             module,
             proposals: vec![],
             proposals: vec![],
             mempool,
             mempool,
-            rank: BigUint::from(0u64),
+            targets_rank: BigUint::from(0u64),
+            hashes_rank: BigUint::from(0u64),
         })
         })
     }
     }
 
 
@@ -466,15 +477,28 @@ impl Fork {
         Ok(proposal)
         Ok(proposal)
     }
     }
 
 
-    /// Auxiliary function to append a proposal and recalculate current fork rank
-    pub async fn append_proposal(&mut self, proposal: blake3::Hash) -> Result<()> {
-        self.proposals.push(proposal);
-        self.rank = self.rank().await?;
+    /// Auxiliary function to append a proposal and update current fork rank.
+    pub async fn append_proposal(&mut self, proposal: &Proposal) -> Result<()> {
+        // Grab next mine target and difficulty
+        let (next_target, next_difficulty) = self.module.next_mine_target_and_difficulty()?;
+
+        // Calculate block rank
+        let (target_distance_sq, hash_distance_sq) = block_rank(&proposal.block, &next_target)?;
+
+        // Update PoW module
+        self.module.append(proposal.block.header.timestamp.0, &next_difficulty);
+
+        // Update fork ranks
+        self.targets_rank += target_distance_sq;
+        self.hashes_rank += hash_distance_sq;
+
+        // Push proposal's hash
+        self.proposals.push(proposal.hash);
 
 
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Auxiliary function to retrieve last proposal
+    /// Auxiliary function to retrieve last proposal.
     pub fn last_proposal(&self) -> Result<Proposal> {
     pub fn last_proposal(&self) -> Result<Proposal> {
         let block = if self.proposals.is_empty() {
         let block = if self.proposals.is_empty() {
             self.overlay.lock().unwrap().last_block()?
             self.overlay.lock().unwrap().last_block()?
@@ -560,38 +584,6 @@ impl Fork {
         Ok(unproposed_txs)
         Ok(unproposed_txs)
     }
     }
 
 
-    /// Auxiliarry function to compute fork's rank, assuming all proposals are valid.
-    pub async fn rank(&self) -> Result<BigUint> {
-        // If the fork is empty its rank is 0
-        if self.proposals.is_empty() {
-            return Ok(0u64.into())
-        }
-
-        // Retrieve the sum of all fork proposals ranks
-        let mut sum = BigUint::from(0_u64);
-        let proposals = self.overlay.lock().unwrap().get_blocks_by_hash(&self.proposals)?;
-        for proposal in &proposals {
-            // For block height < 3 we use the same proposal reference, since
-            // block_rank() will ignore it
-            if proposal.header.height < 3 {
-                sum += block_rank(proposal, proposal).await?;
-                continue
-            }
-
-            // For block height > 2, retrieve their previous previous block
-            let previous =
-                &self.overlay.lock().unwrap().get_blocks_by_hash(&[proposal.header.previous])?[0];
-            let previous_previous =
-                &self.overlay.lock().unwrap().get_blocks_by_hash(&[previous.header.previous])?[0];
-            sum += block_rank(proposal, previous_previous).await?;
-        }
-
-        // Use fork(proposals) length as a multiplier to compute the actual fork rank
-        let rank = proposals.len() as u64 * sum;
-
-        Ok(rank)
-    }
-
     /// Auxiliary function to create a full clone using BlockchainOverlay::full_clone.
     /// Auxiliary function to create a full clone using BlockchainOverlay::full_clone.
     /// Changes to this copy don't affect original fork overlay records, since underlying
     /// Changes to this copy don't affect original fork overlay records, since underlying
     /// overlay pointer have been updated to the cloned one.
     /// overlay pointer have been updated to the cloned one.
@@ -601,8 +593,9 @@ impl Fork {
         let module = self.module.clone();
         let module = self.module.clone();
         let proposals = self.proposals.clone();
         let proposals = self.proposals.clone();
         let mempool = self.mempool.clone();
         let mempool = self.mempool.clone();
-        let rank = self.rank.clone();
+        let targets_rank = self.targets_rank.clone();
+        let hashes_rank = self.hashes_rank.clone();
 
 
-        Ok(Self { blockchain, overlay, module, proposals, mempool, rank })
+        Ok(Self { blockchain, overlay, module, proposals, mempool, targets_rank, hashes_rank })
     }
     }
 }
 }

+ 6 - 8
src/validator/mod.rs

@@ -341,24 +341,22 @@ impl Validator {
         // Grab fork proposals sequence
         // Grab fork proposals sequence
         let forks = self.consensus.forks.read().await;
         let forks = self.consensus.forks.read().await;
         let fork = &forks[finalized_fork.unwrap()];
         let fork = &forks[finalized_fork.unwrap()];
-        let mut proposals = fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
+        let proposals = fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
         drop(forks);
         drop(forks);
 
 
         // Find the excess over finalization threshold
         // Find the excess over finalization threshold
         let excess = (proposals.len() - self.consensus.finalization_threshold) + 1;
         let excess = (proposals.len() - self.consensus.finalization_threshold) + 1;
 
 
-        // Grab non finalized blocks
-        let non_finalized = proposals.split_off(excess);
-
         // Append finalized blocks
         // Append finalized blocks
+        let finalized = &proposals[..excess];
         info!(target: "validator::finalization", "Finalizing proposals:");
         info!(target: "validator::finalization", "Finalizing proposals:");
-        for block in &proposals {
+        for block in finalized {
             info!(target: "validator::finalization", "\t{} - {}", block.hash()?, block.header.height);
             info!(target: "validator::finalization", "\t{} - {}", block.hash()?, block.header.height);
         }
         }
-        self.add_blocks(&proposals).await?;
+        self.add_blocks(finalized).await?;
 
 
-        // Rebuild best fork using rest proposals
-        self.consensus.rebuild_best_fork(&non_finalized).await?;
+        // Rebuild forks starting with the finalized blocks
+        self.consensus.rebuild_forks(finalized).await?;
         info!(target: "validator::finalization", "Finalization completed!");
         info!(target: "validator::finalization", "Finalization completed!");
 
 
         // Release append lock
         // Release append lock

+ 7 - 0
src/validator/pow.rs

@@ -188,6 +188,13 @@ impl PoWModule {
         Ok(BigUint::from_bytes_be(&[0xFF; 32]) / &self.next_difficulty()?)
         Ok(BigUint::from_bytes_be(&[0xFF; 32]) / &self.next_difficulty()?)
     }
     }
 
 
+    /// Compute the next mine target and difficulty
+    pub fn next_mine_target_and_difficulty(&self) -> Result<(BigUint, BigUint)> {
+        let difficulty = self.next_difficulty()?;
+        let mine_target = BigUint::from_bytes_be(&[0xFF; 32]) / &difficulty;
+        Ok((mine_target, difficulty))
+    }
+
     /// Verify provided difficulty corresponds to the next one
     /// Verify provided difficulty corresponds to the next one
     pub fn verify_difficulty(&self, difficulty: &BigUint) -> Result<bool> {
     pub fn verify_difficulty(&self, difficulty: &BigUint) -> Result<bool> {
         Ok(difficulty == &self.next_difficulty()?)
         Ok(difficulty == &self.next_difficulty()?)

+ 45 - 34
src/validator/utils.rs

@@ -16,13 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use darkfi_sdk::crypto::{
-    ecvrf::VrfProof, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
-};
-use darkfi_serial::AsyncDecodable;
+use darkfi_sdk::crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID};
 use log::info;
 use log::info;
 use num_bigint::BigUint;
 use num_bigint::BigUint;
-use smol::io::Cursor;
+use randomx::{RandomXCache, RandomXFlags, RandomXVM};
 
 
 use crate::{
 use crate::{
     blockchain::{BlockInfo, BlockchainOverlayPtr},
     blockchain::{BlockInfo, BlockchainOverlayPtr},
@@ -96,39 +93,35 @@ pub async fn deploy_native_contracts(overlay: &BlockchainOverlayPtr) -> Result<(
     Ok(())
     Ok(())
 }
 }
 
 
-/// Compute a block's rank, assuming that its valid.
-/// Genesis block has rank 0.
-/// First 2 blocks rank is equal to their hash number, since their previous
-/// previous block producer doesn't exist or have a VRF.
-pub async fn block_rank(block: &BlockInfo, previous_previous: &BlockInfo) -> Result<BigUint> {
+/// Compute a block's rank, assuming that its valid, based on provided mining target.
+/// Block's rank is the tuple of its squared mining target distance from max 32 bytes int,
+/// along with its squared RandomX hash number distance from max 32 bytes int.
+/// Genesis block has rank (0, 0).
+pub fn block_rank(block: &BlockInfo, target: &BigUint) -> Result<(BigUint, BigUint)> {
     // Genesis block has rank 0
     // Genesis block has rank 0
     if block.header.height == 0 {
     if block.header.height == 0 {
-        return Ok(0u64.into())
+        return Ok((0u64.into(), 0u64.into()))
     }
     }
 
 
-    // Grab block hash number
-    let hash_number = BigUint::from_bytes_be(block.hash()?.as_bytes());
-
-    // First 2 blocks have rank equal to their block hash number
-    if block.header.height < 3 {
-        return Ok(hash_number)
-    }
+    // Grab the max 32 bytes int
+    let max = BigUint::from_bytes_be(&[0xFF; 32]);
 
 
-    // Extract VRF proof from the previous previous producer transaction
-    let tx = previous_previous.txs.last().unwrap();
-    let data = &tx.calls[0].data.data;
-    let mut decoder = Cursor::new(&data);
-    // PoW uses MoneyPoWRewardParamsV1
-    decoder.set_position(499); // FIXME: This should not be done like this
+    // Compute the squared mining target distance
+    let target_distance = &max - target;
+    let target_distance_sq = &target_distance * &target_distance;
 
 
-    // Get the VRF output as big-endian
-    let vrf_proof: VrfProof = AsyncDecodable::decode_async(&mut decoder).await?;
-    let vrf_output = BigUint::from_bytes_be(vrf_proof.hash_output().as_bytes());
+    // Setup RandomX verifier
+    let flags = RandomXFlags::default();
+    let cache = RandomXCache::new(flags, block.header.previous.as_bytes()).unwrap();
+    let vm = RandomXVM::new(flags, &cache).unwrap();
 
 
-    // Finally, compute the rank
-    let rank = if hash_number != 0u8.into() { vrf_output % hash_number } else { vrf_output };
+    // Compute the output hash distance
+    let out_hash = vm.hash(block.hash()?.as_bytes());
+    let out_hash = BigUint::from_bytes_be(&out_hash);
+    let hash_distance = max - out_hash;
+    let hash_distance_sq = &hash_distance * &hash_distance;
 
 
-    Ok(rank)
+    Ok((target_distance_sq, hash_distance_sq))
 }
 }
 
 
 /// Auxiliary function to calculate the middle value between provided u64 numbers
 /// Auxiliary function to calculate the middle value between provided u64 numbers
@@ -190,8 +183,13 @@ pub fn find_extended_fork_index(forks: &[Fork], proposal: &Proposal) -> Result<(
     Err(Error::ExtendedChainIndexNotFound)
     Err(Error::ExtendedChainIndexNotFound)
 }
 }
 
 
-/// Auxiliary function to find best ranked forks indexes.
-pub fn best_forks_indexes(forks: &[Fork]) -> Result<Vec<usize>> {
+/// Auxiliary function to find best ranked fork.
+/// The best ranked fork is the one with the highest sum of
+/// its blocks squared mining target distances, from max 32
+/// bytes int. In case of a tie, the fork with the highest
+/// sum of its blocks squared RandomX hash number distances,
+/// from max 32 bytes int, wins.
+pub fn best_fork_index(forks: &[Fork]) -> Result<usize> {
     // Check if node has any forks
     // Check if node has any forks
     if forks.is_empty() {
     if forks.is_empty() {
         return Err(Error::ForksNotFound)
         return Err(Error::ForksNotFound)
@@ -201,7 +199,7 @@ pub fn best_forks_indexes(forks: &[Fork]) -> Result<Vec<usize>> {
     let mut best = BigUint::from(0u64);
     let mut best = BigUint::from(0u64);
     let mut indexes = vec![];
     let mut indexes = vec![];
     for (f_index, fork) in forks.iter().enumerate() {
     for (f_index, fork) in forks.iter().enumerate() {
-        let rank = &fork.rank;
+        let rank = &fork.targets_rank;
 
 
         // Fork ranks lower that current best
         // Fork ranks lower that current best
         if rank < &best {
         if rank < &best {
@@ -219,5 +217,18 @@ pub fn best_forks_indexes(forks: &[Fork]) -> Result<Vec<usize>> {
         indexes = vec![f_index];
         indexes = vec![f_index];
     }
     }
 
 
-    Ok(indexes)
+    // If a single best ranking fork exists, return it
+    if indexes.len() == 1 {
+        return Ok(indexes[0])
+    }
+
+    // Break tie using their hash distances rank
+    let mut best_index = indexes[0];
+    for index in &indexes[1..] {
+        if forks[*index].hashes_rank > forks[best_index].hashes_rank {
+            best_index = *index;
+        }
+    }
+
+    Ok(best_index)
 }
 }