Răsfoiți Sursa

validator: current slot generation added

aggstam 3 ani în urmă
părinte
comite
67d5ed7d28

+ 8 - 0
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<u8>`.
+    pub fn get_last(&self) -> Result<Slot> {
+        let found = self.0.lock().unwrap().last(SLED_SLOT_TREE)?.unwrap();
+        let slot = deserialize(&found.1)?;
+        Ok(slot)
+    }
 }

+ 2 - 4
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()",

+ 3 - 0
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,
 

+ 116 - 5
src/validator/consensus/mod.rs

@@ -16,13 +16,14 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+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<blake3::Hash>, Vec<blake3::Hash>)> {
+        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<blake3::Hash>,
+    /// Hot/live slots
+    pub slots: Vec<Slot>,
     /// Valid pending transaction hashes
     pub mempool: Vec<blake3::Hash>,
 }
@@ -280,7 +340,57 @@ pub struct Fork {
 impl Fork {
     pub fn new(blockchain: &Blockchain) -> Result<Self> {
         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<Proposal> {
+        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<Self> {
         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 })
     }
 }