Implement status v2 version (#7590)

N/A


  Implements status v2 as defined in https://github.com/ethereum/consensus-specs/pull/4374/
This commit is contained in:
Pawan Dhananjay
2025-06-12 00:17:06 -07:00
committed by GitHub
parent 5f208bb858
commit 9803d69d80
13 changed files with 208 additions and 78 deletions

View File

@@ -746,6 +746,7 @@ impl<E: EthSpec> PeerDB<E> {
head_root: Hash256::ZERO,
finalized_epoch: Epoch::new(0),
finalized_root: Hash256::ZERO,
earliest_available_slot: Some(Slot::new(0)),
},
},
);

View File

@@ -25,6 +25,7 @@ pub struct SyncInfo {
pub head_root: Hash256,
pub finalized_epoch: Epoch,
pub finalized_root: Hash256,
pub earliest_available_slot: Option<Slot>,
}
impl std::cmp::PartialEq for SyncStatus {

View File

@@ -67,7 +67,13 @@ impl<E: EthSpec> SSZSnappyInboundCodec<E> {
) -> Result<(), RPCError> {
let bytes = match &item {
RpcResponse::Success(resp) => match &resp {
RpcSuccessResponse::Status(res) => res.as_ssz_bytes(),
RpcSuccessResponse::Status(res) => match self.protocol.versioned_protocol {
SupportedProtocol::StatusV1 => res.status_v1().as_ssz_bytes(),
SupportedProtocol::StatusV2 => res.status_v2().as_ssz_bytes(),
_ => {
unreachable!("We only send status responses on negotiating status protocol")
}
},
RpcSuccessResponse::BlocksByRange(res) => res.as_ssz_bytes(),
RpcSuccessResponse::BlocksByRoot(res) => res.as_ssz_bytes(),
RpcSuccessResponse::BlobsByRange(res) => res.as_ssz_bytes(),
@@ -329,7 +335,16 @@ impl<E: EthSpec> Encoder<RequestType<E>> for SSZSnappyOutboundCodec<E> {
fn encode(&mut self, item: RequestType<E>, dst: &mut BytesMut) -> Result<(), Self::Error> {
let bytes = match item {
RequestType::Status(req) => req.as_ssz_bytes(),
RequestType::Status(req) => {
// Send the status message based on the negotiated protocol
match self.protocol.versioned_protocol {
SupportedProtocol::StatusV1 => req.status_v1().as_ssz_bytes(),
SupportedProtocol::StatusV2 => req.status_v2().as_ssz_bytes(),
_ => {
unreachable!("We only send status requests on negotiating status protocol")
}
}
}
RequestType::Goodbye(req) => req.as_ssz_bytes(),
RequestType::BlocksByRange(r) => match r {
OldBlocksByRangeRequest::V1(req) => req.as_ssz_bytes(),
@@ -553,9 +568,12 @@ fn handle_rpc_request<E: EthSpec>(
spec: &ChainSpec,
) -> Result<Option<RequestType<E>>, RPCError> {
match versioned_protocol {
SupportedProtocol::StatusV1 => Ok(Some(RequestType::Status(
StatusMessage::from_ssz_bytes(decoded_buffer)?,
))),
SupportedProtocol::StatusV1 => Ok(Some(RequestType::Status(StatusMessage::V1(
StatusMessageV1::from_ssz_bytes(decoded_buffer)?,
)))),
SupportedProtocol::StatusV2 => Ok(Some(RequestType::Status(StatusMessage::V2(
StatusMessageV2::from_ssz_bytes(decoded_buffer)?,
)))),
SupportedProtocol::GoodbyeV1 => Ok(Some(RequestType::Goodbye(
GoodbyeReason::from_ssz_bytes(decoded_buffer)?,
))),
@@ -666,9 +684,12 @@ fn handle_rpc_response<E: EthSpec>(
fork_name: Option<ForkName>,
) -> Result<Option<RpcSuccessResponse<E>>, RPCError> {
match versioned_protocol {
SupportedProtocol::StatusV1 => Ok(Some(RpcSuccessResponse::Status(
StatusMessage::from_ssz_bytes(decoded_buffer)?,
))),
SupportedProtocol::StatusV1 => Ok(Some(RpcSuccessResponse::Status(StatusMessage::V1(
StatusMessageV1::from_ssz_bytes(decoded_buffer)?,
)))),
SupportedProtocol::StatusV2 => Ok(Some(RpcSuccessResponse::Status(StatusMessage::V2(
StatusMessageV2::from_ssz_bytes(decoded_buffer)?,
)))),
// This case should be unreachable as `Goodbye` has no response.
SupportedProtocol::GoodbyeV1 => Err(RPCError::InvalidData(
"Goodbye RPC message has no valid response".to_string(),
@@ -1036,14 +1057,25 @@ mod tests {
SignedBeaconBlock::from_block(block, Signature::empty())
}
fn status_message() -> StatusMessage {
StatusMessage {
fn status_message_v1() -> StatusMessage {
StatusMessage::V1(StatusMessageV1 {
fork_digest: [0; 4],
finalized_root: Hash256::zero(),
finalized_epoch: Epoch::new(1),
head_root: Hash256::zero(),
head_slot: Slot::new(1),
}
})
}
fn status_message_v2() -> StatusMessage {
StatusMessage::V2(StatusMessageV2 {
fork_digest: [0; 4],
finalized_root: Hash256::zero(),
finalized_epoch: Epoch::new(1),
head_root: Hash256::zero(),
head_slot: Slot::new(1),
earliest_available_slot: Slot::new(0),
})
}
fn bbrange_request_v1() -> OldBlocksByRangeRequest {
@@ -1284,11 +1316,22 @@ mod tests {
assert_eq!(
encode_then_decode_response(
SupportedProtocol::StatusV1,
RpcResponse::Success(RpcSuccessResponse::Status(status_message())),
RpcResponse::Success(RpcSuccessResponse::Status(status_message_v1())),
ForkName::Base,
&chain_spec,
),
Ok(Some(RpcSuccessResponse::Status(status_message())))
Ok(Some(RpcSuccessResponse::Status(status_message_v1())))
);
// A StatusV2 still encodes as a StatusV1 since version is Version::V1
assert_eq!(
encode_then_decode_response(
SupportedProtocol::StatusV1,
RpcResponse::Success(RpcSuccessResponse::Status(status_message_v2())),
ForkName::Fulu,
&chain_spec,
),
Ok(Some(RpcSuccessResponse::Status(status_message_v1())))
);
assert_eq!(
@@ -1716,6 +1759,27 @@ mod tests {
),
Ok(Some(RpcSuccessResponse::MetaData(metadata_v2())))
);
// A StatusV1 still encodes as a StatusV2 since version is Version::V2
assert_eq!(
encode_then_decode_response(
SupportedProtocol::StatusV2,
RpcResponse::Success(RpcSuccessResponse::Status(status_message_v1())),
ForkName::Fulu,
&chain_spec,
),
Ok(Some(RpcSuccessResponse::Status(status_message_v2())))
);
assert_eq!(
encode_then_decode_response(
SupportedProtocol::StatusV2,
RpcResponse::Success(RpcSuccessResponse::Status(status_message_v2())),
ForkName::Fulu,
&chain_spec,
),
Ok(Some(RpcSuccessResponse::Status(status_message_v2())))
);
}
// Test RPCResponse encoding/decoding for V2 messages
@@ -1901,7 +1965,8 @@ mod tests {
let requests: &[RequestType<Spec>] = &[
RequestType::Ping(ping_message()),
RequestType::Status(status_message()),
RequestType::Status(status_message_v1()),
RequestType::Status(status_message_v2()),
RequestType::Goodbye(GoodbyeReason::Fault),
RequestType::BlocksByRange(bbrange_request_v1()),
RequestType::BlocksByRange(bbrange_request_v2()),
@@ -1948,7 +2013,7 @@ mod tests {
let malicious_padding: &'static [u8] = b"\xFE\x00\x00\x00";
// Status message is 84 bytes uncompressed. `max_compressed_len` is 32 + 84 + 84/6 = 130.
let status_message_bytes = StatusMessage {
let status_message_bytes = StatusMessageV1 {
fork_digest: [0; 4],
finalized_root: Hash256::zero(),
finalized_epoch: Epoch::new(1),
@@ -2071,7 +2136,7 @@ mod tests {
assert_eq!(stream_identifier.len(), 10);
// Status message is 84 bytes uncompressed. `max_compressed_len` is 32 + 84 + 84/6 = 130.
let status_message_bytes = StatusMessage {
let status_message_bytes = StatusMessageV1 {
fork_digest: [0; 4],
finalized_root: Hash256::zero(),
finalized_epoch: Epoch::new(1),

View File

@@ -63,7 +63,11 @@ impl Display for ErrorType {
/* Requests */
/// The STATUS request/response handshake message.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
#[superstruct(
variants(V1, V2),
variant_attributes(derive(Encode, Decode, Clone, Debug, PartialEq),)
)]
#[derive(Clone, Debug, PartialEq)]
pub struct StatusMessage {
/// The fork version of the chain we are broadcasting.
pub fork_digest: [u8; 4],
@@ -79,6 +83,43 @@ pub struct StatusMessage {
/// The slot associated with the latest block root.
pub head_slot: Slot,
/// The slot after which we guarantee to have all the blocks
/// and blobs/data columns that we currently advertise.
#[superstruct(only(V2))]
pub earliest_available_slot: Slot,
}
impl StatusMessage {
pub fn status_v1(&self) -> StatusMessageV1 {
match &self {
Self::V1(status) => status.clone(),
Self::V2(status) => StatusMessageV1 {
fork_digest: status.fork_digest,
finalized_root: status.finalized_root,
finalized_epoch: status.finalized_epoch,
head_root: status.head_root,
head_slot: status.head_slot,
},
}
}
pub fn status_v2(&self) -> StatusMessageV2 {
match &self {
Self::V1(status) => StatusMessageV2 {
fork_digest: status.fork_digest,
finalized_root: status.finalized_root,
finalized_epoch: status.finalized_epoch,
head_root: status.head_root,
head_slot: status.head_slot,
// Note: we always produce a V2 message as our local
// status message, so this match arm should ideally never
// be invoked in lighthouse.
earliest_available_slot: Slot::new(0),
},
Self::V2(status) => status.clone(),
}
}
}
/// The PING request/response message.
@@ -726,7 +767,7 @@ impl std::fmt::Display for RpcErrorResponse {
impl std::fmt::Display for StatusMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Status Message: Fork Digest: {:?}, Finalized Root: {}, Finalized Epoch: {}, Head Root: {}, Head Slot: {}", self.fork_digest, self.finalized_root, self.finalized_epoch, self.head_root, self.head_slot)
write!(f, "Status Message: Fork Digest: {:?}, Finalized Root: {}, Finalized Epoch: {}, Head Root: {}, Head Slot: {}, Earliest available slot: {:?}", self.fork_digest(), self.finalized_root(), self.finalized_epoch(), self.head_root(), self.head_slot(), self.earliest_available_slot())
}
}

View File

@@ -298,6 +298,7 @@ pub enum Encoding {
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SupportedProtocol {
StatusV1,
StatusV2,
GoodbyeV1,
BlocksByRangeV1,
BlocksByRangeV2,
@@ -321,6 +322,7 @@ impl SupportedProtocol {
pub fn version_string(&self) -> &'static str {
match self {
SupportedProtocol::StatusV1 => "1",
SupportedProtocol::StatusV2 => "2",
SupportedProtocol::GoodbyeV1 => "1",
SupportedProtocol::BlocksByRangeV1 => "1",
SupportedProtocol::BlocksByRangeV2 => "2",
@@ -344,6 +346,7 @@ impl SupportedProtocol {
pub fn protocol(&self) -> Protocol {
match self {
SupportedProtocol::StatusV1 => Protocol::Status,
SupportedProtocol::StatusV2 => Protocol::Status,
SupportedProtocol::GoodbyeV1 => Protocol::Goodbye,
SupportedProtocol::BlocksByRangeV1 => Protocol::BlocksByRange,
SupportedProtocol::BlocksByRangeV2 => Protocol::BlocksByRange,
@@ -368,6 +371,7 @@ impl SupportedProtocol {
fn currently_supported(fork_context: &ForkContext) -> Vec<ProtocolId> {
let mut supported = vec![
ProtocolId::new(Self::StatusV2, Encoding::SSZSnappy),
ProtocolId::new(Self::StatusV1, Encoding::SSZSnappy),
ProtocolId::new(Self::GoodbyeV1, Encoding::SSZSnappy),
// V2 variants have higher preference then V1
@@ -492,8 +496,8 @@ impl ProtocolId {
pub fn rpc_request_limits(&self, spec: &ChainSpec) -> RpcLimits {
match self.versioned_protocol.protocol() {
Protocol::Status => RpcLimits::new(
<StatusMessage as Encode>::ssz_fixed_len(),
<StatusMessage as Encode>::ssz_fixed_len(),
<StatusMessageV1 as Encode>::ssz_fixed_len(),
<StatusMessageV2 as Encode>::ssz_fixed_len(),
),
Protocol::Goodbye => RpcLimits::new(
<GoodbyeReason as Encode>::ssz_fixed_len(),
@@ -537,8 +541,8 @@ impl ProtocolId {
pub fn rpc_response_limits<E: EthSpec>(&self, fork_context: &ForkContext) -> RpcLimits {
match self.versioned_protocol.protocol() {
Protocol::Status => RpcLimits::new(
<StatusMessage as Encode>::ssz_fixed_len(),
<StatusMessage as Encode>::ssz_fixed_len(),
<StatusMessageV1 as Encode>::ssz_fixed_len(),
<StatusMessageV2 as Encode>::ssz_fixed_len(),
),
Protocol::Goodbye => RpcLimits::new(0, 0), // Goodbye request has no response
Protocol::BlocksByRange => rpc_block_limits_by_fork(fork_context.current_fork()),
@@ -589,6 +593,7 @@ impl ProtocolId {
| SupportedProtocol::LightClientFinalityUpdateV1
| SupportedProtocol::LightClientUpdatesByRangeV1 => true,
SupportedProtocol::StatusV1
| SupportedProtocol::StatusV2
| SupportedProtocol::BlocksByRootV1
| SupportedProtocol::BlocksByRangeV1
| SupportedProtocol::PingV1
@@ -758,7 +763,10 @@ impl<E: EthSpec> RequestType<E> {
/// Gives the corresponding `SupportedProtocol` to this request.
pub fn versioned_protocol(&self) -> SupportedProtocol {
match self {
RequestType::Status(_) => SupportedProtocol::StatusV1,
RequestType::Status(req) => match req {
StatusMessage::V1(_) => SupportedProtocol::StatusV1,
StatusMessage::V2(_) => SupportedProtocol::StatusV2,
},
RequestType::Goodbye(_) => SupportedProtocol::GoodbyeV1,
RequestType::BlocksByRange(req) => match req {
OldBlocksByRangeRequest::V1(_) => SupportedProtocol::BlocksByRangeV1,
@@ -817,10 +825,10 @@ impl<E: EthSpec> RequestType<E> {
pub fn supported_protocols(&self) -> Vec<ProtocolId> {
match self {
// add more protocols when versions/encodings are supported
RequestType::Status(_) => vec![ProtocolId::new(
SupportedProtocol::StatusV1,
Encoding::SSZSnappy,
)],
RequestType::Status(_) => vec![
ProtocolId::new(SupportedProtocol::StatusV1, Encoding::SSZSnappy),
ProtocolId::new(SupportedProtocol::StatusV2, Encoding::SSZSnappy),
],
RequestType::Goodbye(_) => vec![ProtocolId::new(
SupportedProtocol::GoodbyeV1,
Encoding::SSZSnappy,