diff --git a/consensus/src/config.rs b/consensus/src/config.rs index 182b1b7742..552ae196e5 100644 --- a/consensus/src/config.rs +++ b/consensus/src/config.rs @@ -8,10 +8,10 @@ use std::env; use std::sync::LazyLock; use std::time::Duration; -use node_data::message::{MESSAGE_MAX_FAILED_ITERATIONS, MESSAGE_MAX_ITER}; +use node_data::message::MESSAGE_MAX_FAILED_ITERATIONS; /// Maximum number of iterations Consensus runs per a single round. -pub const CONSENSUS_MAX_ITER: u8 = MESSAGE_MAX_ITER; +pub const CONSENSUS_MAX_ITER: u8 = 50; /// Total credits of steps committees pub const PROPOSAL_COMMITTEE_CREDITS: usize = 1; @@ -26,11 +26,25 @@ pub const MAX_BLOCK_SIZE: usize = 1_024 * 1_024; /// Emergency mode is enabled after 16 iterations pub const EMERGENCY_MODE_ITERATION_THRESHOLD: u8 = 16; +pub const EMERGENCY_BLOCK_ITERATION: u8 = u8::MAX; pub const MIN_STEP_TIMEOUT: Duration = Duration::from_secs(7); pub const MAX_STEP_TIMEOUT: Duration = Duration::from_secs(40); pub const TIMEOUT_INCREASE: Duration = Duration::from_secs(2); +// MIN_EMERGENCY_BLOCK_TIME is the minimum time that should elapse since the +// previous block's timestamp for an Emergency Block to be valid. This value +// should be enough to allow other candidates in the same round to be generated +// and accepted. +// The value is obtained by accounting for all possible iterations in a round, +// plus the iteration of the previous block. This is necessary because the +// reference timestamp is the one of the Candidate creation, which is at the +// beginning of the iteration +const MAX_ITER_TIMEOUT: u64 = MAX_STEP_TIMEOUT.as_secs() * 3; +const CONSENSUS_MAX_ITER_EXT: u64 = CONSENSUS_MAX_ITER as u64 + 1; +pub const MIN_EMERGENCY_BLOCK_TIME: Duration = + Duration::from_secs(MAX_ITER_TIMEOUT * CONSENSUS_MAX_ITER_EXT); + mod default { pub const MINIMUM_BLOCK_TIME: u64 = 10; } @@ -84,6 +98,10 @@ pub fn is_emergency_iter(iter: u8) -> bool { iter >= EMERGENCY_MODE_ITERATION_THRESHOLD } +pub fn is_emergency_block(iter: u8) -> bool { + iter == EMERGENCY_BLOCK_ITERATION +} + /// Returns if the next iteration generator needs to be excluded pub fn exclude_next_generator(iter: u8) -> bool { iter < CONSENSUS_MAX_ITER - 1 diff --git a/consensus/src/proposal/block_generator.rs b/consensus/src/proposal/block_generator.rs index 3593d5d4eb..4d1a6f36d7 100644 --- a/consensus/src/proposal/block_generator.rs +++ b/consensus/src/proposal/block_generator.rs @@ -18,7 +18,7 @@ use tracing::{debug, info}; use crate::commons::RoundUpdate; use crate::config::{MAX_BLOCK_SIZE, MAX_NUMBER_OF_FAULTS, MINIMUM_BLOCK_TIME}; use crate::merkle::merkle_root; -use crate::operations::{CallParams, Operations, Voter}; +use crate::operations::{CallParams, Operations}; pub struct Generator { executor: Arc, @@ -35,36 +35,10 @@ impl Generator { iteration: u8, failed_iterations: IterationsInfo, ) -> Result { - // Sign seed - let seed: [u8; 48] = ru - .secret_key - .sign_multisig(ru.pubkey_bls.inner(), &ru.seed().inner()[..]) - .to_bytes(); - - let start = Instant::now(); - let candidate = self - .generate_block( - ru, - Seed::from(seed), - iteration, - failed_iterations, - &[], - ru.att_voters(), - ) + .generate_block(ru, iteration, failed_iterations, &[]) .await?; - info!( - event = "Candidate generated", - hash = &to_str(&candidate.header().hash), - round = candidate.header().height, - iter = candidate.header().iteration, - prev_block = &to_str(&candidate.header().prev_block_hash), - gas_limit = candidate.header().gas_limit, - state_hash = &to_str(&candidate.header().state_hash), - dur = format!("{:?}ms", start.elapsed().as_millis()), - ); - let mut candidate_msg = Candidate { candidate }; candidate_msg.sign(&ru.secret_key, ru.pubkey_bls.inner()); @@ -74,15 +48,22 @@ impl Generator { Ok(candidate_msg.into()) } - async fn generate_block( + pub async fn generate_block( &self, ru: &RoundUpdate, - seed: Seed, iteration: u8, failed_iterations: IterationsInfo, faults: &[Fault], - voters: &[Voter], ) -> Result { + let start = Instant::now(); + + // Sign seed + let seed_sig: [u8; 48] = ru + .secret_key + .sign_multisig(ru.pubkey_bls.inner(), &ru.seed().inner()[..]) + .to_bytes(); + let seed = Seed::from(seed_sig); + // Limit number of faults in the block let faults = if faults.len() > MAX_NUMBER_OF_FAULTS { &faults[..MAX_NUMBER_OF_FAULTS] @@ -128,6 +109,7 @@ impl Generator { // We know for sure that this operation cannot underflow let max_txs_bytes = MAX_BLOCK_SIZE - header_size - faults_size; + let voters = ru.att_voters(); let call_params = CallParams { round: ru.round, @@ -154,10 +136,23 @@ impl Generator { get_current_timestamp(), ); - Block::new(blk_header, txs, faults.to_vec()).map_err(|e| { - crate::errors::OperationError::InvalidEST(anyhow::anyhow!( - "Cannot create new block {e}", - )) - }) + match Block::new(blk_header, txs, faults.to_vec()) { + Ok(blk) => { + info!( + event = "Block generated", + round = blk.header().height, + iter = blk.header().iteration, + prev_block = &to_str(&blk.header().prev_block_hash), + hash = &to_str(&blk.header().hash), + gas_limit = blk.header().gas_limit, + state_hash = &to_str(&blk.header().state_hash), + dur = format!("{:?}ms", start.elapsed().as_millis()), + ); + Ok(blk) + } + Err(e) => Err(crate::errors::OperationError::InvalidEST( + anyhow::anyhow!("Cannot create new block {e}",), + )), + } } } diff --git a/node-data/src/ledger/header.rs b/node-data/src/ledger/header.rs index 0a036ea743..53c4a0c4cb 100644 --- a/node-data/src/ledger/header.rs +++ b/node-data/src/ledger/header.rs @@ -7,7 +7,7 @@ use serde::Serialize; use super::*; -use crate::message::{ConsensusHeader, MESSAGE_MAX_ITER}; +use crate::message::ConsensusHeader; pub type Seed = Signature; #[derive(Eq, PartialEq, Clone, Serialize)] @@ -154,14 +154,6 @@ impl Header { let gas_limit = Self::read_u64_le(r)?; let iteration = Self::read_u8(r)?; - // Iteration is 0-based - if iteration >= MESSAGE_MAX_ITER { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid iteration {iteration})"), - )); - } - let prev_block_cert = Attestation::read(r)?; let failed_iterations = IterationsInfo::read(r)?; diff --git a/node-data/src/message.rs b/node-data/src/message.rs index dc4cbc1e62..e1d81e086d 100644 --- a/node-data/src/message.rs +++ b/node-data/src/message.rs @@ -28,9 +28,6 @@ use crate::{bls, ledger, Serializable, StepName}; pub const TOPIC_FIELD_POS: usize = 1 + 2 + 2; pub const PROTOCOL_VERSION: Version = Version(1, 0, 2); -/// Max value for iteration. -pub const MESSAGE_MAX_ITER: u8 = 50; - /// Block version pub const BLOCK_HEADER_VERSION: u8 = 1; @@ -374,14 +371,6 @@ impl Serializable for ConsensusHeader { let round = Self::read_u64_le(r)?; let iteration = Self::read_u8(r)?; - // Iteration is 0-based - if iteration >= MESSAGE_MAX_ITER { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid iteration {iteration})"), - )); - } - Ok(ConsensusHeader { prev_block_hash, round, diff --git a/node/Cargo.toml b/node/Cargo.toml index d97072fbae..f72fac47d5 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -26,6 +26,7 @@ dusk-bytes = { workspace = true } node-data = { workspace = true } dusk-core = { workspace = true } smallvec = { workspace = true } +rusk-recovery = { workspace = true, features = ["state"] } serde = { workspace = true } humantime-serde = { workspace = true } diff --git a/node/src/chain.rs b/node/src/chain.rs index f092ccf2d7..91fc712759 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -19,6 +19,7 @@ use std::time::Duration; use anyhow::Result; use async_trait::async_trait; +use dusk_consensus::config::is_emergency_block; use dusk_consensus::errors::ConsensusError; pub use header_validation::verify_att; use node_data::events::Event; @@ -171,8 +172,23 @@ impl // Handle a block that originates from a network peer. // By disabling block broadcast, a block may be received // from a peer only after explicit request (on demand). - match fsm.on_block_event(*blk, msg.metadata).await { - Ok(_) => {} + match fsm.on_block_event(*blk, msg.metadata.clone()).await { + Ok(res) => { + if let Some(accepted_blk) = res { + // Repropagate Emergency Blocks + // We already know it's valid because we accepted it + if is_emergency_block(accepted_blk.header().iteration){ + // We build a new `msg` to avoid cloning `blk` when + // passing it to `on_block_event`. + // We copy the metadata to keep the original ray_id. + let mut eb_msg = Message::from(accepted_blk); + eb_msg.metadata = msg.metadata; + if let Err(e) = network.read().await.broadcast(&eb_msg).await { + warn!("Unable to re-broadcast Emergency Block: {e}"); + } + } + } + } Err(err) => { error!(event = "fsm::on_event failed", src = "wire", err = ?err); } diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 2a65a4838d..e0482cdb3d 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -13,7 +13,8 @@ use std::{cmp, env}; use anyhow::{anyhow, Result}; use dusk_consensus::commons::TimeoutSet; use dusk_consensus::config::{ - MAX_ROUND_DISTANCE, MAX_STEP_TIMEOUT, MIN_STEP_TIMEOUT, + is_emergency_block, CONSENSUS_MAX_ITER, MAX_ROUND_DISTANCE, + MAX_STEP_TIMEOUT, MIN_STEP_TIMEOUT, }; use dusk_consensus::errors::{ConsensusError, HeaderError}; use dusk_consensus::operations::Voter; @@ -44,7 +45,7 @@ use crate::database::rocksdb::{ MD_STATE_ROOT_KEY, }; use crate::database::{self, ConsensusStorage, Ledger, Mempool, Metadata}; -use crate::{vm, Message, Network}; +use crate::{vm, Message, Network, DUSK_CONSENSUS_KEY}; const CANDIDATES_DELETION_OFFSET: u64 = 10; @@ -627,7 +628,12 @@ impl Acceptor { if iteration == 0 { return; } - for iter in 0..iteration { + + // In case of Emergency Block, which iteration number is u8::MAX, we + // count failed iterations up to CONSENSUS_MAX_ITER + let last_iter = cmp::min(iteration, CONSENSUS_MAX_ITER); + + for iter in 0..last_iter { let generator = provisioners_list.get_generator(iter, seed, round).to_bs58(); warn!(event = "missed iteration", height = round, iter, generator); @@ -1355,13 +1361,29 @@ pub(crate) async fn verify_block_header( provisioners: &ContextProvisioners, header: &ledger::Header, ) -> Result<(u8, Vec, Vec), HeaderError> { + // Set the expected generator to the one extracted by Deterministic + // Sortition, or, in case of Emergency Block, to the Dusk Consensus Key + let (expected_generator, check_att) = + if is_emergency_block(header.iteration) { + let dusk_key = PublicKey::new(*DUSK_CONSENSUS_KEY); + let dusk_key_bytes = dusk_key.bytes(); + + // We disable the Attestation check since it's not needed to accept + // an Emergency Block + (*dusk_key_bytes, false) + } else { + let iter_generator = provisioners.current().get_generator( + header.iteration, + prev_header.seed, + header.height, + ); + + (iter_generator, true) + }; + + // Verify header validity let validator = Validator::new(db, prev_header, provisioners); - let expected_generator = provisioners.current().get_generator( - header.iteration, - prev_header.seed, - header.height, - ); validator - .execute_checks(header, &expected_generator, false) + .execute_checks(header, &expected_generator, check_att) .await } diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index f4b8fff250..9dfef1b135 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -298,7 +298,7 @@ impl Operations for Executor { ); validator - .execute_checks(candidate_header, expected_generator, true) + .execute_checks(candidate_header, expected_generator, false) .await } diff --git a/node/src/chain/fsm.rs b/node/src/chain/fsm.rs index 41041adf80..307a9a3b0d 100644 --- a/node/src/chain/fsm.rs +++ b/node/src/chain/fsm.rs @@ -13,6 +13,7 @@ use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; +use dusk_consensus::config::is_emergency_block; use metrics::counter; use node_data::ledger::{to_str, Attestation, Block}; use node_data::message::payload::{Inv, Quorum, RatificationResult, Vote}; @@ -28,6 +29,8 @@ use super::acceptor::{Acceptor, RevertTarget}; use crate::database::{ConsensusStorage, Ledger}; use crate::{database, vm, Network}; +use anyhow::{anyhow, Result}; + const DEFAULT_ATT_CACHE_EXPIRY: Duration = Duration::from_secs(60); /// Maximum number of hops between the requester and the node that contains the @@ -168,7 +171,7 @@ impl SimpleFSM { /// If the block is accepted, it returns the block itself pub async fn on_block_event( &mut self, - blk: Block, + mut blk: Block, metadata: Option, ) -> anyhow::Result> { let block_hash = &blk.header().hash; @@ -187,134 +190,136 @@ impl SimpleFSM { return Ok(None); } - let blk = self.attach_att_if_needed(blk); - if let Some(blk) = blk.as_ref() { - let fsm_res = match &mut self.curr { - State::InSync(ref mut curr) => { - if let Some(presync) = - curr.on_block_event(blk, metadata).await? - { - // Transition from InSync to OutOfSync state - curr.on_exiting().await; - - // Enter new state - let mut next = OutOfSyncImpl::new( - self.acc.clone(), - self.network.clone(), - ) - .await; - next.on_entering(presync).await; - self.curr = State::OutOfSync(next); - } - anyhow::Ok(()) - } - State::OutOfSync(ref mut curr) => { - if curr.on_block_event(blk).await? { - // Transition from OutOfSync to InSync state - curr.on_exiting().await; - - // Enter new state - let mut next = InSyncImpl::new( - self.acc.clone(), - self.network.clone(), - self.blacklisted_blocks.clone(), - ); - next.on_entering(blk).await.map_err(|e| { - error!("Unable to enter in_sync state: {e}"); - e - })?; - self.curr = State::InSync(next); - } - anyhow::Ok(()) + // Try attach the Attestation, if necessary + // If can't find the Attestation, the block is discarded + // unless it's an Emergency Blocks, which have no Attestation + if !Self::is_block_attested(&blk) + && !is_emergency_block(blk.header().iteration) + { + if let Err(err) = self.attach_blk_att(&mut blk) { + warn!(event = "block discarded", ?err); + return Ok(None); + } + } + + let fsm_res = match &mut self.curr { + State::InSync(ref mut curr) => { + if let Some(presync) = + curr.on_block_event(&blk, metadata).await? + { + // Transition from InSync to OutOfSync state + curr.on_exiting().await; + + // Enter new state + let mut next = OutOfSyncImpl::new( + self.acc.clone(), + self.network.clone(), + ) + .await; + next.on_entering(presync).await; + self.curr = State::OutOfSync(next); } - }; + anyhow::Ok(()) + } + State::OutOfSync(ref mut curr) => { + if curr.on_block_event(&blk).await? { + // Transition from OutOfSync to InSync state + curr.on_exiting().await; - // Try to detect a stalled chain - // Generally speaking, if a node is receiving future blocks from the - // network but it cannot accept a new block for long time, then - // it might be a sign of a getting stalled on non-main branch. - - let res = self.stalled_sm.on_block_received(blk).await.clone(); - match res { - stalled::State::StalledOnFork( - local_hash_at_fork, - remote_blk, - ) => { - info!( - event = "stalled on fork", - local_hash = to_str(&local_hash_at_fork), - remote_hash = to_str(&remote_blk.header().hash), - remote_height = remote_blk.header().height, + // Enter new state + let mut next = InSyncImpl::new( + self.acc.clone(), + self.network.clone(), + self.blacklisted_blocks.clone(), ); - let mut acc = self.acc.write().await; + next.on_entering(&blk).await.map_err(|e| { + error!("Unable to enter in_sync state: {e}"); + e + })?; + self.curr = State::InSync(next); + } + anyhow::Ok(()) + } + }; - let prev_local_state_root = - acc.db.read().await.view(|t| { - let local_blk = t - .block_header(&local_hash_at_fork)? - .expect("local hash should exist"); + // Try to detect a stalled chain + // Generally speaking, if a node is receiving future blocks from the + // network but it cannot accept a new block for long time, then + // it might be a sign of a getting stalled on non-main branch. - let prev_blk = t - .block_header(&local_blk.prev_block_hash)? - .expect("prev block hash should exist"); + let res = self.stalled_sm.on_block_received(&blk).await.clone(); + match res { + stalled::State::StalledOnFork(local_hash_at_fork, remote_blk) => { + info!( + event = "stalled on fork", + local_hash = to_str(&local_hash_at_fork), + remote_hash = to_str(&remote_blk.header().hash), + remote_height = remote_blk.header().height, + ); + let mut acc = self.acc.write().await; + + let prev_local_state_root = acc.db.read().await.view(|t| { + let local_blk = t + .block_header(&local_hash_at_fork)? + .expect("local hash should exist"); + + let prev_blk = t + .block_header(&local_blk.prev_block_hash)? + .expect("prev block hash should exist"); + + anyhow::Ok(prev_blk.state_hash) + })?; + + match acc + .try_revert(RevertTarget::Commit(prev_local_state_root)) + .await + { + Ok(_) => { + counter!("dusk_revert_count").increment(1); + info!(event = "reverted to last finalized"); + + info!( + event = "recovery block", + height = remote_blk.header().height, + hash = to_str(&remote_blk.header().hash), + ); - anyhow::Ok(prev_blk.state_hash) - })?; + acc.try_accept_block(&remote_blk, true).await?; - match acc - .try_revert(RevertTarget::Commit(prev_local_state_root)) - .await - { - Ok(_) => { - counter!("dusk_revert_count").increment(1); - info!(event = "reverted to last finalized"); + // Black list the block hash to avoid accepting it + // again due to fallback execution + self.blacklisted_blocks + .write() + .await + .insert(local_hash_at_fork); + // Try to reset the stalled chain FSM to `running` + // state + if let Err(err) = + self.stalled_sm.reset(remote_blk.header()) + { info!( - event = "recovery block", - height = remote_blk.header().height, - hash = to_str(&remote_blk.header().hash), - ); - - acc.try_accept_block(&remote_blk, true).await?; - - // Black list the block hash to avoid accepting it - // again due to fallback execution - self.blacklisted_blocks - .write() - .await - .insert(local_hash_at_fork); - - // Try to reset the stalled chain FSM to `running` - // state - if let Err(err) = - self.stalled_sm.reset(remote_blk.header()) - { - info!( - event = "revert failed", - err = format!("{:?}", err) - ); - } - } - Err(e) => { - error!( event = "revert failed", - err = format!("{:?}", e) + err = format!("{err:?}") ); - return Ok(None); } } + Err(e) => { + error!(event = "revert failed", err = format!("{e:?}")); + return Ok(None); + } } - stalled::State::Stalled(_) => { - self.blacklisted_blocks.write().await.clear(); - } - _ => {} } - - // Ensure that an error in FSM does not affect the stalled_sm - fsm_res?; + stalled::State::Stalled(_) => { + self.blacklisted_blocks.write().await.clear(); + } + _ => {} } - Ok(blk) + // Ensure that an error in FSM does not affect the stalled_sm + fsm_res?; + + Ok(Some(blk)) } async fn flood_request_block(&mut self, hash: [u8; 32], att: Attestation) { @@ -465,34 +470,33 @@ impl SimpleFSM { Ok(()) } + // Checks if a block has an Attestation + fn is_block_attested(blk: &Block) -> bool { + blk.header().att != Attestation::default() + } + /// Try to attach the attestation to a block that misses it /// - /// Return None if it's not able to attach the attestation - fn attach_att_if_needed(&mut self, mut blk: Block) -> Option { + /// Return Err if can't find the Attestation in the cache + fn attach_blk_att(&mut self, blk: &mut Block) -> Result<()> { let block_hash = blk.header().hash; - let block_with_att = if blk.header().att == Attestation::default() { - // The default att means the block was retrieved from Candidate - // CF thus missing the attestation. If so, we try to set the valid - // attestation from the cache attestations. - if let Some((att, _)) = - self.attestations_cache.get(&blk.header().hash) - { - blk.set_attestation(*att); - Some(blk) - } else { - error!("att not found for {}", hex::encode(blk.header().hash)); - None - } + // Check if we have the block Attestation in our cache + if let Some((att, _)) = self.attestations_cache.get(&block_hash) { + blk.set_attestation(*att); } else { - Some(blk) - }; + // warn!("Attestation not found for {}", hex::encode(block_hash)); + return Err(anyhow!( + "Attestation not found for {}", + hex::encode(block_hash) + )); + } // Clean up attestation cache self.clean_att_cache(); self.attestations_cache.remove(&block_hash); - block_with_att + Ok(()) } fn clean_att_cache(&mut self) { diff --git a/node/src/chain/header_validation.rs b/node/src/chain/header_validation.rs index 2269760c86..11cc1af510 100644 --- a/node/src/chain/header_validation.rs +++ b/node/src/chain/header_validation.rs @@ -4,12 +4,14 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use std::cmp; use std::collections::BTreeMap; use std::sync::Arc; use dusk_bytes::Serializable; use dusk_consensus::config::{ - is_emergency_iter, MINIMUM_BLOCK_TIME, RELAX_ITERATION_THRESHOLD, + is_emergency_block, is_emergency_iter, CONSENSUS_MAX_ITER, + MINIMUM_BLOCK_TIME, MIN_EMERGENCY_BLOCK_TIME, RELAX_ITERATION_THRESHOLD, }; use dusk_consensus::errors::{ AttestationError, FailedIterationError, HeaderError, @@ -77,7 +79,7 @@ impl<'a, DB: database::DB> Validator<'a, DB> { &self, header: &ledger::Header, expected_generator: &PublicKeyBytes, - disable_att_check: bool, + check_attestation: bool, ) -> Result<(u8, Vec, Vec), HeaderError> { let generator = self.verify_block_generator(header, expected_generator)?; @@ -86,7 +88,7 @@ impl<'a, DB: database::DB> Validator<'a, DB> { let prev_block_voters = self.verify_prev_block_cert(header).await?; let mut block_voters = vec![]; - if !disable_att_check { + if check_attestation { (_, _, block_voters) = verify_att( &header.att, header.to_consensus_header(), @@ -170,6 +172,21 @@ impl<'a, DB: database::DB> Validator<'a, DB> { return Err(HeaderError::BlockTimeLess); } + // The Emergency Block can only be produced after all iterations in a + // round have failed. To ensure Dusk (or anyone in possess of the Dusk + // private key) is not able to shortcircuit a round with an arbitrary + // block, nodes should only accept an Emergency Block if its timestamp + // is higher than the maximum time needed to run all round iterations. + // This guarantees the network has enough time to actually produce a + // block, if possible. + if is_emergency_block(candidate_block.iteration) + && candidate_block.timestamp + < self.prev_header.timestamp + + MIN_EMERGENCY_BLOCK_TIME.as_secs() + { + return Err(HeaderError::BlockTimeLess); + } + let local_time = get_current_timestamp(); if candidate_block.timestamp > local_time + MARGIN_TIMESTAMP { @@ -228,7 +245,9 @@ impl<'a, DB: database::DB> Validator<'a, DB> { &self, candidate_block: &'a ledger::Header, ) -> Result, HeaderError> { - if self.prev_header.height == 0 { + if self.prev_header.height == 0 + || is_emergency_block(self.prev_header.iteration) + { return Ok(vec![]); } @@ -309,7 +328,11 @@ impl<'a, DB: database::DB> Validator<'a, DB> { } } - Ok(candidate_block.iteration - failed_atts) + // In case of Emergency Block, which iteration number is u8::MAX, we + // count failed iterations up to CONSENSUS_MAX_ITER + let last_iter = cmp::min(candidate_block.iteration, CONSENSUS_MAX_ITER); + + Ok(last_iter - failed_atts) } /// Extracts voters list of a block. diff --git a/node/src/lib.rs b/node/src/lib.rs index afb9b84d26..83742911e4 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -30,6 +30,8 @@ use tokio::sync::RwLock; use tokio::task::JoinSet; use tracing::{error, info, warn}; +pub use rusk_recovery_tools::state::DUSK_CONSENSUS_KEY; + /// Filter is used by Network implementor to filter messages before re-routing /// them. It's like the middleware in HTTP pipeline. /// diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 45a225f9a7..23f1af21b7 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -5,11 +5,11 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use std::path::Path; -use std::sync::{mpsc, Arc, LazyLock}; +use std::sync::{mpsc, Arc}; use std::time::{Duration, Instant}; use std::{fs, io}; -use dusk_bytes::{DeserializableSlice, Serializable}; +use dusk_bytes::Serializable; use dusk_consensus::config::{ ratification_extra, ratification_quorum, validation_extra, validation_quorum, MAX_NUMBER_OF_TRANSACTIONS, @@ -28,6 +28,7 @@ use dusk_core::transfer::{ }; use dusk_core::{BlsScalar, Dusk}; use node::vm::bytecode_charge; +use node::DUSK_CONSENSUS_KEY; use node_data::events::contract::{ContractEvent, ContractTxEvent}; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; use parking_lot::RwLock; @@ -45,12 +46,6 @@ use crate::node::{coinbase_value, Rusk, RuskTip}; use crate::Error::InvalidCreditsCount; use crate::{Error, Result}; -pub static DUSK_KEY: LazyLock = LazyLock::new(|| { - let dusk_cpk_bytes = include_bytes!("../../assets/dusk.cpk"); - BlsPublicKey::from_slice(dusk_cpk_bytes) - .expect("Dusk consensus public key to be valid") -}); - impl Rusk { #[allow(clippy::too_many_arguments)] pub fn new>( @@ -880,7 +875,7 @@ fn reward_slash_and_update_root( }); rewards.push(Reward { - account: *DUSK_KEY, + account: *DUSK_CONSENSUS_KEY, value: dusk_value, reason: RewardReason::Other, });