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

consensus: fork state checkpoints logic added

aggstam 3 лет назад
Родитель
Сommit
ba5355f21e

+ 2 - 58
script/research/nodes-tool/src/main.rs

@@ -26,10 +26,9 @@ use darkfi::{
         Blockchain,
     },
     consensus::{
-        block::{Block, BlockProposal, Header, ProposalChain},
+        block::{Block, Header},
         constants::TESTNET_GENESIS_HASH_BYTES,
         lead_info::LeadInfo,
-        state::ConsensusState,
         validator::ValidatorState,
     },
     tx::Transaction,
@@ -38,7 +37,6 @@ use darkfi::{
     Result,
 };
 use darkfi_sdk::crypto::MerkleNode;
-use darkfi_serial::serialize;
 
 #[derive(Debug)]
 struct LeadInfoInfo {
@@ -78,58 +76,6 @@ impl LeadInfoInfo {
     }
 }
 
-#[derive(Debug)]
-struct ProposalInfo {
-    _block: BlockInfo,
-}
-
-impl ProposalInfo {
-    pub fn new(proposal: &BlockProposal) -> ProposalInfo {
-        let _header = proposal.block.header.headerhash();
-        let mut _txs = vec![];
-        for tx in &proposal.block.txs {
-            let hash = blake3::hash(&serialize(tx));
-            _txs.push(hash);
-        }
-        let _lead_info = LeadInfoInfo::new(&proposal.block.lead_info);
-        let _block =
-            BlockInfo { _hash: _header, _magic: proposal.block.magic, _header, _txs, _lead_info };
-        ProposalInfo { _block }
-    }
-}
-
-#[derive(Debug)]
-struct ProposalInfoChain {
-    _proposals: Vec<ProposalInfo>,
-}
-
-impl ProposalInfoChain {
-    pub fn new(proposals: &ProposalChain) -> ProposalInfoChain {
-        let mut _proposals = Vec::new();
-        for proposal in &proposals.proposals {
-            _proposals.push(ProposalInfo::new(&proposal));
-        }
-        ProposalInfoChain { _proposals }
-    }
-}
-
-#[derive(Debug)]
-struct ConsensusInfo {
-    _genesis_ts: Timestamp,
-    _proposals: Vec<ProposalInfoChain>,
-}
-
-impl ConsensusInfo {
-    pub fn new(consensus: &ConsensusState) -> ConsensusInfo {
-        let _genesis_ts = consensus.genesis_ts.clone();
-        let mut _proposals = Vec::new();
-        for proposal in &consensus.proposals {
-            _proposals.push(ProposalInfoChain::new(&proposal));
-        }
-        ConsensusInfo { _genesis_ts, _proposals }
-    }
-}
-
 #[derive(Debug)]
 struct HeaderInfo {
     _hash: blake3::Hash,
@@ -343,15 +289,13 @@ impl BlockchainInfo {
 
 #[derive(Debug)]
 struct StateInfo {
-    _consensus: ConsensusInfo,
     _blockchain: BlockchainInfo,
 }
 
 impl StateInfo {
     pub fn new(state: &ValidatorState) -> StateInfo {
-        let _consensus = ConsensusInfo::new(&state.consensus);
         let _blockchain = BlockchainInfo::new(&state.blockchain);
-        StateInfo { _consensus, _blockchain }
+        StateInfo { _blockchain }
     }
 }
 

+ 0 - 53
src/consensus/block.rs

@@ -21,7 +21,6 @@ use std::fmt;
 use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode};
 use darkfi_serial::{serialize, SerialDecodable, SerialEncodable};
 use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
-use log::debug;
 use pasta_curves::pallas;
 
 use super::{
@@ -271,55 +270,3 @@ impl From<BlockProposal> for BlockInfo {
         block.block
     }
 }
-
-/// This struct represents a sequence of block proposals.
-#[derive(Debug, Clone, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct ProposalChain {
-    pub genesis_block: blake3::Hash,
-    pub proposals: Vec<BlockProposal>,
-}
-
-impl ProposalChain {
-    pub fn new(genesis_block: blake3::Hash, initial_proposal: BlockProposal) -> Self {
-        Self { genesis_block, proposals: vec![initial_proposal] }
-    }
-
-    /// A proposal is considered valid when its parent hash is equal to the
-    /// hash of the previous proposal and their slots are incremental,
-    /// excluding the genesis block proposal.
-    /// Additional validity rules can be applied.
-    pub fn check_proposal(&self, proposal: &BlockProposal, previous: &BlockProposal) -> bool {
-        if proposal.block.header.previous == self.genesis_block {
-            debug!("check_proposal(): Genesis block proposal provided.");
-            return false
-        }
-
-        if proposal.block.header.previous != previous.hash ||
-            proposal.block.header.slot <= previous.block.header.slot
-        {
-            debug!("check_proposal(): Provided proposal is invalid.");
-            return false
-        }
-
-        true
-    }
-
-    /// A proposals chain is considered valid when every proposal is valid,
-    /// based on the `check_proposal` function.
-    pub fn check_chain(&self) -> bool {
-        for (index, proposal) in self.proposals[1..].iter().enumerate() {
-            if !self.check_proposal(proposal, &self.proposals[index]) {
-                return false
-            }
-        }
-
-        true
-    }
-
-    /// Insertion of a valid proposal.
-    pub fn add(&mut self, proposal: &BlockProposal) {
-        if self.check_proposal(proposal, self.proposals.last().unwrap()) {
-            self.proposals.push(proposal.clone());
-        }
-    }
-}

+ 1 - 1
src/consensus/mod.rs

@@ -18,7 +18,7 @@
 
 /// Block definition
 pub mod block;
-pub use block::{Block, BlockInfo, BlockProposal, Header, ProposalChain};
+pub use block::{Block, BlockInfo, BlockProposal, Header};
 
 /// Constants
 pub mod constants;

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

@@ -87,7 +87,7 @@ impl ProtocolProposal {
                 continue
             }
 
-            if let Err(e) = lock.receive_proposal(&proposal_copy).await {
+            if let Err(e) = lock.receive_proposal(&proposal_copy, None).await {
                 error!(
                     "ProtocolProposal::handle_receive_proposal(): receive_proposal error: {}",
                     e

+ 7 - 9
src/consensus/proto/protocol_sync_consensus.rs

@@ -75,17 +75,15 @@ impl ProtocolSyncConsensus {
             // Extra validations can be added here.
             let lock = self.state.read().await;
             let offset = lock.consensus.offset;
-            let proposals = lock.consensus.proposals.clone();
+            let mut forks = vec![];
+            for fork in &lock.consensus.forks {
+                forks.push(fork.clone().into());
+            }
             let unconfirmed_txs = lock.unconfirmed_txs.clone();
             let slot_checkpoints = lock.consensus.slot_checkpoints.clone();
-            let leaders_nullifiers = lock.consensus.leaders_nullifiers.clone();
-            let response = ConsensusResponse {
-                offset,
-                proposals,
-                unconfirmed_txs,
-                slot_checkpoints,
-                leaders_nullifiers,
-            };
+            let nullifiers = lock.consensus.nullifiers.clone();
+            let response =
+                ConsensusResponse { offset, forks, unconfirmed_txs, slot_checkpoints, nullifiers };
             if let Err(e) = self.channel.send(response).await {
                 error!("ProtocolSyncConsensus::handle_receive_request() channel send fail: {}", e);
             };

+ 191 - 44
src/consensus/state.rs

@@ -30,7 +30,7 @@ use super::{
     constants,
     leadcoin::{LeadCoin, LeadCoinSecrets},
     utils::fbig2base,
-    Block, BlockProposal, Float10, ProposalChain,
+    Block, BlockProposal, Float10,
 };
 
 use crate::{blockchain::Blockchain, net, tx::Transaction, util::time::Timestamp, Error, Result};
@@ -50,22 +50,22 @@ pub struct ConsensusState {
     /// Slots offset since genesis,
     pub offset: Option<u64>,
     /// Fork chains containing block proposals
-    pub proposals: Vec<ProposalChain>,
+    pub forks: Vec<Fork>,
     /// Current epoch
     pub epoch: u64,
     /// Current epoch eta
     pub epoch_eta: pallas::Base,
     /// Hot/live slot checkpoints
     pub slot_checkpoints: Vec<SlotCheckpoint>,
+    /// Leaders count history
+    pub leaders_history: Vec<u64>,
     // TODO: Aren't these already in db after finalization?
-    /// Current competing coins
+    /// Canonical competing coins
     pub coins: Vec<LeadCoin>,
-    /// Coin commitments tree
+    /// Canonical coin commitments tree
     pub coins_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
-    /// Seen nullifiers from proposals
-    pub leaders_nullifiers: Vec<pallas::Base>,
-    /// Leaders count history
-    pub leaders_history: Vec<u64>,
+    /// Canonical seen nullifiers from proposals
+    pub nullifiers: Vec<pallas::Base>,
 }
 
 impl ConsensusState {
@@ -82,14 +82,14 @@ impl ConsensusState {
             participating: None,
             checked_finalization: 0,
             offset: None,
-            proposals: vec![],
+            forks: vec![],
             epoch: 0,
             epoch_eta: pallas::Base::one(),
             slot_checkpoints: vec![],
+            leaders_history: vec![0],
             coins: vec![],
             coins_tree: BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(constants::EPOCH_LENGTH * 100),
-            leaders_nullifiers: vec![],
-            leaders_history: vec![0],
+            nullifiers: vec![],
         })
     }
 
@@ -118,10 +118,10 @@ impl ConsensusState {
     /// Finds the last slot a proposal or block was generated.
     pub fn last_slot(&self) -> Result<u64> {
         let mut slot = 0;
-        for chain in &self.proposals {
-            for proposal in &chain.proposals {
-                if proposal.block.header.slot > slot {
-                    slot = proposal.block.header.slot;
+        for chain in &self.forks {
+            for state_checkpoint in &chain.sequence {
+                if state_checkpoint.proposal.block.header.slot > slot {
+                    slot = state_checkpoint.proposal.block.header.slot;
                 }
             }
         }
@@ -195,6 +195,7 @@ impl ConsensusState {
         let eta = self.get_eta();
         if self.coins.len() == 0 {
             self.coins = self.create_coins(eta).await?;
+            self.update_forks_checkpoints();
         }
         self.epoch = epoch;
         self.epoch_eta = eta;
@@ -332,9 +333,9 @@ impl ConsensusState {
         let slot = self.current_slot();
         let previous_slot = slot - 1;
         let mut count = 0;
-        for chain in &self.proposals {
+        for chain in &self.forks {
             // Previous slot proposals exist at end of each fork
-            if chain.proposals.last().unwrap().block.header.slot == previous_slot {
+            if chain.sequence.last().unwrap().proposal.block.header.slot == previous_slot {
                 count += 1;
             }
         }
@@ -436,22 +437,22 @@ impl ConsensusState {
     /// Finds the longest blockchain the node holds and
     /// returns the last block hash and the chain index.
     pub fn longest_chain_last_hash(&self) -> Result<(blake3::Hash, i64)> {
-        let mut longest: Option<ProposalChain> = None;
+        let mut longest: Option<Fork> = None;
         let mut length = 0;
         let mut index = -1;
 
-        if !self.proposals.is_empty() {
-            for (i, chain) in self.proposals.iter().enumerate() {
-                if chain.proposals.len() > length {
+        if !self.forks.is_empty() {
+            for (i, chain) in self.forks.iter().enumerate() {
+                if chain.sequence.len() > length {
                     longest = Some(chain.clone());
-                    length = chain.proposals.len();
+                    length = chain.sequence.len();
                     index = i as i64;
                 }
             }
         }
 
         let hash = match longest {
-            Some(chain) => chain.proposals.last().unwrap().hash,
+            Some(chain) => chain.sequence.last().unwrap().proposal.hash,
             None => self.blockchain.last()?.1,
         };
 
@@ -461,9 +462,9 @@ impl ConsensusState {
     /// Finds the length of longest fork chain the node holds.
     pub fn longest_chain_length(&self) -> usize {
         let mut max = 0;
-        for proposal in &self.proposals {
-            if proposal.proposals.len() > max {
-                max = proposal.proposals.len();
+        for fork in &self.forks {
+            if fork.sequence.len() > max {
+                max = fork.sequence.len();
             }
         }
 
@@ -474,13 +475,13 @@ impl ConsensusState {
     pub fn find_extended_chain_index(&mut self, proposal: &BlockProposal) -> Result<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.proposals.iter().enumerate() {
-            // Traverse proposals in reverse
-            for (p_index, prop) in chain.proposals.iter().enumerate().rev() {
-                if proposal.block.header.previous == prop.hash {
+        let mut state_checkpoint_index = 0;
+        for (c_index, chain) in self.forks.iter().enumerate() {
+            // Traverse sequence in reverse
+            for (sc_index, state_checkpoint) in chain.sequence.iter().enumerate().rev() {
+                if proposal.block.header.previous == state_checkpoint.proposal.hash {
                     chain_index = c_index as i64;
-                    prop_index = p_index;
+                    state_checkpoint_index = sc_index;
                     break
                 }
             }
@@ -504,25 +505,25 @@ impl ConsensusState {
         }
 
         // Found fork chain
-        let chain = &self.proposals[chain_index as usize];
+        let chain = &self.forks[chain_index as usize];
         // Proposal extends fork at last proposal
-        if prop_index == (chain.proposals.len() - 1) {
+        if state_checkpoint_index == (chain.sequence.len() - 1) {
             return Ok(chain_index)
         }
 
         debug!("find_extended_chain_index(): Proposal to fork a forkchain was received.");
-        let mut chain = self.proposals[chain_index as usize].clone();
+        let mut chain = self.forks[chain_index as usize].clone();
         // We keep all proposals until the one it extends
-        chain.proposals.drain((prop_index + 1)..);
-        self.proposals.push(chain);
-        Ok(self.proposals.len() as i64 - 1)
+        chain.sequence.drain((state_checkpoint_index + 1)..);
+        self.forks.push(chain);
+        Ok(self.forks.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.proposals.iter() {
-            for proposal in chain.proposals.iter() {
-                if input_proposal == &proposal.hash {
+        for chain in self.forks.iter() {
+            for state_checkpoint in chain.sequence.iter().rev() {
+                if input_proposal == &state_checkpoint.proposal.hash {
                     return true
                 }
             }
@@ -541,7 +542,7 @@ impl ConsensusState {
             }
             _ => {
                 debug!("set_leader_history(): Checking last proposal of fork: {}", index);
-                let last_proposal = self.proposals[index as usize].proposals.last().unwrap();
+                let last_proposal = &self.forks[index as usize].sequence.last().unwrap().proposal;
                 if last_proposal.block.header.slot == self.current_slot() {
                     // Replacing our last history element with the leaders one
                     self.leaders_history.pop();
@@ -583,6 +584,18 @@ impl ConsensusState {
         }
         Err(Error::SlotCheckpointNotFound(slot))
     }
+
+    /// Auxillary function to update all fork state checkpoints to nodes current canonical states.
+    /// Note: This function should only be invoked once on nodes' coins creation.
+    pub fn update_forks_checkpoints(&mut self) {
+        for fork in &mut self.forks {
+            for state_checkpoint in &mut fork.sequence {
+                state_checkpoint.coins = self.coins.clone();
+                state_checkpoint.coins_tree = self.coins_tree.clone();
+                state_checkpoint.nullifiers = self.nullifiers.clone();
+            }
+        }
+    }
 }
 
 /// Auxiliary structure used for consensus syncing.
@@ -601,13 +614,13 @@ pub struct ConsensusResponse {
     /// Slots offset since genesis,
     pub offset: Option<u64>,
     /// Hot/live data used by the consensus algorithm
-    pub proposals: Vec<ProposalChain>,
+    pub forks: Vec<ForkInfo>,
     /// Pending transactions
     pub unconfirmed_txs: Vec<Transaction>,
     /// Hot/live slot checkpoints
     pub slot_checkpoints: Vec<SlotCheckpoint>,
     /// Seen nullifiers from proposals
-    pub leaders_nullifiers: Vec<pallas::Base>,
+    pub nullifiers: Vec<pallas::Base>,
 }
 
 impl net::Message for ConsensusResponse {
@@ -669,3 +682,137 @@ impl net::Message for SlotCheckpointResponse {
         "slotcheckpointresponse"
     }
 }
+
+/// Auxiliary structure used to keep track of consensus state checkpoints.
+#[derive(Debug, Clone)]
+pub struct StateCheckpoint {
+    /// Block proposal
+    pub proposal: BlockProposal,
+    /// Node competing coins current state
+    pub coins: Vec<LeadCoin>,
+    /// Coin commitments tree current state
+    pub coins_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    /// Seen nullifiers from proposals current state
+    pub nullifiers: Vec<pallas::Base>,
+}
+
+impl StateCheckpoint {
+    pub fn new(
+        proposal: BlockProposal,
+        coins: Vec<LeadCoin>,
+        coins_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+        nullifiers: Vec<pallas::Base>,
+    ) -> Self {
+        Self { proposal, coins, coins_tree, nullifiers }
+    }
+}
+
+/// Auxiliary structure used for forked consensus state checkpoints syncing
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct StateCheckpointInfo {
+    /// Block proposal
+    pub proposal: BlockProposal,
+    /// Seen nullifiers from proposals current state
+    pub nullifiers: Vec<pallas::Base>,
+}
+
+impl From<StateCheckpoint> for StateCheckpointInfo {
+    fn from(state_checkpoint: StateCheckpoint) -> Self {
+        Self { proposal: state_checkpoint.proposal, nullifiers: state_checkpoint.nullifiers }
+    }
+}
+
+impl From<StateCheckpointInfo> for StateCheckpoint {
+    fn from(state_checkpoint_info: StateCheckpointInfo) -> Self {
+        Self {
+            proposal: state_checkpoint_info.proposal,
+            coins: vec![],
+            coins_tree: BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(constants::EPOCH_LENGTH * 100),
+            nullifiers: state_checkpoint_info.nullifiers,
+        }
+    }
+}
+
+/// This struct represents a sequence of consensus state checkpoints.
+#[derive(Debug, Clone)]
+pub struct Fork {
+    pub genesis_block: blake3::Hash,
+    pub sequence: Vec<StateCheckpoint>,
+}
+
+impl Fork {
+    pub fn new(genesis_block: blake3::Hash, initial_state_checkpoint: StateCheckpoint) -> Self {
+        Self { genesis_block, sequence: vec![initial_state_checkpoint] }
+    }
+
+    /// Insertion of a valid state checkpoint.
+    pub fn add(&mut self, state_checkpoint: &StateCheckpoint) {
+        if self.check_state_checkpoint(state_checkpoint, self.sequence.last().unwrap()) {
+            self.sequence.push(state_checkpoint.clone());
+        }
+    }
+
+    /// A fork chain is considered valid when every state checkpoint is valid,
+    /// based on the `check_state_checkpoint` function
+    pub fn check_chain(&self) -> bool {
+        for (index, state_checkpoint) in self.sequence[1..].iter().enumerate() {
+            if !self.check_state_checkpoint(state_checkpoint, &self.sequence[index]) {
+                return false
+            }
+        }
+
+        true
+    }
+
+    /// A state checkpoint is considered valid when its proposal parent hash is equal to the
+    /// hash of the previous checkpoint's proposal and their slots are incremental,
+    /// excluding the genesis block proposal.
+    pub fn check_state_checkpoint(
+        &self,
+        state_checkpoint: &StateCheckpoint,
+        previous: &StateCheckpoint,
+    ) -> bool {
+        if state_checkpoint.proposal.block.header.previous == self.genesis_block {
+            debug!("check_checkpoint(): Genesis block proposal provided.");
+            return false
+        }
+
+        if state_checkpoint.proposal.block.header.previous != previous.proposal.hash ||
+            state_checkpoint.proposal.block.header.slot <= previous.proposal.block.header.slot
+        {
+            debug!("check_checkpoint(): Provided state checkpoint proposal is invalid.");
+            return false
+        }
+
+        // TODO: validate rest checkpoint info(like nullifiers)
+
+        true
+    }
+}
+
+/// Auxiliary structure used for forks syncing
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct ForkInfo {
+    pub genesis_block: blake3::Hash,
+    pub sequence: Vec<StateCheckpointInfo>,
+}
+
+impl From<Fork> for ForkInfo {
+    fn from(fork: Fork) -> Self {
+        let mut sequence = vec![];
+        for state_checkpoint in fork.sequence {
+            sequence.push(state_checkpoint.into());
+        }
+        Self { genesis_block: fork.genesis_block, sequence }
+    }
+}
+
+impl From<ForkInfo> for Fork {
+    fn from(fork_info: ForkInfo) -> Self {
+        let mut sequence = vec![];
+        for checkpoint in fork_info.sequence {
+            sequence.push(checkpoint.into());
+        }
+        Self { genesis_block: fork_info.genesis_block, sequence }
+    }
+}

+ 7 - 3
src/consensus/task/consensus_sync.rs

@@ -49,7 +49,7 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
             // Node verifies response came from a participating node.
             // Extra validations can be added here.
             let response = response_sub.receive().await?;
-            if response.leaders_nullifiers.is_empty() {
+            if response.nullifiers.is_empty() {
                 warn!("Retrieved consensus state from a new node, retrying...");
                 continue
             }
@@ -57,10 +57,14 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
             // Node stores response data.
             let mut lock = state.write().await;
             lock.consensus.offset = response.offset;
-            lock.consensus.proposals = response.proposals.clone();
+            let mut forks = vec![];
+            for fork in &response.forks {
+                forks.push(fork.clone().into());
+            }
+            lock.consensus.forks = forks;
             lock.unconfirmed_txs = response.unconfirmed_txs.clone();
             lock.consensus.slot_checkpoints = response.slot_checkpoints.clone();
-            lock.consensus.leaders_nullifiers = response.leaders_nullifiers.clone();
+            lock.consensus.nullifiers = response.nullifiers.clone();
 
             break
         }

+ 5 - 5
src/consensus/task/proposal.rs

@@ -115,13 +115,13 @@ pub async fn proposal_task(consensus_p2p: P2pPtr, sync_p2p: P2pPtr, state: Valid
         // for that slot.
         let (won, idx) = state.write().await.consensus.is_slot_leader(sigma1, sigma2);
         let result = if won { state.write().await.propose(idx, sigma1, sigma2) } else { Ok(None) };
-        let proposal = match result {
-            Ok(prop) => {
-                if prop.is_none() {
+        let (proposal, coin) = match result {
+            Ok(pair) => {
+                if pair.is_none() {
                     info!("consensus: Node is not the slot lead");
                     continue
                 }
-                prop.unwrap()
+                pair.unwrap()
             }
             Err(e) => {
                 error!("consensus: Block proposal failed: {}", e);
@@ -132,7 +132,7 @@ 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 {
+        match state.write().await.receive_proposal(&proposal, Some((idx, coin))).await {
             Ok(()) => {
                 info!("consensus: Block proposal saved successfully");
                 // Broadcast proposal to other consensus nodes

+ 87 - 51
src/consensus/validator.rs

@@ -37,8 +37,8 @@ use serde_json::json;
 use super::{
     constants,
     leadcoin::LeadCoin,
-    state::{ConsensusState, SlotCheckpoint},
-    BlockInfo, BlockProposal, Header, LeadInfo, LeadProof, ProposalChain,
+    state::{ConsensusState, Fork, SlotCheckpoint, StateCheckpoint},
+    BlockInfo, BlockProposal, Header, LeadInfo, LeadProof,
 };
 
 use crate::{
@@ -252,7 +252,7 @@ impl ValidatorState {
         idx: usize,
         sigma1: pallas::Base,
         sigma2: pallas::Base,
-    ) -> Result<Option<BlockProposal>> {
+    ) -> Result<Option<(BlockProposal, LeadCoin)>> {
         let slot = self.consensus.current_slot();
         let (prev_hash, index) = self.consensus.longest_chain_last_hash().unwrap();
         let unproposed_txs = self.unproposed_txs(index);
@@ -266,12 +266,16 @@ impl ValidatorState {
             hash[0..31].copy_from_slice(&blake3::hash(&serialize(tx)).as_bytes()[0..31]);
             tree.append(&MerkleNode::from(pallas::Base::from_repr(hash).unwrap()));
         }
-
         let root = tree.root(0).unwrap();
 
-        //let eta = self.consensus.epoch_eta;
+        // Checking if extending a fork or canonical
+        let coin = if index == -1 {
+            self.consensus.coins[idx]
+        } else {
+            self.consensus.forks[index as usize].sequence.last().unwrap().coins[idx]
+        };
+
         // Generating leader proof
-        let coin = self.consensus.coins[idx];
         let (proof, public_inputs) =
             coin.create_lead_proof(sigma1, sigma2, self.lead_proving_key.as_ref().unwrap());
 
@@ -297,10 +301,8 @@ impl ValidatorState {
             self.consensus.get_current_offset(slot),
             self.consensus.leaders_history.last().unwrap().clone(),
         );
-        // Replacing old coin with the derived coin
-        self.consensus.coins[idx] = coin.derive_coin(&mut self.consensus.coins_tree);
 
-        Ok(Some(BlockProposal::new(header, unproposed_txs, lead_info)))
+        Ok(Some((BlockProposal::new(header, unproposed_txs, lead_info), coin)))
     }
 
     /// Retrieve all unconfirmed transactions not proposed in previous blocks
@@ -316,9 +318,9 @@ impl ValidatorState {
 
         // We iterate over the fork chain proposals to find already proposed
         // transactions and remove them from the local unproposed_txs vector.
-        let chain = &self.consensus.proposals[index as usize];
-        for proposal in &chain.proposals {
-            for tx in &proposal.block.txs {
+        let chain = &self.consensus.forks[index as usize];
+        for state_checkpoint in &chain.sequence {
+            for tx in &state_checkpoint.proposal.block.txs {
                 if let Some(pos) = unproposed_txs.iter().position(|txs| *txs == *tx) {
                     unproposed_txs.remove(pos);
                 }
@@ -330,7 +332,11 @@ impl ValidatorState {
 
     /// 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<()> {
+    pub async fn receive_proposal(
+        &mut self,
+        proposal: &BlockProposal,
+        coin: Option<(usize, LeadCoin)>,
+    ) -> Result<()> {
         let current = self.consensus.current_slot();
         // Node hasn't started participating
         match self.consensus.participating {
@@ -451,29 +457,48 @@ impl ValidatorState {
             );
         }
 
-        // TODO: Check if proposal coin nullifiers already exist
+        // Create corresponding state checkpoint for validations
+        let mut state_checkpoint = match index {
+            -1 => {
+                // Extends canonical
+                StateCheckpoint::new(
+                    proposal.clone(),
+                    self.consensus.coins.clone(),
+                    self.consensus.coins_tree.clone(),
+                    self.consensus.nullifiers.clone(),
+                )
+            }
+            _ => {
+                // Extends a fork
+                let previous = self.consensus.forks[index as usize].sequence.last().unwrap();
+                StateCheckpoint::new(
+                    proposal.clone(),
+                    previous.coins.clone(),
+                    previous.coins_tree.clone(),
+                    previous.nullifiers.clone(),
+                )
+            }
+        };
+
+        // Check if proposal coin nullifiers already exist in the state checkpoint
         let prop_sn = lf.public_inputs[constants::PI_NULLIFIER_INDEX];
-        for sn in &self.consensus.leaders_nullifiers {
+        for sn in &state_checkpoint.nullifiers {
             if *sn == prop_sn {
                 error!("receive_proposal(): Proposal nullifiers exist.");
                 return Err(Error::ProposalIsSpent)
             }
         }
 
-        // TODO: Check if proposal coin commitments already spent
-        let prop_cm_x: pallas::Base = lf.public_inputs[constants::PI_COMMITMENT_X_INDEX];
-        let prop_cm_y: pallas::Base = lf.public_inputs[constants::PI_COMMITMENT_Y_INDEX];
-
-        // validate that this coin is already published.
         /*
-            let tree_root: MerkleNode = self.consensus.coins_tree.root(0).unwrap();
-            let prop_cm_root: pallas::Base = lf.public_inputs[constants::PI_COMMITMENT_ROOT];
-            if tree_root.inner() <= prop_cm_root {
-                error!("validation of tree root failed");
-                info!("tree_root: {:?}", tree_root.inner());
-                info!("prop_root: {:?}", prop_cm_root);
+        // TODO: Validate that proposal coin is already published.
+        let tree_root: MerkleNode = self.consensus.coins_tree.root(0).unwrap();
+        let prop_cm_root: pallas::Base = lf.public_inputs[constants::PI_COMMITMENT_ROOT];
+        if tree_root.inner() <= prop_cm_root {
+            error!("validation of tree root failed");
+            info!("tree_root: {:?}", tree_root.inner());
+            info!("prop_root: {:?}", prop_cm_root);
         }
-            */
+        */
 
         // Validate state transition against canonical state
         // TODO: This should be validated against fork state
@@ -485,20 +510,24 @@ impl ValidatorState {
 
         // TODO: [PLACEHOLDER] Add rewards validation
 
+        // If proposal came fromself, we derive new coin
+        if let Some((idx, c)) = coin {
+            state_checkpoint.coins[idx] = c.derive_coin(&mut state_checkpoint.coins_tree);
+        }
+        // Store proposal coins nullifiers
+        state_checkpoint.nullifiers.push(prop_sn);
+
         // Extend corresponding chain
         match index {
             -1 => {
-                let pc = ProposalChain::new(self.consensus.genesis_block, proposal.clone());
-                self.consensus.proposals.push(pc);
+                let fork = Fork::new(self.consensus.genesis_block, state_checkpoint);
+                self.consensus.forks.push(fork);
             }
             _ => {
-                self.consensus.proposals[index as usize].add(proposal);
+                self.consensus.forks[index as usize].add(&state_checkpoint);
             }
         };
 
-        // Store proposal coin nullifiers
-        self.consensus.leaders_nullifiers.push(prop_sn);
-
         Ok(())
     }
 
@@ -526,13 +555,13 @@ impl ValidatorState {
         // Set last slot finalization check occured to current slot
         self.consensus.checked_finalization = slot;
 
-        // First we find longest chain without any other forks at same height
-        let mut chain_index = -1;
+        // First we find longest fork without any other forks at same height
+        let mut fork_index = -1;
         // Use this index to extract leaders count sequence from longest fork
         let mut index_for_history = -1;
         let mut max_length = 0;
-        for (index, chain) in self.consensus.proposals.iter().enumerate() {
-            let length = chain.proposals.len();
+        for (index, fork) in self.consensus.forks.iter().enumerate() {
+            let length = fork.sequence.len();
             // Check if greater than max to retain index for history
             if length > max_length {
                 index_for_history = index as i64;
@@ -547,18 +576,18 @@ impl ValidatorState {
             }
             // Check if same length as max
             if length == max_length {
-                // Setting chain_index so we know we have multiple
+                // Setting fork_index so we know we have multiple
                 // forks at same length.
-                chain_index = -2;
+                fork_index = -2;
                 continue
             }
-            // Set chain as max
-            chain_index = index as i64;
+            // Set fork as max
+            fork_index = index as i64;
             max_length = length;
         }
 
         // Check if we found any fork to finalize
-        match chain_index {
+        match fork_index {
             -2 => {
                 debug!("chain_finalization(): Eligible forks with same height exist, nothing to finalize.");
                 self.consensus.set_leader_history(index_for_history);
@@ -569,21 +598,23 @@ impl ValidatorState {
                 self.consensus.set_leader_history(index_for_history);
                 return Ok(vec![])
             }
-            _ => debug!("chain_finalization(): Chain {} can be finalized!", chain_index),
+            _ => debug!("chain_finalization(): Chain {} can be finalized!", fork_index),
         }
 
         // Starting finalization
-        let mut chain = self.consensus.proposals[chain_index as usize].clone();
+        let mut fork = self.consensus.forks[fork_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());
+        let mut last_state_checkpoint = fork.sequence.first().unwrap().clone();
+        for state_checkpoint in &fork.sequence[..bound] {
+            finalized.push(state_checkpoint.proposal.clone().into());
+            last_state_checkpoint = state_checkpoint.clone();
         }
 
-        // Removing finalized proposals from chain
-        chain.proposals.drain(..bound);
+        // Removing finalized proposals state checkpoins from fork
+        fork.sequence.drain(..bound);
 
         // Adding finalized proposals to canonical
         info!("consensus: Adding {} finalized block to canonical chain.", finalized.len());
@@ -624,11 +655,16 @@ impl ValidatorState {
 
         // Setting leaders history to last proposal leaders count
         self.consensus.leaders_history =
-            vec![chain.proposals.last().unwrap().block.lead_info.leaders];
+            vec![fork.sequence.last().unwrap().proposal.block.lead_info.leaders];
 
         // Removing rest forks
-        self.consensus.proposals = vec![];
-        self.consensus.proposals.push(chain);
+        self.consensus.forks = vec![];
+        self.consensus.forks.push(fork);
+
+        // Setting canonical states from last finalized checkpoint
+        self.consensus.coins = last_state_checkpoint.coins;
+        self.consensus.coins_tree = last_state_checkpoint.coins_tree;
+        self.consensus.nullifiers = last_state_checkpoint.nullifiers;
 
         // Adding finalized slot checkpoints to canonical
         let mut bound = 0;