diff --git a/config/src/config.rs b/config/src/config.rs index 430d559a..606c4794 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -10,7 +10,6 @@ use figment::{ use serde::Deserialize; use common::config::types::Forks; -use consensus_core::calculate_fork_version; use crate::base::BaseConfig; use crate::cli::CliConfig; @@ -73,10 +72,6 @@ impl Config { } } - pub fn fork_version(&self, slot: u64) -> Vec { - calculate_fork_version(&self.forks, slot).to_vec() - } - pub fn to_base_config(&self) -> BaseConfig { BaseConfig { rpc_bind_ip: self.rpc_bind_ip.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), diff --git a/consensus-core/src/consensus_core.rs b/consensus-core/src/consensus_core.rs index 7acbe727..b6fc4ed8 100644 --- a/consensus-core/src/consensus_core.rs +++ b/consensus-core/src/consensus_core.rs @@ -2,7 +2,7 @@ use std::cmp; use alloy::primitives::B256; use eyre::Result; -use ssz_types::{BitVector, FixedVector}; +use ssz_types::BitVector; use tracing::{info, warn}; use tree_hash::TreeHash; use zduny_wasm_timer::{SystemTime, UNIX_EPOCH}; @@ -10,90 +10,108 @@ use zduny_wasm_timer::{SystemTime, UNIX_EPOCH}; use common::config::types::Forks; use crate::errors::ConsensusError; +use crate::proof::{ + is_current_committee_proof_valid, is_finality_proof_valid, is_next_committee_proof_valid, +}; use crate::types::bls::{PublicKey, Signature}; use crate::types::{ - FinalityUpdate, GenericUpdate, Header, LightClientStore, OptimisticUpdate, SyncCommittee, - Update, + Bootstrap, FinalityUpdate, GenericUpdate, Header, LightClientStore, OptimisticUpdate, Update, }; use crate::utils::{ - calc_sync_period, compute_domain, compute_fork_data_root, compute_signing_root, is_proof_valid, + calculate_fork_version, compute_committee_sign_root, compute_fork_data_root, + get_participating_keys, }; -pub fn get_participating_keys( - committee: &SyncCommittee, - bitfield: &BitVector, -) -> Result> { - let mut pks: Vec = Vec::new(); +pub fn verify_bootstrap(bootstrap: &Bootstrap, checkpoint: B256) -> Result<()> { + let committee_valid = is_current_committee_proof_valid( + &bootstrap.header, + &bootstrap.current_sync_committee, + &bootstrap.current_sync_committee_branch, + ); + + let header_hash = bootstrap.header.tree_hash_root(); + let header_valid = header_hash == checkpoint; - bitfield.iter().enumerate().for_each(|(i, bit)| { - if bit { - let pk = committee.pubkeys[i].clone(); - pks.push(pk); - } - }); + if !header_valid { + return Err(ConsensusError::InvalidHeaderHash(checkpoint, header_hash).into()); + } + + if !committee_valid { + return Err(ConsensusError::InvalidCurrentSyncCommitteeProof.into()); + } - Ok(pks) + Ok(()) } -pub fn get_bits(bitfield: &BitVector) -> u64 { - bitfield.iter().filter(|v| *v).count() as u64 +pub fn verify_update( + update: &Update, + expected_current_slot: u64, + store: &LightClientStore, + genesis_root: B256, + forks: &Forks, +) -> Result<()> { + let update = GenericUpdate::from(update); + verify_generic_update(&update, expected_current_slot, store, genesis_root, forks) } -pub fn is_finality_proof_valid( - attested_header: &Header, - finality_header: &Header, - finality_branch: &[B256], -) -> bool { - is_proof_valid(attested_header, finality_header, finality_branch, 6, 41) +pub fn verify_finality_update( + update: &FinalityUpdate, + expected_current_slot: u64, + store: &LightClientStore, + genesis_root: B256, + forks: &Forks, +) -> Result<()> { + let update = GenericUpdate::from(update); + verify_generic_update(&update, expected_current_slot, store, genesis_root, forks) } -pub fn is_next_committee_proof_valid( - attested_header: &Header, - next_committee: &SyncCommittee, - next_committee_branch: &[B256], -) -> bool { - is_proof_valid( - attested_header, - next_committee, - next_committee_branch, - 5, - 23, - ) +pub fn verify_optimistic_update( + update: &OptimisticUpdate, + expected_current_slot: u64, + store: &LightClientStore, + genesis_root: B256, + forks: &Forks, +) -> Result<()> { + let update = GenericUpdate::from(update); + verify_generic_update(&update, expected_current_slot, store, genesis_root, forks) } -pub fn is_current_committee_proof_valid( - attested_header: &Header, - current_committee: &SyncCommittee, - current_committee_branch: &[B256], -) -> bool { - is_proof_valid( - attested_header, - current_committee, - current_committee_branch, - 5, - 22, - ) +pub fn apply_bootstrap(store: &mut LightClientStore, bootstrap: &Bootstrap) { + *store = LightClientStore { + finalized_header: bootstrap.header.clone(), + current_sync_committee: bootstrap.current_sync_committee.clone(), + next_sync_committee: None, + optimistic_header: bootstrap.header.clone(), + previous_max_active_participants: 0, + current_max_active_participants: 0, + }; } -pub fn safety_threshold(store: &LightClientStore) -> u64 { - cmp::max( - store.current_max_active_participants, - store.previous_max_active_participants, - ) / 2 +pub fn apply_update(store: &mut LightClientStore, update: &Update) -> Option { + let update = GenericUpdate::from(update); + apply_generic_update(store, &update) } -pub fn has_sync_update(update: &GenericUpdate) -> bool { - update.next_sync_committee.is_some() && update.next_sync_committee_branch.is_some() +pub fn apply_finality_update( + store: &mut LightClientStore, + update: &FinalityUpdate, +) -> Option { + let update = GenericUpdate::from(update); + apply_generic_update(store, &update) } -pub fn has_finality_update(update: &GenericUpdate) -> bool { - update.finalized_header.is_some() && update.finality_branch.is_some() +pub fn apply_optimistic_update( + store: &mut LightClientStore, + update: &OptimisticUpdate, +) -> Option { + let update = GenericUpdate::from(update); + apply_generic_update(store, &update) } // implements state changes from apply_light_client_update and process_light_client_update in // the specification /// Returns the new checkpoint if one is created, otherwise None -pub fn apply_generic_update(store: &mut LightClientStore, update: &GenericUpdate) -> Option { +fn apply_generic_update(store: &mut LightClientStore, update: &GenericUpdate) -> Option { let committee_bits = get_bits(&update.sync_aggregate.sync_committee_bits); store.current_max_active_participants = @@ -169,7 +187,7 @@ pub fn apply_generic_update(store: &mut LightClientStore, update: &GenericUpdate // implements checks from validate_light_client_update and process_light_client_update in the // specification -pub fn verify_generic_update( +fn verify_generic_update( update: &GenericUpdate, expected_current_slot: u64, store: &LightClientStore, @@ -259,59 +277,33 @@ pub fn verify_generic_update( Ok(()) } -pub fn verify_update( - update: &Update, - expected_current_slot: u64, - store: &LightClientStore, - genesis_root: B256, - forks: &Forks, -) -> Result<()> { - let update = GenericUpdate::from(update); - - verify_generic_update(&update, expected_current_slot, store, genesis_root, forks) -} - -pub fn verify_finality_update( - update: &FinalityUpdate, - expected_current_slot: u64, - store: &LightClientStore, - genesis_root: B256, - forks: &Forks, -) -> Result<()> { - let update = GenericUpdate::from(update); +pub fn expected_current_slot(now: SystemTime, genesis_time: u64) -> u64 { + let now = now.duration_since(UNIX_EPOCH).unwrap(); + let since_genesis = now - std::time::Duration::from_secs(genesis_time); - verify_generic_update(&update, expected_current_slot, store, genesis_root, forks) + since_genesis.as_secs() / 12 } -pub fn apply_update(store: &mut LightClientStore, update: &Update) -> Option { - let update = GenericUpdate::from(update); - apply_generic_update(store, &update) +pub fn calc_sync_period(slot: u64) -> u64 { + // 32 slots per epoch + let epoch = slot / 32; + // 256 epochs per sync committee + epoch / 256 } -pub fn apply_finality_update( - store: &mut LightClientStore, - update: &FinalityUpdate, -) -> Option { - let update = GenericUpdate::from(update); - apply_generic_update(store, &update) +pub fn get_bits(bitfield: &BitVector) -> u64 { + bitfield.iter().filter(|v| *v).count() as u64 } -pub fn apply_optimistic_update( - store: &mut LightClientStore, - update: &OptimisticUpdate, -) -> Option { - let update = GenericUpdate::from(update); - apply_generic_update(store, &update) +fn has_sync_update(update: &GenericUpdate) -> bool { + update.next_sync_committee.is_some() && update.next_sync_committee_branch.is_some() } -pub fn expected_current_slot(now: SystemTime, genesis_time: u64) -> u64 { - let now = now.duration_since(UNIX_EPOCH).unwrap(); - let since_genesis = now - std::time::Duration::from_secs(genesis_time); - - since_genesis.as_secs() / 12 +fn has_finality_update(update: &GenericUpdate) -> bool { + update.finalized_header.is_some() && update.finality_branch.is_some() } -pub fn verify_sync_committee_signture( +fn verify_sync_committee_signture( pks: &[PublicKey], attested_header: &Header, signature: &Signature, @@ -322,26 +314,9 @@ pub fn verify_sync_committee_signture( signature.verify(signing_root.as_slice(), pks) } -pub fn compute_committee_sign_root(header: B256, fork_data_root: B256) -> B256 { - let domain_type = [7, 00, 00, 00]; - let domain = compute_domain(domain_type, fork_data_root); - compute_signing_root(header, domain) -} - -pub fn calculate_fork_version(forks: &Forks, slot: u64) -> FixedVector { - let epoch = slot / 32; - - let version = if epoch >= forks.deneb.epoch { - forks.deneb.fork_version - } else if epoch >= forks.capella.epoch { - forks.capella.fork_version - } else if epoch >= forks.bellatrix.epoch { - forks.bellatrix.fork_version - } else if epoch >= forks.altair.epoch { - forks.altair.fork_version - } else { - forks.genesis.fork_version - }; - - FixedVector::from(version.as_slice().to_vec()) +fn safety_threshold(store: &LightClientStore) -> u64 { + cmp::max( + store.current_max_active_participants, + store.previous_max_active_participants, + ) / 2 } diff --git a/consensus-core/src/lib.rs b/consensus-core/src/lib.rs index cd984650..83e334a6 100644 --- a/consensus-core/src/lib.rs +++ b/consensus-core/src/lib.rs @@ -1,6 +1,8 @@ pub mod errors; pub mod types; -pub mod utils; mod consensus_core; +mod proof; +mod utils; + pub use crate::consensus_core::*; diff --git a/consensus-core/src/proof.rs b/consensus-core/src/proof.rs new file mode 100644 index 00000000..a132cf2e --- /dev/null +++ b/consensus-core/src/proof.rs @@ -0,0 +1,70 @@ +use alloy::primitives::B256; +use sha2::{Digest, Sha256}; +use tree_hash::TreeHash; + +use crate::types::{Header, SyncCommittee}; + +pub fn is_finality_proof_valid( + attested_header: &Header, + finality_header: &Header, + finality_branch: &[B256], +) -> bool { + is_proof_valid(attested_header, finality_header, finality_branch, 6, 41) +} + +pub fn is_next_committee_proof_valid( + attested_header: &Header, + next_committee: &SyncCommittee, + next_committee_branch: &[B256], +) -> bool { + is_proof_valid( + attested_header, + next_committee, + next_committee_branch, + 5, + 23, + ) +} + +pub fn is_current_committee_proof_valid( + attested_header: &Header, + current_committee: &SyncCommittee, + current_committee_branch: &[B256], +) -> bool { + is_proof_valid( + attested_header, + current_committee, + current_committee_branch, + 5, + 22, + ) +} + +fn is_proof_valid( + attested_header: &Header, + leaf_object: &T, + branch: &[B256], + depth: usize, + index: usize, +) -> bool { + if branch.len() != depth { + return false; + } + + let mut derived_root = leaf_object.tree_hash_root(); + let mut hasher = Sha256::new(); + + for (i, node) in branch.iter().enumerate() { + if (index / 2usize.pow(i as u32)) % 2 != 0 { + hasher.update(node); + hasher.update(derived_root); + } else { + hasher.update(derived_root); + hasher.update(node); + } + + derived_root = B256::from_slice(&hasher.finalize_reset()); + } + + derived_root == attested_header.state_root +} diff --git a/consensus-core/src/utils.rs b/consensus-core/src/utils.rs index 5bf10001..69f9a922 100644 --- a/consensus-core/src/utils.rs +++ b/consensus-core/src/utils.rs @@ -1,60 +1,65 @@ use alloy::primitives::B256; -use sha2::{Digest, Sha256}; -use ssz_types::FixedVector; +use common::config::types::Forks; +use eyre::Result; +use ssz_types::{BitVector, FixedVector}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; -use crate::types::Header; +use crate::types::{bls::PublicKey, SyncCommittee}; -pub fn calc_sync_period(slot: u64) -> u64 { - // 32 slots per epoch - let epoch = slot / 32; - // 256 epochs per sync committee - epoch / 256 +pub fn compute_committee_sign_root(header: B256, fork_data_root: B256) -> B256 { + let domain_type = [7, 00, 00, 00]; + let domain = compute_domain(domain_type, fork_data_root); + compute_signing_root(header, domain) } -pub fn is_proof_valid( - attested_header: &Header, - leaf_object: &L, - branch: &[B256], - depth: usize, - index: usize, -) -> bool { - if branch.len() != depth { - return false; - } +pub fn calculate_fork_version(forks: &Forks, slot: u64) -> FixedVector { + let epoch = slot / 32; - let mut derived_root = leaf_object.tree_hash_root(); - let mut hasher = Sha256::new(); + let version = if epoch >= forks.deneb.epoch { + forks.deneb.fork_version + } else if epoch >= forks.capella.epoch { + forks.capella.fork_version + } else if epoch >= forks.bellatrix.epoch { + forks.bellatrix.fork_version + } else if epoch >= forks.altair.epoch { + forks.altair.fork_version + } else { + forks.genesis.fork_version + }; - for (i, node) in branch.iter().enumerate() { - if (index / 2usize.pow(i as u32)) % 2 != 0 { - hasher.update(node); - hasher.update(derived_root); - } else { - hasher.update(derived_root); - hasher.update(node); - } + FixedVector::from(version.as_slice().to_vec()) +} - derived_root = B256::from_slice(&hasher.finalize_reset()); - } +pub fn compute_fork_data_root( + current_version: FixedVector, + genesis_validator_root: B256, +) -> B256 { + let fork_data = ForkData { + current_version, + genesis_validator_root, + }; - derived_root == attested_header.state_root + fork_data.tree_hash_root() } -#[derive(Default, Debug, TreeHash)] -struct SigningData { - object_root: B256, - domain: B256, -} +pub fn get_participating_keys( + committee: &SyncCommittee, + bitfield: &BitVector, +) -> Result> { + let mut pks: Vec = Vec::new(); -#[derive(Default, Debug, TreeHash)] -struct ForkData { - current_version: FixedVector, - genesis_validator_root: B256, + bitfield.iter().enumerate().for_each(|(i, bit)| { + if bit { + let pk = committee.pubkeys[i].clone(); + pks.push(pk); + } + }); + + Ok(pks) } -pub fn compute_signing_root(object_root: B256, domain: B256) -> B256 { +fn compute_signing_root(object_root: B256, domain: B256) -> B256 { let data = SigningData { object_root, domain, @@ -63,21 +68,21 @@ pub fn compute_signing_root(object_root: B256, domain: B256) -> B256 { data.tree_hash_root() } -pub fn compute_domain(domain_type: [u8; 4], fork_data_root: B256) -> B256 { +fn compute_domain(domain_type: [u8; 4], fork_data_root: B256) -> B256 { let start = &domain_type; let end = &fork_data_root[..28]; let d = [start, end].concat(); B256::from_slice(d.as_slice()) } -pub fn compute_fork_data_root( +#[derive(Default, Debug, TreeHash)] +struct SigningData { + object_root: B256, + domain: B256, +} + +#[derive(Default, Debug, TreeHash)] +struct ForkData { current_version: FixedVector, genesis_validator_root: B256, -) -> B256 { - let fork_data = ForkData { - current_version, - genesis_validator_root, - }; - - fork_data.tree_hash_root() } diff --git a/consensus/src/consensus.rs b/consensus/src/consensus.rs index efdcaef6..e9115e5a 100644 --- a/consensus/src/consensus.rs +++ b/consensus/src/consensus.rs @@ -26,14 +26,12 @@ use crate::constants::MAX_REQUEST_LIGHT_CLIENT_UPDATES; use crate::database::Database; use consensus_core::{ - apply_finality_update, apply_optimistic_update, apply_update, + apply_bootstrap, apply_finality_update, apply_optimistic_update, apply_update, + calc_sync_period, errors::ConsensusError, - expected_current_slot, get_bits, is_current_committee_proof_valid, - types::{ - ExecutionPayload, FinalityUpdate, GenericUpdate, LightClientStore, OptimisticUpdate, Update, - }, - utils::calc_sync_period, - verify_generic_update, + expected_current_slot, get_bits, + types::{ExecutionPayload, FinalityUpdate, LightClientStore, OptimisticUpdate, Update}, + verify_bootstrap, verify_finality_update, verify_optimistic_update, verify_update, }; pub struct ConsensusClient { @@ -372,62 +370,26 @@ impl Inner { } } - let committee_valid = is_current_committee_proof_valid( - &bootstrap.header, - &bootstrap.current_sync_committee, - &bootstrap.current_sync_committee_branch, - ); - - let header_hash = bootstrap.header.tree_hash_root(); - let header_valid = header_hash == checkpoint; - - if !header_valid { - return Err(ConsensusError::InvalidHeaderHash(checkpoint, header_hash).into()); - } - - if !committee_valid { - return Err(ConsensusError::InvalidCurrentSyncCommitteeProof.into()); - } - - self.store = LightClientStore { - finalized_header: bootstrap.header.clone(), - current_sync_committee: bootstrap.current_sync_committee, - next_sync_committee: None, - optimistic_header: bootstrap.header.clone(), - previous_max_active_participants: 0, - current_max_active_participants: 0, - }; + verify_bootstrap(&bootstrap, checkpoint)?; + apply_bootstrap(&mut self.store, &bootstrap); Ok(()) } pub fn verify_update(&self, update: &Update) -> Result<()> { - let update = GenericUpdate::from(update); - let expected_current_slot = self.expected_current_slot(); - - verify_generic_update( - &update, - expected_current_slot, + verify_update( + update, + self.expected_current_slot(), &self.store, self.config.chain.genesis_root, &self.config.forks, ) } - pub fn apply_update(&mut self, update: &Update) { - let new_checkpoint = apply_update(&mut self.store, update); - if new_checkpoint.is_some() { - self.last_checkpoint = new_checkpoint; - } - } - fn verify_finality_update(&self, update: &FinalityUpdate) -> Result<()> { - let update = GenericUpdate::from(update); - let expected_current_slot = self.expected_current_slot(); - - verify_generic_update( - &update, - expected_current_slot, + verify_finality_update( + update, + self.expected_current_slot(), &self.store, self.config.chain.genesis_root, &self.config.forks, @@ -435,18 +397,22 @@ impl Inner { } fn verify_optimistic_update(&self, update: &OptimisticUpdate) -> Result<()> { - let update = GenericUpdate::from(update); - let expected_current_slot = self.expected_current_slot(); - - verify_generic_update( - &update, - expected_current_slot, + verify_optimistic_update( + update, + self.expected_current_slot(), &self.store, self.config.chain.genesis_root, &self.config.forks, ) } + pub fn apply_update(&mut self, update: &Update) { + let new_checkpoint = apply_update(&mut self.store, update); + if new_checkpoint.is_some() { + self.last_checkpoint = new_checkpoint; + } + } + fn apply_finality_update(&mut self, update: &FinalityUpdate) { let new_checkpoint = apply_finality_update(&mut self.store, update); if new_checkpoint.is_some() { @@ -455,6 +421,14 @@ impl Inner { self.log_finality_update(update); } + fn apply_optimistic_update(&mut self, update: &OptimisticUpdate) { + let new_checkpoint = apply_optimistic_update(&mut self.store, update); + if new_checkpoint.is_some() { + self.last_checkpoint = new_checkpoint; + } + self.log_optimistic_update(update); + } + fn log_finality_update(&self, update: &FinalityUpdate) { let participation = get_bits(&update.sync_aggregate.sync_committee_bits) as f32 / 512f32 * 100f32; @@ -473,14 +447,6 @@ impl Inner { ); } - fn apply_optimistic_update(&mut self, update: &OptimisticUpdate) { - let new_checkpoint = apply_optimistic_update(&mut self.store, update); - if new_checkpoint.is_some() { - self.last_checkpoint = new_checkpoint; - } - self.log_optimistic_update(update); - } - fn log_optimistic_update(&self, update: &OptimisticUpdate) { let participation = get_bits(&update.sync_aggregate.sync_committee_bits) as f32 / 512f32 * 100f32;