|
| 1 | +use std::sync::Arc; |
| 2 | + |
| 3 | +use ethportal_api::{ |
| 4 | + consensus::{ |
| 5 | + header::BeaconBlockHeader, |
| 6 | + historical_summaries::{HistoricalSummaries, HistoricalSummary}, |
| 7 | + }, |
| 8 | + light_client::{ |
| 9 | + finality_update::LightClientFinalityUpdate, |
| 10 | + header::{LightClientHeader, LightClientHeaderElectra}, |
| 11 | + optimistic_update::LightClientOptimisticUpdate, |
| 12 | + }, |
| 13 | +}; |
| 14 | +use parking_lot::RwLock; |
| 15 | +use ssz::Decode; |
| 16 | +use store::ChainHeadStore; |
| 17 | + |
| 18 | +use crate::TrinValidationAssets; |
| 19 | + |
| 20 | +mod store; |
| 21 | + |
| 22 | +/// Responsible for maintaining and providing the data at the head of the chain. |
| 23 | +/// |
| 24 | +/// This structure has some main properties: |
| 25 | +/// - easy to clone and pass around |
| 26 | +/// - none of the functions is async or blocking |
| 27 | +#[derive(Clone)] |
| 28 | +pub struct ChainHead { |
| 29 | + store: Arc<RwLock<ChainHeadStore>>, |
| 30 | +} |
| 31 | + |
| 32 | +impl ChainHead { |
| 33 | + /// Creates new istance that assumes that head of the chain is first Pectra block. |
| 34 | + pub fn new_pectra_defaults() -> Self { |
| 35 | + let pectra_header = pectra_light_client_header(); |
| 36 | + Self { |
| 37 | + store: Arc::new(RwLock::new(ChainHeadStore::new( |
| 38 | + pectra_header.clone(), |
| 39 | + pectra_header, |
| 40 | + pectra_historical_summaries(), |
| 41 | + ))), |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + /// Returns the latest beacon block header. |
| 46 | + pub fn latest_beacon_header(&self) -> BeaconBlockHeader { |
| 47 | + self.store.read().latest.beacon().clone() |
| 48 | + } |
| 49 | + |
| 50 | + /// Returns the latest finalized beacon block header. |
| 51 | + pub fn finalized_beacon_header(&self) -> BeaconBlockHeader { |
| 52 | + self.store.read().finalized.beacon().clone() |
| 53 | + } |
| 54 | + |
| 55 | + /// Returns [HistoricalSummary] that corresponds to the provided slot. |
| 56 | + /// |
| 57 | + /// It returns `None` if slot is pre Capella, or is not yet known. |
| 58 | + pub fn historical_summary(&self, slot: u64) -> Option<HistoricalSummary> { |
| 59 | + self.store.read().historical_summary(slot) |
| 60 | + } |
| 61 | + |
| 62 | + /// Updates the latest beacon block headers, if newer. |
| 63 | + /// |
| 64 | + /// This functions assumes that update is valid. |
| 65 | + pub fn process_optimistic_update(&self, update: LightClientOptimisticUpdate) { |
| 66 | + self.store |
| 67 | + .write() |
| 68 | + .update_latest_if_newer(update.attested_header()); |
| 69 | + } |
| 70 | + |
| 71 | + /// Updates the latest finalized beacon block headers, if newer. |
| 72 | + /// |
| 73 | + /// This functions assumes that update is valid. |
| 74 | + pub fn process_finalized_update(&self, update: LightClientFinalityUpdate) { |
| 75 | + let mut store = self.store.write(); |
| 76 | + store.update_latest_if_newer(update.attested_header()); |
| 77 | + store.update_finalized_if_newer(update.finalized_header()); |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +/// Returns the [LightClientHeader] that corresponds to the first Pectra block. |
| 82 | +/// |
| 83 | +/// Value is embedded in the codebase. |
| 84 | +fn pectra_light_client_header() -> LightClientHeader { |
| 85 | + let header: LightClientHeaderElectra = serde_yaml::from_reader( |
| 86 | + TrinValidationAssets::get("validation_assets/chain_head/pectra_light_client_header.yaml") |
| 87 | + .expect("Should be able to load embedded light client header") |
| 88 | + .data |
| 89 | + .as_ref(), |
| 90 | + ) |
| 91 | + .expect("Should be able to deserialize embedded light client header"); |
| 92 | + LightClientHeader::Electra(header) |
| 93 | +} |
| 94 | + |
| 95 | +/// Returns [HistoricalSummaries] that are created at the first Pectra block. |
| 96 | +/// |
| 97 | +/// Value is embedded in the codebase. |
| 98 | +fn pectra_historical_summaries() -> HistoricalSummaries { |
| 99 | + HistoricalSummaries::from_ssz_bytes( |
| 100 | + &TrinValidationAssets::get("validation_assets/chain_head/pectra_historical_summaries.ssz") |
| 101 | + .expect("Should be able to load embedded historical summaries") |
| 102 | + .data, |
| 103 | + ) |
| 104 | + .expect("Should be able to decode embedded historical summaries") |
| 105 | +} |
| 106 | + |
| 107 | +#[cfg(test)] |
| 108 | +mod tests { |
| 109 | + use alloy::primitives::b256; |
| 110 | + use ethportal_api::consensus::{ |
| 111 | + constants::{ELECTRA_FORK_EPOCH, SLOTS_PER_EPOCH}, |
| 112 | + historical_summaries::{historical_summary_index, HistoricalSummaries}, |
| 113 | + }; |
| 114 | + use tree_hash::TreeHash; |
| 115 | + use trin_utils::submodules::read_ssz_portal_spec_tests_file; |
| 116 | + |
| 117 | + #[test] |
| 118 | + fn pectra_light_client_header() { |
| 119 | + let header = super::pectra_light_client_header(); |
| 120 | + assert_eq!(header.beacon().slot, ELECTRA_FORK_EPOCH * SLOTS_PER_EPOCH); |
| 121 | + assert_eq!( |
| 122 | + header.beacon().tree_hash_root(), |
| 123 | + b256!("0x9c30624f15e4df4e8f819f89db8b930f36b561b7f70905688ea208d22fb0b822") |
| 124 | + ); |
| 125 | + } |
| 126 | + |
| 127 | + #[test] |
| 128 | + fn pectra_historical_summaries() { |
| 129 | + let historical_summaries = super::pectra_historical_summaries(); |
| 130 | + |
| 131 | + // Check that it has expected number of historical summaries |
| 132 | + let electra_slot = ELECTRA_FORK_EPOCH * SLOTS_PER_EPOCH; |
| 133 | + let expected_historical_summaries_count = historical_summary_index(electra_slot).unwrap(); |
| 134 | + assert_eq!( |
| 135 | + historical_summaries.len(), |
| 136 | + expected_historical_summaries_count |
| 137 | + ); |
| 138 | + |
| 139 | + // Check that it partially matches historical summaries from portal_spec_tests repo |
| 140 | + let historical_summaries_from_spec_test: HistoricalSummaries = read_ssz_portal_spec_tests_file( |
| 141 | + "tests/mainnet/history/headers_with_proof/beacon_data/historical_summaries_at_slot_11476992.ssz", |
| 142 | + ).unwrap(); |
| 143 | + |
| 144 | + let common_historical_summaries = usize::min( |
| 145 | + historical_summaries.len(), |
| 146 | + historical_summaries_from_spec_test.len(), |
| 147 | + ); |
| 148 | + |
| 149 | + assert_eq!( |
| 150 | + historical_summaries[..common_historical_summaries], |
| 151 | + historical_summaries_from_spec_test[..common_historical_summaries] |
| 152 | + ); |
| 153 | + } |
| 154 | +} |
0 commit comments