Forráskód Böngészése

consensus: fixes and additions

create coins after sync, eta sync fixed, bootstrap logic added so nodes can start proposing immediately, node wait genesis timestamp if in future
aggstam 3 éve
szülő
commit
c8165d5320

+ 1 - 1
src/consensus/constants.rs

@@ -31,7 +31,7 @@ lazy_static! {
     pub static ref TESTNET_GENESIS_HASH_BYTES: blake3::Hash = blake3::hash(b"darkfi_testnet");
 
     /// Genesis timestamp for the testnet chain
-    pub static ref TESTNET_GENESIS_TIMESTAMP: Timestamp = Timestamp(1670506503);
+    pub static ref TESTNET_GENESIS_TIMESTAMP: Timestamp = Timestamp(1671329520);
 
     // Commonly used Float10
     pub static ref  FLOAT10_ZERO: Float10 = Float10::from_str_native("0").unwrap().with_precision(RADIX_BITS).value();

+ 6 - 2
src/consensus/proto/protocol_sync_consensus.rs

@@ -82,6 +82,7 @@ impl ProtocolSyncConsensus {
 
             // Extra validations can be added here.
             let lock = self.state.read().await;
+            let bootstrap_slot = lock.consensus.bootstrap_slot;
             let offset = lock.consensus.offset;
             let mut forks = vec![];
             for fork in &lock.consensus.forks {
@@ -92,6 +93,7 @@ impl ProtocolSyncConsensus {
             let leaders_history = lock.consensus.leaders_history.clone();
             let nullifiers = lock.consensus.nullifiers.clone();
             let response = ConsensusResponse {
+                bootstrap_slot,
                 offset,
                 forks,
                 unconfirmed_txs,
@@ -122,8 +124,10 @@ impl ProtocolSyncConsensus {
             );
 
             // Extra validations can be added here.
-            let is_empty = self.state.read().await.consensus.slot_checkpoints.is_empty();
-            let response = ConsensusSlotCheckpointsResponse { is_empty };
+            let lock = self.state.read().await;
+            let bootstrap_slot = lock.consensus.bootstrap_slot;
+            let is_empty = lock.consensus.slot_checkpoints.is_empty();
+            let response = ConsensusSlotCheckpointsResponse { bootstrap_slot, is_empty };
             if let Err(e) = self.channel.send(response).await {
                 error!("ProtocolSyncConsensus::handle_receive_slot_checkpoints_request() channel send fail: {}", e);
             };

+ 31 - 12
src/consensus/state.rs

@@ -46,6 +46,8 @@ pub struct ConsensusState {
     pub genesis_ts: Timestamp,
     /// Genesis block hash
     pub genesis_block: blake3::Hash,
+    /// Slot the network was bootstrapped
+    pub bootstrap_slot: u64,
     /// Participating start slot
     pub participating: Option<u64>,
     /// Node is able to propose proposals
@@ -84,6 +86,7 @@ impl ConsensusState {
             blockchain,
             genesis_ts,
             genesis_block,
+            bootstrap_slot: 0,
             participating: None,
             proposing: false,
             checked_finalization: 0,
@@ -184,28 +187,40 @@ impl ConsensusState {
         self.slot_checkpoints.push(checkpoint);
     }
 
-    /// Check if new epoch has started, to create new epoch coins.
-    /// Returns flag to signify if epoch has changed and vector of
-    /// new epoch competing coins.
+    // Initialize node lead coins and set current epoch and eta.
+    pub async fn init_coins(&mut self) -> Result<()> {
+        self.epoch = self.current_epoch();
+        if self.slot_checkpoints.is_empty() {
+            self.epoch_eta = self.get_eta();
+            // Create slot checkpoint if not on genesis slot (already in db)
+            if self.current_slot() != 0 {
+                let (sigma1, sigma2) = self.sigmas();
+                self.generate_slot_checkpoint(sigma1, sigma2);
+            }
+        } else {
+            let last_slot_checkpoint = self.slot_checkpoints.last().unwrap();
+            self.epoch_eta = last_slot_checkpoint.eta;
+        };
+        self.coins = self.create_coins(self.epoch_eta).await?;
+        self.update_forks_checkpoints();
+
+        Ok(())
+    }
+
+    /// Check if new epoch has started and generate slot checkpoint.
+    /// Returns flag to signify if epoch has changed.
     pub async fn epoch_changed(
         &mut self,
         sigma1: pallas::Base,
         sigma2: pallas::Base,
     ) -> Result<bool> {
+        self.generate_slot_checkpoint(sigma1, sigma2);
         let epoch = self.current_epoch();
         if epoch <= self.epoch {
-            self.generate_slot_checkpoint(sigma1, sigma2);
             return Ok(false)
         }
-
-        let eta = self.get_eta();
-        if self.coins.is_empty() {
-            self.coins = self.create_coins(eta).await?;
-            self.update_forks_checkpoints();
-        }
         self.epoch = epoch;
-        self.epoch_eta = eta;
-        self.generate_slot_checkpoint(sigma1, sigma2);
+        self.epoch_eta = self.get_eta();
 
         Ok(true)
     }
@@ -683,6 +698,8 @@ impl net::Message for ConsensusRequest {
 /// Auxiliary structure used for consensus syncing.
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct ConsensusResponse {
+    /// Slot the network was bootstrapped
+    pub bootstrap_slot: u64,
     /// Slots offset since genesis,
     pub offset: Option<u64>,
     /// Hot/live data used by the consensus algorithm
@@ -716,6 +733,8 @@ impl net::Message for ConsensusSlotCheckpointsRequest {
 /// Auxiliary structure used for consensus syncing.
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct ConsensusSlotCheckpointsResponse {
+    /// Node known bootstrap slot
+    pub bootstrap_slot: u64,
     /// Node has hot/live slot checkpoints
     pub is_empty: bool,
 }

+ 13 - 1
src/consensus/task/consensus_sync.rs

@@ -42,6 +42,9 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
     // called 'exact_size_is_empty'.
     if values.len() == 0 {
         warn!("Node is not connected to other nodes");
+        let mut lock = state.write().await;
+        lock.consensus.bootstrap_slot = lock.consensus.current_slot();
+        lock.consensus.init_coins().await?;
         info!("Consensus state synced!");
         return Ok(true)
     }
@@ -60,6 +63,10 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
 
         // Node checks response
         let response = response_sub.receive().await?;
+        if response.bootstrap_slot == state.read().await.consensus.current_slot() {
+            warn!("Network was just bootstraped, checking rest nodes");
+            continue
+        }
         if response.is_empty {
             warn!("Node has not seen any slot checkpoints, retrying...");
             continue
@@ -76,7 +83,10 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
     // If no peer knows about any slot checkpoints, that means that the network was bootstrapped or restarted
     // and no node has started consensus.
     if peer.is_none() {
-        warn!("No node that has seen any slot checkpoints was found.");
+        warn!("No node that has seen any slot checkpoints was found, or network was just boostrapped.");
+        let mut lock = state.write().await;
+        lock.consensus.bootstrap_slot = lock.consensus.current_slot();
+        lock.consensus.init_coins().await?;
         info!("Consensus state synced!");
         return Ok(true)
     }
@@ -112,11 +122,13 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
     for fork in &response.forks {
         forks.push(fork.clone().into());
     }
+    lock.consensus.bootstrap_slot = response.bootstrap_slot;
     lock.consensus.forks = forks;
     lock.unconfirmed_txs = response.unconfirmed_txs.clone();
     lock.consensus.slot_checkpoints = response.slot_checkpoints.clone();
     lock.consensus.leaders_history = response.leaders_history.clone();
     lock.consensus.nullifiers = response.nullifiers.clone();
+    lock.consensus.init_coins().await?;
 
     info!("Consensus state synced!");
     Ok(false)

+ 12 - 2
src/consensus/task/proposal.rs

@@ -25,7 +25,7 @@ use super::consensus_sync_task;
 use crate::{
     consensus::{constants, ValidatorStatePtr},
     net::P2pPtr,
-    util::async_util::sleep,
+    util::{async_util::sleep, time::Timestamp},
 };
 
 /// async task used for participating in the consensus protocol
@@ -35,6 +35,16 @@ pub async fn proposal_task(
     state: ValidatorStatePtr,
     ex: Arc<smol::Executor<'_>>,
 ) {
+    // Check if network is configured to start in the future
+    // NOTE: This should always be true when bootstrapping or restarting a network.
+    let current_ts = Timestamp::current_time();
+    let genesis_ts = state.read().await.consensus.genesis_ts;
+    if current_ts < genesis_ts {
+        let diff = genesis_ts.0 - current_ts.0;
+        info!("consensus: Waiting for network bootstrap: {} seconds", diff);
+        sleep(diff as u64).await;
+    }
+
     let mut retries = 0;
     // Sync loop
     loop {
@@ -100,7 +110,7 @@ async fn consensus_loop(
     // Note: when a node can start produce proposals is only enforced in code,
     // where we verify if the hardware can keep up with the consensus, by
     // counting how many consecutive slots node successfully listened and process
-    // everything. Aditionally, we check each proposer coin creation slot to be
+    // everything. Additionally, we check each proposer coin creation slot to be
     // greater than an epoch length. Later, this will be enforced via contract,
     // where it will be explicit when a node can produce proposals,
     // and after which slot they can be considered as valid.

+ 4 - 1
src/consensus/validator.rs

@@ -380,10 +380,13 @@ impl ValidatorState {
         }
 
         // Verify that proposer can produce proposals.
+        // Nodes that created coins in the bootstrap slot can propose immediately.
         // NOTE: Later, this will be enforced via contract, where it will be explicit
         // when a node can produce proposals, and after which slot they can be considered as valid.
         let elapsed_slots = current - lf.coin_slot;
-        if elapsed_slots <= (constants::EPOCH_LENGTH as u64) {
+        if lf.coin_slot != self.consensus.bootstrap_slot &&
+            elapsed_slots <= (constants::EPOCH_LENGTH as u64)
+        {
             warn!(
                 "receive_proposal(): Proposer {} is not eligible to produce proposals",
                 lf.public_key