Explorar o código

src/consensus: fork chains to use BlockProposal strusture, cleanup, encode_payload! macro added

aggstam %!s(int64=4) %!d(string=hai) anos
pai
achega
56186fc49e

+ 13 - 7
script/research/validatord/src/main.rs

@@ -98,7 +98,7 @@ struct Opt {
 async fn proposal_task(p2p: net::P2pPtr, state: StatePtr, database: &sled::Db) {
     // Node signals the network that it starts participating
     let participant =
-        Participant::new(state.read().unwrap().id, state.read().unwrap().get_current_epoch());
+        Participant::new(state.read().unwrap().id, state.read().unwrap().current_epoch());
     state.write().unwrap().append_participant(participant.clone());
     let result = p2p.broadcast(participant).await;
     match result {
@@ -107,15 +107,17 @@ async fn proposal_task(p2p: net::P2pPtr, state: StatePtr, database: &sled::Db) {
     }
 
     // After initialization node should wait for next epoch
-    let seconds_until_next_epoch = state.read().unwrap().get_seconds_until_next_epoch_start();
+    let seconds_until_next_epoch = state.read().unwrap().next_epoch_start();
     info!("Waiting for next epoch({:?} sec)...", seconds_until_next_epoch);
     thread::sleep(seconds_until_next_epoch);
 
     loop {
+        // Node refreshes participants records
         state.write().unwrap().refresh_participants();
 
-        let result = if state.write().unwrap().check_if_epoch_leader() {
-            state.read().unwrap().propose_block()
+        // Node checks if its the epoch leader to generate a new proposal for that epoch
+        let result = if state.write().unwrap().is_epoch_leader() {
+            state.read().unwrap().propose()
         } else {
             Ok(None)
         };
@@ -124,9 +126,10 @@ async fn proposal_task(p2p: net::P2pPtr, state: StatePtr, database: &sled::Db) {
                 if proposal.is_none() {
                     info!("Node is not the epoch leader. Sleeping till next epoch...");
                 } else {
+                    // Leader creates a vote for the proposal and broadcasts them both
                     let unwrapped = proposal.unwrap();
                     info!("Node is the epoch leader. Proposed block: {:?}", unwrapped);
-                    let vote = state.write().unwrap().receive_proposed_block(&unwrapped, true);
+                    let vote = state.write().unwrap().receive_proposal(&unwrapped);
                     match vote {
                         Ok(x) => {
                             if x.is_none() {
@@ -154,9 +157,10 @@ async fn proposal_task(p2p: net::P2pPtr, state: StatePtr, database: &sled::Db) {
                     }
                 }
             }
-            Err(e) => error!("Broadcast failed. Error: {:?}", e),
+            Err(e) => error!("Block proposal failed. Error: {:?}", e),
         }
 
+        // Current node state is flushed to sled database
         let result = state.read().unwrap().save(database);
         match result {
             Ok(()) => (),
@@ -164,7 +168,9 @@ async fn proposal_task(p2p: net::P2pPtr, state: StatePtr, database: &sled::Db) {
                 error!("State could not be flushed: {:?}", e)
             }
         };
-        let seconds_until_next_epoch = state.read().unwrap().get_seconds_until_next_epoch_start();
+
+        // Node waits untile next epoch
+        let seconds_until_next_epoch = state.read().unwrap().next_epoch_start();
         info!("Waiting for next epoch({:?} sec)...", seconds_until_next_epoch);
         thread::sleep(seconds_until_next_epoch);
     }

+ 1 - 1
script/research/validatord/src/protocols/protocol_proposal.rs

@@ -46,7 +46,7 @@ impl ProtocolProposal {
                 proposal
             );
             let proposal_copy = (*proposal).clone();
-            let vote = self.state.write().unwrap().receive_proposed_block(&proposal_copy, false);
+            let vote = self.state.write().unwrap().receive_proposal(&proposal_copy);
             match vote {
                 Ok(x) => {
                     if x.is_none() {

+ 41 - 20
src/consensus/block.rs

@@ -1,9 +1,4 @@
-use std::{
-    hash::{Hash, Hasher},
-    io,
-};
-
-use super::{metadata::Metadata, participant::Participant, tx::Tx};
+use std::io;
 
 use crate::{
     crypto::{keypair::PublicKey, schnorr::Signature},
@@ -12,8 +7,10 @@ use crate::{
     Result,
 };
 
+use super::{metadata::Metadata, participant::Participant, tx::Tx, util::Timestamp};
+
 /// This struct represents a tuple of the form (st, sl, txs, metadata).
-/// Each blocks parent hash h may be computed simply as a hash of the parent block.
+/// Each blocks parent hash st may be computed simply as a hash of the parent block.
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct Block {
     /// Previous block hash
@@ -31,12 +28,17 @@ impl Block {
         st: blake3::Hash,
         sl: u64,
         txs: Vec<Tx>,
+        timestamp: Timestamp,
         proof: String,
         r: String,
         s: String,
         participants: Vec<Participant>,
     ) -> Block {
-        Block { st, sl, txs, metadata: Metadata::new(proof, r, s, participants) }
+        Block { st, sl, txs, metadata: Metadata::new(timestamp, proof, r, s, participants) }
+    }
+
+    pub fn from_proposal(proposal: BlockProposal) -> Block {
+        Block { st: proposal.st, sl: proposal.sl, txs: proposal.txs, metadata: proposal.metadata }
     }
 }
 
@@ -46,13 +48,10 @@ impl PartialEq for Block {
     }
 }
 
-impl Hash for Block {
-    fn hash<H: Hasher>(&self, hasher: &mut H) {
-        format!("{:?}{:?}{:?}", self.st, self.sl, self.txs).hash(hasher);
-    }
-}
+impl_vec!(Block);
 
-#[derive(Debug, Clone, PartialEq, SerialEncodable, SerialDecodable)]
+/// This struct represents a Block proposal, used for consensus.
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct BlockProposal {
     /// leader public key
     pub public_key: PublicKey,
@@ -66,6 +65,8 @@ pub struct BlockProposal {
     pub sl: u64,
     /// Transactions payload
     pub txs: Vec<Tx>,
+    /// Additional proposal information
+    pub metadata: Metadata,
 }
 
 impl BlockProposal {
@@ -76,8 +77,32 @@ impl BlockProposal {
         st: blake3::Hash,
         sl: u64,
         txs: Vec<Tx>,
+        timestamp: Timestamp,
+        proof: String,
+        r: String,
+        s: String,
+        participants: Vec<Participant>,
     ) -> BlockProposal {
-        BlockProposal { public_key, signature, id, st, sl, txs }
+        BlockProposal {
+            public_key,
+            signature,
+            id,
+            st,
+            sl,
+            txs,
+            metadata: Metadata::new(timestamp, proof, r, s, participants),
+        }
+    }
+}
+
+impl PartialEq for BlockProposal {
+    fn eq(&self, other: &Self) -> bool {
+        self.public_key == other.public_key &&
+            self.signature == other.signature &&
+            self.id == other.id &&
+            self.st == other.st &&
+            self.sl == other.sl &&
+            self.txs == other.txs
     }
 }
 
@@ -87,8 +112,4 @@ impl net::Message for BlockProposal {
     }
 }
 
-pub fn proposal_eq_block(proposal: &BlockProposal, block: &Block) -> bool {
-    proposal.st == block.st && proposal.sl == block.sl && proposal.txs == block.txs
-}
-
-impl_vec!(Block);
+impl_vec!(BlockProposal);

+ 95 - 20
src/consensus/blockchain.rs

@@ -1,12 +1,17 @@
 use std::io;
 
+use log::debug;
+
 use crate::{
-    impl_vec,
+    encode_payload, impl_vec,
     util::serial::{serialize, Decodable, Encodable, SerialDecodable, SerialEncodable, VarInt},
     Result,
 };
 
-use super::{block::Block, util::GENESIS_HASH_BYTES};
+use super::{
+    block::{Block, BlockProposal},
+    util::GENESIS_HASH_BYTES,
+};
 
 /// This struct represents a sequence of blocks starting with the genesis block.
 #[derive(Debug, Clone, PartialEq, SerialEncodable, SerialDecodable)]
@@ -15,38 +20,46 @@ pub struct Blockchain {
 }
 
 impl Blockchain {
-    pub fn new(intial_block: Block) -> Blockchain {
-        Blockchain { blocks: vec![intial_block] }
+    pub fn new(initial_block: Block) -> Blockchain {
+        Blockchain { blocks: vec![initial_block] }
     }
 
     /// A block is considered valid when its parent hash is equal to the hash of the
     /// previous block and their epochs are incremental, exluding genesis.
     /// Additional validity rules can be applied.
-    pub fn check_block_validity(&self, block: &Block, previous_block: &Block) {
-        assert!(block.st.as_bytes() != &GENESIS_HASH_BYTES, "Genesis block provided.");
-        let serialized = serialize(previous_block);
-        let previous_block_hash = blake3::hash(&serialized);
-        assert!(
-            block.st == previous_block_hash && block.sl > previous_block.sl,
-            "Provided block is invalid."
-        );
-    }
-
-    /// A blockchain is considered valid, when every block is valid, based on check_block_validity method.
-    pub fn check_chain_validity(&self) {
+    pub fn check_block(&self, block: &Block, previous: &Block) -> Result<bool> {
+        if block.st.as_bytes() == &GENESIS_HASH_BYTES {
+            debug!("Genesis block provided.");
+            return Ok(false)
+        }
+        let mut buf = vec![];
+        encode_payload!(&mut buf, previous.st, previous.sl, previous.txs);
+        let previous_hash = blake3::hash(&serialize(&buf));
+        if block.st != previous_hash || block.sl <= previous.sl {
+            debug!("Provided block is invalid.");
+            return Ok(false)
+        }
+        Ok(true)
+    }
+
+    /// A blockchain is considered valid, when every block is valid, based on check_block function.
+    pub fn check_chain(&self) -> bool {
         for (index, block) in self.blocks[1..].iter().enumerate() {
-            self.check_block_validity(block, &self.blocks[index])
+            if !self.check_block(block, &self.blocks[index]).unwrap() {
+                return false
+            }
         }
+        true
     }
 
     /// Insertion of a valid block.
-    pub fn add_block(&mut self, block: &Block) {
-        self.check_block_validity(block, self.blocks.last().unwrap());
+    pub fn add(&mut self, block: &Block) {
+        self.check_block(block, self.blocks.last().unwrap()).unwrap();
         self.blocks.push(block.clone());
     }
 
     /// Blockchain notarization check.
-    pub fn is_notarized(&self) -> bool {
+    pub fn notarized(&self) -> bool {
         for block in &self.blocks {
             if !block.metadata.sm.notarized {
                 return false
@@ -57,3 +70,65 @@ impl Blockchain {
 }
 
 impl_vec!(Blockchain);
+
+/// This struct represents a sequence of block proposals.
+#[derive(Debug, Clone, PartialEq, SerialEncodable, SerialDecodable)]
+pub struct ProposalsChain {
+    pub proposals: Vec<BlockProposal>,
+}
+
+impl ProposalsChain {
+    pub fn new(initial_proposal: BlockProposal) -> ProposalsChain {
+        ProposalsChain { proposals: vec![initial_proposal] }
+    }
+
+    /// A proposal is considered valid when its parent hash is equal to the hash of the
+    /// previous proposal and their epochs are incremental, exluding genesis block proposal.
+    /// Additional validity rules can be applied.
+    pub fn check_proposal(
+        &self,
+        proposal: &BlockProposal,
+        previous: &BlockProposal,
+    ) -> Result<bool> {
+        if proposal.st.as_bytes() == &GENESIS_HASH_BYTES {
+            debug!("Genesis block proposal provided.");
+            return Ok(false)
+        }
+        let mut buf = vec![];
+        encode_payload!(&mut buf, previous.st, previous.sl, previous.txs);
+        let previous_hash = blake3::hash(&serialize(&buf));
+        if proposal.st != previous_hash || proposal.sl <= previous.sl {
+            debug!("Provided proposal is invalid.");
+            return Ok(false)
+        }
+        Ok(true)
+    }
+
+    /// A proposals chain is considered valid, when every proposal is valid, based on 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]).unwrap() {
+                return false
+            }
+        }
+        true
+    }
+
+    /// Insertion of a valid proposal.
+    pub fn add(&mut self, proposal: &BlockProposal) {
+        self.check_proposal(proposal, self.proposals.last().unwrap()).unwrap();
+        self.proposals.push(proposal.clone());
+    }
+
+    /// Proposals chain notarization check.
+    pub fn notarized(&self) -> bool {
+        for proposal in &self.proposals {
+            if !proposal.metadata.sm.notarized {
+                return false
+            }
+        }
+        true
+    }
+}
+
+impl_vec!(ProposalsChain);

+ 11 - 9
src/consensus/metadata.rs

@@ -1,28 +1,30 @@
 use crate::util::serial::{SerialDecodable, SerialEncodable};
 
-use super::{
-    participant::Participant,
-    util::{get_current_time, Timestamp},
-    vote::Vote,
-};
+use super::{participant::Participant, util::Timestamp, vote::Vote};
 
 /// This struct represents additional Block information used by the consensus protocol.
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct Metadata {
+    /// Block creation timestamp
+    pub timestamp: Timestamp,
     /// Block information used by Ouroboros consensus
     pub om: OuroborosMetadata,
     /// Block information used by Streamlet consensus
     pub sm: StreamletMetadata,
-    /// Block recieval timestamp
-    pub timestamp: Timestamp,
 }
 
 impl Metadata {
-    pub fn new(proof: String, r: String, s: String, participants: Vec<Participant>) -> Metadata {
+    pub fn new(
+        timestamp: Timestamp,
+        proof: String,
+        r: String,
+        s: String,
+        participants: Vec<Participant>,
+    ) -> Metadata {
         Metadata {
+            timestamp,
             om: OuroborosMetadata::new(proof, r, s),
             sm: StreamletMetadata::new(participants),
-            timestamp: get_current_time(),
         }
     }
 }

+ 1 - 1
src/consensus/mod.rs

@@ -7,7 +7,7 @@ pub mod tx;
 pub mod util;
 pub mod vote;
 
-pub use block::{proposal_eq_block, Block, BlockProposal};
+pub use block::{Block, BlockProposal};
 pub use blockchain::Blockchain;
 pub use metadata::Metadata;
 pub use participant::Participant;

+ 219 - 193
src/consensus/state.rs

@@ -1,5 +1,6 @@
 use chrono::{NaiveDateTime, Utc};
 use log::{debug, error};
+use rand::rngs::OsRng;
 use std::{
     collections::{hash_map::DefaultHasher, BTreeMap},
     hash::{Hash, Hasher},
@@ -12,17 +13,17 @@ use crate::{
         keypair::{PublicKey, SecretKey},
         schnorr::{SchnorrPublic, SchnorrSecret},
     },
+    encode_payload,
     util::serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable},
     Error, Result,
 };
-use rand::rngs::OsRng;
 
 use super::{
-    block::{proposal_eq_block, Block, BlockProposal},
-    blockchain::Blockchain,
+    block::{Block, BlockProposal},
+    blockchain::{Blockchain, ProposalsChain},
     participant::Participant,
     tx::Tx,
-    util::{Timestamp, GENESIS_HASH_BYTES},
+    util::{get_current_time, Timestamp, GENESIS_HASH_BYTES},
     vote::Vote,
 };
 
@@ -34,16 +35,17 @@ pub type StatePtr = Arc<RwLock<State>>;
 
 /// This struct represents the state of a consensus node.
 /// Each node is numbered and has a secret-public keys pair, to sign messages.
-/// Nodes hold a set of Blockchains(some of which are not notarized)
+/// Nodes hold the canonical(finalized) blockchain, a set of fork chains containing proposals
 /// and a set of unconfirmed pending transactions.
+/// Additionally, each node keeps tracks of all participating nodes.
 #[derive(Debug, SerialEncodable, SerialDecodable)]
 pub struct State {
     pub id: u64,
-    pub genesis_time: Timestamp,
-    pub secret_key: SecretKey,
-    pub public_key: PublicKey,
-    pub canonical_blockchain: Blockchain,
-    pub node_blockchains: Vec<Blockchain>,
+    pub genesis: Timestamp,
+    pub secret: SecretKey,
+    pub public: PublicKey,
+    pub blockchain: Blockchain,
+    pub proposals: Vec<ProposalsChain>,
     pub unconfirmed_txs: Vec<Tx>,
     pub orphan_votes: Vec<Vote>,
     pub participants: BTreeMap<u64, Participant>,
@@ -51,16 +53,16 @@ pub struct State {
 }
 
 impl State {
-    pub fn new(id: u64, genesis_time: Timestamp, init_block: Block) -> State {
+    pub fn new(id: u64, genesis: Timestamp, init_block: Block) -> State {
         // TODO: clock sync
         let secret = SecretKey::random(&mut OsRng);
         State {
             id,
-            genesis_time,
-            secret_key: secret,
-            public_key: PublicKey::from_secret(secret),
-            canonical_blockchain: Blockchain::new(init_block),
-            node_blockchains: Vec::new(),
+            genesis,
+            secret,
+            public: PublicKey::from_secret(secret),
+            blockchain: Blockchain::new(init_block),
+            proposals: Vec::new(),
             unconfirmed_txs: Vec::new(),
             orphan_votes: Vec::new(),
             participants: BTreeMap::new(),
@@ -80,9 +82,9 @@ impl State {
 
     /// Node calculates seconds until next epoch starting time.
     /// Epochs duration is configured using the delta value.
-    pub fn get_seconds_until_next_epoch_start(&self) -> Duration {
-        let start_time = NaiveDateTime::from_timestamp(self.genesis_time.0, 0);
-        let current_epoch = self.get_current_epoch() + 1;
+    pub fn next_epoch_start(&self) -> Duration {
+        let start_time = NaiveDateTime::from_timestamp(self.genesis.0, 0);
+        let current_epoch = self.current_epoch() + 1;
         let next_epoch_start_timestamp =
             (current_epoch * (2 * DELTA)) + (start_time.timestamp() as u64);
         let next_epoch_start =
@@ -94,14 +96,14 @@ impl State {
 
     /// Node calculates current epoch, based on elapsed time from the genesis block.
     /// Epochs duration is configured using the delta value.
-    pub fn get_current_epoch(&self) -> u64 {
-        self.genesis_time.clone().elapsed() / (2 * DELTA)
+    pub fn current_epoch(&self) -> u64 {
+        self.genesis.clone().elapsed() / (2 * DELTA)
     }
 
     /// Node finds epochs leader, using a simple hash method.
     /// Leader calculation is based on how many nodes are participating in the network.
-    pub fn get_epoch_leader(&mut self) -> u64 {
-        let epoch = self.get_current_epoch();
+    pub fn epoch_leader(&mut self) -> u64 {
+        let epoch = self.current_epoch();
         let mut hasher = DefaultHasher::new();
         epoch.hash(&mut hasher);
         self.zero_participants_check();
@@ -110,41 +112,42 @@ impl State {
     }
 
     /// Node checks if they are the current epoch leader.
-    pub fn check_if_epoch_leader(&mut self) -> bool {
-        let leader = self.get_epoch_leader();
+    pub fn is_epoch_leader(&mut self) -> bool {
+        let leader = self.epoch_leader();
         self.id == leader
     }
 
     /// Node generates a block proposal for the current epoch,
     /// containing all uncorfirmed transactions.
-    /// Block extends the longest notarized blockchain the node holds.
-    pub fn propose_block(&self) -> Result<Option<BlockProposal>> {
-        let epoch = self.get_current_epoch();
-        let longest_notarized_chain = self.find_longest_notarized_chain();
-        let serialized = serialize(longest_notarized_chain.blocks.last().unwrap());
-        let hash = blake3::hash(&serialized);
-        let unproposed_txs = self.get_unproposed_txs();
+    /// Proposal extends the longest notarized fork chain the node holds.
+    pub fn propose(&self) -> Result<Option<BlockProposal>> {
+        let epoch = self.current_epoch();
+        let previous_hash = self.longest_notarized_chain_last_hash().unwrap();
+        let unproposed_txs = self.unproposed_txs();
         let mut encoded_block = vec![];
-        hash.encode(&mut encoded_block)?;
-        epoch.encode(&mut encoded_block)?;
-        unproposed_txs.encode(&mut encoded_block)?;
-        let signed_block = self.secret_key.sign(&encoded_block[..]);
+        encode_payload!(&mut encoded_block, previous_hash, epoch, unproposed_txs);
+        let signed_block = self.secret.sign(&encoded_block[..]);
         Ok(Some(BlockProposal::new(
-            self.public_key,
+            self.public,
             signed_block,
             self.id,
-            hash,
+            previous_hash,
             epoch,
             unproposed_txs,
+            get_current_time(),
+            String::from("proof"),
+            String::from("r"),
+            String::from("s"),
+            self.participants.values().cloned().collect(),
         )))
     }
 
     /// Node retrieves all unconfiremd transactions not proposed in previous blocks.
-    pub fn get_unproposed_txs(&self) -> Vec<Tx> {
+    pub fn unproposed_txs(&self) -> Vec<Tx> {
         let mut unproposed_txs = self.unconfirmed_txs.clone();
-        for blockchain in &self.node_blockchains {
-            for block in &blockchain.blocks {
-                for tx in &block.txs {
+        for chain in &self.proposals {
+            for proposal in &chain.proposals {
+                for tx in &proposal.txs {
                     if let Some(pos) = unproposed_txs.iter().position(|txs| *txs == *tx) {
                         unproposed_txs.remove(pos);
                     }
@@ -154,168 +157,197 @@ impl State {
         unproposed_txs
     }
 
-    /// Finds the longest fully notarized blockchain the node holds.
-    pub fn find_longest_notarized_chain(&self) -> &Blockchain {
-        let mut longest_notarized_chain = &self.canonical_blockchain;
-        let mut length = 0;
-        for blockchain in &self.node_blockchains {
-            if blockchain.is_notarized() && blockchain.blocks.len() > length {
-                length = blockchain.blocks.len();
-                longest_notarized_chain = blockchain;
+    /// Finds the longest fully notarized blockchain the node holds and returns the last block hash.
+    pub fn longest_notarized_chain_last_hash(&self) -> Result<blake3::Hash> {
+        let mut buf = vec![];
+        if !self.proposals.is_empty() {
+            let mut longest_notarized_chain = &self.proposals[0];
+            let mut length = longest_notarized_chain.proposals.len();
+            if self.proposals.len() > 1 {
+                for chain in &self.proposals[1..] {
+                    if chain.notarized() && chain.proposals.len() > length {
+                        length = chain.proposals.len();
+                        longest_notarized_chain = chain;
+                    }
+                }
             }
-        }
-        longest_notarized_chain
+            let last = longest_notarized_chain.proposals.last().unwrap();
+            encode_payload!(&mut buf, last.st, last.sl, last.txs);
+        } else {
+            let last = self.blockchain.blocks.last().unwrap();
+            encode_payload!(&mut buf, last.st, last.sl, last.txs);
+        };
+        Ok(blake3::hash(&serialize(&buf)))
     }
 
     /// Node receives the proposed block, verifies its sender(epoch leader),
     /// and proceeds with voting on it.
-    pub fn receive_proposed_block(
-        &mut self,
-        proposed_block: &BlockProposal,
-        leader: bool,
-    ) -> Result<Option<Vote>> {
-        assert!(self.get_epoch_leader() == proposed_block.id);
+    pub fn receive_proposal(&mut self, proposal: &BlockProposal) -> Result<Option<Vote>> {
+        let leader = self.epoch_leader();
+        if leader != proposal.id {
+            debug!(
+                "Received proposal not from epoch leader ({:?}). Proposer: {:?}",
+                leader, proposal.id
+            );
+            return Ok(None)
+        }
         let mut encoded_block = vec![];
-        proposed_block.st.encode(&mut encoded_block)?;
-        proposed_block.sl.encode(&mut encoded_block)?;
-        proposed_block.txs.encode(&mut encoded_block)?;
-        assert!(proposed_block.public_key.verify(&encoded_block[..], &proposed_block.signature));
-        self.vote_block(proposed_block, leader)
+        encode_payload!(&mut encoded_block, proposal.st, proposal.sl, proposal.txs);
+        if !proposal.public_key.verify(&encoded_block[..], &proposal.signature) {
+            debug!("Proposer signature couldn't be verified. Proposer: {:?}", proposal.id);
+            return Ok(None)
+        }
+        self.vote(proposal)
     }
 
-    /// Given a block, node finds which blockchain it extends.
-    /// If block extends the canonical blockchain, a new fork blockchain is created.
-    /// Node votes on the block, only if it extends the longest notarized chain it has seen.
-    pub fn vote_block(&mut self, proposal: &BlockProposal, leader: bool) -> Result<Option<Vote>> {
+    /// Given a proposal, node finds which blockchain it extends.
+    /// If proposal extends the canonical blockchain, a new fork chain is created.
+    /// Node votes on the proposal, only if it extends the longest notarized fork chain it has seen.
+    pub fn vote(&mut self, proposal: &BlockProposal) -> Result<Option<Vote>> {
         self.zero_participants_check();
-        let mut block = Block::new(
-            proposal.st.clone(),
-            proposal.sl,
-            proposal.txs.clone(),
-            String::from("proof"),
-            String::from("r"),
-            String::from("s"),
-            self.participants.values().cloned().collect(),
-        );
+        let mut proposal = proposal.clone();
+
+        // Generate proposal hash
+        let mut buf = vec![];
+        encode_payload!(&mut buf, proposal.st, proposal.sl, proposal.txs);
+        let proposal_hash = blake3::hash(&serialize(&buf));
 
         // Add orphan votes
         let mut orphans = Vec::new();
-        for (index, vote) in self.orphan_votes.iter().enumerate() {
-            if proposal_eq_block(&vote.block, &block) {
-                block.metadata.sm.votes.push(vote.clone());
-                orphans.push(index);
+        for vote in self.orphan_votes.iter() {
+            if vote.proposal == proposal_hash {
+                proposal.metadata.sm.votes.push(vote.clone());
+                orphans.push(vote.clone());
             }
         }
-        for index in orphans {
-            self.orphan_votes.remove(index);
+        for vote in orphans {
+            self.orphan_votes.retain(|v| *v != vote);
         }
 
-        let index = self.find_extended_blockchain_index(&block, leader);
+        let index = self.find_extended_chain_index(&proposal).unwrap();
 
         if index == -2 {
             return Ok(None)
         }
-        let blockchain = match index {
+        let chain = match index {
             -1 => {
-                let blockchain = Blockchain::new(block);
-                self.node_blockchains.push(blockchain);
-                self.node_blockchains.last().unwrap()
+                let proposalschain = ProposalsChain::new(proposal.clone());
+                self.proposals.push(proposalschain);
+                self.proposals.last().unwrap()
             }
             _ => {
-                self.node_blockchains[index as usize].add_block(&block);
-                &self.node_blockchains[index as usize]
+                self.proposals[index as usize].add(&proposal);
+                &self.proposals[index as usize]
             }
         };
 
-        if self.extends_notarized_blockchain(blockchain) {
-            let mut encoded_proposal = vec![];
-            proposal.encode(&mut encoded_proposal)?;
-            let signed_proposal = self.secret_key.sign(&encoded_proposal[..]);
-            return Ok(Some(Vote::new(self.public_key, signed_proposal, proposal.clone(), self.id)))
+        if self.extends_notarized_chain(chain) {
+            let mut encoded_hash = vec![];
+            encode_payload!(&mut encoded_hash, proposal_hash);
+            let signed_hash = self.secret.sign(&encoded_hash[..]);
+            return Ok(Some(Vote::new(
+                self.public,
+                signed_hash,
+                proposal_hash,
+                proposal.sl,
+                self.id,
+            )))
         }
         Ok(None)
     }
 
-    /// Node verifies if provided blockchain is notarized excluding the last block.
-    pub fn extends_notarized_blockchain(&self, blockchain: &Blockchain) -> bool {
-        for block in &blockchain.blocks[..(blockchain.blocks.len() - 1)] {
-            if !block.metadata.sm.notarized {
+    /// Node verifies if provided chain is notarized excluding the last block.
+    pub fn extends_notarized_chain(&self, chain: &ProposalsChain) -> bool {
+        for proposal in &chain.proposals[..(chain.proposals.len() - 1)] {
+            if !proposal.metadata.sm.notarized {
                 return false
             }
         }
         true
     }
 
-    /// Given a block, node finds the index of the blockchain it extends.
-    pub fn find_extended_blockchain_index(&self, block: &Block, leader: bool) -> i64 {
-        for (index, blockchain) in self.node_blockchains.iter().enumerate() {
-            let last_block = blockchain.blocks.last().unwrap();
-            let last_block_hash = blake3::hash(&serialize(last_block));
-            if (leader && block.st == last_block_hash && block.sl >= last_block.sl) ||
-                (!leader && block.st == last_block_hash && block.sl > last_block.sl)
-            {
-                return index as i64
+    /// Given a proposal, node finds the index of the chain it extends.
+    pub fn find_extended_chain_index(&self, proposal: &BlockProposal) -> Result<i64> {
+        for (index, chain) in self.proposals.iter().enumerate() {
+            let last = chain.proposals.last().unwrap();
+            let mut buf = vec![];
+            encode_payload!(&mut buf, last.st, last.sl, last.txs);
+            let hash = blake3::hash(&serialize(&buf));
+            if proposal.st == hash && proposal.sl > last.sl {
+                return Ok(index as i64)
+            }
+            if proposal.st == last.st && proposal.sl == last.sl {
+                debug!("Proposal already received.");
+                return Ok(-2)
             }
         }
 
-        let last_block = self.canonical_blockchain.blocks.last().unwrap();
-        let last_block_hash = blake3::hash(&serialize(last_block));
-        if (leader && block.st != last_block_hash || block.sl < last_block.sl) ||
-            (!leader && block.st != last_block_hash || block.sl <= last_block.sl)
-        {
-            debug!("Proposed block doesn't extend any known chains.");
-            return -2
+        let last = self.blockchain.blocks.last().unwrap();
+        let mut buf = vec![];
+        encode_payload!(&mut buf, last.st, last.sl, last.txs);
+        let hash = blake3::hash(&serialize(&buf));
+        if proposal.st != hash || proposal.sl <= last.sl {
+            debug!("Proposal doesn't extend any known chains.");
+            return Ok(-2)
         }
-        -1
+        Ok(-1)
     }
 
-    /// Node receives a vote for a block.
+    /// Node receives a vote for a proposal.
     /// First, sender is verified using their public key.
-    /// Block is searched in nodes blockchains.
-    /// If the vote wasn't received before, it is appended to block votes list.
-    /// When a node sees 2n/3 votes for a block it notarizes it.
-    /// When a block gets notarized, the transactions it contains are removed from
+    /// Proposal is searched in nodes fork chains.
+    /// If the vote wasn't received before, it is appended to proposal votes list.
+    /// When a node sees 2n/3 votes for a proposal it notarizes it.
+    /// When a proposal gets notarized, the transactions it contains are removed from
     /// nodes unconfirmed transactions list.
-    /// Finally, we check if the notarization of the block can finalize parent blocks
-    /// in its blockchain.
+    /// Finally, we check if the notarization of the proposal can finalize parent proposals
+    /// in its chain.
     pub fn receive_vote(&mut self, vote: &Vote) -> bool {
-        let mut encoded_block = vec![];
-        let result = vote.block.encode(&mut encoded_block);
+        let mut encoded_proposal = vec![];
+        let result = vote.proposal.encode(&mut encoded_proposal);
         match result {
             Ok(_) => (),
             Err(e) => {
-                error!("Block encoding failed. Error: {:?}", e);
+                error!("Proposal encoding failed. Error: {:?}", e);
                 return false
             }
         };
-        assert!(&vote.node_public_key.verify(&encoded_block[..], &vote.vote));
+
+        if !vote.public_key.verify(&encoded_proposal[..], &vote.vote) {
+            debug!("Voter signature couldn't be verified. Voter: {:?}", vote.id);
+            return false
+        }
 
         let nodes_count = self.participants.len();
         self.zero_participants_check();
 
-        let vote_block = self.find_block(&vote.block);
-        if vote_block == None {
-            debug!("Received vote for unknown block.");
+        let proposal = self.find_proposal(&vote.proposal).unwrap();
+        if proposal == None {
+            debug!("Received vote for unknown proposal.");
             if !self.orphan_votes.contains(vote) {
                 self.orphan_votes.push(vote.clone());
             }
             return false
         }
 
-        let (unwrapped_vote_block, blockchain_index) = vote_block.unwrap();
-        if !unwrapped_vote_block.metadata.sm.votes.contains(vote) {
-            unwrapped_vote_block.metadata.sm.votes.push(vote.clone());
+        let (unwrapped, chain_index) = proposal.unwrap();
+        if !unwrapped.metadata.sm.votes.contains(vote) {
+            unwrapped.metadata.sm.votes.push(vote.clone());
 
-            if !unwrapped_vote_block.metadata.sm.notarized &&
-                unwrapped_vote_block.metadata.sm.votes.len() > (2 * nodes_count / 3)
+            if !unwrapped.metadata.sm.notarized &&
+                unwrapped.metadata.sm.votes.len() > (2 * nodes_count / 3)
             {
-                unwrapped_vote_block.metadata.sm.notarized = true;
-                self.check_blockchain_finalization(blockchain_index);
+                unwrapped.metadata.sm.notarized = true;
+                self.chain_finalization(chain_index);
             }
 
             // updating participant vote
-            let mut participant = self.participants.get(&vote.id).unwrap().clone();
-            participant.voted = Some(vote.block.sl);
+            let exists = self.participants.get(&vote.id);
+            let mut participant = match exists {
+                Some(p) => p.clone(),
+                None => Participant::new(vote.id, vote.sl),
+            };
+            participant.voted = Some(vote.sl);
             self.participants.insert(participant.id, participant);
 
             return true
@@ -323,86 +355,79 @@ impl State {
         false
     }
 
-    /// Node searches it the blockchains it holds for provided block.
-    pub fn find_block(&mut self, vote_block: &BlockProposal) -> Option<(&mut Block, i64)> {
-        for (index, blockchain) in &mut self.node_blockchains.iter_mut().enumerate() {
-            for block in blockchain.blocks.iter_mut().rev() {
-                if proposal_eq_block(vote_block, block) {
-                    return Some((block, index as i64))
+    /// Node searches it the chains it holds for provided proposal.
+    pub fn find_proposal(
+        &mut self,
+        vote_proposal: &blake3::Hash,
+    ) -> Result<Option<(&mut BlockProposal, i64)>> {
+        for (index, chain) in &mut self.proposals.iter_mut().enumerate() {
+            for proposal in chain.proposals.iter_mut().rev() {
+                let mut buf = vec![];
+                encode_payload!(&mut buf, proposal.st, proposal.sl, proposal.txs);
+                let proposal_hash = blake3::hash(&serialize(&buf));
+                if vote_proposal == &proposal_hash {
+                    return Ok(Some((proposal, index as i64)))
                 }
             }
         }
-
-        for block in &mut self.canonical_blockchain.blocks.iter_mut().rev() {
-            if proposal_eq_block(vote_block, block) {
-                return Some((block, -1))
-            }
-        }
-        None
+        Ok(None)
     }
 
-    /// Node checks if the index blockchain can be finalized.
+    /// Provided an index, node checks if chain can be finalized.
     /// Consensus finalization logic: If node has observed the notarization of 3 consecutive
-    /// blocks in a fork chain, it finalizes (appends to canonical blockchain) all blocks up to the middle block.
-    /// When fork chain blocks are finalized, rest fork chains not starting by those blocks are removed.
-    pub fn check_blockchain_finalization(&mut self, blockchain_index: i64) {
-        let blockchain = if blockchain_index == -1 {
-            &mut self.canonical_blockchain
-        } else {
-            &mut self.node_blockchains[blockchain_index as usize]
-        };
-
-        let blockchain_len = blockchain.blocks.len();
-        if blockchain_len > 2 {
-            let mut consecutive_notarized = 0;
-            for block in &blockchain.blocks {
-                if block.metadata.sm.notarized {
-                    consecutive_notarized += 1;
+    /// proposals in a fork chain, it finalizes (appends to canonical blockchain) all proposals up to the middle block.
+    /// When fork chain proposals are finalized, rest fork chains not starting by those proposals are removed.
+    pub fn chain_finalization(&mut self, chain_index: i64) {
+        let chain = &mut self.proposals[chain_index as usize];
+        let len = chain.proposals.len();
+        if len > 2 {
+            let mut consecutive = 0;
+            for proposal in &chain.proposals {
+                if proposal.metadata.sm.notarized {
+                    consecutive += 1;
                 } else {
                     break
                 }
             }
 
-            if consecutive_notarized > 2 {
-                let mut finalized_blocks = Vec::new();
-                for block in &mut blockchain.blocks[..(consecutive_notarized - 1)] {
-                    block.metadata.sm.finalized = true;
-                    finalized_blocks.push(block.clone());
-                    for tx in block.txs.clone() {
+            if consecutive > 2 {
+                let mut finalized = Vec::new();
+                for proposal in &mut chain.proposals[..(consecutive - 1)] {
+                    proposal.metadata.sm.finalized = true;
+                    finalized.push(proposal.clone());
+                    for tx in proposal.txs.clone() {
                         if let Some(pos) = self.unconfirmed_txs.iter().position(|txs| *txs == tx) {
                             self.unconfirmed_txs.remove(pos);
                         }
                     }
                 }
-                blockchain.blocks.drain(0..(consecutive_notarized - 1));
-                for block in &finalized_blocks {
-                    self.canonical_blockchain.blocks.push(block.clone());
+                chain.proposals.drain(0..(consecutive - 1));
+                for proposal in &finalized {
+                    self.blockchain.blocks.push(Block::from_proposal(proposal.clone()));
                 }
 
-                let last_finalized_block = self.canonical_blockchain.blocks.last().unwrap();
-                let last_finalized_block_hash = blake3::hash(&serialize(last_finalized_block));
-                let mut dropped_blockchains = Vec::new();
-                for (index, blockchain) in self.node_blockchains.iter().enumerate() {
-                    let first_block = blockchain.blocks.first().unwrap();
-                    if first_block.st != last_finalized_block_hash ||
-                        first_block.sl <= last_finalized_block.sl
-                    {
-                        dropped_blockchains.push(index);
+                let last = self.blockchain.blocks.last().unwrap();
+                let hash = blake3::hash(&serialize(last));
+                let mut dropped = Vec::new();
+                for chain in self.proposals.iter() {
+                    let first = chain.proposals.first().unwrap();
+                    if first.st != hash || first.sl <= last.sl {
+                        dropped.push(chain.clone());
                     }
                 }
-                for index in dropped_blockchains {
-                    self.node_blockchains.remove(index);
+                for chain in dropped {
+                    self.proposals.retain(|c| *c != chain);
                 }
 
                 // Remove orphan votes
                 let mut orphans = Vec::new();
-                for (index, vote) in self.orphan_votes.iter().enumerate() {
-                    if vote.block.sl <= last_finalized_block.sl {
-                        orphans.push(index);
+                for vote in self.orphan_votes.iter() {
+                    if vote.sl <= last.sl {
+                        orphans.push(vote.clone());
                     }
                 }
-                for index in orphans {
-                    self.orphan_votes.remove(index);
+                for vote in orphans {
+                    self.orphan_votes.retain(|v| *v != vote);
                 }
             }
         }
@@ -438,7 +463,7 @@ impl State {
         self.pending_participants = Vec::new();
 
         let mut inactive = Vec::new();
-        let previous_epoch = self.get_current_epoch() - 1;
+        let previous_epoch = self.current_epoch() - 1;
         for (index, participant) in self.participants.clone().iter() {
             match participant.voted {
                 Some(epoch) => {
@@ -492,6 +517,7 @@ impl State {
             blake3::Hash::from(GENESIS_HASH_BYTES),
             0,
             vec![],
+            get_current_time(),
             String::from("proof"),
             String::from("r"),
             String::from("s"),

+ 1 - 0
src/consensus/tx.rs

@@ -6,6 +6,7 @@ use crate::{
     Result,
 };
 
+/// Temporary structure used to represent transactions.
 #[derive(Debug, Clone, PartialEq, SerialEncodable, SerialDecodable)]
 pub struct Tx {
     pub payload: String,

+ 2 - 2
src/consensus/util.rs

@@ -2,14 +2,14 @@ use chrono::{NaiveDateTime, Utc};
 
 use crate::util::serial::{SerialDecodable, SerialEncodable};
 
-// Serialized blake3 hash bytes for character "⊥"
+/// Serialized blake3 hash bytes for character "⊥"
 pub const GENESIS_HASH_BYTES: [u8; 32] = [
     254, 233, 82, 102, 23, 208, 153, 87, 96, 165, 163, 194, 238, 7, 1, 88, 14, 1, 249, 118, 197,
     29, 180, 211, 87, 66, 59, 38, 86, 54, 12, 39,
 ];
 
 /// Util structure to represend chrono UTC timestamps.
-#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
+#[derive(Debug, Clone, PartialEq, SerialDecodable, SerialEncodable)]
 pub struct Timestamp(pub i64);
 
 impl Timestamp {

+ 14 - 8
src/consensus/vote.rs

@@ -1,7 +1,5 @@
 use std::io;
 
-use super::block::BlockProposal;
-
 use crate::{
     crypto::{keypair::PublicKey, schnorr::Signature},
     impl_vec, net,
@@ -9,22 +7,30 @@ use crate::{
     Result,
 };
 
-/// This struct represents a tuple of the form (vote, B, id).
+/// This struct represents a Vote, used by Streamlet consensus.
 #[derive(Debug, Clone, PartialEq, SerialDecodable, SerialEncodable)]
 pub struct Vote {
     /// Node public key
-    pub node_public_key: PublicKey,
+    pub public_key: PublicKey,
     /// signed block
     pub vote: Signature,
-    /// block proposal to vote on
-    pub block: BlockProposal,
+    /// block proposal hash to vote on
+    pub proposal: blake3::Hash,
+    /// Slot uid, generated by the beacon
+    pub sl: u64,
     /// node id
     pub id: u64,
 }
 
 impl Vote {
-    pub fn new(node_public_key: PublicKey, vote: Signature, block: BlockProposal, id: u64) -> Vote {
-        Vote { node_public_key, vote, block, id }
+    pub fn new(
+        public_key: PublicKey,
+        vote: Signature,
+        proposal: blake3::Hash,
+        sl: u64,
+        id: u64,
+    ) -> Vote {
+        Vote { public_key, vote, proposal, sl, id }
     }
 }
 

+ 31 - 0
src/util/serial.rs

@@ -657,6 +657,16 @@ tuple_encode!(T0, T1, T2, T3);
 tuple_encode!(T0, T1, T2, T3, T4, T5);
 tuple_encode!(T0, T1, T2, T3, T4, T5, T6, T7);
 
+/// Encode a dynamic set of arguments to a buffer.
+#[macro_export]
+macro_rules! encode_payload {
+    ($buf: expr, $($args: expr), *) => {{
+        $(
+            $args.encode($buf)?;
+        )*
+    }}
+}
+
 #[cfg(test)]
 mod tests {
     use super::{
@@ -982,4 +992,25 @@ mod tests {
         assert_eq!(t2, t2_de);
         assert_eq!(t3_de, TestDerive3 { foo: 30, bar: 0, meh: 44 });
     }
+
+    #[test]
+    fn encode_payload_test() -> Result<()> {
+        let mut buf = vec![];
+        encode_payload!(&mut buf, 1_i32, 2_i32, b"Hello World");
+        assert_eq!(
+            buf,
+            [1, 0, 0, 0, 2, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
+        );
+
+        let mut buf = vec![];
+        encode_payload!(&mut buf, 1.5f64, -1i64, true, 0x10000, [0xfe, 0xff, 0x00, 0x00, 0x00]);
+        assert_eq!(
+            buf,
+            [
+                0, 0, 0, 0, 0, 0, 248, 63, 255, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 1, 0,
+                254, 255, 0, 0, 0
+            ]
+        );
+        Ok(())
+    }
 }