Skip to content

Commit

Permalink
Merge pull request #3120 from dusk-network/emergency_block_simple
Browse files Browse the repository at this point in the history
consensus: accept Dusk-signed Emergency Block
  • Loading branch information
fed-franz authored Dec 20, 2024
2 parents 60d4280 + dbbad71 commit c98ad03
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 214 deletions.
22 changes: 20 additions & 2 deletions consensus/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
67 changes: 31 additions & 36 deletions consensus/src/proposal/block_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Operations> {
executor: Arc<T>,
Expand All @@ -35,36 +35,10 @@ impl<T: Operations> Generator<T> {
iteration: u8,
failed_iterations: IterationsInfo,
) -> Result<Message, crate::errors::OperationError> {
// 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());
Expand All @@ -74,15 +48,22 @@ impl<T: Operations> Generator<T> {
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<Block, crate::errors::OperationError> {
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]
Expand Down Expand Up @@ -128,6 +109,7 @@ impl<T: Operations> Generator<T> {

// 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,
Expand All @@ -154,10 +136,23 @@ impl<T: Operations> Generator<T> {
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}",),
)),
}
}
}
10 changes: 1 addition & 9 deletions node-data/src/ledger/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)?;

Expand Down
11 changes: 0 additions & 11 deletions node-data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
20 changes: 18 additions & 2 deletions node/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -171,8 +172,23 @@ impl<N: Network, DB: database::DB, VM: vm::VMExecution>
// 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);
}
Expand Down
40 changes: 31 additions & 9 deletions node/src/chain/acceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -627,7 +628,12 @@ impl<DB: database::DB, VM: vm::VMExecution, N: Network> Acceptor<N, DB, VM> {
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);
Expand Down Expand Up @@ -1355,13 +1361,29 @@ pub(crate) async fn verify_block_header<DB: database::DB>(
provisioners: &ContextProvisioners,
header: &ledger::Header,
) -> Result<(u8, Vec<Voter>, Vec<Voter>), 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
}
2 changes: 1 addition & 1 deletion node/src/chain/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ impl<DB: database::DB, VM: vm::VMExecution> Operations for Executor<DB, VM> {
);

validator
.execute_checks(candidate_header, expected_generator, true)
.execute_checks(candidate_header, expected_generator, false)
.await
}

Expand Down
Loading

0 comments on commit c98ad03

Please sign in to comment.