Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions finalizer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ commonware-codec.workspace = true
commonware-consensus.workspace = true
commonware-cryptography.workspace = true
commonware-math.workspace = true
commonware-parallel.workspace = true
commonware-runtime.workspace = true
commonware-storage.workspace = true
commonware-p2p.workspace = true
Expand Down
70 changes: 34 additions & 36 deletions finalizer/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,44 +426,42 @@ impl<
let new_height = block.height();
let mut epoch_change = false; // Store finalizes checkpoint to database
if is_last_block_of_epoch(self.protocol_consts.epoch_num_of_blocks, new_height) {
if let Some(finalization) = finalization {
// The finalized signatures should always be included on the last block
// of the epoch. However, there is an edge case, where the block after
// last block of the epoch arrived out of order.
// This is not critical and will likely never happen on all validators
// at the same time.
debug_assert!(block.header.digest == finalization.proposal.payload);
// The syncer will always send the last block of an epoch together with
// the finalization.
let finalization = finalization
.expect("finalization is always included for the last block of an epoch");
debug_assert!(block.header.digest == finalization.proposal.payload);
// Get participant count from the certificate signers
let participant_count = finalization.certificate.signers.len();

// Store the finalized block header in the database
let finalized_header =
FinalizedHeader::new(block.header.clone(), finalization, participant_count);

// Get participant count from the certificate signers
let participant_count = finalization.certificate.signers.len();

// Store the finalized block header in the database
let finalized_header =
FinalizedHeader::new(block.header.clone(), finalization, participant_count);

#[cfg(feature = "prom")]
let header_start = Instant::now();
self.db
.store_finalized_header(self.canonical_state.epoch, &finalized_header)
.await;
#[cfg(feature = "prom")]
{
let header_duration = header_start.elapsed().as_micros() as f64;
histogram!("finalizer_db_finalized_header_write_micros")
.record(header_duration);
}
#[cfg(feature = "prom")]
let header_start = Instant::now();
self.db
.store_finalized_header(self.canonical_state.epoch, &finalized_header)
.await;
#[cfg(feature = "prom")]
{
let header_duration = header_start.elapsed().as_micros() as f64;
histogram!("finalizer_db_finalized_header_write_micros").record(header_duration);
}

#[cfg(debug_assertions)]
{
let gauge: Gauge = Gauge::default();
gauge.set(new_height as i64);
self.context.register(
format!("<header>{}</header><prev_header>{}</prev_header>_finalized_header_stored",
hex::encode(finalized_header.header.digest), hex::encode(finalized_header.header.prev_epoch_header_hash)),
"chain height",
gauge
);
}
#[cfg(debug_assertions)]
{
let gauge: Gauge = Gauge::default();
gauge.set(new_height as i64);
self.context.register(
format!(
"<header>{}</header><prev_header>{}</prev_header>_finalized_header_stored",
hex::encode(finalized_header.header.digest),
hex::encode(finalized_header.header.prev_epoch_header_hash)
),
"chain height",
gauge,
);
}

// Apply protocol parameter changes
Expand Down
4 changes: 2 additions & 2 deletions finalizer/src/ingress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ impl<S: Scheme<B::Commitment>, B: ConsensusBlock + Committable> FinalizerMailbox
maybe_checkpoint
}

pub async fn get_finalized_header(&mut self, height: u64) -> Option<FinalizedHeader<S>> {
pub async fn get_finalized_header(&mut self, epoch: u64) -> Option<FinalizedHeader<S>> {
let (response, rx) = oneshot::channel();
let request = ConsensusStateRequest::GetFinalizedHeader(height);
let request = ConsensusStateRequest::GetFinalizedHeader(epoch);

let _ = self
.sender
Expand Down
70 changes: 69 additions & 1 deletion finalizer/src/tests/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,76 @@ use alloy_rpc_types_engine::{
ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, ExecutionPayloadV2,
ExecutionPayloadV3, ForkchoiceState, PayloadId, PayloadStatus, PayloadStatusEnum,
};
use commonware_consensus::simplex::scheme::bls12381_multisig;
use commonware_consensus::simplex::types::{Finalization, Finalize, Proposal};
use commonware_consensus::types::{Epoch, Round, View};
use commonware_cryptography::bls12381::primitives::{group, variant::MinPk};
use commonware_cryptography::{Signer as _, ed25519};
use commonware_math::algebra::Random;
use commonware_parallel::Sequential;
use commonware_utils::ordered::{BiMap, Map};
use summit_types::network_oracle::NetworkOracle;
use summit_types::{Block, EngineClient, PublicKey};
use summit_types::{Block, Digest, EngineClient, PublicKey};

pub type MultisigScheme = bls12381_multisig::Scheme<ed25519::PublicKey, MinPk>;

/// Creates BLS multisig schemes for testing finalization certificates.
pub fn create_test_schemes(num_validators: u32) -> Vec<MultisigScheme> {
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);

const NAMESPACE: &[u8] = b"test";

// Generate ed25519 participants
let mut participants = Vec::with_capacity(num_validators as usize);
for i in 0..num_validators {
let key = ed25519::PrivateKey::from_seed(i as u64);
participants.push(key.public_key());
}
let participants = Map::from_iter_dedup(participants.into_iter().map(|p| (p, ())));
let participants = participants.into_keys();

// Generate BLS keys
let mut bls_privates = Vec::with_capacity(num_validators as usize);
for _ in 0..num_validators {
bls_privates.push(group::Private::random(&mut rng));
}
let bls_public: Vec<_> = bls_privates
.iter()
.map(|sk| commonware_cryptography::bls12381::primitives::ops::compute_public::<MinPk>(sk))
.collect();

let signers_map = Map::from_iter_dedup(participants.clone().into_iter().zip(bls_public));
let signers = BiMap::try_from(signers_map).expect("BLS public keys should be unique");

bls_privates
.into_iter()
.filter_map(|sk| bls12381_multisig::Scheme::signer(NAMESPACE, signers.clone(), sk))
.collect()
}

/// Creates a finalization certificate for a block.
pub fn make_finalization(
block_digest: Digest,
height: u64,
parent_view: u64,
schemes: &[MultisigScheme],
quorum: usize,
) -> Finalization<MultisigScheme, Digest> {
let proposal = Proposal {
round: Round::new(Epoch::new(0), View::new(height)),
parent: View::new(parent_view),
payload: block_digest,
};

let finalizes: Vec<_> = schemes
.iter()
.take(quorum)
.map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
.collect();

Finalization::from_finalizes(&schemes[0], &finalizes, &Sequential).unwrap()
}

/// Minimal mock EngineClient that accepts all blocks
#[derive(Clone)]
Expand Down
19 changes: 16 additions & 3 deletions finalizer/src/tests/state_queries.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Tests for finalizer state query methods.

use super::mocks::{MockEngineClient, MockNetworkOracle};
use super::mocks::{MockEngineClient, MockNetworkOracle, create_test_schemes, make_finalization};
use crate::actor::Finalizer;
use crate::config::{FinalizerConfig, ProtocolConsts};
use alloy_primitives::{Address, U256};
Expand Down Expand Up @@ -223,11 +223,18 @@ fn test_get_latest_epoch() {
"Should still be epoch 0 before block 4"
);

// Create BLS signing schemes for finalization certificates
let schemes = create_test_schemes(4);
let quorum = 3;

// Finalize block 4 (last block of epoch 0, triggers epoch change to 1)
// The last block of an epoch requires a finalization certificate
let block4 = create_test_block_with_epoch(parent_digest, 4, 5, 10004, 0);
let block4_digest = block4.digest();
let finalization4 = make_finalization(block4_digest, 4, 3, &schemes, quorum);
let (ack, _) = Exact::handle();
mailbox
.report(Update::FinalizedBlock((block4, None), ack))
.report(Update::FinalizedBlock((block4, Some(finalization4)), ack))
.await;
context.sleep(Duration::from_millis(100)).await;

Expand Down Expand Up @@ -319,12 +326,18 @@ fn test_get_epoch_genesis_hash() {
context.sleep(Duration::from_millis(50)).await;
}

// Create BLS signing schemes for finalization certificates
let schemes = create_test_schemes(4);
let quorum = 3;

// Finalize block 4 (last block of epoch 0, triggers epoch change)
// The last block of an epoch requires a finalization certificate
let block4 = create_test_block_with_epoch(parent_digest, 4, 5, 12004, 0);
let block4_digest = block4.digest();
let finalization4 = make_finalization(block4_digest, 4, 3, &schemes, quorum);
let (ack, _) = Exact::handle();
mailbox
.report(Update::FinalizedBlock((block4, None), ack))
.report(Update::FinalizedBlock((block4, Some(finalization4)), ack))
.await;
context.sleep(Duration::from_millis(100)).await;

Expand Down
13 changes: 10 additions & 3 deletions finalizer/src/tests/validator_lifecycle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Tests for validator lifecycle: exit, removal from committee, etc.

use super::mocks::{MockEngineClient, MockNetworkOracle};
use super::mocks::{MockEngineClient, MockNetworkOracle, create_test_schemes, make_finalization};
use crate::actor::Finalizer;
use crate::config::{FinalizerConfig, ProtocolConsts};
use alloy_primitives::{Address, U256};
Expand Down Expand Up @@ -208,6 +208,10 @@ fn test_validator_exit_triggers_cancellation() {
let genesis_block = Block::genesis(genesis_hash);
let mut parent_digest = genesis_block.digest();

// Create BLS signing schemes for finalization certificates
let schemes = create_test_schemes(4);
let quorum = 3;

// Finalize blocks 1-3 (epoch 0 with epoch_num_of_blocks = 5)
for height in 1..4 {
let block =
Expand All @@ -229,11 +233,14 @@ fn test_validator_exit_triggers_cancellation() {

// Finalize block 4 (last block of epoch 0)
// This triggers update_validator_committee which sets validator_exit = true
// The last block of an epoch requires a finalization certificate
let block4 = create_test_block_with_epoch(parent_digest, 4, 5, 13004, 0);
parent_digest = block4.digest();
let block4_digest = block4.digest();
parent_digest = block4_digest;
let finalization4 = make_finalization(block4_digest, 4, 3, &schemes, quorum);
let (ack, _) = Exact::handle();
mailbox
.report(Update::FinalizedBlock((block4, None), ack))
.report(Update::FinalizedBlock((block4, Some(finalization4)), ack))
.await;
context.sleep(Duration::from_millis(100)).await;

Expand Down
5 changes: 3 additions & 2 deletions node/src/bin/protocol_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

println!("Protocol parameter change test completed successfully!");

Ok(())
Ok::<(), Box<dyn std::error::Error>>(())
}
})
})?;
std::process::exit(0);
}

async fn send_protocol_params_transaction<P>(
Expand Down
2 changes: 1 addition & 1 deletion node/src/bin/stake_and_checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}

println!("All nodes shut down cleanly");
Ok(())
std::process::exit(0);
}

fn copy_dir_all(src: &str, dst: &str) -> std::io::Result<()> {
Expand Down
5 changes: 3 additions & 2 deletions node/src/bin/stake_and_join_with_outdated_ckpt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,9 +623,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}

println!("All nodes shut down cleanly");
Ok(())
Ok::<(), Box<dyn std::error::Error>>(())
}
})
})?;
std::process::exit(0);
}

fn copy_dir_all(src: &str, dst: &str) -> std::io::Result<()> {
Expand Down
5 changes: 3 additions & 2 deletions node/src/bin/sync_from_genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,10 @@ address = "{}"
}


Ok(())
Ok::<(), Box<dyn std::error::Error>>(())
}
})
})?;
std::process::exit(0);
}

async fn send_withdrawal_transaction<P>(
Expand Down
5 changes: 3 additions & 2 deletions node/src/bin/withdraw_and_exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
panic!("Validator should not be on the consensus state anymore");
}

Ok(())
Ok::<(), Box<dyn std::error::Error>>(())
}
})
})?;
std::process::exit(0);
}

async fn send_withdrawal_transaction<P>(
Expand Down
8 changes: 4 additions & 4 deletions rpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ impl SummitApiServer for SummitRpcServer {
return Err(RpcError::CheckpointNotFound.into());
};

// try to get the finalized header for the last block
// try to get the finalized header for this epoch
let maybe_header = self
.finalizer_mailbox
.clone()
.get_finalized_header(last_block.height())
.get_finalized_header(epoch)
.await;

let Some(header) = maybe_header else {
Expand All @@ -89,11 +89,11 @@ impl SummitApiServer for SummitRpcServer {
return Err(RpcError::CheckpointNotFound.into());
};

// try to get the finalized header for the last block
// try to get the finalized header for this epoch
let maybe_header = self
.finalizer_mailbox
.clone()
.get_finalized_header(last_block.height())
.get_finalized_header(epoch)
.await;

let Some(header) = maybe_header else {
Expand Down
Loading