diff --git a/src/blockchain/slot_store.rs b/src/blockchain/slot_store.rs index 3b51b4f62..76718e7ce 100644 --- a/src/blockchain/slot_store.rs +++ b/src/blockchain/slot_store.rs @@ -260,4 +260,12 @@ impl SlotStoreOverlay { Ok(ret) } + + /// Fetch the last slot from the overlay, based on the `Ord` + /// implementation for `Vec`. + pub fn get_last(&self) -> Result { + let found = self.0.lock().unwrap().last(SLED_SLOT_TREE)?.unwrap(); + let slot = deserialize(&found.1)?; + Ok(slot) + } } diff --git a/src/consensus/proto/protocol_sync.rs b/src/consensus/proto/protocol_sync.rs index 57357f3d0..3a30a52aa 100644 --- a/src/consensus/proto/protocol_sync.rs +++ b/src/consensus/proto/protocol_sync.rs @@ -172,8 +172,7 @@ impl ProtocolSync { let lock = self.state.read().await; let current = lock.consensus.time_keeper.current_slot(); let participating = lock.consensus.participating; - if participating.is_some() { - let slot = participating.unwrap(); + if let Some(slot) = participating { if current >= slot { debug!( target: "consensus::protocol_sync::handle_receive_block()", @@ -308,8 +307,7 @@ impl ProtocolSync { let lock = self.state.read().await; let current = lock.consensus.time_keeper.current_slot(); let participating = lock.consensus.participating; - if participating.is_some() { - let slot = participating.unwrap(); + if let Some(slot) = participating { if current >= slot { debug!( target: "consensus::protocol_sync::handle_receive_slot()", diff --git a/src/error.rs b/src/error.rs index 1fa1bea14..086d906af 100644 --- a/src/error.rs +++ b/src/error.rs @@ -260,6 +260,9 @@ pub enum Error { #[error("Proposal contains missmatched hashes")] ProposalHashesMissmatchError, + #[error("Proposal contains unknown slots")] + ProposalContainsUnknownSlots, + #[error("Proposal contains missmatched headers")] ProposalHeadersMissmatchError, diff --git a/src/validator/consensus/mod.rs b/src/validator/consensus/mod.rs index db91fb14a..8e9f8f7b1 100644 --- a/src/validator/consensus/mod.rs +++ b/src/validator/consensus/mod.rs @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +use darkfi_sdk::blockchain::{PidOutput, PreviousSlot, Slot}; use darkfi_serial::{SerialDecodable, SerialEncodable}; use log::{error, warn}; use crate::{ blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr}, util::time::TimeKeeper, - validator::verify_block, + validator::{consensus::pid::slot_pid_output, verify_block}, Error, Result, }; @@ -64,6 +65,45 @@ impl Consensus { } } + /// Generate current hot/live slot for all current forks. + pub fn generate_slot(&mut self) -> Result<()> { + // Grab current slot id + let id = self.time_keeper.current_slot(); + + // If no forks exist, create a new one as a basis to extend + if self.forks.is_empty() { + self.forks.push(Fork::new(&self.blockchain)?); + } + + // Grab previous slot information + let (producers, last_hashes, second_to_last_hashes) = self.previous_slot_info(id - 1)?; + + for fork in self.forks.iter_mut() { + fork.generate_slot(id, producers, &last_hashes, &second_to_last_hashes)?; + } + + Ok(()) + } + + /// Retrieve previous slot producers, last proposal hashes, + /// and their second to last hashes, from all current forks. + fn previous_slot_info(&self, slot: u64) -> Result<(u64, Vec, Vec)> { + let mut producers = 0; + let mut last_hashes = vec![]; + let mut second_to_last_hashes = vec![]; + + for fork in &self.forks { + let last_proposal = fork.last_proposal()?; + if last_proposal.block.header.slot == slot { + producers += 1; + } + last_hashes.push(last_proposal.hash); + second_to_last_hashes.push(last_proposal.block.header.previous); + } + + Ok((producers, last_hashes, second_to_last_hashes)) + } + /// Given a proposal, the node verifys it and finds which fork it extends. /// If the proposal extends the canonical blockchain, a new fork chain is created. /// A proposal is considered valid when the following rules apply: @@ -71,7 +111,9 @@ impl Consensus { /// 2. Proposal refers to current slot /// 3. Proposal hash matches the actual block one /// 4. Block transactions don't exceed set limit - /// 5. Block is valid + /// 5. If proposal extends a known fork, verify block slots + /// correspond to the fork hot/live ones + /// 6. Block is valid /// Additional validity rules can be applied. pub async fn append_proposal(&mut self, proposal: &Proposal) -> Result<()> { // Generate a time keeper for current slot @@ -115,13 +157,28 @@ impl Consensus { // Check if proposal extends any existing forks let (mut fork, index) = self.find_extended_fork(proposal).await?; + // Verify block slots correspond to the forks' hot/live ones (5) + if !fork.slots.is_empty() && fork.slots != proposal.block.slots { + return Err(Error::ProposalContainsUnknownSlots) + } + + // Insert last block slot so transactions can be validated against. + // Rest (empty) slots will be inserted along with the block. + // Since this fork uses an overlay clone, original overlay is not affected. + fork.overlay.lock().unwrap().slots.insert(&[proposal + .block + .slots + .last() + .unwrap() + .clone()])?; + // Grab overlay last block let previous = fork.overlay.lock().unwrap().last_block()?; // Retrieve expected reward let expected_reward = next_block_reward(); - // Verify proposal block (5) + // Verify proposal block (6) if verify_block( &fork.overlay, &time_keeper, @@ -141,6 +198,7 @@ impl Consensus { // If a fork index was found, replace forks with the mutated one, // otherwise push the new fork. fork.proposals.push(proposal.hash); + fork.slots = vec![]; match index { Some(i) => { self.forks[i] = fork; @@ -273,6 +331,8 @@ pub struct Fork { pub overlay: BlockchainOverlayPtr, /// Fork proposal hashes sequence pub proposals: Vec, + /// Hot/live slots + pub slots: Vec, /// Valid pending transaction hashes pub mempool: Vec, } @@ -280,7 +340,57 @@ pub struct Fork { impl Fork { pub fn new(blockchain: &Blockchain) -> Result { let overlay = BlockchainOverlay::new(blockchain)?; - Ok(Self { overlay, proposals: vec![], mempool: vec![] }) + Ok(Self { overlay, proposals: vec![], slots: vec![], mempool: vec![] }) + } + + /// Auxiliary function to retrieve last proposal + pub fn last_proposal(&self) -> Result { + let block = if self.proposals.is_empty() { + self.overlay.lock().unwrap().last_block()? + } else { + self.overlay.lock().unwrap().get_blocks_by_hash(&[*self.proposals.last().unwrap()])?[0] + .clone() + }; + + Ok(Proposal::new(block)) + } + + /// Generate current hot/live slot + pub fn generate_slot( + &mut self, + id: u64, + producers: u64, + last_hashes: &[blake3::Hash], + second_to_last_hashes: &[blake3::Hash], + ) -> Result<()> { + // Grab last known fork slot + let previous_slot = if self.slots.is_empty() { + self.overlay.lock().unwrap().slots.get_last()? + } else { + self.slots.last().unwrap().clone() + }; + + // Generate previous slot information + let previous = PreviousSlot::new( + producers, + last_hashes.to_vec(), + second_to_last_hashes.to_vec(), + previous_slot.pid.error, + ); + + // Generate PID controller output + let (f, error, sigma1, sigma2) = slot_pid_output(&previous_slot, producers); + let pid = PidOutput::new(f, error, sigma1, sigma2); + + // Each slot starts as an empty slot(not reward) when generated, carrying + // last eta + let last_eta = previous_slot.last_eta; + let total_tokens = previous_slot.total_tokens + previous_slot.reward; + let reward = 0; + let slot = Slot::new(id, previous, pid, last_eta, total_tokens, reward); + self.slots.push(slot); + + Ok(()) } /// Auxiliary function to create a full clone using BlockchainOverlay::full_clone. @@ -289,9 +399,10 @@ impl Fork { pub fn full_clone(&self) -> Result { let overlay = self.overlay.lock().unwrap().full_clone()?; let proposals = self.proposals.clone(); + let slots = self.slots.clone(); let mempool = self.mempool.clone(); - Ok(Self { overlay, proposals, mempool }) + Ok(Self { overlay, proposals, slots, mempool }) } }