Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

consensus: accept Dusk-signed Emergency Block #3120

Merged
merged 10 commits into from
Dec 20, 2024
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)
fed-franz marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading