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

consensus: major changes implemented

finalization syncing period implemented, finalization logic improvements, fork logic improvements, use blockhash instead of header hash as identifiers, remove redundant DELTA, minor fixes
aggstam 3 лет назад
Родитель
Сommit
ee10a54648

+ 1 - 1
script/research/nodes-tool/src/main.rs

@@ -301,7 +301,7 @@ async fn generate(name: &str, folder: &str) -> Result<()> {
 
     // Data export    
     let state =
-        ValidatorState::new(&sled_db, genesis_ts, genesis_data, wallet, vec![], vec![]).await?;
+        ValidatorState::new(&sled_db, genesis_ts, genesis_data, wallet, vec![]).await?;
     println!("Exporting data for {:?}", name);
     let info = StateInfo::new(&*state.read().await);
     let info_string = format!("{:#?}", info);

+ 33 - 23
src/blockchain/blockstore.rs

@@ -29,7 +29,7 @@ const SLED_BLOCK_TREE: &[u8] = b"_blocks";
 const SLED_BLOCK_ORDER_TREE: &[u8] = b"_block_order";
 
 /// The `HeaderStore` is a `sled` tree storing all the blockchain's blocks' headers
-/// where the key is the headers's hash, and value is the serialized header.
+/// where the key is the headers' hash, and value is the serialized header.
 #[derive(Clone)]
 pub struct HeaderStore(sled::Tree);
 
@@ -115,7 +115,7 @@ impl HeaderStore {
 }
 
 /// The `BlockStore` is a `sled` tree storing all the blockchain's blocks
-/// where the key is the block's headers' hash, and value is the serialized block.
+/// where the key is the blocks' hash, and value is the serialized block.
 #[derive(Clone)]
 pub struct BlockStore(sled::Tree);
 
@@ -135,32 +135,42 @@ impl BlockStore {
 
     /// Insert a slice of [`Block`] into the store. With sled, the
     /// operation is done as a batch.
-    /// The block's header is used as the key, while value is the serialized [`Block`] itself.
-    pub fn insert(&self, blocks: &[Block]) -> Result<()> {
+    /// The block are hashed with BLAKE3 and this blockhash is used as
+    /// the key, while value is the serialized [`Block`] itself.
+    /// On success, the function returns the block hashes in the same order.
+    pub fn insert(&self, blocks: &[Block]) -> Result<Vec<blake3::Hash>> {
+        let mut ret = Vec::with_capacity(blocks.len());
         let mut batch = sled::Batch::default();
 
         for block in blocks {
-            batch.insert(block.header.as_bytes(), serialize(block));
+            let serialized = serialize(block);
+            let blockhash = blake3::hash(&serialized);
+            batch.insert(blockhash.as_bytes(), serialized);
+            ret.push(blockhash);
         }
 
         self.0.apply_batch(batch)?;
-        Ok(())
+        Ok(ret)
     }
 
-    /// Check if the blockstore contains a given headerhash.
-    pub fn contains(&self, headerhash: &blake3::Hash) -> Result<bool> {
-        Ok(self.0.contains_key(headerhash.as_bytes())?)
+    /// Check if the blockstore contains a given blockhash.
+    pub fn contains(&self, blockhash: &blake3::Hash) -> Result<bool> {
+        Ok(self.0.contains_key(blockhash.as_bytes())?)
     }
 
-    /// Fetch given headerhashes from the blockstore.
+    /// Fetch given blockhashhashes from the blockstore.
     /// The resulting vector contains `Option`, which is `Some` if the block
     /// was found in the blockstore, and otherwise it is `None`, if it has not.
     /// The second parameter is a boolean which tells the function to fail in
     /// case at least one block was not found.
-    pub fn get(&self, headerhashes: &[blake3::Hash], strict: bool) -> Result<Vec<Option<Block>>> {
-        let mut ret = Vec::with_capacity(headerhashes.len());
-
-        for hash in headerhashes {
+    pub fn get(
+        &self,
+        blockhashhashes: &[blake3::Hash],
+        strict: bool,
+    ) -> Result<Vec<Option<Block>>> {
+        let mut ret = Vec::with_capacity(blockhashhashes.len());
+
+        for hash in blockhashhashes {
             if let Some(found) = self.0.get(hash.as_bytes())? {
                 let block = deserialize(&found)?;
                 ret.push(Some(block));
@@ -177,7 +187,7 @@ impl BlockStore {
     }
 
     /// Retrieve all blocks from the blockstore in the form of a tuple
-    /// (`headerhash`, `block`).
+    /// (`blockhash`, `block`).
     /// Be careful as this will try to load everything in memory.
     pub fn get_all(&self) -> Result<Vec<(blake3::Hash, Block)>> {
         let mut blocks = vec![];
@@ -195,7 +205,7 @@ impl BlockStore {
 
 /// The `BlockOrderStore` is a `sled` tree storing the order of the
 /// blockchain's slots, where the key is the slot uid, and the value is
-/// the block's headers' hash. [`BlockStore`] can be queried with this hash.
+/// the blocks' hash. [`BlockStore`] can be queried with this hash.
 #[derive(Clone)]
 pub struct BlockOrderStore(sled::Tree);
 
@@ -208,15 +218,15 @@ impl BlockOrderStore {
         // In case the store is empty, initialize it with the genesis block.
         if store.0.is_empty() {
             let genesis_block = Block::genesis_block(genesis_ts, genesis_data);
-            store.insert(&[0], &[genesis_block.header])?;
+            store.insert(&[0], &[genesis_block.blockhash()])?;
         }
 
         Ok(store)
     }
 
-    /// Insert a slice of slots and headerhashes into the store. With sled, the
+    /// Insert a slice of slots and blockhashes into the store. With sled, the
     /// operation is done as a batch.
-    /// The block slot is used as the key, and the headerhash is used as value.
+    /// The block slot is used as the key, and the blockhash is used as value.
     pub fn insert(&self, slots: &[u64], hashes: &[blake3::Hash]) -> Result<()> {
         assert_eq!(slots.len(), hashes.len());
         let mut batch = sled::Batch::default();
@@ -259,7 +269,7 @@ impl BlockOrderStore {
     }
 
     /// Retrieve all slots from the blockorderstore in the form of a tuple
-    /// (`slot`, `headerhash`).
+    /// (`slot`, `blockhash`).
     /// Be careful as this will try to load everything in memory.
     pub fn get_all(&self) -> Result<Vec<(u64, blake3::Hash)>> {
         let mut slots = vec![];
@@ -288,8 +298,8 @@ impl BlockOrderStore {
             if let Some(found) = self.0.get_gt(key.to_be_bytes())? {
                 let key_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap();
                 key = u64::from_be_bytes(key_bytes);
-                let header_hash = deserialize(&found.1)?;
-                ret.push(header_hash);
+                let blockhash = deserialize(&found.1)?;
+                ret.push(blockhash);
                 counter += 1;
                 continue
             }
@@ -299,7 +309,7 @@ impl BlockOrderStore {
         Ok(ret)
     }
 
-    /// Fetch the last block headerhash in the tree, based on the `Ord`
+    /// Fetch the last blockhash in the tree, based on the `Ord`
     /// implementation for `Vec<u8>`. This should not be able to
     /// fail because we initialize the store with the genesis block.
     pub fn get_last(&self) -> Result<(u64, blake3::Hash)> {

+ 16 - 12
src/blockchain/mod.rs

@@ -99,18 +99,18 @@ impl Blockchain {
         // TODO: Make db writes here completely atomic
         for block in blocks {
             // Store transactions
-            let _tx_hashes = self.transactions.insert(&block.txs)?;
+            self.transactions.insert(&block.txs)?;
 
             // Store header
-            let headerhash = self.headers.insert(&[block.header.clone()])?;
-            ret.push(headerhash[0]);
+            self.headers.insert(&[block.header.clone()])?;
 
             // Store block
             let blk: Block = Block::from(block.clone());
-            self.blocks.insert(&[blk])?;
+            let blockhash = self.blocks.insert(&[blk])?;
+            ret.push(blockhash[0]);
 
             // Store block order
-            self.order.insert(&[block.header.slot], &[headerhash[0]])?;
+            self.order.insert(&[block.header.slot], &[blockhash[0]])?;
         }
 
         Ok(ret)
@@ -126,19 +126,21 @@ impl Blockchain {
         // TODO: Check if we have all transactions
 
         // Check provided info produces the same hash
-        Ok(blockhash == block.header.headerhash())
+        Ok(blockhash == block.blockhash())
     }
 
     /// Retrieve [`BlockInfo`]s by given hashes. Fails if any of them are not found.
     pub fn get_blocks_by_hash(&self, hashes: &[blake3::Hash]) -> Result<Vec<BlockInfo>> {
         let mut ret = Vec::with_capacity(hashes.len());
 
-        let headers = self.headers.get(hashes, true)?;
         let blocks = self.blocks.get(hashes, true)?;
 
-        for (i, header) in headers.iter().enumerate() {
-            let header = header.clone().unwrap();
-            let block = blocks[i].clone().unwrap();
+        for (i, block) in blocks.iter().enumerate() {
+            let block = block.clone().unwrap();
+
+            let headers = self.headers.get(&[block.header], true)?;
+            // Since we used strict get, its safe to unwrap here
+            let header = headers[0].clone().unwrap();
 
             let txs = self.transactions.get(&block.txs, true)?;
             let txs = txs.iter().map(|x| x.clone().unwrap()).collect();
@@ -177,8 +179,10 @@ impl Blockchain {
 
     /// Retrieve last finalized block leader proof hash.
     pub fn get_last_proof_hash(&self) -> Result<blake3::Hash> {
-        let (slot, _) = self.last().unwrap();
-        let block = &self.get_blocks_by_slot(&[slot]).unwrap()[0];
+        let (_, hash) = self.last().unwrap();
+        let blocks = self.blocks.get(&[hash], true)?;
+        // Since we used strict get, its safe to unwrap here
+        let block = blocks[0].clone().unwrap();
         let hash = blake3::hash(&serialize(&block.metadata.proof));
         Ok(hash)
     }

+ 9 - 4
src/consensus/block.rs

@@ -219,6 +219,8 @@ impl net::Message for BlockResponse {
 /// This struct represents a block proposal, used for consensus.
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct BlockProposal {
+    /// Block hash
+    pub hash: blake3::Hash,
     /// Block header hash
     pub header: blake3::Hash,
     /// Block data
@@ -229,14 +231,16 @@ impl BlockProposal {
     #[allow(clippy::too_many_arguments)]
     pub fn new(header: Header, txs: Vec<Transaction>, metadata: Metadata) -> Self {
         let block = BlockInfo::new(header, txs, metadata);
+        let hash = block.blockhash();
         let header = block.header.headerhash();
-        Self { header, block }
+        Self { hash, header, block }
     }
 }
 
 impl PartialEq for BlockProposal {
     fn eq(&self, other: &Self) -> bool {
-        self.header == other.header &&
+        self.hash == other.hash &&
+            self.header == other.header &&
             self.block.header == other.block.header &&
             self.block.txs == other.block.txs
     }
@@ -245,8 +249,9 @@ impl PartialEq for BlockProposal {
 impl fmt::Display for BlockProposal {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         formatter.write_fmt(format_args!(
-            "BlockProposal {{ leader addr: {}, hash: {}, epoch: {}, slot: {}, txs: {} }}",
+            "BlockProposal {{ leader public key: {}, hash: {}, header: {}, epoch: {}, slot: {}, txs: {} }}",
             self.block.metadata.public_key,
+            self.hash,
             self.header,
             self.block.header.epoch,
             self.block.header.slot,
@@ -289,7 +294,7 @@ impl ProposalChain {
             return false
         }
 
-        if proposal.block.header.previous != previous.header ||
+        if proposal.block.header.previous != previous.hash ||
             proposal.block.header.slot <= previous.block.header.slot
         {
             debug!("check_proposal(): Provided proposal is invalid.");

+ 3 - 3
src/consensus/constants.rs

@@ -46,12 +46,12 @@ pub const BLOCK_INFO_MAGIC_BYTES: [u8; 4] = [0x90, 0x44, 0xf1, 0xf6];
 /// Number of slots in one epoch
 pub const EPOCH_LENGTH: usize = 10;
 
+/// Slot time in seconds
+pub const SLOT_TIME: u64 = 20;
+
 /// Block leader reward
 pub const REWARD: u64 = 420;
 
-/// `2 * DELTA` represents slot time
-pub const DELTA: u64 = 20;
-
 /// Leader proofs k for zk proof rows (rows=2^k)
 pub const LEADER_PROOF_K: u32 = 11;
 

+ 2 - 3
src/consensus/metadata.rs

@@ -22,7 +22,6 @@ use darkfi_sdk::{
 };
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 use log::error;
-use rand::rngs::OsRng;
 
 use super::leadcoin::LeadCoin;
 use crate::{
@@ -47,10 +46,10 @@ pub struct Metadata {
     pub proof: LeadProof,
 }
 
-// FIXME: Why do we even need default() ?
 impl Default for Metadata {
+    /// Default Metadata used in genesis block generation
     fn default() -> Self {
-        let keypair = Keypair::random(&mut OsRng);
+        let keypair = Keypair::default();
         let signature = Signature::dummy();
         let public_inputs = vec![];
         let winning_index = 0;

+ 1 - 1
src/consensus/proto/protocol_proposal.rs

@@ -81,7 +81,7 @@ impl ProtocolProposal {
             let proposal_copy = (*proposal).clone();
 
             // Verify we have the proposal already
-            if self.state.read().await.proposal_exists(&proposal_copy.header) {
+            if self.state.read().await.proposal_exists(&proposal_copy.hash) {
                 debug!("ProtocolProposal::handle_receive_proposal(): Proposal already received.");
                 continue
             }

+ 1 - 4
src/consensus/proto/protocol_sync.rs

@@ -123,10 +123,7 @@ impl ProtocolSync {
                 }
             };
 
-            info!(
-                "ProtocolSync::handle_receive_block(): Received block: {}",
-                info.header.headerhash()
-            );
+            info!("ProtocolSync::handle_receive_block(): Received block: {}", info.blockhash());
 
             debug!("ProtocolSync::handle_receive_block(): Processing received block");
             let info_copy = (*info).clone();

+ 110 - 93
src/consensus/state.rs

@@ -39,7 +39,9 @@ use pasta_curves::{
 use rand::{rngs::OsRng, thread_rng, Rng};
 
 use super::{
-    constants::{DELTA, EPOCH_LENGTH, LEADER_PROOF_K, LOTTERY_HEAD_START, P, RADIX_BITS, REWARD},
+    constants::{
+        EPOCH_LENGTH, LEADER_PROOF_K, LOTTERY_HEAD_START, P, RADIX_BITS, REWARD, SLOT_TIME,
+    },
     leadcoin::{LeadCoin, LeadCoinSecrets},
     utils::fbig2base,
     Block, BlockInfo, BlockProposal, Float10, Header, LeadProof, Metadata, ProposalChain,
@@ -76,8 +78,7 @@ pub struct ConsensusState {
 
 impl ConsensusState {
     pub fn new(genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> {
-        let genesis_block =
-            blake3::hash(&serialize(&Block::genesis_block(genesis_ts, genesis_data)));
+        let genesis_block = Block::genesis_block(genesis_ts, genesis_data).blockhash();
 
         Ok(Self {
             genesis_ts,
@@ -243,9 +244,9 @@ impl ValidatorState {
     }
 
     /// Calculates current slot, based on elapsed time from the genesis block.
-    /// Slot duration is configured using the `DELTA` value.
+    /// Slot duration is configured using the `SLOT_TIME` constant.
     pub fn current_slot(&self) -> u64 {
-        self.consensus.genesis_ts.elapsed() / (2 * DELTA)
+        self.consensus.genesis_ts.elapsed() / SLOT_TIME
     }
 
     /// Calculates the relative number of the provided slot.
@@ -275,12 +276,12 @@ impl ValidatorState {
     }
 
     /// Calculates seconds until next Nth slot starting time.
-    /// Slots duration is configured using the delta value.
+    /// Slots duration is configured using the SLOT_TIME constant.
     pub fn next_n_slot_start(&self, n: u64) -> Duration {
         assert!(n > 0);
         let start_time = NaiveDateTime::from_timestamp(self.consensus.genesis_ts.0, 0);
         let current_slot = self.current_slot() + n;
-        let next_slot_start = (current_slot * (2 * DELTA)) + (start_time.timestamp() as u64);
+        let next_slot_start = (current_slot * SLOT_TIME) + (start_time.timestamp() as u64);
         let next_slot_start = NaiveDateTime::from_timestamp(next_slot_start as i64, 0);
         let current_time = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
         let diff = next_slot_start - current_time;
@@ -563,33 +564,27 @@ impl ValidatorState {
         }
 
         let hash = match longest {
-            Some(chain) => chain.proposals.last().unwrap().header,
+            Some(chain) => chain.proposals.last().unwrap().hash,
             None => self.blockchain.last()?.1,
         };
 
         Ok((hash, index))
     }
 
-    /// Given a proposal, the node verify its sender (slot leader), finds which blockchain
-    /// it extends and check if it can be finalized. If the proposal extends
-    /// the canonical blockchain, a new fork chain is created.
-    pub async fn receive_proposal(
-        &mut self,
-        proposal: &BlockProposal,
-    ) -> Result<Option<Vec<BlockInfo>>> {
+    /// Given a proposal, the node verify its sender (slot leader) and finds which blockchain
+    /// it extends. If the proposal extends the canonical blockchain, a new fork chain is created.
+    pub async fn receive_proposal(&mut self, proposal: &BlockProposal) -> Result<()> {
         let current = self.current_slot();
         // Node hasn't started participating
         match self.participating {
             Some(start) => {
                 if current < start {
-                    return Ok(None)
+                    return Ok(())
                 }
             }
-            None => return Ok(None),
+            None => return Ok(()),
         }
 
-        // TODO: proposal should contain encrypted block info
-        // we decrypt and check blockhash/headerhash is not tampered with
         let md = &proposal.block.metadata;
         let hdr = &proposal.block.header;
 
@@ -600,6 +595,16 @@ impl ValidatorState {
             return Err(Error::InvalidSignature)
         }
 
+        // Check if proposal hash matches actual one
+        let proposal_hash = proposal.block.blockhash();
+        if proposal.hash != proposal_hash {
+            warn!(
+                "receive_proposal(): Received proposal contains mismatched hashes: {} - {}",
+                proposal.hash, proposal_hash
+            );
+            return Err(Error::ProposalHashesMissmatchError)
+        }
+
         // Check if proposal header matches actual one
         let proposal_header = hdr.headerhash();
         if proposal.header != proposal_header {
@@ -635,8 +640,7 @@ impl ValidatorState {
 
         // TODO: [PLACEHOLDER] Add rewards validation
 
-        // Check if proposal fork has can be finalized, to broadcast those blocks
-        let mut to_broadcast = vec![];
+        // Extend corresponding chain
         match index {
             -1 => {
                 let pc = ProposalChain::new(self.consensus.genesis_block, proposal.clone());
@@ -644,65 +648,65 @@ impl ValidatorState {
             }
             _ => {
                 self.consensus.proposals[index as usize].add(proposal);
-                match self.chain_finalization(index as usize).await {
-                    Ok(v) => {
-                        to_broadcast = v;
-                    }
-                    Err(e) => {
-                        error!("receive_proposal(): Block finalization failed: {}", e);
-                        return Err(e)
-                    }
-                }
             }
         };
 
-        Ok(Some(to_broadcast))
+        Ok(())
     }
 
-    /// Given a proposal, find the index of the chain it extends.
+    /// Given a proposal, find the index of the fork chain it extends.
     pub fn find_extended_chain_index(&mut self, proposal: &BlockProposal) -> Result<i64> {
-        let mut fork = None;
-        for (index, chain) in self.consensus.proposals.iter().enumerate() {
-            let last = chain.proposals.last().unwrap();
-            let hash = last.header;
-            if proposal.block.header.previous == hash &&
-                proposal.block.header.slot > last.block.header.slot
-            {
-                return Ok(index as i64)
+        // We iterate through all forks to find which fork to extend
+        let mut chain_index = -1;
+        let mut prop_index = 0;
+        for (c_index, chain) in self.consensus.proposals.iter().enumerate() {
+            // Traverse proposals in reverse
+            for (p_index, prop) in chain.proposals.iter().enumerate().rev() {
+                if proposal.block.header.previous == prop.hash {
+                    chain_index = c_index as i64;
+                    prop_index = p_index;
+                    break
+                }
             }
-
-            if proposal.block.header.previous == last.block.header.previous &&
-                proposal.block.header.slot > last.block.header.slot
-            {
-                fork = Some(chain.clone());
+            if chain_index != -1 {
                 break
             }
         }
 
-        if let Some(mut chain) = fork {
-            debug!("find_extended_chain_index(): Proposal to fork a forkchain was received.");
-            chain.proposals.pop(); // removing last block to create the fork
-            if !chain.proposals.is_empty() {
-                // if len is 0 we will verify against blockchain last block
-                self.consensus.proposals.push(chain);
-                return Ok(self.consensus.proposals.len() as i64 - 1)
+        // If no fork was found, we check with canonical
+        if chain_index == -1 {
+            let (last_slot, last_block) = self.blockchain.last()?;
+            if proposal.block.header.previous != last_block ||
+                proposal.block.header.slot <= last_slot
+            {
+                debug!("find_extended_chain_index(): Proposal doesn't extend any known chain");
+                return Ok(-2)
             }
+
+            // Proposal extends canonical chain
+            return Ok(-1)
         }
 
-        let (last_slot, last_block) = self.blockchain.last()?;
-        if proposal.block.header.previous != last_block || proposal.block.header.slot <= last_slot {
-            debug!("find_extended_chain_index(): Proposal doesn't extend any known chain");
-            return Ok(-2)
+        // Found fork chain
+        let chain = &self.consensus.proposals[chain_index as usize];
+        // Proposal extends fork at last proposal
+        if prop_index == (chain.proposals.len() - 1) {
+            return Ok(chain_index)
         }
 
-        Ok(-1)
+        debug!("find_extended_chain_index(): Proposal to fork a forkchain was received.");
+        let mut chain = self.consensus.proposals[chain_index as usize].clone();
+        // We keep all proposals until the one it extends
+        chain.proposals.drain((prop_index + 1)..);
+        self.consensus.proposals.push(chain);
+        Ok(self.consensus.proposals.len() as i64 - 1)
     }
 
     /// Search the chains we're holding for the given proposal.
     pub fn proposal_exists(&self, input_proposal: &blake3::Hash) -> bool {
         for chain in self.consensus.proposals.iter() {
             for proposal in chain.proposals.iter() {
-                if input_proposal == &proposal.header {
+                if input_proposal == &proposal.hash {
                     return true
                 }
             }
@@ -722,42 +726,65 @@ impl ValidatorState {
         Ok(())
     }
 
-    /// Provided an index, the node checks if the chain can be finalized.
+    /// Node checks if any of the fork chains can be finalized.
     /// Consensus finalization logic:
     /// - If the node has observed the creation of 3 proposals in a fork chain and no other
     ///   forks exists at same or greater height, it finalizes (appends to canonical blockchain)
     ///   all proposals up to the last one.
-    /// When fork chain proposals are finalized, the rest of fork chains not
-    /// starting by those proposals are removed.
-    pub async fn chain_finalization(&mut self, chain_index: usize) -> Result<Vec<BlockInfo>> {
-        let length = self.consensus.proposals[chain_index].proposals.len();
-        if length < 3 {
-            debug!(
-                "chain_finalization(): Less than 3 proposals in chain {}, nothing to finalize",
-                chain_index
-            );
-            return Ok(vec![])
+    /// When fork chain proposals are finalized, the rest of fork chains are removed.
+    pub async fn chain_finalization(&mut self) -> Result<Vec<BlockInfo>> {
+        // First we find longest chain without any other forks at same height
+        let mut chain_index = -1;
+        let mut max_length = 0;
+        for (index, chain) in self.consensus.proposals.iter().enumerate() {
+            let length = chain.proposals.len();
+            // Ignore forks with less that 3 blocks
+            if length < 3 {
+                continue
+            }
+            // Check if less than max
+            if length < max_length {
+                continue
+            }
+            // Check if same length as max
+            if length == max_length {
+                // Setting chain_index so we know we have multiple
+                // forks at same length.
+                chain_index = -2;
+                continue
+            }
+            // Set chain as max
+            chain_index = index as i64;
+            max_length = length;
         }
 
-        for (i, c) in self.consensus.proposals.iter().enumerate() {
-            if i == chain_index {
-                continue
+        // Check if we found any fork to finalize
+        match chain_index {
+            -2 => {
+                debug!("chain_finalization(): Eligible forks with same heigh exist, nothing to finalize");
+                return Ok(vec![])
             }
-            if c.proposals.len() >= length {
-                debug!("chain_finalization(): Same or greater length fork chain exists, nothing to finalize");
+            -1 => {
+                debug!("chain_finalization(): All chains have less than 3 proposals, nothing to finalize");
                 return Ok(vec![])
             }
+            _ => debug!("chain_finalization(): Chain {} can be finalized!", chain_index),
         }
 
-        let chain = &mut self.consensus.proposals[chain_index];
-        let bound = length - 1;
-        let mut finalized = vec![];
-        for proposal in &mut chain.proposals[..bound] {
+        // Starting finalization
+        let mut chain = self.consensus.proposals[chain_index as usize].clone();
+
+        // Retrieving proposals to finalize
+        let bound = max_length - 1;
+        let mut finalized: Vec<BlockInfo> = vec![];
+        for proposal in &chain.proposals[..bound] {
             finalized.push(proposal.clone().into());
         }
 
-        chain.proposals.drain(0..bound);
+        // Removing finalized proposals from chain
+        chain.proposals.drain(..bound);
 
+        // Adding finalized proposals to canonical
         info!("consensus: Adding {} finalized block to canonical chain.", finalized.len());
         let blockhashes = match self.blockchain.add(&finalized) {
             Ok(v) => v,
@@ -767,6 +794,7 @@ impl ValidatorState {
             }
         };
 
+        // Validating state transitions
         for proposal in &finalized {
             // TODO: Is this the right place? We're already doing this in protocol_sync.
             // TODO: These state transitions have already been checked. (I wrote this, but where?)
@@ -779,20 +807,9 @@ impl ValidatorState {
             }
         }
 
-        let last_block = *blockhashes.last().unwrap();
-        let last_slot = finalized.last().unwrap().header.slot;
-
-        let mut dropped = vec![];
-        for chain in self.consensus.proposals.iter() {
-            let first = chain.proposals.first().unwrap();
-            if first.block.header.previous != last_block || first.block.header.slot <= last_slot {
-                dropped.push(chain.clone());
-            }
-        }
-
-        for chain in dropped {
-            self.consensus.proposals.retain(|c| *c != chain);
-        }
+        // Removing rest forks
+        self.consensus.proposals = vec![];
+        self.consensus.proposals.push(chain);
 
         Ok(finalized)
     }

+ 37 - 22
src/consensus/task/proposal.rs

@@ -25,14 +25,14 @@ use crate::{consensus::ValidatorStatePtr, net::P2pPtr, util::async_util::sleep};
 
 /// async task used for participating in the consensus protocol
 pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: ValidatorStatePtr) {
-    // Node waits just before the current or next epoch end, so it can
+    // Node waits just before the current or next epoch last finalization syncing period, so it can
     // start syncing latest state.
     let mut seconds_until_next_epoch = state.read().await.next_n_epoch_start(1);
-    let one_sec = Duration::new(1, 0);
+    let three_secs = Duration::new(3, 0);
 
     loop {
-        if seconds_until_next_epoch > one_sec {
-            seconds_until_next_epoch -= one_sec;
+        if seconds_until_next_epoch > three_secs {
+            seconds_until_next_epoch -= three_secs;
             break
         }
 
@@ -59,11 +59,39 @@ pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: Valid
     }
 
     loop {
+        // Node sleeps until finalization sync period start (2 seconds before next slot)
+        let seconds_sync_period =
+            (state.read().await.next_n_slot_start(1) - Duration::new(2, 0)).as_secs();
+        info!("consensus: Waiting for finalization sync period ({} sec)", seconds_sync_period);
+        sleep(seconds_sync_period).await;
+
+        // Check if any forks can be finalized
+        match state.write().await.chain_finalization().await {
+            Ok(to_broadcast) => {
+                // Broadcast finalized blocks info, if any:
+                if to_broadcast.len() > 0 {
+                    info!("consensus: Broadcasting finalized blocks");
+                    for info in to_broadcast {
+                        match sync_p2p.broadcast(info).await {
+                            Ok(()) => info!("consensus: Broadcasted block"),
+                            Err(e) => error!("consensus: Failed broadcasting block: {}", e),
+                        }
+                    }
+                } else {
+                    info!("consensus: No finalized blocks to broadcast");
+                }
+            }
+            Err(e) => {
+                error!("consensus: Finalization check failed: {}", e);
+            }
+        }
+
+        // Node sleeps until next slot
         let seconds_next_slot = state.read().await.next_n_slot_start(1).as_secs();
         info!("consensus: Waiting for next slot ({} sec)", seconds_next_slot);
         sleep(seconds_next_slot).await;
 
-        // Node checks if epoch has changed, to broadcast a new participation message
+        // Node checks if epoch has changed, to generate new epoch coins
         let epoch_changed = state.write().await.epoch_changed().await;
         match epoch_changed {
             Ok(changed) => {
@@ -103,30 +131,17 @@ pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: Valid
             }
         };
 
+        // Node stores the proposal and broadcast to rest nodes
         info!("consensus: Node is the slot leader: Proposed block: {}", proposal);
         debug!("consensus: Full proposal: {:?}", proposal);
         match state.write().await.receive_proposal(&proposal).await {
-            Ok(to_broadcast) => {
-                info!("consensus: Block proposal  saved successfully");
-                // Broadcast block to other consensus nodes
+            Ok(()) => {
+                info!("consensus: Block proposal saved successfully");
+                // Broadcast proposal to other consensus nodes
                 match consensus_p2p.broadcast(proposal).await {
                     Ok(()) => info!("consensus: Proposal broadcasted successfully"),
                     Err(e) => error!("consensus: Failed broadcasting proposal: {}", e),
                 }
-                // Broadcast finalized blocks info, if any:
-                if let Some(blocks) = to_broadcast {
-                    if blocks.len() > 0 {
-                        info!("consensus: Broadcasting finalized blocks");
-                        for info in blocks {
-                            match sync_p2p.broadcast(info).await {
-                                Ok(()) => info!("consensus: Broadcasted block"),
-                                Err(e) => error!("consensus: Failed broadcasting block: {}", e),
-                            }
-                        }
-                    }
-                } else {
-                    info!("consensus: No finalized blocks to broadcast");
-                }
             }
             Err(e) => {
                 error!("consensus: Block proposal save failed: {}", e);

+ 3 - 0
src/error.rs

@@ -232,6 +232,9 @@ pub enum Error {
     #[error("Check if proposal extends any existing fork chains failed")]
     ExtendedChainIndexNotFound,
 
+    #[error("Proposal contains missmatched hashes")]
+    ProposalHashesMissmatchError,
+
     #[error("Proposal contains missmatched headers")]
     ProposalHeadersMissmatchError,
 

+ 9 - 0
src/sdk/src/crypto/keypair.rs

@@ -52,6 +52,15 @@ impl Keypair {
     }
 }
 
+impl Default for Keypair {
+    /// Default Keypair used in genesis block generation
+    fn default() -> Self {
+        let secret = SecretKey::from(pallas::Base::from(42));
+        let public = PublicKey::from_secret(secret);
+        Self { secret, public }
+    }
+}
+
 /// Structure holding a secret key, wrapping a `pallas::Base` element.
 #[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
 pub struct SecretKey(pallas::Base);

+ 30 - 30
src/zk/circuit/lead_contract.rs

@@ -344,12 +344,12 @@ impl Circuit<pallas::Base> for LeadContract {
             .map(|typed_path| gen_const_array(|i| typed_path[i].inner()));
 
         /*
-        let coin1_commit_root = assign_free_advice(
-            layouter.namespace(|| "witness coin_commitment_root"),
-            config.advices[8],
-            self.coin1_commit_root,
-    )?;
-        */
+            let coin1_commit_root = assign_free_advice(
+                layouter.namespace(|| "witness coin_commitment_root"),
+                config.advices[8],
+                self.coin1_commit_root,
+        )?;
+            */
 
         let coin1_sk = assign_free_advice(
             layouter.namespace(|| "witness coin1_sk"),
@@ -385,12 +385,12 @@ impl Circuit<pallas::Base> for LeadContract {
         )?;
 
         /*
-        let coin1_serial = assign_free_advice(
-            layouter.namespace(|| "witness coin1_serial"),
-            config.advices[8],
-            self.coin1_serial,
-    )?;
-        */
+            let coin1_serial = assign_free_advice(
+                layouter.namespace(|| "witness coin1_serial"),
+                config.advices[8],
+                self.coin1_serial,
+        )?;
+            */
 
         let coin1_value = assign_free_advice(
             layouter.namespace(|| "witness coin1_value"),
@@ -434,12 +434,12 @@ impl Circuit<pallas::Base> for LeadContract {
         )?;
 
         /*
-        let rho = NonIdentityPoint::new(
-            ecc_chip.clone(),
-            layouter.namespace(|| "witness rho"),
-            self.rho.as_ref().map(|cm| cm.to_affine()),
-    )?;
-        */
+            let rho = NonIdentityPoint::new(
+                ecc_chip.clone(),
+                layouter.namespace(|| "witness rho"),
+                self.rho.as_ref().map(|cm| cm.to_affine()),
+        )?;
+            */
 
         let zero = assign_free_advice(
             layouter.namespace(|| "witness constant zero"),
@@ -683,12 +683,12 @@ impl Circuit<pallas::Base> for LeadContract {
         // Constrain derived `sn_commit` to be equal to witnessed `coin1_serial`.
         info!("coin1 cm root LHS: {:?}", coin1_cm_root.value());
         /*
-        info!("coin1 cm root RHS: {:?}", coin1_commit_root.value());
-        layouter.assign_region(
-        || "coin1_cm_root equality",
-        |mut region| region.constrain_equal(coin1_cm_root.cell(), coin1_commit_root.cell()),
-    )?;
-         */
+            info!("coin1 cm root RHS: {:?}", coin1_commit_root.value());
+            layouter.assign_region(
+            || "coin1_cm_root equality",
+            |mut region| region.constrain_equal(coin1_cm_root.cell(), coin1_commit_root.cell()),
+        )?;
+             */
         layouter.constrain_instance(
             coin1_cm_root.cell(),
             config.primary,
@@ -696,12 +696,12 @@ impl Circuit<pallas::Base> for LeadContract {
         )?;
         info!("coin1 serial commit LHS: {:?}", sn_commit.value());
         /*
-        info!("coin1 serial commit RHS: {:?}", coin1_serial.value());
-        layouter.assign_region(
-        || "sn_commit equality",
-        |mut region| region.constrain_equal(sn_commit.cell(), coin1_serial.cell()),
-    )?;
-         */
+            info!("coin1 serial commit RHS: {:?}", coin1_serial.value());
+            layouter.assign_region(
+            || "sn_commit equality",
+            |mut region| region.constrain_equal(sn_commit.cell(), coin1_serial.cell()),
+        )?;
+             */
         layouter.constrain_instance(
             sn_commit.cell(),
             config.primary,