Просмотр исходного кода

validator: changed consensus finalization logic

skoupidi 2 лет назад
Родитель
Сommit
ed8e485575
3 измененных файлов с 96 добавлено и 32 удалено
  1. 9 5
      doc/src/arch/consensus.md
  2. 64 12
      src/validator/consensus.rs
  3. 23 15
      src/validator/mod.rs

+ 9 - 5
doc/src/arch/consensus.md

@@ -169,14 +169,18 @@ Extending the canonical blockchain with a new block proposal:
 
 When the finalization check kicks in, each node will grab its best fork.
 
+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,
+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 finalize all block proposals, excluding the last ($n$)-block proposal, by
-appending them to the canonical blockchain. We exclude the last ($n$)-block
-proposal to eliminate network race conditions for blocks of the same height.
+will push (finalize) its first proposal to the canonical blockchain. The fork
+acts as a queue (buffer) for to-be-finalized proposals.
 
-Once finalized, all the remaining fork chains are removed from the node's
-memory pool.
+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.
 
 Because of this design, finalization cannot occur while there are competing
 fork chains of the same rank whose length exceeds the security threshold. In

+ 64 - 12
src/validator/consensus.rs

@@ -202,13 +202,14 @@ impl Consensus {
         Ok((fork, None))
     }
 
+    /// Check if best fork proposals can be finalized.
     /// Consensus finalization logic:
-    /// - If the current best fork has reached greater length than the security threshold, and
-    ///   no other fork exist with same rank, all proposals excluding the last one in that fork
-    //    can be finalized (append to canonical blockchain).
-    /// When best fork can be finalized, blocks(proposals) should be appended to canonical, excluding the
-    /// last one, and fork should be rebuilt.
-    pub async fn finalization(&self) -> Result<Vec<BlockInfo>> {
+    /// - If the current best fork has reached greater length than the security threshold,
+    ///   and no other fork exist with same rank, first proposal(s) in that fork can be
+    ///   appended to canonical blockchain (finalize).
+    /// When best fork can be finalized, first block(s) should be appended to canonical,
+    /// and fork should be rebuilt.
+    pub async fn finalization(&self) -> Result<Option<usize>> {
         debug!(target: "validator::consensus::finalization", "Started finalization check");
 
         // Grab best forks
@@ -217,7 +218,7 @@ impl Consensus {
         // 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(vec![])
+            return Ok(None)
         }
 
         // Grag the actual best fork
@@ -227,16 +228,13 @@ impl Consensus {
         let length = fork.proposals.len();
         if length < self.finalization_threshold {
             debug!(target: "validator::consensus::finalization", "Nothing to finalize yet, best fork size: {}", length);
-            return Ok(vec![])
+            return Ok(None)
         }
 
-        // Grab finalized blocks
-        let finalized = fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
-
         // Drop forks lock
         drop(forks);
 
-        Ok(finalized)
+        Ok(Some(forks_indexes[0]))
     }
 
     /// Auxilliary function to retrieve a fork proposals.
@@ -311,6 +309,60 @@ impl Consensus {
 
         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<()> {
+        // Grab a lock over current forks
+        let mut forks = self.forks.write().await;
+
+        // Purge existing forks;
+        *forks = vec![];
+
+        // Create a new fork extending canonical
+        let mut fork = Fork::new(self.blockchain.clone(), self.module.read().await.clone()).await?;
+
+        // Check if we got any proposals to append
+        if proposals.is_empty() {
+            // Push the 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);
+
+        Ok(())
+    }
 }
 
 /// This struct represents a block proposal, used for consensus.

+ 23 - 15
src/validator/mod.rs

@@ -320,9 +320,9 @@ impl Validator {
         Ok(())
     }
 
-    /// The node checks if proposals can be finalized.
-    /// If proposals are found, node appends them to canonical, excluding the
-    /// last one, and rebuild the finalized fork to contain the last one.
+    /// The node checks if best fork can be finalized.
+    /// If proposals can be finalized, node appends them to canonical,
+    /// and rebuilds the best fork.
     pub async fn finalization(&self) -> Result<Vec<BlockInfo>> {
         // Grab append lock so no new proposals can be appended while
         // we execute finalization
@@ -330,33 +330,41 @@ impl Validator {
 
         info!(target: "validator::finalization", "Performing finalization check");
 
-        // Grab blocks that can be finalized
-        let mut finalized = self.consensus.finalization().await?;
-        if finalized.is_empty() {
+        // Grab best fork index that can be finalized
+        let finalized_fork = self.consensus.finalization().await?;
+        if finalized_fork.is_none() {
             info!(target: "validator::finalization", "No proposals can be finalized");
             drop(append_lock);
             return Ok(vec![])
         }
 
-        // Exclude last proposal
-        let last = finalized.pop().unwrap();
+        // Grab fork proposals sequence
+        let forks = self.consensus.forks.read().await;
+        let fork = &forks[finalized_fork.unwrap()];
+        let mut proposals = fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
+        drop(forks);
+
+        // Find the excess over finalization threshold
+        let excess = (proposals.len() - self.consensus.finalization_threshold) + 1;
+
+        // Grab non finalized blocks
+        let non_finalized = proposals.split_off(excess);
 
         // Append finalized blocks
-        info!(target: "validator::finalization", "Finalizing {} proposals:", finalized.len());
-        for block in &finalized {
+        info!(target: "validator::finalization", "Finalizing proposals:");
+        for block in &proposals {
             info!(target: "validator::finalization", "\t{} - {}", block.hash()?, block.header.height);
         }
-        self.add_blocks(&finalized).await?;
+        self.add_blocks(&proposals).await?;
 
-        // Rebuild best fork using last proposal
-        *self.consensus.forks.write().await = vec![];
-        self.consensus.append_proposal(&Proposal::new(last)?).await?;
+        // Rebuild best fork using rest proposals
+        self.consensus.rebuild_best_fork(&non_finalized).await?;
         info!(target: "validator::finalization", "Finalization completed!");
 
         // Release append lock
         drop(append_lock);
 
-        Ok(finalized)
+        Ok(proposals)
     }
 
     // ==========================