|
@@ -14,6 +14,7 @@ use crate::{
|
|
|
keypair::{PublicKey, SecretKey},
|
|
keypair::{PublicKey, SecretKey},
|
|
|
schnorr::{SchnorrPublic, SchnorrSecret},
|
|
schnorr::{SchnorrPublic, SchnorrSecret},
|
|
|
},
|
|
},
|
|
|
|
|
+ net,
|
|
|
util::serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable},
|
|
util::serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable},
|
|
|
Error, Result,
|
|
Error, Result,
|
|
|
};
|
|
};
|
|
@@ -28,11 +29,11 @@ use super::{
|
|
|
vote::Vote,
|
|
vote::Vote,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const DELTA: u64 = 60;
|
|
|
|
|
|
|
+pub const DELTA: u64 = 10;
|
|
|
const SLED_CONSESUS_STATE_TREE: &[u8] = b"_consensus_state";
|
|
const SLED_CONSESUS_STATE_TREE: &[u8] = b"_consensus_state";
|
|
|
|
|
|
|
|
/// This struct represents the information required by the consensus algorithm.
|
|
/// This struct represents the information required by the consensus algorithm.
|
|
|
-#[derive(Debug, SerialEncodable, SerialDecodable)]
|
|
|
|
|
|
|
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
|
|
|
pub struct ConsensusState {
|
|
pub struct ConsensusState {
|
|
|
/// Genesis block creation timestamp
|
|
/// Genesis block creation timestamp
|
|
|
pub genesis: Timestamp,
|
|
pub genesis: Timestamp,
|
|
@@ -67,6 +68,32 @@ impl ConsensusState {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/// Auxilary structure used for consensus syncing.
|
|
|
|
|
+#[derive(Debug, SerialEncodable, SerialDecodable)]
|
|
|
|
|
+pub struct ConsensusRequest {
|
|
|
|
|
+ /// Validator id
|
|
|
|
|
+ pub id: u64,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl net::Message for ConsensusRequest {
|
|
|
|
|
+ fn name() -> &'static str {
|
|
|
|
|
+ "consensusrequest"
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Auxilary structure used for consensus syncing.
|
|
|
|
|
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
|
|
|
|
|
+pub struct ConsensusResponse {
|
|
|
|
|
+ /// Hot/live data used by the consensus algorithm
|
|
|
|
|
+ pub consensus: ConsensusState,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl net::Message for ConsensusResponse {
|
|
|
|
|
+ fn name() -> &'static str {
|
|
|
|
|
+ "consensusresponse"
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// Atomic pointer to validator state.
|
|
/// Atomic pointer to validator state.
|
|
|
pub type ValidatorStatePtr = Arc<RwLock<ValidatorState>>;
|
|
pub type ValidatorStatePtr = Arc<RwLock<ValidatorState>>;
|
|
|
|
|
|
|
@@ -88,6 +115,8 @@ pub struct ValidatorState {
|
|
|
pub unconfirmed_txs: Vec<Tx>,
|
|
pub unconfirmed_txs: Vec<Tx>,
|
|
|
/// Genesis block hash, used for validations
|
|
/// Genesis block hash, used for validations
|
|
|
pub genesis_block: blake3::Hash,
|
|
pub genesis_block: blake3::Hash,
|
|
|
|
|
+ /// Participation flag
|
|
|
|
|
+ pub participating: bool,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl ValidatorState {
|
|
impl ValidatorState {
|
|
@@ -100,6 +129,7 @@ impl ValidatorState {
|
|
|
let blockchain = Blockchain::new(&db, genesis)?;
|
|
let blockchain = Blockchain::new(&db, genesis)?;
|
|
|
let unconfirmed_txs = Vec::new();
|
|
let unconfirmed_txs = Vec::new();
|
|
|
let genesis_block = blake3::hash(&serialize(&Block::genesis_block(genesis)));
|
|
let genesis_block = blake3::hash(&serialize(&Block::genesis_block(genesis)));
|
|
|
|
|
+ let participating = false;
|
|
|
Ok(Arc::new(RwLock::new(ValidatorState {
|
|
Ok(Arc::new(RwLock::new(ValidatorState {
|
|
|
id,
|
|
id,
|
|
|
secret,
|
|
secret,
|
|
@@ -109,6 +139,7 @@ impl ValidatorState {
|
|
|
blockchain,
|
|
blockchain,
|
|
|
unconfirmed_txs,
|
|
unconfirmed_txs,
|
|
|
genesis_block,
|
|
genesis_block,
|
|
|
|
|
+ participating,
|
|
|
})))
|
|
})))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -148,7 +179,6 @@ impl ValidatorState {
|
|
|
let epoch = self.current_epoch();
|
|
let epoch = self.current_epoch();
|
|
|
let mut hasher = DefaultHasher::new();
|
|
let mut hasher = DefaultHasher::new();
|
|
|
epoch.hash(&mut hasher);
|
|
epoch.hash(&mut hasher);
|
|
|
- self.zero_participants_check();
|
|
|
|
|
let pos = hasher.finish() % (self.consensus.participants.len() as u64);
|
|
let pos = hasher.finish() % (self.consensus.participants.len() as u64);
|
|
|
self.consensus.participants.iter().nth(pos as usize).unwrap().1.id
|
|
self.consensus.participants.iter().nth(pos as usize).unwrap().1.id
|
|
|
}
|
|
}
|
|
@@ -164,8 +194,8 @@ impl ValidatorState {
|
|
|
/// Proposal extends the longest notarized fork chain the node holds.
|
|
/// Proposal extends the longest notarized fork chain the node holds.
|
|
|
pub fn propose(&self) -> Result<Option<BlockProposal>> {
|
|
pub fn propose(&self) -> Result<Option<BlockProposal>> {
|
|
|
let epoch = self.current_epoch();
|
|
let epoch = self.current_epoch();
|
|
|
- let previous_hash = self.longest_notarized_chain_last_hash().unwrap();
|
|
|
|
|
- let unproposed_txs = self.unproposed_txs();
|
|
|
|
|
|
|
+ let (previous_hash, index) = self.longest_notarized_chain_last_hash().unwrap();
|
|
|
|
|
+ let unproposed_txs = self.unproposed_txs(index);
|
|
|
let metadata = Metadata::new(
|
|
let metadata = Metadata::new(
|
|
|
get_current_time(),
|
|
get_current_time(),
|
|
|
String::from("proof"),
|
|
String::from("proof"),
|
|
@@ -189,30 +219,43 @@ impl ValidatorState {
|
|
|
)))
|
|
)))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// Node retrieves all unconfiremd transactions not proposed in previous blocks.
|
|
|
|
|
- pub fn unproposed_txs(&self) -> Vec<Tx> {
|
|
|
|
|
|
|
+ /// Node retrieves all unconfirmed transactions not proposed
|
|
|
|
|
+ /// in previous blocks of provided index chain.
|
|
|
|
|
+ pub fn unproposed_txs(&self, index: i64) -> Vec<Tx> {
|
|
|
let mut unproposed_txs = self.unconfirmed_txs.clone();
|
|
let mut unproposed_txs = self.unconfirmed_txs.clone();
|
|
|
- for chain in &self.consensus.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);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // If index is -1(canonical blockchain) a new fork chain will be generated,
|
|
|
|
|
+ // therefore all unproposed transactions can be included in the proposal.
|
|
|
|
|
+ if index == -1 {
|
|
|
|
|
+ return unproposed_txs
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // We iterate 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.txs {
|
|
|
|
|
+ if let Some(pos) = unproposed_txs.iter().position(|txs| *txs == *tx) {
|
|
|
|
|
+ unproposed_txs.remove(pos);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
unproposed_txs
|
|
unproposed_txs
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// 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> {
|
|
|
|
|
|
|
+ /// Finds the longest fully notarized blockchain the node holds and returns the last block hash
|
|
|
|
|
+ /// and the chain index.
|
|
|
|
|
+ pub fn longest_notarized_chain_last_hash(&self) -> Result<(blake3::Hash, i64)> {
|
|
|
let mut longest_notarized_chain: Option<ProposalsChain> = None;
|
|
let mut longest_notarized_chain: Option<ProposalsChain> = None;
|
|
|
let mut length = 0;
|
|
let mut length = 0;
|
|
|
|
|
+ let mut index = -1;
|
|
|
if !self.consensus.proposals.is_empty() {
|
|
if !self.consensus.proposals.is_empty() {
|
|
|
- for chain in &self.consensus.proposals[1..] {
|
|
|
|
|
|
|
+ for (i, chain) in self.consensus.proposals.iter().enumerate() {
|
|
|
if chain.notarized() && chain.proposals.len() > length {
|
|
if chain.notarized() && chain.proposals.len() > length {
|
|
|
longest_notarized_chain = Some(chain.clone());
|
|
longest_notarized_chain = Some(chain.clone());
|
|
|
length = chain.proposals.len();
|
|
length = chain.proposals.len();
|
|
|
|
|
+ index = i as i64;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -222,12 +265,17 @@ impl ValidatorState {
|
|
|
None => self.blockchain.last()?.unwrap().1,
|
|
None => self.blockchain.last()?.unwrap().1,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- Ok(hash)
|
|
|
|
|
|
|
+ Ok((hash, index))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Node receives the proposed block, verifies its sender(epoch leader),
|
|
/// Node receives the proposed block, verifies its sender(epoch leader),
|
|
|
/// and proceeds with voting on it.
|
|
/// and proceeds with voting on it.
|
|
|
pub fn receive_proposal(&mut self, proposal: &BlockProposal) -> Result<Option<Vote>> {
|
|
pub fn receive_proposal(&mut self, proposal: &BlockProposal) -> Result<Option<Vote>> {
|
|
|
|
|
+ // Node hasn't started participating
|
|
|
|
|
+ if !self.participating {
|
|
|
|
|
+ return Ok(None)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
let leader = self.epoch_leader();
|
|
let leader = self.epoch_leader();
|
|
|
if leader != proposal.id {
|
|
if leader != proposal.id {
|
|
|
debug!(
|
|
debug!(
|
|
@@ -256,7 +304,6 @@ impl ValidatorState {
|
|
|
/// If proposal extends the canonical blockchain, a new fork chain is created.
|
|
/// 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.
|
|
/// 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>> {
|
|
pub fn vote(&mut self, proposal: &BlockProposal) -> Result<Option<Vote>> {
|
|
|
- self.zero_participants_check();
|
|
|
|
|
let mut proposal = proposal.clone();
|
|
let mut proposal = proposal.clone();
|
|
|
|
|
|
|
|
// Generate proposal hash
|
|
// Generate proposal hash
|
|
@@ -318,7 +365,7 @@ impl ValidatorState {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Given a proposal, node finds the index of the chain it extends.
|
|
/// Given a proposal, node finds the index of the chain it extends.
|
|
|
- pub fn find_extended_chain_index(&self, proposal: &BlockProposal) -> Result<i64> {
|
|
|
|
|
|
|
+ pub fn find_extended_chain_index(&mut self, proposal: &BlockProposal) -> Result<i64> {
|
|
|
for (index, chain) in self.consensus.proposals.iter().enumerate() {
|
|
for (index, chain) in self.consensus.proposals.iter().enumerate() {
|
|
|
let last = chain.proposals.last().unwrap();
|
|
let last = chain.proposals.last().unwrap();
|
|
|
let hash = last.hash();
|
|
let hash = last.hash();
|
|
@@ -350,6 +397,11 @@ impl ValidatorState {
|
|
|
/// Finally, we check if the notarization of the proposal can finalize parent proposals
|
|
/// Finally, we check if the notarization of the proposal can finalize parent proposals
|
|
|
/// in its chain.
|
|
/// in its chain.
|
|
|
pub fn receive_vote(&mut self, vote: &Vote) -> Result<(bool, Option<Vec<BlockInfo>>)> {
|
|
pub fn receive_vote(&mut self, vote: &Vote) -> Result<(bool, Option<Vec<BlockInfo>>)> {
|
|
|
|
|
+ // Node hasn't started participating
|
|
|
|
|
+ if !self.participating {
|
|
|
|
|
+ return Ok((false, None))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
let mut encoded_proposal = vec![];
|
|
let mut encoded_proposal = vec![];
|
|
|
let result = vote.proposal.encode(&mut encoded_proposal);
|
|
let result = vote.proposal.encode(&mut encoded_proposal);
|
|
|
match result {
|
|
match result {
|
|
@@ -366,8 +418,6 @@ impl ValidatorState {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let nodes_count = self.consensus.participants.len();
|
|
let nodes_count = self.consensus.participants.len();
|
|
|
- self.zero_participants_check();
|
|
|
|
|
-
|
|
|
|
|
// Checking that the voter can actually vote.
|
|
// Checking that the voter can actually vote.
|
|
|
match self.consensus.participants.get(&vote.id) {
|
|
match self.consensus.participants.get(&vote.id) {
|
|
|
Some(participant) => {
|
|
Some(participant) => {
|
|
@@ -440,6 +490,17 @@ impl ValidatorState {
|
|
|
Ok(None)
|
|
Ok(None)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// Note removes provided transactions vector, from unconfirmed_txs, if they exist.
|
|
|
|
|
+ pub fn remove_txs(&mut self, transactions: Vec<Tx>) -> Result<()> {
|
|
|
|
|
+ for tx in transactions {
|
|
|
|
|
+ if let Some(pos) = self.unconfirmed_txs.iter().position(|txs| *txs == tx) {
|
|
|
|
|
+ self.unconfirmed_txs.remove(pos);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/// Provided an index, node checks if chain 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
|
|
/// Consensus finalization logic: If node has observed the notarization of 3 consecutive
|
|
|
/// proposals in a fork chain, it finalizes (appends to canonical blockchain) all proposals up to the middle block.
|
|
/// proposals in a fork chain, it finalizes (appends to canonical blockchain) all proposals up to the middle block.
|
|
@@ -463,15 +524,11 @@ impl ValidatorState {
|
|
|
for proposal in &mut chain.proposals[..(consecutive - 1)] {
|
|
for proposal in &mut chain.proposals[..(consecutive - 1)] {
|
|
|
proposal.sm.finalized = true;
|
|
proposal.sm.finalized = true;
|
|
|
finalized.push(proposal.clone());
|
|
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);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
chain.proposals.drain(0..(consecutive - 1));
|
|
chain.proposals.drain(0..(consecutive - 1));
|
|
|
for proposal in &finalized {
|
|
for proposal in &finalized {
|
|
|
self.blockchain.add_by_proposal(proposal.clone())?;
|
|
self.blockchain.add_by_proposal(proposal.clone())?;
|
|
|
|
|
+ self.remove_txs(proposal.txs.clone())?;
|
|
|
to_broadcast.push(BlockInfo::new(
|
|
to_broadcast.push(BlockInfo::new(
|
|
|
proposal.st,
|
|
proposal.st,
|
|
|
proposal.sl,
|
|
proposal.sl,
|
|
@@ -518,17 +575,6 @@ impl ValidatorState {
|
|
|
true
|
|
true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// This prevent the extreme case scenario where network is initialized, but some nodes
|
|
|
|
|
- /// have not pushed the initial participants in the map.
|
|
|
|
|
- pub fn zero_participants_check(&mut self) {
|
|
|
|
|
- if self.consensus.participants.len() == 0 {
|
|
|
|
|
- for participant in &self.consensus.pending_participants {
|
|
|
|
|
- self.consensus.participants.insert(participant.id, participant.clone());
|
|
|
|
|
- }
|
|
|
|
|
- self.consensus.pending_participants = Vec::new();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/// Node refreshes participants map, to retain only the active ones.
|
|
/// Node refreshes participants map, to retain only the active ones.
|
|
|
/// Active nodes are considered those who joined or voted on previous epoch.
|
|
/// Active nodes are considered those who joined or voted on previous epoch.
|
|
|
pub fn refresh_participants(&mut self) {
|
|
pub fn refresh_participants(&mut self) {
|
|
@@ -557,6 +603,12 @@ impl ValidatorState {
|
|
|
for index in inactive {
|
|
for index in inactive {
|
|
|
self.consensus.participants.remove(&index);
|
|
self.consensus.participants.remove(&index);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if self.consensus.participants.is_empty() {
|
|
|
|
|
+ // If no nodes are active, node becomes a single node network.
|
|
|
|
|
+ let participant = Participant::new(self.id, self.current_epoch());
|
|
|
|
|
+ self.consensus.pending_participants.push(participant);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Util function to save the current consensus state to provided file path.
|
|
/// Util function to save the current consensus state to provided file path.
|