From da878c7a876f9208c87408a90479ff6de10756ef Mon Sep 17 00:00:00 2001 From: goshawk Date: Wed, 10 Jan 2024 17:23:44 +0200 Subject: [PATCH 01/11] consensus: Add verify_block_header method in Operations trait --- consensus/src/operations.rs | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 consensus/src/operations.rs diff --git a/consensus/src/operations.rs b/consensus/src/operations.rs new file mode 100644 index 0000000000..83af1b4bad --- /dev/null +++ b/consensus/src/operations.rs @@ -0,0 +1,68 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use std::fmt; + +use node_data::ledger::{Header, SpentTransaction, Transaction}; + +pub type StateRoot = [u8; 32]; +pub type EventHash = [u8; 32]; + +#[derive(Debug)] +pub enum Error { + Failed, +} + +#[derive(Default, Clone, Debug)] +pub struct CallParams { + pub round: u64, + pub block_gas_limit: u64, + pub generator_pubkey: node_data::bls::PublicKey, +} + +#[derive(Default)] +pub struct Output { + pub txs: Vec, + pub verification_output: VerificationOutput, + pub discarded_txs: Vec, +} + +#[derive(Debug, Default, PartialEq)] +pub struct VerificationOutput { + pub state_root: StateRoot, + pub event_hash: EventHash, +} + +impl fmt::Display for VerificationOutput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "VerificationOutput {{ state_root: {}, event_hash: {} }}", + hex::encode(self.state_root), + hex::encode(self.event_hash) + ) + } +} + +#[async_trait::async_trait] +pub trait Operations: Send + Sync { + async fn verify_block_header( + &self, + candidate_header: &Header, + disable_winning_cert_check: bool, + ) -> Result<(), Error>; + + async fn verify_state_transition( + &self, + params: CallParams, + txs: Vec, + ) -> Result; + + async fn execute_state_transition( + &self, + params: CallParams, + ) -> Result; +} From 375a8651e008aabae61f2aa8bf186262b7e79810 Mon Sep 17 00:00:00 2001 From: goshawk Date: Wed, 10 Jan 2024 17:26:02 +0200 Subject: [PATCH 02/11] consensus: Verify candidate block header before vote casting --- consensus/src/validation/step.rs | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/consensus/src/validation/step.rs b/consensus/src/validation/step.rs index cfc171324a..9915265f6f 100644 --- a/consensus/src/validation/step.rs +++ b/consensus/src/validation/step.rs @@ -5,8 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::commons::{ConsensusError, Database, RoundUpdate}; -use crate::contract_state::{CallParams, Operations}; use crate::execution_ctx::ExecutionCtx; +use crate::operations::{CallParams, Operations}; use crate::validation::handler; use anyhow::anyhow; use dusk_bytes::DeserializableSlice; @@ -56,17 +56,36 @@ impl ValidationStep { ) { let hash = candidate.header().hash; + if hash == [0u8; 32] { + // Vote Nil + Self::cast_vote([0u8; 32], ru, iteration, outbound, inbound).await; + return; + } + + // Verify candidate header all fields except the winning certificate + if let Err(err) = executor + .lock() + .await + .verify_block_header(candidate.header(), true) + .await + { + error!(event = "invalid_header", ?err); + return; + }; + // Call VST for non-empty blocks - if hash != [0u8; 32] { - if let Err(err) = Self::call_vst(&candidate, &ru, executor).await { - error!( - event = "failed_vst_call", - reason = format!("{:?}", err) - ); - return; - } + if let Err(err) = Self::call_vst(&candidate, &ru, executor).await { + error!(event = "failed_vst_call", ?err); } + } + async fn cast_vote( + hash: [u8; 32], + ru: RoundUpdate, + iteration: u8, + outbound: AsyncQueue, + inbound: AsyncQueue, + ) { let hdr = message::Header { pubkey_bls: ru.pubkey_bls.clone(), round: ru.round, From 185b4422acbbca5698a9ca726e4ad48ab29549c5 Mon Sep 17 00:00:00 2001 From: goshawk Date: Wed, 10 Jan 2024 17:26:40 +0200 Subject: [PATCH 03/11] consensus: Rename contract_state.rs to operations.rs --- consensus/src/consensus.rs | 2 +- consensus/src/contract_state.rs | 62 ----------------------- consensus/src/execution_ctx.rs | 2 +- consensus/src/lib.rs | 2 +- consensus/src/phase.rs | 2 +- consensus/src/proposal/block_generator.rs | 8 +-- consensus/src/proposal/step.rs | 2 +- consensus/src/ratification/step.rs | 2 +- 8 files changed, 10 insertions(+), 72 deletions(-) delete mode 100644 consensus/src/contract_state.rs diff --git a/consensus/src/consensus.rs b/consensus/src/consensus.rs index 58158130d9..beb951893c 100644 --- a/consensus/src/consensus.rs +++ b/consensus/src/consensus.rs @@ -6,7 +6,7 @@ use crate::commons::{ConsensusError, Database, QuorumMsgSender, RoundUpdate}; use crate::config::CONSENSUS_MAX_ITER; -use crate::contract_state::Operations; +use crate::operations::Operations; use crate::phase::Phase; use node_data::ledger::Block; diff --git a/consensus/src/contract_state.rs b/consensus/src/contract_state.rs deleted file mode 100644 index df55e4680b..0000000000 --- a/consensus/src/contract_state.rs +++ /dev/null @@ -1,62 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -use std::fmt; - -use node_data::ledger::{SpentTransaction, Transaction}; - -pub type StateRoot = [u8; 32]; -pub type EventHash = [u8; 32]; - -#[derive(Debug)] -pub enum Error { - Failed, -} - -#[derive(Default, Clone, Debug)] -pub struct CallParams { - pub round: u64, - pub block_gas_limit: u64, - pub generator_pubkey: node_data::bls::PublicKey, -} - -#[derive(Default)] -pub struct Output { - pub txs: Vec, - pub verification_output: VerificationOutput, - pub discarded_txs: Vec, -} - -#[derive(Debug, Default, PartialEq)] -pub struct VerificationOutput { - pub state_root: StateRoot, - pub event_hash: EventHash, -} - -impl fmt::Display for VerificationOutput { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "VerificationOutput {{ state_root: {}, event_hash: {} }}", - hex::encode(self.state_root), - hex::encode(self.event_hash) - ) - } -} - -#[async_trait::async_trait] -pub trait Operations: Send + Sync { - async fn verify_state_transition( - &self, - params: CallParams, - txs: Vec, - ) -> Result; - - async fn execute_state_transition( - &self, - params: CallParams, - ) -> Result; -} diff --git a/consensus/src/execution_ctx.rs b/consensus/src/execution_ctx.rs index 0edbea542b..789c98f19f 100644 --- a/consensus/src/execution_ctx.rs +++ b/consensus/src/execution_ctx.rs @@ -6,10 +6,10 @@ use crate::commons::{ConsensusError, Database, QuorumMsgSender, RoundUpdate}; -use crate::contract_state::Operations; use crate::iteration_ctx::IterationCtx; use crate::msg_handler::HandleMsgOutput::{Pending, Ready}; use crate::msg_handler::MsgHandler; +use crate::operations::Operations; use crate::queue::Queue; use crate::step_votes_reg::SafeCertificateInfoRegistry; use crate::user::committee::Committee; diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index d864d897d7..a74c0360c4 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -12,9 +12,9 @@ pub mod user; mod aggregator; pub mod config; -pub mod contract_state; mod execution_ctx; mod msg_handler; +pub mod operations; mod phase; mod proposal; mod queue; diff --git a/consensus/src/phase.rs b/consensus/src/phase.rs index 44c1a34fdb..9603f38923 100644 --- a/consensus/src/phase.rs +++ b/consensus/src/phase.rs @@ -5,8 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::commons::{ConsensusError, Database}; -use crate::contract_state::Operations; use crate::execution_ctx::ExecutionCtx; +use crate::operations::Operations; use node_data::message::Message; use node_data::StepName; diff --git a/consensus/src/proposal/block_generator.rs b/consensus/src/proposal/block_generator.rs index 801ae386be..97ca4a9e32 100644 --- a/consensus/src/proposal/block_generator.rs +++ b/consensus/src/proposal/block_generator.rs @@ -5,12 +5,12 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::commons::RoundUpdate; -use crate::contract_state::CallParams; +use crate::operations::CallParams; use node_data::ledger::{to_str, Block, Certificate, IterationsInfo, Seed}; use crate::config; -use crate::contract_state::Operations; use crate::merkle::merkle_root; +use crate::operations::Operations; use dusk_bytes::Serializable; use node_data::ledger; @@ -35,7 +35,7 @@ impl Generator { ru: &RoundUpdate, iteration: u8, failed_iterations: Vec>, - ) -> Result { + ) -> Result { // Sign seed let seed = ru .secret_key @@ -82,7 +82,7 @@ impl Generator { seed: Seed, iteration: u8, failed_iterations: Vec>, - ) -> Result { + ) -> Result { let start_time = Instant::now(); let call_params = CallParams { diff --git a/consensus/src/proposal/step.rs b/consensus/src/proposal/step.rs index 296dd43cff..d8bdfe520d 100644 --- a/consensus/src/proposal/step.rs +++ b/consensus/src/proposal/step.rs @@ -5,9 +5,9 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::commons::{ConsensusError, Database}; -use crate::contract_state::Operations; use crate::execution_ctx::ExecutionCtx; use crate::msg_handler::{HandleMsgOutput, MsgHandler}; +use crate::operations::Operations; use node_data::message::Message; use std::cmp; use std::sync::Arc; diff --git a/consensus/src/ratification/step.rs b/consensus/src/ratification/step.rs index c70bb154f0..45e80b08f4 100644 --- a/consensus/src/ratification/step.rs +++ b/consensus/src/ratification/step.rs @@ -5,8 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::commons::{ConsensusError, Database, RoundUpdate}; -use crate::contract_state::Operations; use crate::execution_ctx::ExecutionCtx; +use crate::operations::Operations; use std::marker::PhantomData; use crate::msg_handler::{HandleMsgOutput, MsgHandler}; From 777aedb1791f54ed93c7205838c167f5dd048ab1 Mon Sep 17 00:00:00 2001 From: goshawk Date: Thu, 11 Jan 2024 09:34:52 +0200 Subject: [PATCH 04/11] node: Complete implementation of block header validation --- node/src/chain/header_validation.rs | 261 ++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 node/src/chain/header_validation.rs diff --git a/node/src/chain/header_validation.rs b/node/src/chain/header_validation.rs new file mode 100644 index 0000000000..547096e05d --- /dev/null +++ b/node/src/chain/header_validation.rs @@ -0,0 +1,261 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::database; +use crate::database::Ledger; +use anyhow::anyhow; +use dusk_consensus::quorum::verifiers; +use dusk_consensus::quorum::verifiers::QuorumResult; +use dusk_consensus::user::committee::CommitteeSet; +use dusk_consensus::user::provisioners::{ContextProvisioners, Provisioners}; +use node_data::ledger::to_str; +use node_data::ledger::Signature; +use node_data::{ledger, StepName}; +use std::sync::Arc; +use tokio::sync::RwLock; +use tracing::info; + +/// An implementation of the all validation checks of a candidate block header +/// according to current context +pub(crate) struct Validator { + pub(crate) db: Arc>, + block: ledger::Header, + provisioners: ContextProvisioners, +} + +impl<'a, DB: database::DB> Validator { + pub fn new( + db: Arc>, + block: ledger::Header, + provisioners: ContextProvisioners, + ) -> Self { + Self { + db, + block, + provisioners, + } + } + + /// Executes check points to make sure a candidate header is fully valid + /// + /// * `disable_winner_cert_check` - disables the check of the winning + /// certificate + pub async fn execute_checks( + &self, + candidate_block: &'a ledger::Header, + disable_winner_cert_check: bool, + ) -> anyhow::Result { + self.verify_basic_fields(candidate_block).await?; + self.verify_prev_block_cert(candidate_block).await?; + + if !disable_winner_cert_check { + self.verify_winning_cert(candidate_block).await?; + } + + self.verify_failed_iterations(candidate_block).await + } + + /// Verifies any non-certificate field + pub async fn verify_basic_fields( + &self, + candidate_block: &'a ledger::Header, + ) -> anyhow::Result<()> { + if candidate_block.version > 0 { + return Err(anyhow!("unsupported block version")); + } + + if candidate_block.hash == [0u8; 32] { + return Err(anyhow!("empty block hash")); + } + + if candidate_block.height != self.block.height + 1 { + return Err(anyhow!( + "invalid block height block_height: {:?}, curr_height: {:?}", + candidate_block.height, + self.block.height, + )); + } + + if candidate_block.prev_block_hash != self.block.hash { + return Err(anyhow!("invalid previous block hash")); + } + + // Ensure block is not already in the ledger + self.db.read().await.view(|v| { + if Ledger::get_block_exists(&v, &candidate_block.hash)? { + return Err(anyhow!("block already exists")); + } + + Ok(()) + })?; + + Ok(()) + } + + pub async fn verify_prev_block_cert( + &self, + candidate_block: &'a ledger::Header, + ) -> anyhow::Result<()> { + if self.block.height == 0 { + return Ok(()); + } + + let prev_block_seed = self.db.read().await.view(|v| { + let prev_block = + Ledger::fetch_block_by_height(&v, self.block.height - 1)? + .ok_or_else(|| anyhow::anyhow!("could not fetch block"))?; + + Ok::<_, anyhow::Error>(prev_block.header().seed) + })?; + + verify_block_cert( + prev_block_seed, + self.provisioners.prev(), + self.block.hash, + self.block.height, + &candidate_block.prev_block_cert, + self.block.iteration, + true, + ) + .await?; + + Ok(()) + } + + pub async fn verify_failed_iterations( + &self, + candidate_block: &'a ledger::Header, + ) -> anyhow::Result { + // Verify Failed iterations + let mut attested = true; + + for (iter, cert) in candidate_block + .failed_iterations + .cert_list + .iter() + .enumerate() + { + if let Some(cert) = cert { + info!(event = "verify_cert", cert_type = "failed_cert", iter); + + let quorums = verify_block_cert( + self.block.seed, + self.provisioners.current(), + [0u8; 32], + candidate_block.height, + cert, + iter as u8, + false, + ) + .await?; + + attested = attested + && quorums.0.quorum_reached() + && quorums.1.quorum_reached(); + } else { + attested = false; + } + } + + Ok(attested) + } + + pub async fn verify_winning_cert( + &self, + candidate_block: &'a ledger::Header, + ) -> anyhow::Result<()> { + verify_block_cert( + self.block.seed, + self.provisioners.current(), + candidate_block.hash, + candidate_block.height, + &candidate_block.cert, + candidate_block.iteration, + true, + ) + .await?; + + Ok(()) + } +} + +pub async fn verify_block_cert( + curr_seed: Signature, + curr_eligible_provisioners: &Provisioners, + block_hash: [u8; 32], + height: u64, + cert: &ledger::Certificate, + iteration: u8, + enable_quorum_check: bool, +) -> anyhow::Result<(QuorumResult, QuorumResult)> { + let committee = RwLock::new(CommitteeSet::new(curr_eligible_provisioners)); + + let hdr = node_data::message::Header { + topic: node_data::message::Topics::Unknown, + pubkey_bls: node_data::bls::PublicKey::default(), + round: height, + iteration, + block_hash, + }; + + let mut result = (QuorumResult::default(), QuorumResult::default()); + + // Verify validation + match verifiers::verify_step_votes( + &cert.validation, + &committee, + curr_seed, + &hdr, + StepName::Validation, + enable_quorum_check, + ) + .await + { + Ok(validation_quorum_result) => { + result.0 = validation_quorum_result; + } + Err(e) => { + return Err(anyhow!( + "invalid validation, hash = {}, round = {}, iter = {}, seed = {}, sv = {:?}, err = {}", + to_str(&hdr.block_hash), + hdr.round, + iteration, + to_str(&curr_seed.inner()), + cert.validation, + e + )); + } + }; + + // Verify ratification + match verifiers::verify_step_votes( + &cert.ratification, + &committee, + curr_seed, + &hdr, + StepName::Ratification, + enable_quorum_check, + ) + .await + { + Ok(ratification_quorum_result) => { + result.1 = ratification_quorum_result; + } + Err(e) => { + return Err(anyhow!( + "invalid ratification, hash = {}, round = {}, iter = {}, seed = {}, sv = {:?}, err = {}", + to_str(&hdr.block_hash), + hdr.round, + iteration, + to_str(&curr_seed.inner()), + cert.ratification, + e, + )); + } + } + + Ok(result) +} From e0d6809c91802b6c99621d974aa90da73b4fb51d Mon Sep 17 00:00:00 2001 From: goshawk Date: Thu, 11 Jan 2024 09:45:45 +0200 Subject: [PATCH 05/11] node: Call execute_checks in Operations::verify_block_header --- node/src/chain/consensus.rs | 58 ++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index 369eb3cba1..c18d9e02a4 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -9,11 +9,11 @@ use crate::{vm, Message, Network}; use async_trait::async_trait; use dusk_consensus::commons::{ConsensusError, RoundUpdate}; use dusk_consensus::consensus::Consensus; -use dusk_consensus::contract_state::{ +use dusk_consensus::operations::{ CallParams, Error, Operations, Output, VerificationOutput, }; -use dusk_consensus::user::provisioners::Provisioners; -use node_data::ledger::{Block, Hash, Transaction}; +use dusk_consensus::user::provisioners::ContextProvisioners; +use node_data::ledger::{Block, Hash, Header, Transaction}; use node_data::message::payload::GetCandidate; use node_data::message::AsyncQueue; use node_data::message::{Payload, Topics}; @@ -21,6 +21,8 @@ use tokio::sync::{oneshot, Mutex, RwLock}; use tokio::task::JoinHandle; use tracing::{error, info, trace, warn}; +use crate::chain::header_validation::Validator; +use node_data::ledger; use std::sync::Arc; use std::time::Duration; @@ -73,17 +75,23 @@ impl Task { pub(crate) fn spawn( &mut self, most_recent_block: &node_data::ledger::Block, - provisioners: Arc, + provisioners_list: ContextProvisioners, db: &Arc>, vm: &Arc>, network: &Arc>, ) { + let current = provisioners_list.to_current(); let c = Consensus::new( self.main_inbound.clone(), self.outbound.clone(), self.quorum_inbound.clone(), self.outbound.clone(), - Arc::new(Mutex::new(Executor::new(db, vm))), + Arc::new(Mutex::new(Executor::new( + db, + vm, + most_recent_block.header().clone(), + provisioners_list, // TODO: Avoid cloning + ))), Arc::new(Mutex::new(CandidateDB::new(db.clone(), network.clone()))), ); @@ -98,8 +106,7 @@ impl Task { self.task_id += 1; - let (all_num, eligible_num) = - provisioners.get_provisioners_info(ru.round); + let (all_num, eligible_num) = current.get_provisioners_info(ru.round); info!( event = "spawn consensus", @@ -115,7 +122,7 @@ impl Task { self.running_task = Some(( tokio::spawn(async move { - let cons_result = c.spin(ru, provisioners, cancel_rx).await; + let cons_result = c.spin(ru, current.into(), cancel_rx).await; if let Err(e) = result_queue.send(cons_result).await { error!("Unable to send consensus result to queue {e}") } @@ -262,24 +269,55 @@ impl dusk_consensus::commons::Database pub struct Executor { db: Arc>, vm: Arc>, + mrb_header: ledger::Header, + provisioners: ContextProvisioners, } impl Executor { - fn new(db: &Arc>, vm: &Arc>) -> Self { + fn new( + db: &Arc>, + vm: &Arc>, + mrb_header: ledger::Header, + provisioners: ContextProvisioners, + ) -> Self { Executor { db: db.clone(), vm: vm.clone(), + mrb_header, + provisioners, } } } #[async_trait::async_trait] impl Operations for Executor { + async fn verify_block_header( + &self, + candidate_header: &Header, + disable_winning_cert_check: bool, + ) -> Result<(), Error> { + let validator = Validator::new( + self.db.clone(), + self.mrb_header.clone(), + self.provisioners.clone(), + ); + + validator + .execute_checks(candidate_header, disable_winning_cert_check) + .await + .map_err(|err| { + error!("failed to verify header {}", err); + Error::Failed + })?; + + Ok(()) + } + async fn verify_state_transition( &self, params: CallParams, txs: Vec, - ) -> Result { + ) -> Result { info!("verifying state"); let vm = self.vm.read().await; From bf1bc790ae048d5a4d4ab31511871122dacbabc0 Mon Sep 17 00:00:00 2001 From: goshawk Date: Fri, 12 Jan 2024 10:09:49 +0200 Subject: [PATCH 06/11] rusk: Rename contract_state to operations --- rusk/src/lib/error.rs | 2 +- rusk/src/lib/lib.rs | 2 +- rusk/src/lib/vm.rs | 2 +- rusk/tests/common/state.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rusk/src/lib/error.rs b/rusk/src/lib/error.rs index 40761b9366..d5986e2e75 100644 --- a/rusk/src/lib/error.rs +++ b/rusk/src/lib/error.rs @@ -7,7 +7,7 @@ use std::{fmt, io}; use dusk_bls12_381::BlsScalar; -use dusk_consensus::contract_state::VerificationOutput; +use dusk_consensus::operations::VerificationOutput; use rusk_abi::dusk::Dusk; #[derive(Debug)] diff --git a/rusk/src/lib/lib.rs b/rusk/src/lib/lib.rs index fdf0e74c92..3e0405d4c0 100644 --- a/rusk/src/lib/lib.rs +++ b/rusk/src/lib/lib.rs @@ -28,7 +28,7 @@ use tracing::{error, info}; use bytecheck::CheckBytes; use dusk_bls12_381::BlsScalar; use dusk_bls12_381_sign::PublicKey as BlsPublicKey; -use dusk_consensus::contract_state::VerificationOutput; +use dusk_consensus::operations::VerificationOutput; use dusk_pki::{PublicKey, ViewKey}; use node_data::ledger::{SpentTransaction, Transaction}; use parking_lot::{Mutex, MutexGuard}; diff --git a/rusk/src/lib/vm.rs b/rusk/src/lib/vm.rs index 36f2b2c966..8694d0c408 100644 --- a/rusk/src/lib/vm.rs +++ b/rusk/src/lib/vm.rs @@ -5,7 +5,7 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use dusk_bytes::DeserializableSlice; -use dusk_consensus::contract_state::{CallParams, VerificationOutput}; +use dusk_consensus::operations::{CallParams, VerificationOutput}; use dusk_consensus::user::provisioners::Provisioners; use dusk_consensus::user::stake::Stake; use node::vm::VMExecution; diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index 0cc3fb05fb..b202ad1142 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -11,7 +11,7 @@ use rusk::{Result, Rusk}; use rusk_recovery_tools::state::{self, Snapshot}; use dusk_bls12_381_sign::PublicKey; -use dusk_consensus::contract_state::CallParams; +use dusk_consensus::operations::CallParams; use dusk_wallet_core::Transaction as PhoenixTransaction; use node_data::ledger::{Block, Header, SpentTransaction}; use tracing::info; From 05574d6ddd06ee877031af5a128f35e26f6f0b4d Mon Sep 17 00:00:00 2001 From: goshawk Date: Fri, 12 Jan 2024 10:10:42 +0200 Subject: [PATCH 07/11] testbed: Enable dusk_consensus=debug log filter --- node/testbed.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/testbed.sh b/node/testbed.sh index 8dc5da859b..c7199c705b 100755 --- a/node/testbed.sh +++ b/node/testbed.sh @@ -51,7 +51,7 @@ RUSK_STATE_PATH=${RUSK_STATE_PATH} cargo r --release -p rusk -- recovery-state --init $GENESIS_PATH echo "starting node $ID ..." echo "${KEYS_PATH}/node_$ID.keys" - RUSK_STATE_PATH=${RUSK_STATE_PATH} ./target/release/rusk --kadcast-bootstrap "$BOOTSTRAP_ADDR" --kadcast-public-address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="$NODE_FOLDER" --http-listen-addr "$WS_LISTEN_ADDR" --delay-on-resp-msg=10 > "${TEMPD}/node_${ID}.log" & + RUSK_STATE_PATH=${RUSK_STATE_PATH} ./target/release/rusk --kadcast-bootstrap "$BOOTSTRAP_ADDR" --kadcast-public-address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" --log-filter="dusk_consensus=debug" --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="$NODE_FOLDER" --http-listen-addr "$WS_LISTEN_ADDR" --delay-on-resp-msg=10 > "${TEMPD}/node_${ID}.log" & } ## Use ~/.cargo/bin/tokio-console --retain-for 0s http://127.0.0.1:10000 to connect console to first node @@ -69,7 +69,7 @@ RUST_LOG="info" TOKIO_CONSOLE_BIND="127.0.0.1:$T_BIND_PORT" \ cargo --config 'build.rustflags = ["--cfg", "tokio_unstable"]' run --features with_telemetry --bin rusk-node --\ - --kadcast_bootstrap "$BOOTSTRAP_ADDR" --kadcast_public_address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" \ + --kadcast_bootstrap "$BOOTSTRAP_ADDR" --kadcast_public_address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" --log-filter="dusk_consensus=debug" \ --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="${TEMPD}/db/${ID}" --config="default.config.toml" > "${TEMPD}/node_${ID}.log" & } From aaeacc3c93fffcd38152489d9ac401dda001dbc4 Mon Sep 17 00:00:00 2001 From: goshawk Date: Fri, 12 Jan 2024 10:15:35 +0200 Subject: [PATCH 08/11] WIP --- node/src/chain.rs | 2 ++ node/src/chain/acceptor.rs | 11 +++++------ node/src/vm.rs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/node/src/chain.rs b/node/src/chain.rs index 61b2709bec..7e11963e9f 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -10,6 +10,8 @@ mod fallback; mod fsm; mod genesis; +mod header_validation; + use self::acceptor::Acceptor; use self::fsm::SimpleFSM; use crate::database::Ledger; diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index f876c5cd05..8fbafb1211 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -132,11 +132,11 @@ impl Acceptor { } async fn spawn_task(&self) { - let provisioners = self.provisioners_list.read().await.to_current(); + let provisioners_list = self.provisioners_list.read().await.clone(); self.task.write().await.spawn( self.mrb.read().await.inner(), - Arc::new(provisioners), + provisioners_list, &self.db, &self.vm, &self.network, @@ -374,7 +374,7 @@ impl Acceptor { if enable_consensus { task.spawn( mrb.inner(), - Arc::new(provisioners_list.to_current()), + provisioners_list.clone(), &self.db, &self.vm, &self.network, @@ -473,8 +473,7 @@ impl Acceptor { pub(crate) async fn restart_consensus(&mut self) { let mut task = self.task.write().await; let mrb = self.mrb.read().await; - let provisioners_list = - self.provisioners_list.read().await.to_current(); + let provisioners_list = self.provisioners_list.read().await.clone(); task.abort_with_wait().await; info!( @@ -486,7 +485,7 @@ impl Acceptor { task.spawn( mrb.inner(), - Arc::new(provisioners_list), + provisioners_list, &self.db, &self.vm, &self.network, diff --git a/node/src/vm.rs b/node/src/vm.rs index c7d1998cd6..e77845c87a 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -5,7 +5,7 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use dusk_consensus::{ - contract_state::CallParams, contract_state::VerificationOutput, + operations::CallParams, operations::VerificationOutput, user::provisioners::Provisioners, }; use node_data::ledger::{Block, SpentTransaction, Transaction}; From efb96975fe3848c99d23f5e81233e1369a9a23a3 Mon Sep 17 00:00:00 2001 From: goshawk Date: Fri, 12 Jan 2024 12:33:58 +0200 Subject: [PATCH 09/11] consensus: Cast a vote on fully valid candidate --- consensus/src/proposal/step.rs | 2 +- consensus/src/ratification/step.rs | 2 +- consensus/src/validation/step.rs | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/consensus/src/proposal/step.rs b/consensus/src/proposal/step.rs index d8bdfe520d..ce2919150d 100644 --- a/consensus/src/proposal/step.rs +++ b/consensus/src/proposal/step.rs @@ -44,7 +44,7 @@ impl ProposalStep { round: u64, iteration: u8, ) { - debug!(event = "init", name = self.name(), round, iteration,) + debug!(event = "init", name = self.name(), round, iter = iteration,) } pub async fn run( diff --git a/consensus/src/ratification/step.rs b/consensus/src/ratification/step.rs index 158be0ea69..4c4194615f 100644 --- a/consensus/src/ratification/step.rs +++ b/consensus/src/ratification/step.rs @@ -106,7 +106,7 @@ impl RatificationStep { event = "init", name = self.name(), round = round, - iteration = iteration, + iter = iteration, hash = to_str(&handler.validation_result().hash), fsv_bitset = handler.validation_result().sv.bitset, quorum_type = format!("{:?}", handler.validation_result().quorum) diff --git a/consensus/src/validation/step.rs b/consensus/src/validation/step.rs index 2f477aa83f..a5dbfee7f6 100644 --- a/consensus/src/validation/step.rs +++ b/consensus/src/validation/step.rs @@ -54,16 +54,16 @@ impl ValidationStep { inbound: AsyncQueue, executor: Arc>, ) { - // TODO: Verify Block Header let hash = candidate.header().hash; + // A Validation step with empty/default Block produces a Nil Vote if hash == [0u8; 32] { - // Vote Nil Self::cast_vote([0u8; 32], ru, iteration, outbound, inbound).await; return; } // Verify candidate header all fields except the winning certificate + // NB: Winning certificate is produced only on reaching consensus if let Err(err) = executor .lock() .await @@ -74,15 +74,17 @@ impl ValidationStep { return; }; - // Call VST for non-empty blocks - if let Err(err) = Self::call_vst(&candidate, ru, executor).await { + // Call Verify State Transition to make sure transactions set is valid + if let Err(err) = Self::call_vst(candidate, ru, executor).await { error!(event = "failed_vst_call", ?err); } + + Self::cast_vote(hash, ru, iteration, outbound, inbound).await; } async fn cast_vote( hash: [u8; 32], - ru: RoundUpdate, + ru: &RoundUpdate, iteration: u8, outbound: AsyncQueue, inbound: AsyncQueue, @@ -202,7 +204,7 @@ impl ValidationStep { event = "init", name = self.name(), round, - iteration, + iter = iteration, hash = to_str(&handler.candidate.header().hash), ) } From ccbe339072b8f02c69f6401639fb244085a78b5a Mon Sep 17 00:00:00 2001 From: goshawk Date: Fri, 12 Jan 2024 14:13:13 +0200 Subject: [PATCH 10/11] node: Use header::Validator in block acceptance procedure --- node/src/chain.rs | 1 - node/src/chain/acceptor.rs | 184 +--------------------------- node/src/chain/consensus.rs | 4 +- node/src/chain/header_validation.rs | 12 +- 4 files changed, 14 insertions(+), 187 deletions(-) diff --git a/node/src/chain.rs b/node/src/chain.rs index 7e11963e9f..856f253b6d 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -17,7 +17,6 @@ use self::fsm::SimpleFSM; use crate::database::Ledger; use crate::{database, vm, Network}; use crate::{LongLivedService, Message}; -pub use acceptor::verify_block_cert; use anyhow::Result; use async_trait::async_trait; use dusk_consensus::commons::ConsensusError; diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 8fbafb1211..9bf8d751e3 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -9,22 +9,19 @@ use crate::{vm, Message, Network}; use anyhow::{anyhow, Result}; use dusk_consensus::commons::ConsensusError; use dusk_consensus::config::CONSENSUS_ROLLING_FINALITY_THRESHOLD; -use dusk_consensus::user::committee::CommitteeSet; use dusk_consensus::user::provisioners::{ContextProvisioners, Provisioners}; use node_data::ledger::{ - self, to_str, Block, BlockWithLabel, Label, Seed, Signature, - SpentTransaction, + self, to_str, Block, BlockWithLabel, Label, Seed, SpentTransaction, }; use node_data::message::AsyncQueue; use node_data::message::Payload; -use node_data::StepName; + use std::sync::Arc; use tokio::sync::RwLock; use tracing::{info, warn}; -use dusk_consensus::quorum::verifiers::{self, QuorumResult}; - use super::consensus::Task; +use crate::chain::header_validation::Validator; #[allow(dead_code)] pub(crate) enum RevertTarget { @@ -551,178 +548,9 @@ pub(crate) async fn verify_block_header( db: Arc>, mrb: &ledger::Header, provisioners: &ContextProvisioners, - new_blk: &ledger::Header, + candidate_header: &ledger::Header, ) -> anyhow::Result { - let mrb_eligible_provisioners = provisioners.current(); - let prev_eligible_provisioners = provisioners.prev(); - if new_blk.version > 0 { - return Err(anyhow!("unsupported block version")); - } - - if new_blk.hash == [0u8; 32] { - return Err(anyhow!("empty block hash")); - } - - if new_blk.height != mrb.height + 1 { - return Err(anyhow!( - "invalid block height block_height: {:?}, curr_height: {:?}", - new_blk.height, - mrb.height, - )); - } - - if new_blk.prev_block_hash != mrb.hash { - return Err(anyhow!("invalid previous block hash")); - } - - // Ensure block is not already in the ledger - db.read().await.view(|v| { - if Ledger::get_block_exists(&v, &new_blk.hash)? { - return Err(anyhow!("block already exists")); - } - - Ok(()) - })?; - - // Verify prev_block_cert field - if mrb.height >= 1 { - let prev_block_seed = db.read().await.view(|v| { - let prev_block = Ledger::fetch_block_by_height(&v, mrb.height - 1)? - .ok_or_else(|| anyhow::anyhow!("could not fetch block"))?; - - Ok::<_, anyhow::Error>(prev_block.header().seed) - })?; - - // Terms in use - // genesis_blk -> ... -> prev_block -> most_recent_block(mrb) -> new_blk - // (pending to be accepted) - verify_block_cert( - prev_block_seed, - prev_eligible_provisioners, - mrb.hash, - mrb.height, - &new_blk.prev_block_cert, - mrb.iteration, - true, - ) - .await?; - } - - // Verify Failed iterations - let mut attested = true; - - for (iter, cert) in new_blk.failed_iterations.cert_list.iter().enumerate() { - if let Some(cert) = cert { - info!(event = "verify_cert", cert_type = "failed_cert", iter); - - let quorums = verify_block_cert( - mrb.seed, - mrb_eligible_provisioners, - [0u8; 32], - new_blk.height, - cert, - iter as u8, - false, - ) - .await?; - - attested = attested - && quorums.0.quorum_reached() - && quorums.1.quorum_reached(); - } else { - attested = false; - } - } - - // Verify Certificate - verify_block_cert( - mrb.seed, - mrb_eligible_provisioners, - new_blk.hash, - new_blk.height, - &new_blk.cert, - new_blk.iteration, - true, - ) - .await?; - - Ok(attested) -} - -pub async fn verify_block_cert( - curr_seed: Signature, - curr_eligible_provisioners: &Provisioners, - block_hash: [u8; 32], - height: u64, - cert: &ledger::Certificate, - iteration: u8, - enable_quorum_check: bool, -) -> anyhow::Result<(QuorumResult, QuorumResult)> { - let committee = RwLock::new(CommitteeSet::new(curr_eligible_provisioners)); - - let hdr = node_data::message::Header { - topic: node_data::message::Topics::Unknown, - pubkey_bls: node_data::bls::PublicKey::default(), - round: height, - iteration, - block_hash, - }; - - let mut result = (QuorumResult::default(), QuorumResult::default()); - - // Verify validation - match verifiers::verify_step_votes( - &cert.validation, - &committee, - curr_seed, - &hdr, - StepName::Validation, - enable_quorum_check, - ) - .await - { - Ok(validation_quorum_result) => { - result.0 = validation_quorum_result; - } - Err(e) => { - return Err(anyhow!( - "invalid validation, hash = {}, round = {}, iter = {}, seed = {}, sv = {:?}, err = {}", - to_str(&hdr.block_hash), - hdr.round, - iteration, - to_str(&curr_seed.inner()), - cert.validation, - e - )); - } - }; - - // Verify ratification - match verifiers::verify_step_votes( - &cert.ratification, - &committee, - curr_seed, - &hdr, - StepName::Ratification, - enable_quorum_check, - ) - .await - { - Ok(ratification_quorum_result) => { - result.1 = ratification_quorum_result; - } - Err(e) => { - return Err(anyhow!( - "invalid ratification, hash = {}, round = {}, iter = {}, seed = {}, sv = {:?}, err = {}", - to_str(&hdr.block_hash), - hdr.round, - iteration, - to_str(&curr_seed.inner()), - cert.ratification, - e, - )); - } - } + let validator = Validator::new(db, mrb, provisioners); - Ok(result) + validator.execute_checks(candidate_header, false).await } diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index c18d9e02a4..02856d9f8a 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -298,8 +298,8 @@ impl Operations for Executor { ) -> Result<(), Error> { let validator = Validator::new( self.db.clone(), - self.mrb_header.clone(), - self.provisioners.clone(), + &self.mrb_header, + &self.provisioners, ); validator diff --git a/node/src/chain/header_validation.rs b/node/src/chain/header_validation.rs index 547096e05d..7cce97835b 100644 --- a/node/src/chain/header_validation.rs +++ b/node/src/chain/header_validation.rs @@ -20,17 +20,17 @@ use tracing::info; /// An implementation of the all validation checks of a candidate block header /// according to current context -pub(crate) struct Validator { +pub(crate) struct Validator<'a, DB: database::DB> { pub(crate) db: Arc>, - block: ledger::Header, - provisioners: ContextProvisioners, + block: &'a ledger::Header, + provisioners: &'a ContextProvisioners, } -impl<'a, DB: database::DB> Validator { +impl<'a, DB: database::DB> Validator<'a, DB> { pub fn new( db: Arc>, - block: ledger::Header, - provisioners: ContextProvisioners, + block: &'a ledger::Header, + provisioners: &'a ContextProvisioners, ) -> Self { Self { db, From daf8df9229dcb2841659369baa0dd21a732ef273 Mon Sep 17 00:00:00 2001 From: goshawk Date: Fri, 12 Jan 2024 17:27:41 +0200 Subject: [PATCH 11/11] consensus: Log send_vote events --- consensus/src/ratification/step.rs | 12 +++++------- consensus/src/validation/step.rs | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/consensus/src/ratification/step.rs b/consensus/src/ratification/step.rs index 4c4194615f..ad4b8dec00 100644 --- a/consensus/src/ratification/step.rs +++ b/consensus/src/ratification/step.rs @@ -18,7 +18,7 @@ use node_data::message::{AsyncQueue, Message, Payload, Topics}; use std::sync::Arc; use tokio::sync::Mutex; -use tracing::{debug, error}; +use tracing::{error, info, Instrument}; pub struct RatificationStep { handler: Arc>, @@ -54,12 +54,8 @@ impl RatificationStep { }, ); - debug!( - event = "voting", - vtype = "ratification", - hash = to_str(&result.hash), - validation_bitset = result.sv.bitset - ); + // Publish ratification vote + info!(event = "send_vote", validation_bitset = result.sv.bitset); // Publish outbound.send(msg.clone()).await.unwrap_or_else(|err| { @@ -123,6 +119,7 @@ impl RatificationStep { if ctx.am_member(committee) { let mut handler = self.handler.lock().await; + let hash = to_str(&handler.validation_result().hash); let vote_msg = Self::try_vote( &ctx.round_update, @@ -130,6 +127,7 @@ impl RatificationStep { handler.validation_result(), ctx.outbound.clone(), ) + .instrument(tracing::info_span!("ratification", hash,)) .await; // Collect my own vote diff --git a/consensus/src/validation/step.rs b/consensus/src/validation/step.rs index a5dbfee7f6..7012bb3f39 100644 --- a/consensus/src/validation/step.rs +++ b/consensus/src/validation/step.rs @@ -17,7 +17,7 @@ use node_data::message::{self, AsyncQueue, Message, Payload, Topics}; use std::sync::Arc; use tokio::sync::Mutex; use tokio::task::JoinSet; -use tracing::{debug, error, Instrument}; +use tracing::{debug, error, info, Instrument}; pub struct ValidationStep { handler: Arc>, @@ -42,7 +42,7 @@ impl ValidationStep { ) .await } - .instrument(tracing::info_span!("voting", hash)), + .instrument(tracing::info_span!("validation", hash,)), ); } @@ -54,32 +54,33 @@ impl ValidationStep { inbound: AsyncQueue, executor: Arc>, ) { - let hash = candidate.header().hash; + let header = candidate.header(); // A Validation step with empty/default Block produces a Nil Vote - if hash == [0u8; 32] { + if header.hash == [0u8; 32] { Self::cast_vote([0u8; 32], ru, iteration, outbound, inbound).await; return; } - // Verify candidate header all fields except the winning certificate + // Verify candidate header (all fields except the winning certificate) // NB: Winning certificate is produced only on reaching consensus if let Err(err) = executor .lock() .await - .verify_block_header(candidate.header(), true) + .verify_block_header(header, true) .await { - error!(event = "invalid_header", ?err); + error!(event = "invalid_header", ?err, ?header); return; }; // Call Verify State Transition to make sure transactions set is valid if let Err(err) = Self::call_vst(candidate, ru, executor).await { error!(event = "failed_vst_call", ?err); + return; } - Self::cast_vote(hash, ru, iteration, outbound, inbound).await; + Self::cast_vote(header.hash, ru, iteration, outbound, inbound).await; } async fn cast_vote( @@ -106,7 +107,7 @@ impl ValidationStep { ); // Publish validation vote - debug!(event = "voting", vtype = "validation", hash = to_str(&hash)); + info!(event = "send_vote"); // Publish outbound.send(msg.clone()).await.unwrap_or_else(|err| {