diff --git a/core/src/block_creation_loop.rs b/core/src/block_creation_loop.rs index 8ba2fdd9af..d81b5dd5fb 100644 --- a/core/src/block_creation_loop.rs +++ b/core/src/block_creation_loop.rs @@ -30,6 +30,7 @@ use { bank::{Bank, NewBankOptions}, bank_forks::BankForks, block_component_processor::BlockComponentProcessor, + validated_block_footer::ValidatedBlockFooter, }, solana_version::version, solana_votor::{common::block_timeout, event::LeaderWindowInfo}, @@ -464,13 +465,15 @@ fn record_and_complete_block( let working_bank = w_poh_recorder.working_bank().unwrap(); let footer = produce_block_footer(working_bank.bank.clone_without_scheduler()); + let footer = ValidatedBlockFooter::new_unchecked_for_block_producer(footer); + BlockComponentProcessor::update_bank_with_footer( working_bank.bank.clone_without_scheduler(), &footer, ); drop(bank); - w_poh_recorder.tick_alpenglow(max_tick_height, footer); + w_poh_recorder.tick_alpenglow(max_tick_height, footer.into()); Ok(()) } diff --git a/runtime/src/block_component_processor.rs b/runtime/src/block_component_processor.rs index 805cd99e85..478199fd4c 100644 --- a/runtime/src/block_component_processor.rs +++ b/runtime/src/block_component_processor.rs @@ -1,9 +1,11 @@ use { - crate::bank::Bank, + crate::{ + bank::Bank, + validated_block_footer::{ValidatedBlockFooter, ValidatedBlockFooterError}, + }, solana_clock::{Slot, DEFAULT_MS_PER_SLOT}, solana_entry::block_component::{ - BlockFooterV1, BlockMarkerV1, VersionedBlockFooter, VersionedBlockHeader, - VersionedBlockMarker, + BlockMarkerV1, VersionedBlockFooter, VersionedBlockHeader, VersionedBlockMarker, }, solana_votor_messages::migration::MigrationStatus, std::sync::Arc, @@ -22,8 +24,8 @@ pub enum BlockComponentProcessorError { MultipleBlockHeaders, #[error("BlockComponent detected pre-migration")] BlockComponentPreMigration, - #[error("Nanosecond clock out of bounds")] - NanosecondClockOutOfBounds, + #[error("invalid block footer")] + InvalidBlockFooter(#[from] ValidatedBlockFooterError), } #[derive(Default)] @@ -119,9 +121,9 @@ impl BlockComponentProcessor { let footer = match footer { VersionedBlockFooter::V1(footer) | VersionedBlockFooter::Current(footer) => footer, }; - - Self::enforce_nanosecond_clock_bounds(bank.clone(), parent_bank.clone(), footer)?; - Self::update_bank_with_footer(bank, footer); + let validated_block_footer = + ValidatedBlockFooter::try_new(&parent_bank, &bank, footer.clone())?; + Self::update_bank_with_footer(bank, &validated_block_footer); self.has_footer = true; Ok(()) @@ -139,34 +141,6 @@ impl BlockComponentProcessor { Ok(()) } - fn enforce_nanosecond_clock_bounds( - bank: Arc, - parent_bank: Arc, - footer: &BlockFooterV1, - ) -> Result<(), BlockComponentProcessorError> { - // Get parent time from nanosecond clock account - // If nanosecond clock hasn't been populated, don't enforce the bounds; note that the - // nanosecond clock is populated as soon as Alpenglow migration is complete. - let Some(parent_time_nanos) = parent_bank.get_nanosecond_clock() else { - return Ok(()); - }; - - let parent_slot = parent_bank.slot(); - let current_time_nanos = footer.block_producer_time_nanos as i64; - let current_slot = bank.slot(); - - let (lower_bound_nanos, upper_bound_nanos) = - Self::nanosecond_time_bounds(parent_slot, parent_time_nanos, current_slot); - - let is_valid = - lower_bound_nanos <= current_time_nanos && current_time_nanos <= upper_bound_nanos; - - match is_valid { - true => Ok(()), - false => Err(BlockComponentProcessorError::NanosecondClockOutOfBounds), - } - } - /// Given the parent slot, parent time, and slot, calculate the lower and upper /// bounds for the block producer time. We return (lower_bound, upper_bound), where both bounds /// are inclusive. I.e., the working bank time is valid if @@ -189,9 +163,9 @@ impl BlockComponentProcessor { (min_working_bank_time, max_working_bank_time) } - pub fn update_bank_with_footer(bank: Arc, footer: &BlockFooterV1) { + pub fn update_bank_with_footer(bank: Arc, footer: &ValidatedBlockFooter) { // Update clock sysvar - bank.update_clock_from_footer(footer.block_producer_time_nanos as i64); + bank.update_clock_from_footer(footer.block_producer_time_ns()); // TODO: rewards } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a15bfba9a4..b808ac6353 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -39,6 +39,7 @@ pub mod stakes; pub mod static_ids; pub mod status_cache; pub mod transaction_batch; +pub mod validated_block_footer; pub mod vote_sender_types; #[macro_use] diff --git a/runtime/src/validated_block_footer.rs b/runtime/src/validated_block_footer.rs new file mode 100644 index 0000000000..647fb5cea9 --- /dev/null +++ b/runtime/src/validated_block_footer.rs @@ -0,0 +1,126 @@ +use { + crate::{bank::Bank, block_component_processor::BlockComponentProcessor}, + solana_entry::block_component::BlockFooterV1, + solana_hash::Hash, + thiserror::Error, +}; + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum ValidatedBlockFooterError { + #[error("clock out of bounds")] + ClockOutOfBounds, + #[error("clock overflow")] + ClockOverflow, + #[error("bank hash mismatch")] + BankHashMismatch, +} + +pub struct ValidatedBlockFooter { + bank_hash: Hash, + block_producer_time_ns: i64, + block_user_agent: Vec, +} + +impl ValidatedBlockFooter { + /// Creates a new [`ValidatedBlockFooter`] for the block the block producer. + /// + /// Warning! No validation checks are performed, this should not be called during replay but only during block production. + pub fn new_unchecked_for_block_producer(footer: BlockFooterV1) -> Self { + let BlockFooterV1 { + bank_hash, + block_producer_time_nanos, + block_user_agent, + } = footer; + Self { + bank_hash, + block_producer_time_ns: block_producer_time_nanos.try_into().unwrap(), + block_user_agent, + } + } + + /// Creates a [`ValidatedBlockFooter`] and fails if the footer is found to be invalid. + pub fn try_new( + parent_bank: &Bank, + bank: &Bank, + footer: BlockFooterV1, + ) -> Result { + let BlockFooterV1 { + bank_hash, + block_producer_time_nanos, + block_user_agent, + } = footer; + let block_producer_time_ns = validate_clock(parent_bank, bank, block_producer_time_nanos)?; + if bank_hash != bank.hash() { + return Err(ValidatedBlockFooterError::BankHashMismatch); + } + Ok(Self { + bank_hash, + block_producer_time_ns, + block_user_agent, + }) + } + + pub fn block_producer_time_ns(&self) -> i64 { + self.block_producer_time_ns + } + + pub fn bank_hash(&self) -> Hash { + self.bank_hash + } + + pub fn block_user_agent(&self) -> &[u8] { + &self.block_user_agent + } +} + +impl From for BlockFooterV1 { + fn from(footer: ValidatedBlockFooter) -> Self { + let ValidatedBlockFooter { + bank_hash, + block_producer_time_ns, + block_user_agent, + } = footer; + let block_producer_time_nanos = block_producer_time_ns + .try_into() + .expect("i64 to u64 should not overflow"); + BlockFooterV1 { + bank_hash, + block_producer_time_nanos, + block_user_agent, + } + } +} + +fn validate_clock( + parent_bank: &Bank, + bank: &Bank, + block_producer_time_ns: u64, +) -> Result { + let block_producer_time_ns = block_producer_time_ns + .try_into() + .map_err(|_| ValidatedBlockFooterError::ClockOverflow)?; + + // Get parent time from nanosecond clock account + // If nanosecond clock hasn't been populated, don't enforce the bounds; note that the + // nanosecond clock is populated as soon as Alpenglow migration is complete. + let Some(parent_time_nanos) = parent_bank.get_nanosecond_clock() else { + return Ok(block_producer_time_ns); + }; + + let parent_slot = parent_bank.slot(); + let current_slot = bank.slot(); + + let (lower_bound_ns, upper_bound_ns) = BlockComponentProcessor::nanosecond_time_bounds( + parent_slot, + parent_time_nanos, + current_slot, + ); + + let is_valid = + lower_bound_ns <= block_producer_time_ns && block_producer_time_ns <= upper_bound_ns; + + match is_valid { + true => Ok(block_producer_time_ns), + false => Err(ValidatedBlockFooterError::ClockOutOfBounds), + } +}