diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 55d6ae29efb..b23ed9b5f1b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1026,6 +1026,18 @@ impl BeaconChain { Ok(self.store.get_state(state_root, slot)?) } + /// Returns stored light client update at the given sync_committee_period, if any. + /// + /// ## Errors + /// + /// May return a database error. + pub fn get_light_client_update( + &self, + sync_committee_period: u64, + ) -> Result>, Error> { + Ok(self.store.get_light_client_update(sync_committee_period)?) + } + /// Return the sync committee at `slot + 1` from the canonical chain. /// /// This is useful when dealing with sync committee messages, because messages are signed diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 7c431ebccca..f5d6d69a026 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -6,7 +6,8 @@ use slot_clock::SlotClock; use std::time::Duration; use strum::AsRefStr; use types::{ - light_client_update::Error as LightClientUpdateError, LightClientFinalityUpdate, Slot, + light_client_update::Error as LightClientUpdateError, LightClientFinalityUpdate, + LightClientUpdate, Slot, }; /// Returned when a light client finality update was not successfully verified. It might not have been verified for @@ -69,7 +70,7 @@ impl VerifiedLightClientFinalityUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_finality_slot = light_client_finality_update.finalized_header.slot; + let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon.slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_finality_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -90,7 +91,7 @@ impl VerifiedLightClientFinalityUpdate { .get_blinded_block(&finalized_block_root)? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() { - Some(update) => update.finalized_header.slot, + Some(update) => update.finalized_header.beacon.slot, None => Slot::new(0), }; @@ -112,13 +113,15 @@ impl VerifiedLightClientFinalityUpdate { } let head_state = &head.snapshot.beacon_state; - let finality_update = LightClientFinalityUpdate::new( + let update = LightClientUpdate::new( &chain.spec, head_state, - head_block, + &head_block.clone_as_blinded(), &mut attested_state, - &finalized_block, + &attested_block, + Some(finalized_block), )?; + let finality_update = LightClientFinalityUpdate::from_light_client_update(update); // verify that the gossiped finality update is the same as the locally constructed one. if finality_update != light_client_finality_update { diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index 20d7181808a..dcf65bcdc29 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -7,7 +7,8 @@ use slot_clock::SlotClock; use std::time::Duration; use strum::AsRefStr; use types::{ - light_client_update::Error as LightClientUpdateError, LightClientOptimisticUpdate, Slot, + light_client_update::Error as LightClientUpdateError, LightClientOptimisticUpdate, + LightClientUpdate, Slot, }; /// Returned when a light client optimistic update was not successfully verified. It might not have been verified for @@ -73,7 +74,7 @@ impl VerifiedLightClientOptimisticUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.slot; + let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon.slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_optimistic_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -86,11 +87,11 @@ impl VerifiedLightClientOptimisticUpdate { .get_blinded_block(&attested_block_root)? .ok_or(Error::FailedConstructingUpdate)?; - let attested_state = chain + let mut attested_state = chain .get_state(&attested_block.state_root(), Some(attested_block.slot()))? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_optimistic_update_slot = match latest_seen_optimistic_update.as_ref() { - Some(update) => update.attested_header.slot, + Some(update) => update.attested_header.beacon.slot, None => Slot::new(0), }; @@ -115,14 +116,23 @@ impl VerifiedLightClientOptimisticUpdate { // otherwise queue let canonical_root = light_client_optimistic_update .attested_header + .beacon .canonical_root(); if canonical_root != head_block.message().parent_root() { return Err(Error::UnknownBlockParentRoot(canonical_root)); } - let optimistic_update = - LightClientOptimisticUpdate::new(&chain.spec, head_block, &attested_state)?; + let head_state = &head.snapshot.beacon_state; + let update = LightClientUpdate::new( + &chain.spec, + head_state, + &head_block.clone_as_blinded(), + &mut attested_state, + &attested_block, + None, + )?; + let optimistic_update = LightClientOptimisticUpdate::from_light_client_update(update); // verify that the gossiped optimistic update is the same as the locally constructed one. if optimistic_update != light_client_optimistic_update { diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index 73906b1b586..e8d19f48a93 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -114,6 +114,7 @@ pub fn migrate_schema( Ok(()) } + (SchemaVersion(14), SchemaVersion(15)) => db.store_schema_version_atomically(to, vec![]), // Anything else is an error. (_, _) => Err(HotColdDBError::UnsupportedSchemaVersion { target_version: to, diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index 89670a2eb3c..88d6a835a47 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -502,6 +502,7 @@ impl PeerManager { Protocol::BlocksByRange => PeerAction::MidToleranceError, Protocol::BlocksByRoot => PeerAction::MidToleranceError, Protocol::LightClientBootstrap => PeerAction::LowToleranceError, + Protocol::LightClientUpdatesByRange => PeerAction::LowToleranceError, Protocol::Goodbye => PeerAction::LowToleranceError, Protocol::MetaData => PeerAction::LowToleranceError, Protocol::Status => PeerAction::LowToleranceError, @@ -519,6 +520,7 @@ impl PeerManager { Protocol::BlocksByRoot => return, Protocol::Goodbye => return, Protocol::LightClientBootstrap => return, + Protocol::LightClientUpdatesByRange => return, Protocol::MetaData => PeerAction::LowToleranceError, Protocol::Status => PeerAction::LowToleranceError, } @@ -534,6 +536,7 @@ impl PeerManager { Protocol::BlocksByRange => PeerAction::MidToleranceError, Protocol::BlocksByRoot => PeerAction::MidToleranceError, Protocol::LightClientBootstrap => return, + Protocol::LightClientUpdatesByRange => return, Protocol::Goodbye => return, Protocol::MetaData => return, Protocol::Status => return, diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index eccbf0dd623..d2c07840e40 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -17,7 +17,8 @@ use std::sync::Arc; use tokio_util::codec::{Decoder, Encoder}; use types::{ light_client_bootstrap::LightClientBootstrap, EthSpec, ForkContext, ForkName, Hash256, - SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockMerge, + LightClientUpdate, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, + SignedBeaconBlockMerge, }; use unsigned_varint::codec::Uvi; @@ -71,6 +72,7 @@ impl Encoder> for SSZSnappyInboundCodec< RPCResponse::BlocksByRange(res) => res.as_ssz_bytes(), RPCResponse::BlocksByRoot(res) => res.as_ssz_bytes(), RPCResponse::LightClientBootstrap(res) => res.as_ssz_bytes(), + RPCResponse::LightClientUpdatesByRange(res) => res.as_ssz_bytes(), RPCResponse::Pong(res) => res.data.as_ssz_bytes(), RPCResponse::MetaData(res) => // Encode the correct version of the MetaData response based on the negotiated version. @@ -232,6 +234,7 @@ impl Encoder> for SSZSnappyOutboundCodec< OutboundRequest::Ping(req) => req.as_ssz_bytes(), OutboundRequest::MetaData(_) => return Ok(()), // no metadata to encode OutboundRequest::LightClientBootstrap(req) => req.as_ssz_bytes(), + OutboundRequest::LightClientUpdatesByRange(req) => req.as_ssz_bytes(), }; // SSZ encoded bytes should be within `max_packet_size` if bytes.len() > self.max_packet_size { @@ -479,6 +482,9 @@ fn handle_v1_request( root: Hash256::from_ssz_bytes(decoded_buffer)?, }, ))), + Protocol::LightClientUpdatesByRange => Ok(Some(InboundRequest::LightClientUpdatesByRange( + LightClientUpdatesByRangeRequest::from_ssz_bytes(decoded_buffer)?, + ))), // MetaData requests return early from InboundUpgrade and do not reach the decoder. // Handle this case just for completeness. Protocol::MetaData => { @@ -553,6 +559,9 @@ fn handle_v1_response( Protocol::LightClientBootstrap => Ok(Some(RPCResponse::LightClientBootstrap( LightClientBootstrap::from_ssz_bytes(decoded_buffer)?, ))), + Protocol::LightClientUpdatesByRange => Ok(Some(RPCResponse::LightClientUpdatesByRange( + Arc::new(LightClientUpdate::from_ssz_bytes(decoded_buffer)?), + ))), } } @@ -879,6 +888,9 @@ mod tests { OutboundRequest::LightClientBootstrap(bootstrap) => { assert_eq!(decoded, InboundRequest::LightClientBootstrap(bootstrap)) } + OutboundRequest::LightClientUpdatesByRange(urange) => { + assert_eq!(decoded, InboundRequest::LightClientUpdatesByRange(urange)) + } } } } diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 5da595c3db7..d8441962a85 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -5,7 +5,7 @@ use regex::bytes::Regex; use serde::Serialize; use ssz_derive::{Decode, Encode}; use ssz_types::{ - typenum::{U1024, U256}, + typenum::{U1024, U128, U256}, VariableList, }; use std::ops::Deref; @@ -13,13 +13,18 @@ use std::sync::Arc; use strum::IntoStaticStr; use superstruct::superstruct; use types::{ - light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot, + light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, Hash256, LightClientUpdate, + SignedBeaconBlock, Slot, }; /// Maximum number of blocks in a single request. pub type MaxRequestBlocks = U1024; pub const MAX_REQUEST_BLOCKS: u64 = 1024; +/// Maximum number of light client updates in a single request. +pub type MaxRequestLightClientUpdates = U128; +pub const MAX_REQUEST_LIGHT_CLIENT_UPDATES: u64 = 128; + /// Maximum length of error message. pub type MaxErrorLen = U256; pub const MAX_ERROR_LEN: u64 = 256; @@ -248,6 +253,10 @@ pub enum RPCResponse { /// A response to a get LIGHTCLIENT_BOOTSTRAP request. LightClientBootstrap(LightClientBootstrap), + /// A response to a get LIGHTCLIENT_UPDATES_BY_RANGE request. A None signifies + /// the end of the batch. + LightClientUpdatesByRange(Arc>), + /// A PONG response to a PING request. Pong(Ping), @@ -263,6 +272,9 @@ pub enum ResponseTermination { /// Blocks by root stream termination. BlocksByRoot, + + /// Light client updates by range stream termination. + LightClientUpdatesByRange, } /// The structured response containing a result/code indicating success or failure @@ -284,6 +296,13 @@ pub struct LightClientBootstrapRequest { pub root: Hash256, } +/// Request `LightClientUpdate`s by range for lightclients peers. +#[derive(Encode, Decode, Clone, Debug, PartialEq)] +pub struct LightClientUpdatesByRangeRequest { + pub start_period: u64, + pub count: u64, +} + /// The code assigned to an erroneous `RPCResponse`. #[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)] #[strum(serialize_all = "snake_case")] @@ -333,6 +352,7 @@ impl RPCCodedResponse { RPCResponse::Pong(_) => false, RPCResponse::MetaData(_) => false, RPCResponse::LightClientBootstrap(_) => false, + RPCResponse::LightClientUpdatesByRange(_) => true, }, RPCCodedResponse::Error(_, _) => true, // Stream terminations are part of responses that have chunks @@ -368,6 +388,7 @@ impl RPCResponse { RPCResponse::Pong(_) => Protocol::Ping, RPCResponse::MetaData(_) => Protocol::MetaData, RPCResponse::LightClientBootstrap(_) => Protocol::LightClientBootstrap, + RPCResponse::LightClientUpdatesByRange(_) => Protocol::LightClientUpdatesByRange, } } } @@ -404,7 +425,18 @@ impl std::fmt::Display for RPCResponse { RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data), RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()), RPCResponse::LightClientBootstrap(bootstrap) => { - write!(f, "LightClientBootstrap Slot: {}", bootstrap.header.slot) + write!( + f, + "LightClientBootstrap Slot: {}", + bootstrap.header.beacon.slot + ) + } + RPCResponse::LightClientUpdatesByRange(update) => { + write!( + f, + "LightClientUpdatesByRange: Signature slot: {}", + update.signature_slot + ) } } } diff --git a/beacon_node/lighthouse_network/src/rpc/mod.rs b/beacon_node/lighthouse_network/src/rpc/mod.rs index 203a642a8be..9ecaa16c117 100644 --- a/beacon_node/lighthouse_network/src/rpc/mod.rs +++ b/beacon_node/lighthouse_network/src/rpc/mod.rs @@ -27,7 +27,8 @@ pub(crate) use protocol::{InboundRequest, RPCProtocol}; pub use handler::SubstreamId; pub use methods::{ BlocksByRangeRequest, BlocksByRootRequest, GoodbyeReason, LightClientBootstrapRequest, - MaxRequestBlocks, RPCResponseErrorCode, ResponseTermination, StatusMessage, MAX_REQUEST_BLOCKS, + LightClientUpdatesByRangeRequest, MaxRequestBlocks, RPCResponseErrorCode, ResponseTermination, + StatusMessage, MAX_REQUEST_BLOCKS, MAX_REQUEST_LIGHT_CLIENT_UPDATES, }; pub(crate) use outbound::OutboundRequest; pub use protocol::{max_rpc_size, Protocol, RPCError}; @@ -132,6 +133,11 @@ impl RPC { Duration::from_secs(10), ) .n_every(Protocol::BlocksByRoot, 128, Duration::from_secs(10)) + .n_every( + Protocol::LightClientUpdatesByRange, + methods::MAX_REQUEST_LIGHT_CLIENT_UPDATES, + Duration::from_secs(10), + ) .build() .expect("Configuration parameters are valid"); RPC { @@ -301,6 +307,9 @@ where match end { ResponseTermination::BlocksByRange => Protocol::BlocksByRange, ResponseTermination::BlocksByRoot => Protocol::BlocksByRoot, + ResponseTermination::LightClientUpdatesByRange => { + Protocol::LightClientUpdatesByRange + } }, ), }, diff --git a/beacon_node/lighthouse_network/src/rpc/outbound.rs b/beacon_node/lighthouse_network/src/rpc/outbound.rs index 774303800e8..41d41758a86 100644 --- a/beacon_node/lighthouse_network/src/rpc/outbound.rs +++ b/beacon_node/lighthouse_network/src/rpc/outbound.rs @@ -39,6 +39,7 @@ pub enum OutboundRequest { BlocksByRange(OldBlocksByRangeRequest), BlocksByRoot(BlocksByRootRequest), LightClientBootstrap(LightClientBootstrapRequest), + LightClientUpdatesByRange(LightClientUpdatesByRangeRequest), Ping(Ping), MetaData(PhantomData), } @@ -85,10 +86,11 @@ impl OutboundRequest { ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy), ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy), ], - // Note: This match arm is technically unreachable as we only respond to light client requests + // Note: Light client match arms are technically unreachable as we only respond to light client requests // that we generate from the beacon state. // We do not make light client rpc requests from the beacon node OutboundRequest::LightClientBootstrap(_) => vec![], + OutboundRequest::LightClientUpdatesByRange(_) => vec![], } } /* These functions are used in the handler for stream management */ @@ -103,6 +105,7 @@ impl OutboundRequest { OutboundRequest::Ping(_) => 1, OutboundRequest::MetaData(_) => 1, OutboundRequest::LightClientBootstrap(_) => 1, + OutboundRequest::LightClientUpdatesByRange(_) => 1, } } @@ -116,6 +119,7 @@ impl OutboundRequest { OutboundRequest::Ping(_) => Protocol::Ping, OutboundRequest::MetaData(_) => Protocol::MetaData, OutboundRequest::LightClientBootstrap(_) => Protocol::LightClientBootstrap, + OutboundRequest::LightClientUpdatesByRange(_) => Protocol::LightClientUpdatesByRange, } } @@ -128,6 +132,7 @@ impl OutboundRequest { OutboundRequest::BlocksByRange(_) => ResponseTermination::BlocksByRange, OutboundRequest::BlocksByRoot(_) => ResponseTermination::BlocksByRoot, OutboundRequest::LightClientBootstrap(_) => unreachable!(), + OutboundRequest::LightClientUpdatesByRange(_) => unreachable!(), OutboundRequest::Status(_) => unreachable!(), OutboundRequest::Goodbye(_) => unreachable!(), OutboundRequest::Ping(_) => unreachable!(), @@ -186,7 +191,10 @@ impl std::fmt::Display for OutboundRequest { OutboundRequest::Ping(ping) => write!(f, "Ping: {}", ping.data), OutboundRequest::MetaData(_) => write!(f, "MetaData request"), OutboundRequest::LightClientBootstrap(bootstrap) => { - write!(f, "Lightclient Bootstrap: {}", bootstrap.root) + write!(f, "Light client bootstrap: {}", bootstrap.root) + } + OutboundRequest::LightClientUpdatesByRange(req) => { + write!(f, "Light client updates by range: {:?}", req) } } } diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index 1f40f81971c..3055aa7f3a6 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -155,6 +155,8 @@ pub enum Protocol { MetaData, /// The `LightClientBootstrap` protocol name. LightClientBootstrap, + /// The `LightClientUpdatesByRange` protocol name. + LightClientUpdatesByRange, } /// RPC Versions @@ -182,6 +184,7 @@ impl std::fmt::Display for Protocol { Protocol::Ping => "ping", Protocol::MetaData => "metadata", Protocol::LightClientBootstrap => "light_client_bootstrap", + Protocol::LightClientUpdatesByRange => "light_client_updates_by_range", }; f.write_str(repr) } @@ -238,6 +241,11 @@ impl UpgradeInfo for RPCProtocol { Version::V1, Encoding::SSZSnappy, )); + supported_protocols.push(ProtocolId::new( + Protocol::LightClientUpdatesByRange, + Version::V1, + Encoding::SSZSnappy, + )); } supported_protocols } @@ -305,6 +313,10 @@ impl ProtocolId { ::ssz_fixed_len(), ::ssz_fixed_len(), ), + Protocol::LightClientUpdatesByRange => RpcLimits::new( + ::ssz_fixed_len(), + ::ssz_fixed_len(), + ), Protocol::MetaData => RpcLimits::new(0, 0), // Metadata requests are empty } } @@ -332,6 +344,9 @@ impl ProtocolId { ::ssz_fixed_len(), ::ssz_fixed_len(), ), + Protocol::LightClientUpdatesByRange => { + rpc_block_limits_by_fork(fork_context.current_fork()) + } } } @@ -438,6 +453,7 @@ pub enum InboundRequest { BlocksByRange(OldBlocksByRangeRequest), BlocksByRoot(BlocksByRootRequest), LightClientBootstrap(LightClientBootstrapRequest), + LightClientUpdatesByRange(LightClientUpdatesByRangeRequest), Ping(Ping), MetaData(PhantomData), } @@ -456,6 +472,7 @@ impl InboundRequest { InboundRequest::Ping(_) => 1, InboundRequest::MetaData(_) => 1, InboundRequest::LightClientBootstrap(_) => 1, + InboundRequest::LightClientUpdatesByRange(req) => req.count, } } @@ -469,6 +486,7 @@ impl InboundRequest { InboundRequest::Ping(_) => Protocol::Ping, InboundRequest::MetaData(_) => Protocol::MetaData, InboundRequest::LightClientBootstrap(_) => Protocol::LightClientBootstrap, + InboundRequest::LightClientUpdatesByRange(_) => Protocol::LightClientUpdatesByRange, } } @@ -485,6 +503,9 @@ impl InboundRequest { InboundRequest::Ping(_) => unreachable!(), InboundRequest::MetaData(_) => unreachable!(), InboundRequest::LightClientBootstrap(_) => unreachable!(), + InboundRequest::LightClientUpdatesByRange(_) => { + ResponseTermination::LightClientUpdatesByRange + } } } } @@ -591,6 +612,9 @@ impl std::fmt::Display for InboundRequest { InboundRequest::LightClientBootstrap(bootstrap) => { write!(f, "LightClientBootstrap: {}", bootstrap.root) } + InboundRequest::LightClientUpdatesByRange(req) => { + write!(f, "LightClientUpdates by range: {:?}", req) + } } } } diff --git a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs index 6ba9f6e9419..d49135e3320 100644 --- a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs +++ b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs @@ -75,6 +75,8 @@ pub struct RPCRateLimiter { bbroots_rl: Limiter, /// LightClientBootstrap rate limiter. lcbootstrap_rl: Limiter, + /// LightClientUpdatesByRange rate limiter. + lcupdatesbyrange_rl: Limiter, } /// Error type for non conformant requests @@ -102,6 +104,8 @@ pub struct RPCRateLimiterBuilder { bbroots_quota: Option, /// Quota for the LightClientBootstrap protocol. lcbootstrap_quota: Option, + /// Quota for the LightClientUpdatesByRange protocol. + lcupdatesbyrange_quota: Option, } impl RPCRateLimiterBuilder { @@ -121,6 +125,7 @@ impl RPCRateLimiterBuilder { Protocol::BlocksByRange => self.bbrange_quota = q, Protocol::BlocksByRoot => self.bbroots_quota = q, Protocol::LightClientBootstrap => self.lcbootstrap_quota = q, + Protocol::LightClientUpdatesByRange => self.lcupdatesbyrange_quota = q, } self } @@ -163,7 +168,9 @@ impl RPCRateLimiterBuilder { let lcbootstrap_quote = self .lcbootstrap_quota .ok_or("LightClientBootstrap quota not specified")?; - + let lcupdatesbyrange_quota = self + .lcupdatesbyrange_quota + .ok_or("LightClientUpdatesByRange quota not specified")?; // create the rate limiters let ping_rl = Limiter::from_quota(ping_quota)?; let metadata_rl = Limiter::from_quota(metadata_quota)?; @@ -172,6 +179,7 @@ impl RPCRateLimiterBuilder { let bbroots_rl = Limiter::from_quota(bbroots_quota)?; let bbrange_rl = Limiter::from_quota(bbrange_quota)?; let lcbootstrap_rl = Limiter::from_quota(lcbootstrap_quote)?; + let lcupdatesbyrange_rl = Limiter::from_quota(lcupdatesbyrange_quota)?; // check for peers to prune every 30 seconds, starting in 30 seconds let prune_every = tokio::time::Duration::from_secs(30); @@ -186,6 +194,7 @@ impl RPCRateLimiterBuilder { bbroots_rl, bbrange_rl, lcbootstrap_rl, + lcupdatesbyrange_rl, init_time: Instant::now(), }) } @@ -210,6 +219,7 @@ impl RPCRateLimiter { Protocol::BlocksByRange => &mut self.bbrange_rl, Protocol::BlocksByRoot => &mut self.bbroots_rl, Protocol::LightClientBootstrap => &mut self.lcbootstrap_rl, + Protocol::LightClientUpdatesByRange => &mut self.lcupdatesbyrange_rl, }; check(limiter) } diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index 849a86f51ba..d940d91945a 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -1,12 +1,15 @@ use std::sync::Arc; use libp2p::core::connection::ConnectionId; -use types::{light_client_bootstrap::LightClientBootstrap, EthSpec, SignedBeaconBlock}; +use types::{ + light_client_bootstrap::LightClientBootstrap, EthSpec, LightClientUpdate, SignedBeaconBlock, +}; use crate::rpc::{ methods::{ BlocksByRangeRequest, BlocksByRootRequest, LightClientBootstrapRequest, - OldBlocksByRangeRequest, RPCCodedResponse, RPCResponse, ResponseTermination, StatusMessage, + LightClientUpdatesByRangeRequest, OldBlocksByRangeRequest, RPCCodedResponse, RPCResponse, + ResponseTermination, StatusMessage, }, OutboundRequest, SubstreamId, }; @@ -36,6 +39,8 @@ pub enum Request { BlocksByRoot(BlocksByRootRequest), // light client bootstrap request LightClientBootstrap(LightClientBootstrapRequest), + // light client updates by range request + LightClientUpdatesByRange(LightClientUpdatesByRangeRequest), } impl std::convert::From for OutboundRequest { @@ -50,6 +55,7 @@ impl std::convert::From for OutboundRequest { }) } Request::LightClientBootstrap(b) => OutboundRequest::LightClientBootstrap(b), + Request::LightClientUpdatesByRange(r) => OutboundRequest::LightClientUpdatesByRange(r), Request::Status(s) => OutboundRequest::Status(s), } } @@ -71,6 +77,8 @@ pub enum Response { BlocksByRoot(Option>>), /// A response to a LightClientUpdate request. LightClientBootstrap(LightClientBootstrap), + /// A response to a LightClientUpdatesByRange request. + LightClientUpdatesByRange(Option>>), } impl std::convert::From> for RPCCodedResponse { @@ -88,6 +96,12 @@ impl std::convert::From> for RPCCodedResponse { RPCCodedResponse::Success(RPCResponse::LightClientBootstrap(b)) } + Response::LightClientUpdatesByRange(r) => match r { + Some(b) => RPCCodedResponse::Success(RPCResponse::LightClientUpdatesByRange(b)), + None => RPCCodedResponse::StreamTermination( + ResponseTermination::LightClientUpdatesByRange, + ), + }, } } } diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 5b3598216b5..969b8fc70b3 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -995,6 +995,10 @@ impl Network { Request::BlocksByRoot { .. } => { metrics::inc_counter_vec(&metrics::TOTAL_RPC_REQUESTS, &["blocks_by_root"]) } + Request::LightClientUpdatesByRange(_) => metrics::inc_counter_vec( + &metrics::TOTAL_RPC_REQUESTS, + &["light_client_updates_by_range"], + ), } NetworkEvent::RequestReceived { peer_id, @@ -1266,6 +1270,21 @@ impl Network { ); Some(event) } + InboundRequest::LightClientUpdatesByRange(req) => { + let methods::LightClientUpdatesByRangeRequest { + start_period, + count, + } = req; + let event = self.build_request( + peer_request_id, + peer_id, + Request::LightClientUpdatesByRange(LightClientUpdatesByRangeRequest { + start_period, + count, + }), + ); + Some(event) + } } } Ok(RPCReceived::Response(id, resp)) => { @@ -1293,16 +1312,24 @@ impl Network { RPCResponse::BlocksByRoot(resp) => { self.build_response(id, peer_id, Response::BlocksByRoot(Some(resp))) } - // Should never be reached + // These should never be reached RPCResponse::LightClientBootstrap(bootstrap) => { self.build_response(id, peer_id, Response::LightClientBootstrap(bootstrap)) } + RPCResponse::LightClientUpdatesByRange(resp) => self.build_response( + id, + peer_id, + Response::LightClientUpdatesByRange(Some(resp)), + ), } } Ok(RPCReceived::EndOfStream(id, termination)) => { let response = match termination { ResponseTermination::BlocksByRange => Response::BlocksByRange(None), ResponseTermination::BlocksByRoot => Response::BlocksByRoot(None), + ResponseTermination::LightClientUpdatesByRange => { + Response::LightClientUpdatesByRange(None) + } }; self.build_response(id, peer_id, response) } diff --git a/beacon_node/lighthouse_network/tests/common.rs b/beacon_node/lighthouse_network/tests/common.rs index b67b412cfc2..432693bfca0 100644 --- a/beacon_node/lighthouse_network/tests/common.rs +++ b/beacon_node/lighthouse_network/tests/common.rs @@ -79,6 +79,7 @@ pub fn build_config(port: u16, mut boot_nodes: Vec) -> NetworkConfig { config.enr_address = Some("127.0.0.1".parse().unwrap()); config.boot_nodes_enr.append(&mut boot_nodes); config.network_dir = path.into_path(); + config.enable_light_client_server = true; // Reduce gossipsub heartbeat parameters config.gs_config = GossipsubConfigBuilder::from(config.gs_config) .heartbeat_initial_delay(Duration::from_millis(500)) diff --git a/beacon_node/network/src/beacon_processor/mod.rs b/beacon_node/network/src/beacon_processor/mod.rs index 8118443a65b..188a1a867c0 100644 --- a/beacon_node/network/src/beacon_processor/mod.rs +++ b/beacon_node/network/src/beacon_processor/mod.rs @@ -47,7 +47,9 @@ use futures::stream::{Stream, StreamExt}; use futures::task::Poll; use lighthouse_network::rpc::LightClientBootstrapRequest; use lighthouse_network::{ - rpc::{BlocksByRangeRequest, BlocksByRootRequest, StatusMessage}, + rpc::{ + BlocksByRangeRequest, BlocksByRootRequest, LightClientUpdatesByRangeRequest, StatusMessage, + }, Client, MessageId, NetworkGlobals, PeerId, PeerRequestId, }; use logging::TimeLatch; @@ -174,6 +176,10 @@ const MAX_BLOCKS_BY_ROOTS_QUEUE_LEN: usize = 1_024; /// will be stored before we start dropping them. const MAX_LIGHT_CLIENT_BOOTSTRAP_QUEUE_LEN: usize = 1_024; +/// The maximum number of queued `LightClientUpdatesByRangeRequest` objects received from the network RPC that +/// will be stored before we start dropping them. +const MAX_LIGHT_CLIENT_UPDATES_BY_RANGE_QUEUE_LEN: usize = 1_024; + /// The name of the manager tokio task. const MANAGER_TASK_NAME: &str = "beacon_processor_manager"; @@ -216,6 +222,7 @@ pub const STATUS_PROCESSING: &str = "status_processing"; pub const BLOCKS_BY_RANGE_REQUEST: &str = "blocks_by_range_request"; pub const BLOCKS_BY_ROOTS_REQUEST: &str = "blocks_by_roots_request"; pub const LIGHT_CLIENT_BOOTSTRAP_REQUEST: &str = "light_client_bootstrap"; +pub const LIGHT_CLIENT_UPDATES_BY_RANGE_REQUEST: &str = "light_client_updates_by_range"; pub const UNKNOWN_BLOCK_ATTESTATION: &str = "unknown_block_attestation"; pub const UNKNOWN_BLOCK_AGGREGATE: &str = "unknown_block_aggregate"; pub const UNKNOWN_LIGHT_CLIENT_UPDATE: &str = "unknown_light_client_update"; @@ -631,6 +638,22 @@ impl WorkEvent { } } + /// Create a new work event to process `LightClientUpdatesByRangeRequest`s from the RPC network. + pub fn light_client_updates_by_range_request( + peer_id: PeerId, + request_id: PeerRequestId, + request: LightClientUpdatesByRangeRequest, + ) -> Self { + Self { + drop_during_sync: true, + work: Work::LightClientUpdatesByRangeRequest { + peer_id, + request_id, + request, + }, + } + } + /// Get a `str` representation of the type of work this `WorkEvent` contains. pub fn work_type(&self) -> &'static str { self.work.str_id() @@ -845,6 +868,11 @@ pub enum Work { request_id: PeerRequestId, request: LightClientBootstrapRequest, }, + LightClientUpdatesByRangeRequest { + peer_id: PeerId, + request_id: PeerRequestId, + request: LightClientUpdatesByRangeRequest, + }, } impl Work { @@ -870,6 +898,7 @@ impl Work { Work::BlocksByRangeRequest { .. } => BLOCKS_BY_RANGE_REQUEST, Work::BlocksByRootsRequest { .. } => BLOCKS_BY_ROOTS_REQUEST, Work::LightClientBootstrapRequest { .. } => LIGHT_CLIENT_BOOTSTRAP_REQUEST, + Work::LightClientUpdatesByRangeRequest { .. } => LIGHT_CLIENT_UPDATES_BY_RANGE_REQUEST, Work::UnknownBlockAttestation { .. } => UNKNOWN_BLOCK_ATTESTATION, Work::UnknownBlockAggregate { .. } => UNKNOWN_BLOCK_AGGREGATE, Work::UnknownLightClientOptimisticUpdate { .. } => UNKNOWN_LIGHT_CLIENT_UPDATE, @@ -1021,6 +1050,8 @@ impl BeaconProcessor { let mut bbrange_queue = FifoQueue::new(MAX_BLOCKS_BY_RANGE_QUEUE_LEN); let mut bbroots_queue = FifoQueue::new(MAX_BLOCKS_BY_ROOTS_QUEUE_LEN); let mut lcbootstrap_queue = FifoQueue::new(MAX_LIGHT_CLIENT_BOOTSTRAP_QUEUE_LEN); + let mut lcupdatesbyrange_queue = + FifoQueue::new(MAX_LIGHT_CLIENT_UPDATES_BY_RANGE_QUEUE_LEN); // Channels for sending work to the re-process scheduler (`work_reprocessing_tx`) and to // receive them back once they are ready (`ready_work_rx`). let (ready_work_tx, ready_work_rx) = mpsc::channel(MAX_SCHEDULED_WORK_QUEUE_LEN); @@ -1370,6 +1401,9 @@ impl BeaconProcessor { Work::LightClientBootstrapRequest { .. } => { lcbootstrap_queue.push(work, work_id, &self.log) } + Work::LightClientUpdatesByRangeRequest { .. } => { + lcupdatesbyrange_queue.push(work, work_id, &self.log) + } Work::UnknownBlockAttestation { .. } => { unknown_block_attestation_queue.push(work) } @@ -1789,6 +1823,16 @@ impl BeaconProcessor { } => task_spawner.spawn_blocking(move || { worker.handle_light_client_bootstrap(peer_id, request_id, request) }), + /* + * Processing of light client updates by range requests from other peers. + */ + Work::LightClientUpdatesByRangeRequest { + peer_id, + request_id, + request, + } => task_spawner.spawn_blocking(move || { + worker.handle_light_client_updates_by_range_request(peer_id, request_id, request) + }), Work::UnknownBlockAttestation { message_id, peer_id, diff --git a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs index bfa0ea516fa..03168eea601 100644 --- a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs +++ b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs @@ -212,9 +212,9 @@ impl Worker { request: LightClientBootstrapRequest, ) { let block_root = request.root; - let state_root = match self.chain.get_blinded_block(&block_root) { + let signed_block = match self.chain.get_blinded_block(&block_root) { Ok(signed_block) => match signed_block { - Some(signed_block) => signed_block.state_root(), + Some(signed_block) => signed_block, None => { self.send_error_response( peer_id, @@ -235,7 +235,7 @@ impl Worker { return; } }; - let mut beacon_state = match self.chain.get_state(&state_root, None) { + let mut beacon_state = match self.chain.get_state(&signed_block.state_root(), None) { Ok(beacon_state) => match beacon_state { Some(state) => state, None => { @@ -258,18 +258,19 @@ impl Worker { return; } }; - let bootstrap = match LightClientBootstrap::from_beacon_state(&mut beacon_state) { - Ok(bootstrap) => bootstrap, - Err(_) => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not avaiable".into(), - request_id, - ); - return; - } - }; + let bootstrap = + match LightClientBootstrap::new(&self.chain.spec, &mut beacon_state, &signed_block) { + Ok(bootstrap) => bootstrap, + Err(_) => { + self.send_error_response( + peer_id, + RPCResponseErrorCode::ResourceUnavailable, + "Bootstrap not avaiable".into(), + request_id, + ); + return; + } + }; self.send_response( peer_id, Response::LightClientBootstrap(bootstrap), @@ -454,4 +455,106 @@ impl Worker { "load_blocks_by_range_blocks", ); } + + /// Handle a `LightClientUpdatesByRange` request from the peer. + pub fn handle_light_client_updates_by_range_request( + self, + peer_id: PeerId, + request_id: PeerRequestId, + mut req: LightClientUpdatesByRangeRequest, + ) { + debug!(self.log, "Received BlocksByRange Request"; + "peer_id" => %peer_id, + "count" => req.count, + "start_period" => req.start_period, + ); + + // Should not send more than max request blocks + if req.count > MAX_REQUEST_LIGHT_CLIENT_UPDATES { + req.count = MAX_REQUEST_LIGHT_CLIENT_UPDATES; + } + + let mut light_client_updates_sent = 0; + let mut send_response = true; + + // No async necessary here because we don't need to hit the execution layer. + for i in req.start_period..req.start_period + req.count { + match self.chain.get_light_client_update(i) { + Ok(Some(update)) => { + light_client_updates_sent += 1; + self.send_network_message(NetworkMessage::SendResponse { + peer_id, + response: Response::LightClientUpdatesByRange(Some(Arc::new(update))), + id: request_id, + }); + } + Ok(None) => { + error!( + self.log, + "Light client update is not in the store"; + "request_period" => i + ); + break; + } + Err(e) => { + error!( + self.log, + "Error fetching light client update for peer"; + "request_period" => i, + "error" => ?e + ); + + // send the stream terminator + self.send_error_response( + peer_id, + RPCResponseErrorCode::ServerError, + "Failed fetching light client updates".into(), + request_id, + ); + send_response = false; + break; + } + } + } + + let current_period = self + .chain + .slot() + .unwrap_or_else(|_| self.chain.slot_clock.genesis_slot()) + .epoch(T::EthSpec::slots_per_epoch()) + .sync_committee_period(&self.chain.spec) + .unwrap_or(0); + + if light_client_updates_sent < (req.count as usize) { + debug!( + self.log, + "LightClientUpdatesByRange outgoing response processed"; + "peer" => %peer_id, + "msg" => "Failed to return all requested light client updates", + "start_period" => req.start_period, + "current_period" => current_period, + "requested" => req.count, + "returned" => light_client_updates_sent + ); + } else { + debug!( + self.log, + "LightClientUpdatesByRange outgoing response processed"; + "peer" => %peer_id, + "start_period" => req.start_period, + "current_period" => current_period, + "requested" => req.count, + "returned" => light_client_updates_sent + ); + } + + if send_response { + // send the stream terminator + self.send_network_message(NetworkMessage::SendResponse { + peer_id, + response: Response::LightClientUpdatesByRange(None), + id: request_id, + }); + } + } } diff --git a/beacon_node/network/src/router/mod.rs b/beacon_node/network/src/router/mod.rs index ce98337cfed..fd918363d11 100644 --- a/beacon_node/network/src/router/mod.rs +++ b/beacon_node/network/src/router/mod.rs @@ -171,6 +171,9 @@ impl Router { Request::LightClientBootstrap(request) => self .processor .on_lightclient_bootstrap(peer_id, id, request), + Request::LightClientUpdatesByRange(request) => self + .processor + .on_light_client_updates_by_range_request(peer_id, id, request), } } @@ -196,6 +199,7 @@ impl Router { .on_blocks_by_root_response(peer_id, request_id, beacon_block); } Response::LightClientBootstrap(_) => unreachable!(), + Response::LightClientUpdatesByRange(_) => unreachable!(), } } diff --git a/beacon_node/network/src/router/processor.rs b/beacon_node/network/src/router/processor.rs index 999ba29e90a..2a5d15aaacb 100644 --- a/beacon_node/network/src/router/processor.rs +++ b/beacon_node/network/src/router/processor.rs @@ -173,6 +173,18 @@ impl Processor { )) } + /// Handle a `LightClientUpdatesByRange` request from the peer. + pub fn on_light_client_updates_by_range_request( + &mut self, + peer_id: PeerId, + request_id: PeerRequestId, + request: LightClientUpdatesByRangeRequest, + ) { + self.send_beacon_processor_work(BeaconWorkEvent::light_client_updates_by_range_request( + peer_id, request_id, request, + )) + } + /// Handle a `BlocksByRange` request from the peer. pub fn on_blocks_by_range_request( &mut self, diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 4f63f4e7f97..9bbb04bff75 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -91,6 +91,7 @@ pub enum HotColdDBError { MissingEpochBoundaryState(Hash256), MissingSplitState(Hash256, Slot), MissingExecutionPayload(Hash256), + MissingLightClientUpdate(u64), MissingFullBlockExecutionPayloadPruned(Hash256, Slot), MissingAnchorInfo, HotStateSummaryError(BeaconStateError), @@ -518,6 +519,58 @@ impl, Cold: ItemStore> HotColdDB } } + pub fn put_light_client_update(&self, update: LightClientUpdate) -> Result<(), Error> { + let sync_committee_period = update.signature_slot.epoch(E::slots_per_epoch()) + / self.spec.epochs_per_sync_committee_period; + let key = sync_committee_period.as_u64().to_le_bytes(); + if update.signature_slot < self.get_split_slot() { + self.cold_db.put_bytes( + DBColumn::LightClientUpdate.into(), + &key, + &update.as_ssz_bytes(), + ) + } else { + self.hot_db.put_bytes( + DBColumn::LightClientUpdate.into(), + &key, + &update.as_ssz_bytes(), + ) + } + } + + /// Fetch a light client update from the store. + pub fn get_light_client_update( + &self, + sync_committee_period: u64, + ) -> Result>, Error> { + let start_slot = self.spec.altair_fork_epoch.map(|altair_fork_epoch| { + let epochs_passed = + Epoch::new(sync_committee_period) * self.spec.epochs_per_sync_committee_period; + let start_epoch = altair_fork_epoch + epochs_passed; + start_epoch.start_slot(E::slots_per_epoch()) + }); + + let column = DBColumn::LightClientUpdate.into(); + let key = sync_committee_period.to_le_bytes(); + if let Some(slot) = start_slot { + if slot < self.get_split_slot() { + self.cold_db + .get_bytes(column, &key)? + .map(|bytes| LightClientUpdate::from_ssz_bytes(&bytes)) + .transpose() + .map_err(|e| e.into()) + } else { + self.hot_db + .get_bytes(column, &key)? + .map(|bytes| LightClientUpdate::from_ssz_bytes(&bytes)) + .transpose() + .map_err(|e| e.into()) + } + } else { + Err(HotColdDBError::MissingLightClientUpdate(sync_committee_period).into()) + } + } + /// Fetch a state from the store, but don't compute all of the values when replaying blocks /// upon that state (e.g., state roots). Additionally, only states from the hot store are /// returned. diff --git a/beacon_node/store/src/impls/light_client_update.rs b/beacon_node/store/src/impls/light_client_update.rs new file mode 100644 index 00000000000..fdff50f73b3 --- /dev/null +++ b/beacon_node/store/src/impls/light_client_update.rs @@ -0,0 +1,17 @@ +use crate::{DBColumn, Error, StoreItem}; +use ssz::{Decode, Encode}; +use types::{EthSpec, LightClientUpdate}; + +impl StoreItem for LightClientUpdate { + fn db_column() -> DBColumn { + DBColumn::LightClientUpdate + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + Ok(Self::from_ssz_bytes(bytes)?) + } +} diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 75aeca058b5..89c5761a021 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -211,6 +211,8 @@ pub enum DBColumn { /// For Optimistically Imported Merge Transition Blocks #[strum(serialize = "otb")] OptimisticTransitionBlock, + #[strum(serialize = "lcu")] + LightClientUpdate, } /// A block from the database, which might have an execution payload or not. diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 87f5ebe8b3c..082340ef861 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -52,6 +52,7 @@ pub mod historical_batch; pub mod indexed_attestation; pub mod light_client_bootstrap; pub mod light_client_finality_update; +pub mod light_client_header; pub mod light_client_optimistic_update; pub mod light_client_update; pub mod pending_attestation; @@ -139,8 +140,11 @@ pub use crate::free_attestation::FreeAttestation; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::light_client_bootstrap::LightClientBootstrap; pub use crate::light_client_finality_update::LightClientFinalityUpdate; +pub use crate::light_client_header::LightClientHeader; pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; +pub use crate::light_client_update::LightClientUpdate; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; pub use crate::payload::{BlindedPayload, BlockType, ExecPayload, FullPayload}; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index d2a46c04a43..ba303743148 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,7 +1,13 @@ -use super::{BeaconBlockHeader, BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; -use crate::{light_client_update::*, test_utils::TestRandom}; +use super::{ + BeaconState, ChainSpec, EthSpec, FixedVector, Hash256, LightClientHeader, + SignedBlindedBeaconBlock, SyncCommittee, +}; +use crate::{ + beacon_state::Error as BeaconStateError, light_client_update::*, test_utils::TestRandom, +}; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; +use ssz_types::Error as SszTypesError; use std::sync::Arc; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -12,22 +18,58 @@ use tree_hash::TreeHash; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] #[serde(bound = "T: EthSpec")] pub struct LightClientBootstrap { - /// Requested beacon block header. - pub header: BeaconBlockHeader, + /// Requested light client header. + pub header: LightClientHeader, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee pub current_sync_committee_branch: FixedVector, } +pub enum Error { + AltairForkNotActive, + InvalidState, + BeaconStateError(BeaconStateError), + SszTypesError(SszTypesError), +} + +impl From for Error { + fn from(e: SszTypesError) -> Error { + Error::SszTypesError(e) + } +} + +impl From for Error { + fn from(e: BeaconStateError) -> Error { + Error::BeaconStateError(e) + } +} + impl LightClientBootstrap { - pub fn from_beacon_state(beacon_state: &mut BeaconState) -> Result { + pub fn new( + chain_spec: &ChainSpec, + beacon_state: &mut BeaconState, + block: &SignedBlindedBeaconBlock, + ) -> Result { + let altair_fork_epoch = chain_spec + .altair_fork_epoch + .ok_or(Error::AltairForkNotActive)?; + if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { + return Err(Error::AltairForkNotActive); + } + + if beacon_state.slot() != beacon_state.latest_block_header().slot { + return Err(Error::InvalidState); + } let mut header = beacon_state.latest_block_header().clone(); header.state_root = beacon_state.tree_hash_root(); + if header.tree_hash_root() != block.message().tree_hash_root() { + return Err(Error::InvalidState); + } let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; Ok(LightClientBootstrap { - header, + header: LightClientHeader::from_block(block), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index cae6266f9e7..958f4c4397e 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,12 +1,8 @@ -use super::{ - BeaconBlockHeader, EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, - Slot, SyncAggregate, -}; -use crate::{light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec}; +use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; +use crate::{light_client_update::*, test_utils::TestRandom, LightClientHeader}; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. @@ -15,9 +11,9 @@ use tree_hash::TreeHash; #[serde(bound = "T: EthSpec")] pub struct LightClientFinalityUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate @@ -27,43 +23,14 @@ pub struct LightClientFinalityUpdate { } impl LightClientFinalityUpdate { - pub fn new( - chain_spec: &ChainSpec, - beacon_state: &BeaconState, - block: &SignedBeaconBlock, - attested_state: &mut BeaconState, - finalized_block: &SignedBlindedBeaconBlock, - ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); + pub fn from_light_client_update(update: LightClientUpdate) -> LightClientFinalityUpdate { + Self { + attested_header: update.attested_header, + finalized_header: update.finalized_header, + finality_branch: update.finality_branch, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot, } - - let sync_aggregate = block.message().body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.update_tree_hash_cache()?; - // Build finalized header from finalized block - let finalized_header = finalized_block.message().block_header(); - - if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { - return Err(Error::InvalidFinalizedBlock); - } - - let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - Ok(Self { - attested_header, - finalized_header, - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) } } diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs new file mode 100644 index 00000000000..22b089083e9 --- /dev/null +++ b/consensus/types/src/light_client_header.rs @@ -0,0 +1,40 @@ +use super::{BeaconBlockHeader, SignedBlindedBeaconBlock}; +use crate::{test_utils::TestRandom, EthSpec, Hash256, Slot}; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; + +/// A LightClientHeader is a header that is verified by a light client. +#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] +pub struct LightClientHeader { + // Beacon block header + pub beacon: BeaconBlockHeader, +} + +impl LightClientHeader { + pub fn from_block(block: &SignedBlindedBeaconBlock) -> Self { + Self { + beacon: block.message().block_header(), + } + } + + pub fn zeros() -> Self { + Self { + beacon: BeaconBlockHeader { + slot: Slot::new(0), + proposer_index: 0, + parent_root: Hash256::zero(), + state_root: Hash256::zero(), + body_root: Hash256::zero(), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_tests!(LightClientHeader); +} diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 8dda8cd5aed..8b268a75846 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,11 +1,8 @@ -use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; -use crate::{ - light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, -}; +use super::{EthSpec, LightClientHeader, LightClientUpdate, Slot, SyncAggregate}; +use crate::test_utils::TestRandom; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. @@ -14,7 +11,7 @@ use tree_hash::TreeHash; #[serde(bound = "T: EthSpec")] pub struct LightClientOptimisticUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// current sync aggreggate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature @@ -22,31 +19,12 @@ pub struct LightClientOptimisticUpdate { } impl LightClientOptimisticUpdate { - pub fn new( - chain_spec: &ChainSpec, - block: &SignedBeaconBlock, - attested_state: &BeaconState, - ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); + pub fn from_light_client_update(update: LightClientUpdate) -> Self { + Self { + attested_header: update.attested_header.clone(), + sync_aggregate: update.sync_aggregate.clone(), + signature_slot: update.signature_slot, } - - let sync_aggregate = block.message().body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.tree_hash_root(); - Ok(Self { - attested_header, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) } } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 7d01f39bfc8..637941ea944 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,6 +1,8 @@ -use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; -use crate::{beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec}; -use safe_arith::ArithError; +use super::{EthSpec, FixedVector, Hash256, LightClientHeader, Slot, SyncAggregate, SyncCommittee}; +use crate::{ + beacon_state, test_utils::TestRandom, BeaconState, ChainSpec, SignedBlindedBeaconBlock, +}; +use safe_arith::{ArithError, SafeArith}; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U5, U6}; @@ -29,6 +31,10 @@ pub enum Error { NotEnoughSyncCommitteeParticipants, MismatchingPeriods, InvalidFinalizedBlock, + InvalidAttestedBlock, + InvalidAttestedState, + InvalidBlock, + SlotMismatch, } impl From for Error { @@ -56,29 +62,30 @@ impl From for Error { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] #[serde(bound = "T: EthSpec")] pub struct LightClientUpdate { - /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + /// The last `LightClientHeader` from the last attested block by the sync committee. + pub attested_header: LightClientHeader, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, /// Merkle proof for next sync committee pub next_sync_committee_branch: FixedVector, - /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + /// The last `LightClientHeader` from the last attested finalized block (end of epoch). + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate pub sync_aggregate: SyncAggregate, - /// Slot of the sync aggregated singature + /// Slot of the sync aggregated signature pub signature_slot: Slot, } impl LightClientUpdate { pub fn new( - chain_spec: ChainSpec, - beacon_state: BeaconState, - block: BeaconBlock, + chain_spec: &ChainSpec, + beacon_state: &BeaconState, + block: &SignedBlindedBeaconBlock, attested_state: &mut BeaconState, - finalized_block: BeaconBlock, + attested_block: &SignedBlindedBeaconBlock, + finalized_block: Option>, ) -> Result { let altair_fork_epoch = chain_spec .altair_fork_epoch @@ -87,46 +94,196 @@ impl LightClientUpdate { return Err(Error::AltairForkNotActive); } - let sync_aggregate = block.body().sync_aggregate()?; + let sync_aggregate = block.message().body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); } - let signature_period = block.epoch().sync_committee_period(&chain_spec)?; + let mut header = beacon_state.latest_block_header().clone(); + if beacon_state.slot() != header.slot { + return Err(Error::SlotMismatch); + } + header.state_root = beacon_state.tree_hash_root(); + if header.tree_hash_root() != block.message().tree_hash_root() { + return Err(Error::InvalidBlock); + } + + let signature_period = block.message().epoch().sync_committee_period(chain_spec)?; + if attested_state.slot() != attested_state.latest_block_header().slot { + return Err(Error::SlotMismatch); + } // Compute and validate attested header. let mut attested_header = attested_state.latest_block_header().clone(); attested_header.state_root = attested_state.tree_hash_root(); - let attested_period = attested_header - .slot - .epoch(T::slots_per_epoch()) - .sync_committee_period(&chain_spec)?; + + if attested_header.tree_hash_root() != attested_block.message().tree_hash_root() + || block.message().parent_root() != attested_block.message().tree_hash_root() + { + return Err(Error::InvalidAttestedBlock); + } + let attested_period = attested_block + .message() + .epoch() + .sync_committee_period(chain_spec)?; if attested_period != signature_period { return Err(Error::MismatchingPeriods); } // Build finalized header from finalized block - let finalized_header = BeaconBlockHeader { - slot: finalized_block.slot(), - proposer_index: finalized_block.proposer_index(), - parent_root: finalized_block.parent_root(), - state_root: finalized_block.state_root(), - body_root: finalized_block.body_root(), + let (finalized_header, finality_branch) = if let Some(finalized_block) = finalized_block { + let finalized_header = if finalized_block.message().slot() != chain_spec.genesis_slot { + let finalized_header = LightClientHeader::from_block(&finalized_block); + if finalized_header.beacon.tree_hash_root() + != attested_state.finalized_checkpoint().root + { + return Err(Error::InvalidFinalizedBlock); + } + finalized_header + } else { + if attested_state.finalized_checkpoint().root != Hash256::zero() { + return Err(Error::InvalidAttestedState); + } + LightClientHeader::zeros() + }; + let finality_branch = + FixedVector::new(attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?)?; + (finalized_header, finality_branch) + } else { + ( + LightClientHeader::zeros(), + FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN])?, + ) }; - if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { - return Err(Error::InvalidFinalizedBlock); - } + let next_sync_committee_branch = attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; - let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header, + attested_header: LightClientHeader::from_block(attested_block), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, finalized_header, - finality_branch: FixedVector::new(finality_branch)?, + finality_branch, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), }) } + + // Returns true if self is a better update than the old_update and false otherwise. + pub fn is_better_update(&self, old_update: &Self, chain_spec: &ChainSpec) -> bool { + let max_active_participants = self.sync_aggregate.sync_committee_bits.len(); + let new_num_active_participants = self.sync_aggregate.num_set_bits(); + let old_num_active_participants = old_update.sync_aggregate.num_set_bits(); + // Compare supermajority (> 2/3) sync committee participation + // unwrap is safe because sync committee size is small + let new_has_supermajority = new_num_active_participants.safe_mul(3).unwrap() + >= max_active_participants.safe_mul(2).unwrap(); + let old_has_supermajority = old_num_active_participants.safe_mul(3).unwrap() + >= max_active_participants.safe_mul(2).unwrap(); + + if new_has_supermajority != old_has_supermajority { + return new_has_supermajority & !old_has_supermajority; + } + if !new_has_supermajority && new_num_active_participants != old_num_active_participants { + return new_num_active_participants > old_num_active_participants; + } + // Compare presence of relevant sync committee + let new_is_sync_committee_update = self.next_sync_committee_branch + != FixedVector::new(vec![Hash256::zero(); NEXT_SYNC_COMMITTEE_PROOF_LEN]).unwrap(); + let old_is_sync_committee_update = old_update.next_sync_committee_branch + != FixedVector::new(vec![Hash256::zero(); NEXT_SYNC_COMMITTEE_PROOF_LEN]).unwrap(); + let new_has_relevant_sync_committee = new_is_sync_committee_update + && self + .attested_header + .beacon + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec) + == self + .signature_slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec); + let old_has_relevant_sync_committee = old_is_sync_committee_update + && old_update + .attested_header + .beacon + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec) + == old_update + .signature_slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec); + + if new_has_relevant_sync_committee != old_has_relevant_sync_committee { + return new_has_relevant_sync_committee; + } + // Compare indication of finality + let new_has_finality = self.finality_branch + != FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN]).unwrap(); + let old_has_finality = old_update.finality_branch + != FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN]).unwrap(); + if new_has_finality != old_has_finality { + return new_has_finality; + } + + // Compare sync committee finality + if new_has_finality { + let new_has_sync_committee_finality = self + .finalized_header + .beacon + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec) + == self + .attested_header + .beacon + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec); + let old_has_sync_committee_finality = old_update + .finalized_header + .beacon + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec) + == old_update + .attested_header + .beacon + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(chain_spec); + if new_has_sync_committee_finality != old_has_sync_committee_finality { + return new_has_sync_committee_finality; + } + } + + // Tiebreaker 1: Sync committee participation beyond supermajorit + if new_num_active_participants != old_num_active_participants { + return new_num_active_participants > old_num_active_participants; + } + // Tiebreaker 2: Prefer older data (fewer changes to best) + if self.attested_header.beacon.slot != old_update.attested_header.beacon.slot { + return self.attested_header.beacon.slot < old_update.attested_header.beacon.slot; + } + self.signature_slot < old_update.signature_slot + } + + // Returns a LightClientUpdate that is zeroed out for testing purposes. + pub fn zeros() -> Self { + Self { + attested_header: LightClientHeader::zeros(), + next_sync_committee: Arc::new(SyncCommittee::temporary().unwrap()), + next_sync_committee_branch: FixedVector::new(vec![ + Hash256::zero(); + NEXT_SYNC_COMMITTEE_PROOF_LEN + ]) + .unwrap(), + finalized_header: LightClientHeader::zeros(), + finality_branch: FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN]) + .unwrap(), + sync_aggregate: SyncAggregate::empty(), + signature_slot: Slot::new(0), + } + } } #[cfg(test)] diff --git a/testing/ef_tests/src/cases.rs b/testing/ef_tests/src/cases.rs index 216912a4f14..0b1936857a3 100644 --- a/testing/ef_tests/src/cases.rs +++ b/testing/ef_tests/src/cases.rs @@ -27,6 +27,7 @@ mod shuffling; mod ssz_generic; mod ssz_static; mod transition; +mod update_ranking; pub use self::fork_choice::*; pub use bls_aggregate_sigs::*; @@ -51,6 +52,7 @@ pub use shuffling::*; pub use ssz_generic::*; pub use ssz_static::*; pub use transition::TransitionTest; +pub use update_ranking::*; pub trait LoadCase: Sized { /// Load the test case from a test case directory. diff --git a/testing/ef_tests/src/cases/update_ranking.rs b/testing/ef_tests/src/cases/update_ranking.rs new file mode 100644 index 00000000000..ed47b8044ad --- /dev/null +++ b/testing/ef_tests/src/cases/update_ranking.rs @@ -0,0 +1,65 @@ +use super::*; +use crate::case_result::compare_result_detailed; +use crate::decode::{ssz_decode_file_with, yaml_decode_file}; +use compare_fields_derive::CompareFields; +use serde_derive::Deserialize; +use ssz::Decode; +use types::{EthSpec, ForkName, LightClientUpdate}; + +#[derive(Debug, Clone, Deserialize, CompareFields, PartialEq)] +struct BoolWrapper { + wrapped_bool: bool, +} + +impl BoolWrapper { + fn new(b: bool) -> Self { + Self { wrapped_bool: b } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Metadata { + pub updates_count: usize, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(bound = "E: EthSpec")] +pub struct UpdateRanking { + pub metadata: Metadata, + pub updates: Vec>, +} + +impl LoadCase for UpdateRanking { + fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result { + let metadata: Metadata = yaml_decode_file(&path.join("meta.yaml"))?; + let updates = (0..metadata.updates_count) + .map(|i| { + let filename = format!("updates_{}.ssz_snappy", i); + ssz_decode_file_with(&path.join(filename), |bytes| { + LightClientUpdate::from_ssz_bytes(bytes) + }) + }) + .collect::, _>>()?; + + Ok(Self { metadata, updates }) + } +} + +impl Case for UpdateRanking { + fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { + let spec = &testing_spec::(fork_name); + let mut forward_bool = true; + let mut rev_bool = false; + + self.updates.iter().enumerate().for_each(|(ind, update)| { + self.updates.iter().skip(ind + 1).for_each(|other_update| { + forward_bool &= update.is_better_update(other_update, spec); + rev_bool |= other_update.is_better_update(update, spec); + }); + }); + let forward_result: Result = Ok(BoolWrapper::new(forward_bool)); + let rev_result: Result = Ok(BoolWrapper::new(rev_bool)); + compare_result_detailed(&forward_result, &Some(BoolWrapper::new(true)))?; + compare_result_detailed(&rev_result, &Some(BoolWrapper::new(false))) + } +} diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index 13f70fea716..7eaef6c4097 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -321,6 +321,30 @@ impl Handler for ShufflingHandler { } } +#[derive(Derivative)] +#[derivative(Default(bound = ""))] +pub struct UpdateRankingHandler(PhantomData); + +impl Handler for UpdateRankingHandler { + type Case = cases::UpdateRanking; + + fn config_name() -> &'static str { + E::name() + } + + fn runner_name() -> &'static str { + "light_client" + } + + fn handler_name(&self) -> String { + "update_ranking".into() + } + + fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { + ForkName::list_all()[1..].to_vec().contains(&fork_name) + } +} + #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct SanityBlocksHandler(PhantomData); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 87a6bec71b5..9f72fc0bc51 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -82,6 +82,11 @@ fn operations_execution_payload_blinded() { OperationsHandler::>::default().run(); } +#[test] +fn update_ranking() { + UpdateRankingHandler::::default().run(); +} + #[test] fn sanity_blocks() { SanityBlocksHandler::::default().run();