diff --git a/consensus/src/operations.rs b/consensus/src/operations.rs index 8c47b098a2..ec1642fe12 100644 --- a/consensus/src/operations.rs +++ b/consensus/src/operations.rs @@ -9,6 +9,8 @@ use std::io; use std::time::Duration; use execution_core::StakePublicKey; +use node_data::ledger::Fault; +use node_data::ledger::InvalidFault; use node_data::ledger::{Block, Header, Slash, SpentTransaction, Transaction}; use node_data::StepName; use thiserror::Error; @@ -29,6 +31,8 @@ pub enum Error { MetricsUpdate(anyhow::Error), #[error("Invalid Iteration Info {0}")] InvalidIterationInfo(io::Error), + #[error("Invalid Faults {0}")] + InvalidFaults(InvalidFault), } impl From for Error { @@ -37,6 +41,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: InvalidFault) -> Self { + Self::InvalidFaults(value) + } +} + #[derive(Default, Clone, Debug)] pub struct CallParams { pub round: u64, @@ -78,6 +88,12 @@ pub trait Operations: Send + Sync { disable_winning_att_check: bool, ) -> Result<(u8, Vec, Vec), Error>; + async fn verify_faults( + &self, + block_height: u64, + faults: &[Fault], + ) -> Result<(), Error>; + async fn verify_state_transition( &self, blk: &Block, diff --git a/consensus/src/proposal/block_generator.rs b/consensus/src/proposal/block_generator.rs index 135f2b5e2a..b70f4eba13 100644 --- a/consensus/src/proposal/block_generator.rs +++ b/consensus/src/proposal/block_generator.rs @@ -52,6 +52,7 @@ impl Generator { Seed::from(seed), iteration, failed_iterations, + &[], ru.att_voters(), ) .await?; @@ -88,10 +89,11 @@ impl Generator { seed: Seed, iteration: u8, failed_iterations: IterationsInfo, + faults: &[Fault], voters: &[VoterWithCredits], ) -> Result { let to_slash = - Slash::from_iterations_and_faults(&failed_iterations, &faults)?; + Slash::from_iterations_and_faults(&failed_iterations, faults)?; let call_params = CallParams { round: ru.round, diff --git a/consensus/src/validation/step.rs b/consensus/src/validation/step.rs index c0a6191f7e..fadb4a3f13 100644 --- a/consensus/src/validation/step.rs +++ b/consensus/src/validation/step.rs @@ -81,23 +81,27 @@ impl ValidationStep { // Verify candidate header (all fields except the winning attestation) // NB: Winning attestation is produced only on reaching consensus - match executor.verify_block_header(header, true).await { + let vote = match executor.verify_block_header(header, true).await { Ok((_, voters, _)) => { // Call Verify State Transition to make sure transactions set is // valid - // Voters here is the list of provisioners that validated the - // Tip of the current Block generator - let vote = + // TODO: Verify Faults + if let Err(err) = executor + .verify_faults(header.height, candidate.faults()) + .await + { + error!(event = "invalid faults", ?err); + Vote::Invalid(header.hash) + } else { match Self::call_vst(candidate, &voters, &executor).await { Ok(_) => Vote::Valid(header.hash), Err(err) => { error!(event = "failed_vst_call", ?err); Vote::Invalid(header.hash) } - }; - - Self::cast_vote(vote, ru, iteration, outbound, inbound).await; + } + } } Err(err) => { error!(event = "invalid_header", ?err, ?header); @@ -105,16 +109,11 @@ impl ValidationStep { // the block producer. // However, this is already verified in the Candidate message // verification, so it's safe to vote invalid here - Self::cast_vote( - Vote::Invalid(header.hash), - ru, - iteration, - outbound, - inbound, - ) - .await; + Vote::Invalid(header.hash) } - } + }; + + Self::cast_vote(vote, ru, iteration, outbound, inbound).await; } async fn cast_vote( diff --git a/node-data/src/ledger.rs b/node-data/src/ledger.rs index f8e93e2ea8..574ecc96d6 100644 --- a/node-data/src/ledger.rs +++ b/node-data/src/ledger.rs @@ -14,7 +14,7 @@ mod transaction; pub use transaction::{SpentTransaction, Transaction}; mod faults; -pub use faults::Fault; +pub use faults::{Fault, Slash, SlashType, InvalidFault}; mod attestation; pub use attestation::{ @@ -24,7 +24,7 @@ pub use attestation::{ use crate::bls::PublicKeyBytes; use crate::Serializable; -use dusk_bytes::DeserializableSlice; +// use dusk_bytes::DeserializableSlice; use rusk_abi::hash::Hasher; use sha3::Digest; use std::io::{self, Read, Write}; diff --git a/node-data/src/ledger/faults.rs b/node-data/src/ledger/faults.rs index 57bdbd5a22..9c59040f89 100644 --- a/node-data/src/ledger/faults.rs +++ b/node-data/src/ledger/faults.rs @@ -46,6 +46,8 @@ pub enum InvalidFault { RoundMismatch, #[error("Invalid Signature {0}")] InvalidSignature(BlsSigError), + #[error("Generic error {0}")] + Other(String), } impl From for InvalidFault { @@ -328,10 +330,10 @@ impl Slash { })) } - pub fn from_header(header: &Header) -> Result, io::Error> { + pub fn from_block(block: &Block) -> Result, io::Error> { Self::from_iterations_and_faults( - &header.failed_iterations, - &header.faults, + &block.header().failed_iterations, + block.faults(), ) } diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 8dc66ed113..f5b4b13fcc 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -299,7 +299,7 @@ impl Acceptor { let mut changed_provisioners = vec![reward, dusk_reward]; // Update provisioners if a slash has been applied - let slashed = Slash::from_header(blk.header())? + let slashed = Slash::from_block(blk)? .into_iter() .map(|f| ProvisionerChange::Slash(f.provisioner)); changed_provisioners.extend(slashed); @@ -454,6 +454,9 @@ impl Acceptor { let vm = self.vm.write().await; let (txs, rolling_result) = self.db.read().await.update(|db| { + + // TODO: Verify Faults + let (txs, verification_output) = vm.accept(blk, Some(&prev_block_voters[..]))?; @@ -480,7 +483,7 @@ impl Acceptor { header.height, ); - for slashed in Slash::from_header(blk.header())? { + for slashed in Slash::from_block(blk)? { info!("Slashed {}", slashed.provisioner.to_base58()); slashed_count += 1; } diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index 8280a2450a..64c792827f 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -15,7 +15,7 @@ use dusk_consensus::operations::{ }; use dusk_consensus::queue::MsgRegistry; use dusk_consensus::user::provisioners::ContextProvisioners; -use node_data::ledger::{Block, Header}; +use node_data::ledger::{Block, Fault, Header}; use node_data::message::AsyncQueue; use tokio::sync::{oneshot, Mutex, RwLock}; @@ -261,6 +261,19 @@ impl Operations for Executor { .map_err(operations::Error::InvalidHeader) } + async fn verify_faults( + &self, + block_height: u64, + faults: &[Fault], + ) -> Result<(), Error> { + let validator = Validator::new( + self.db.clone(), + &self.tip_header, + &self.provisioners, + ); + Ok(validator.verify_faults(block_height, faults).await?) + } + async fn verify_state_transition( &self, blk: &Block, diff --git a/node/src/chain/header_validation.rs b/node/src/chain/header_validation.rs index 42a727b327..59f4bcaf35 100644 --- a/node/src/chain/header_validation.rs +++ b/node/src/chain/header_validation.rs @@ -15,8 +15,7 @@ use dusk_consensus::quorum::verifiers::QuorumResult; use dusk_consensus::user::committee::{Committee, CommitteeSet}; use dusk_consensus::user::provisioners::{ContextProvisioners, Provisioners}; use execution_core::stake::EPOCH; -use node_data::ledger::Signature; -use node_data::ledger::{to_str, Header, Seed}; +use node_data::ledger::{to_str, Fault, InvalidFault, Seed, Signature}; use node_data::message::payload::RatificationResult; use node_data::message::ConsensusHeader; use node_data::{ledger, StepName}; @@ -76,7 +75,7 @@ impl<'a, DB: database::DB> Validator<'a, DB> { self.verify_success_att(candidate_block).await?; } - self.verify_faults(candidate_block).await?; + // self.verify_faults(candidate_block).await?; let pni = self.verify_failed_iterations(candidate_block).await?; Ok((pni, prev_block_voters, candidate_block_voters)) @@ -283,36 +282,43 @@ impl<'a, DB: database::DB> Validator<'a, DB> { /// Verify faults inside a block. pub async fn verify_faults( &self, - header: &ledger::Header, - ) -> anyhow::Result<()> { - verify_faults(self.db.clone(), header).await + current_height: u64, + faults: &[Fault], + ) -> Result<(), InvalidFault> { + verify_faults(self.db.clone(), current_height, faults).await } } +// TODO: verify faults pub async fn verify_faults( db: Arc>, - header: &Header, -) -> anyhow::Result<()> { - for f in &header.faults { - let fault_header = f.validate(header.height)?; - db.read().await.view(|db| { - let (prev_header, _) = db - .fetch_block_header(&fault_header.prev_block_hash)? - .ok_or(anyhow::anyhow!("Slashing a non accepted header"))?; - if prev_header.height != fault_header.round - 1 { - anyhow::bail!("Invalid height for fault"); - } + current_height: u64, + faults: &[Fault], +) -> Result<(), InvalidFault> { + for f in faults { + let fault_header = f.validate(current_height)?; + db.read() + .await + .view(|db| { + let (prev_header, _) = db + .fetch_block_header(&fault_header.prev_block_hash)? + .ok_or(anyhow::anyhow!("Slashing a non accepted header"))?; + if prev_header.height != fault_header.round - 1 { + anyhow::bail!("Invalid height for fault"); + } - // FIX_ME: Instead of fetching all store faults, check the fault id - // directly This needs the fault id to be changed into - // "HEIGHT|TYPE|PROV_KEY" - let stored_faults = db.fetch_faults(fault_header.round - EPOCH)?; - if stored_faults.iter().any(|other| f.same(other)) { - anyhow::bail!("Double fault detected"); - } + // FIX_ME: Instead of fetching all store faults, check the fault + // id directly This needs the fault id to be + // changed into "HEIGHT|TYPE|PROV_KEY" + let stored_faults = + db.fetch_faults(fault_header.round - EPOCH)?; + if stored_faults.iter().any(|other| f.same(other)) { + anyhow::bail!("Double fault detected"); + } - Ok(()) - })?; + Ok(()) + }) + .map_err(|e| InvalidFault::Other(format!("{e:?}")))?; } Ok(()) } diff --git a/rusk/benches/block_ingestion.rs b/rusk/benches/block_ingestion.rs index 37c1ab97ad..f872d08e97 100644 --- a/rusk/benches/block_ingestion.rs +++ b/rusk/benches/block_ingestion.rs @@ -106,7 +106,7 @@ pub fn accept_benchmark(c: &mut Criterion) { generator, txs, None, - &[], + vec![], None, ) .expect("Accepting transactions should succeed"); diff --git a/rusk/src/lib/chain/vm.rs b/rusk/src/lib/chain/vm.rs index 6a5e363976..c0d699e4ee 100644 --- a/rusk/src/lib/chain/vm.rs +++ b/rusk/src/lib/chain/vm.rs @@ -50,7 +50,7 @@ impl VMExecution for Rusk { let generator = StakePublicKey::from_slice(&generator.0) .map_err(|e| anyhow::anyhow!("Error in from_slice {e:?}"))?; - let slashing = Slash::from_header(blk.header())?; + let slashing = Slash::from_block(blk)?; let (_, verification_output) = self .verify_transactions( @@ -76,7 +76,7 @@ impl VMExecution for Rusk { let generator = StakePublicKey::from_slice(&generator.0) .map_err(|e| anyhow::anyhow!("Error in from_slice {e:?}"))?; - let slashing = Slash::from_header(blk.header())?; + let slashing = Slash::from_block(blk)?; let (txs, verification_output) = self .accept_transactions(