diff --git a/crates/blockchain/src/key_manager.rs b/crates/blockchain/src/key_manager.rs index 9a9b8b18..2be82fe1 100644 --- a/crates/blockchain/src/key_manager.rs +++ b/crates/blockchain/src/key_manager.rs @@ -1,11 +1,12 @@ use std::collections::HashMap; +use std::time::Instant; use ethlambda_types::{ attestation::{AttestationData, XmssSignature}, primitives::{H256, HashTreeRoot as _}, signature::{ValidatorSecretKey, ValidatorSignature}, }; -use tracing::info; +use tracing::{info, warn}; use crate::metrics; @@ -48,6 +49,14 @@ impl KeyManager { self.keys.keys().copied().collect() } + /// Advances every validator's XMSS preparation windows to cover slot + pub fn advance_keys_to(&mut self, slot: u32) { + for (validator_id, key_pair) in self.keys.iter_mut() { + advance_key(*validator_id, &mut key_pair.attestation_key, slot); + advance_key(*validator_id, &mut key_pair.proposal_key, slot); + } + } + /// Signs an attestation using the validator's attestation key. pub fn sign_attestation( &mut self, @@ -85,6 +94,7 @@ impl KeyManager { // Multiple advances may be needed if the node was offline for an extended period. if !key_pair.attestation_key.is_prepared_for(slot) { info!(validator_id, slot, "Advancing XMSS key preparation window"); + let start = Instant::now(); while !key_pair.attestation_key.is_prepared_for(slot) { let before = key_pair.attestation_key.get_prepared_interval(); key_pair.attestation_key.advance_preparation(); @@ -95,6 +105,12 @@ impl KeyManager { ))); } } + info!( + validator_id, + slot, + elapsed_ms = start.elapsed().as_millis() as u64, + "Advanced XMSS key preparation window" + ); } let signature: ValidatorSignature = { @@ -130,6 +146,7 @@ impl KeyManager { validator_id, slot, "Advancing XMSS proposal key preparation window" ); + let start = Instant::now(); while !key_pair.proposal_key.is_prepared_for(slot) { let before = key_pair.proposal_key.get_prepared_interval(); key_pair.proposal_key.advance_preparation(); @@ -140,6 +157,12 @@ impl KeyManager { ))); } } + info!( + validator_id, + slot, + elapsed_ms = start.elapsed().as_millis() as u64, + "Advanced XMSS proposal key preparation window" + ); } let signature: ValidatorSignature = key_pair @@ -153,6 +176,28 @@ impl KeyManager { } } +fn advance_key(validator_id: u64, key: &mut ValidatorSecretKey, slot: u32) { + if key.is_prepared_for(slot) { + return; + } + info!(validator_id, slot, "Advancing XMSS key preparation window"); + let start = Instant::now(); + while !key.is_prepared_for(slot) { + let before = key.get_prepared_interval(); + key.advance_preparation(); + if key.get_prepared_interval() == before { + warn!(validator_id, slot, "XMSS key activation interval exhausted"); + break; + } + } + info!( + validator_id, + slot, + elapsed_ms = start.elapsed().as_millis() as u64, + "Advanced XMSS key preparation window" + ); +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 28390c3f..f07a7a7e 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -64,7 +64,19 @@ impl BlockChain { metrics::set_is_aggregator(aggregator.is_enabled()); metrics::set_node_sync_status(metrics::SyncStatus::Idle); let genesis_time = store.config().genesis_time; - let key_manager = key_manager::KeyManager::new(validator_keys); + let mut key_manager = key_manager::KeyManager::new(validator_keys); + + // Catch XMSS keys up to the current slot before the first tick + // store.time() doesn't work here: after an offline gap it lags wall-clock by + // exactly the gap we need to catch up through + let now_ms = SystemTime::UNIX_EPOCH + .elapsed() + .expect("already past the unix epoch") + .as_millis() as u64; + let current_slot = + (now_ms.saturating_sub(genesis_time * 1000) / MILLISECONDS_PER_SLOT) as u32; + key_manager.advance_keys_to(current_slot); + let handle = BlockChainServer { store, p2p: None, @@ -195,6 +207,9 @@ impl BlockChainServer { metrics::update_safe_target_slot(self.store.safe_target_slot()); // Update head slot metric (head may change when attestations are promoted at intervals 0/4) metrics::update_head_slot(self.store.head_slot()); + + // Advance XMSS keys for next slot so the signing paths don't have to + self.key_manager.advance_keys_to((slot + 1) as u32); } /// Kick off a committee-signature aggregation session: