Переглянути джерело

validator: changed consensus finalization logic

skoupidi 2 роки тому
батько
коміт
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.
 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
 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
 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
 Because of this design, finalization cannot occur while there are competing
 fork chains of the same rank whose length exceeds the security threshold. In
 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))
         Ok((fork, None))
     }
     }
 
 
+    /// Check if best fork proposals can be finalized.
     /// Consensus finalization logic:
     /// 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");
         debug!(target: "validator::consensus::finalization", "Started finalization check");
 
 
         // Grab best forks
         // Grab best forks
@@ -217,7 +218,7 @@ impl Consensus {
         // Check if multiple forks with same rank were found
         // Check if multiple forks with same rank were found
         if forks_indexes.len() > 1 {
         if forks_indexes.len() > 1 {
             debug!(target: "validator::consensus::finalization", "Multiple best ranked forks were found");
             debug!(target: "validator::consensus::finalization", "Multiple best ranked forks were found");
-            return Ok(vec![])
+            return Ok(None)
         }
         }
 
 
         // Grag the actual best fork
         // Grag the actual best fork
@@ -227,16 +228,13 @@ impl Consensus {
         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);
-            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 lock
         drop(forks);
         drop(forks);
 
 
-        Ok(finalized)
+        Ok(Some(forks_indexes[0]))
     }
     }
 
 
     /// Auxilliary function to retrieve a fork proposals.
     /// Auxilliary function to retrieve a fork proposals.
@@ -311,6 +309,60 @@ 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<()> {
+        // 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.
 /// This struct represents a block proposal, used for consensus.

+ 23 - 15
src/validator/mod.rs

@@ -320,9 +320,9 @@ impl Validator {
         Ok(())
         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>> {
     pub async fn finalization(&self) -> Result<Vec<BlockInfo>> {
         // Grab append lock so no new proposals can be appended while
         // Grab append lock so no new proposals can be appended while
         // we execute finalization
         // we execute finalization
@@ -330,33 +330,41 @@ impl Validator {
 
 
         info!(target: "validator::finalization", "Performing finalization check");
         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");
             info!(target: "validator::finalization", "No proposals can be finalized");
             drop(append_lock);
             drop(append_lock);
             return Ok(vec![])
             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
         // 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);
             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!");
         info!(target: "validator::finalization", "Finalization completed!");
 
 
         // Release append lock
         // Release append lock
         drop(append_lock);
         drop(append_lock);
 
 
-        Ok(finalized)
+        Ok(proposals)
     }
     }
 
 
     // ==========================
     // ==========================