Skip to content

Commit 56fc676

Browse files
committed
feat: update beacon content types to support Pectra
1 parent dc2abb9 commit 56fc676

File tree

9 files changed

+324
-125
lines changed

9 files changed

+324
-125
lines changed

bin/portal-bridge/src/bridge/beacon.rs

+10-13
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use alloy::primitives::B256;
88
use anyhow::{bail, ensure};
99
use ethportal_api::{
1010
consensus::historical_summaries::{
11-
HistoricalSummariesStateProof, HistoricalSummariesWithProof,
11+
HistoricalSummariesProofDeneb, HistoricalSummariesWithProof,
12+
HistoricalSummariesWithProofDeneb,
1213
},
1314
light_client::update::LightClientUpdate,
1415
types::{
@@ -17,10 +18,7 @@ use ethportal_api::{
1718
HistoricalSummariesWithProofKey, LightClientFinalityUpdateKey,
1819
LightClientOptimisticUpdateKey,
1920
},
20-
content_value::beacon::{
21-
ForkVersionedHistoricalSummariesWithProof, ForkVersionedLightClientUpdate,
22-
LightClientUpdatesByRange,
23-
},
21+
content_value::beacon::{ForkVersionedLightClientUpdate, LightClientUpdatesByRange},
2422
network::Subnetwork,
2523
portal_wire::OfferTrace,
2624
},
@@ -478,14 +476,12 @@ impl BeaconBridge {
478476
"Historical summaries proof length is not 5"
479477
);
480478
let historical_summaries = beacon_state.historical_summaries;
481-
let historical_summaries_with_proof = ForkVersionedHistoricalSummariesWithProof {
482-
fork_name: ForkName::Deneb,
483-
historical_summaries_with_proof: HistoricalSummariesWithProof {
479+
let historical_summaries_with_proof =
480+
HistoricalSummariesWithProof::Deneb(HistoricalSummariesWithProofDeneb {
484481
epoch: state_epoch,
485482
historical_summaries,
486-
proof: HistoricalSummariesStateProof::from(historical_summaries_proof),
487-
},
488-
};
483+
proof: HistoricalSummariesProofDeneb::from(historical_summaries_proof),
484+
});
489485
info!(
490486
epoch = %state_epoch,
491487
"Generated HistoricalSummariesWithProof",
@@ -494,8 +490,9 @@ impl BeaconBridge {
494490
BeaconContentKey::HistoricalSummariesWithProof(HistoricalSummariesWithProofKey {
495491
epoch: state_epoch,
496492
});
497-
let content_value =
498-
BeaconContentValue::HistoricalSummariesWithProof(historical_summaries_with_proof);
493+
let content_value = BeaconContentValue::HistoricalSummariesWithProof(
494+
historical_summaries_with_proof.into(),
495+
);
499496

500497
Self::spawn_offer_tasks(beacon_network, content_key, content_value, metrics, census);
501498
finalized_state_root.lock().await.state_root = latest_finalized_state_root;

crates/ethportal-api/src/types/consensus/beacon_state.rs

+46
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,52 @@ impl BeaconStateDeneb {
240240
}
241241
}
242242

243+
impl BeaconStateElectra {
244+
pub fn build_historical_summaries_proof(&self) -> Vec<B256> {
245+
let leaves = [
246+
self.genesis_time.tree_hash_root(),
247+
self.genesis_validators_root.tree_hash_root(),
248+
self.slot.tree_hash_root(),
249+
self.fork.tree_hash_root(),
250+
self.latest_block_header.tree_hash_root(),
251+
self.block_roots.tree_hash_root(),
252+
self.state_roots.tree_hash_root(),
253+
self.historical_roots.tree_hash_root(),
254+
self.eth1_data.tree_hash_root(),
255+
self.eth1_data_votes.tree_hash_root(),
256+
self.eth1_deposit_index.tree_hash_root(),
257+
self.validators.tree_hash_root(),
258+
self.balances.tree_hash_root(),
259+
self.randao_mixes.tree_hash_root(),
260+
self.slashings.tree_hash_root(),
261+
self.previous_epoch_participation.tree_hash_root(),
262+
self.current_epoch_participation.tree_hash_root(),
263+
self.justification_bits.tree_hash_root(),
264+
self.previous_justified_checkpoint.tree_hash_root(),
265+
self.current_justified_checkpoint.tree_hash_root(),
266+
self.finalized_checkpoint.tree_hash_root(),
267+
self.inactivity_scores.tree_hash_root(),
268+
self.current_sync_committee.tree_hash_root(),
269+
self.next_sync_committee.tree_hash_root(),
270+
self.latest_execution_payload_header.tree_hash_root(),
271+
self.next_withdrawal_index.tree_hash_root(),
272+
self.next_withdrawal_validator_index.tree_hash_root(),
273+
self.historical_summaries.tree_hash_root(),
274+
self.deposit_requests_start_index.tree_hash_root(),
275+
self.deposit_balance_to_consume.tree_hash_root(),
276+
self.exit_balance_to_consume.tree_hash_root(),
277+
self.earliest_exit_epoch.tree_hash_root(),
278+
self.consolidation_balance_to_consume.tree_hash_root(),
279+
self.earliest_consolidation_epoch.tree_hash_root(),
280+
self.pending_deposits.tree_hash_root(),
281+
self.pending_partial_withdrawals.tree_hash_root(),
282+
self.pending_consolidations.tree_hash_root(),
283+
];
284+
285+
build_merkle_proof_for_index(leaves, 27)
286+
}
287+
}
288+
243289
/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
244290
///
245291
/// Spec v0.12.1

crates/ethportal-api/src/types/consensus/historical_summaries.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use alloy::primitives::B256;
22
use serde::{Deserialize, Serialize};
33
use ssz_derive::{Decode, Encode};
44
use ssz_types::{typenum, FixedVector, VariableList};
5+
use superstruct::superstruct;
56
use tree_hash_derive::TreeHash;
67

78
/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch`
@@ -16,12 +17,29 @@ pub struct HistoricalSummary {
1617
}
1718

1819
pub type HistoricalSummaries = VariableList<HistoricalSummary, typenum::U16777216>;
19-
pub type HistoricalSummariesStateProof = FixedVector<B256, typenum::U5>;
20+
21+
pub const HISTORICAL_SUMMARIES_GINDEX_DENEB: usize = 59;
22+
pub const HISTORICAL_SUMMARIES_GINDEX_ELECTRA: usize = 91;
23+
24+
pub type HistoricalSummariesProofDeneb = FixedVector<B256, typenum::U5>;
25+
pub type HistoricalSummariesProofElectra = FixedVector<B256, typenum::U6>;
2026

2127
/// A historical summaries BeaconState field with proof.
22-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
28+
#[superstruct(
29+
variants(Deneb, Electra),
30+
variant_attributes(
31+
derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode,),
32+
serde(deny_unknown_fields),
33+
)
34+
)]
35+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode)]
36+
#[serde(untagged)]
37+
#[ssz(enum_behaviour = "transparent")]
2338
pub struct HistoricalSummariesWithProof {
2439
pub epoch: u64,
2540
pub historical_summaries: HistoricalSummaries,
26-
pub proof: HistoricalSummariesStateProof,
41+
#[superstruct(only(Deneb), partial_getter(rename = "proof_deneb"))]
42+
pub proof: HistoricalSummariesProofDeneb,
43+
#[superstruct(only(Electra), partial_getter(rename = "proof_electra"))]
44+
pub proof: HistoricalSummariesProofElectra,
2745
}

crates/ethportal-api/src/types/content_value/beacon.rs

+79-33
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use ssz::{Decode, DecodeError, Encode};
55
use ssz_types::{typenum::U128, VariableList};
66

77
use crate::{
8+
consensus::historical_summaries::{
9+
HistoricalSummariesWithProof, HistoricalSummariesWithProofElectra,
10+
},
811
light_client::{
912
bootstrap::{LightClientBootstrapDeneb, LightClientBootstrapElectra},
1013
finality_update::{LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra},
@@ -14,7 +17,7 @@ use crate::{
1417
types::{
1518
consensus::{
1619
fork::{ForkDigest, ForkName},
17-
historical_summaries::HistoricalSummariesWithProof,
20+
historical_summaries::HistoricalSummariesWithProofDeneb,
1821
light_client::{
1922
bootstrap::{
2023
LightClientBootstrap, LightClientBootstrapBellatrix,
@@ -482,52 +485,92 @@ pub struct ForkVersionedHistoricalSummariesWithProof {
482485
pub historical_summaries_with_proof: HistoricalSummariesWithProof,
483486
}
484487

485-
impl ForkVersionedHistoricalSummariesWithProof {
486-
pub fn encode(&self) -> Vec<u8> {
487-
let fork_digest = self.fork_name.as_fork_digest();
488-
489-
let mut data = fork_digest.to_vec();
490-
data.extend(self.historical_summaries_with_proof.as_ssz_bytes());
491-
data
488+
impl From<HistoricalSummariesWithProof> for ForkVersionedHistoricalSummariesWithProof {
489+
fn from(historical_summaries_with_proof: HistoricalSummariesWithProof) -> Self {
490+
let fork_name = match &historical_summaries_with_proof {
491+
HistoricalSummariesWithProof::Deneb(_) => ForkName::Deneb,
492+
HistoricalSummariesWithProof::Electra(_) => ForkName::Electra,
493+
};
494+
Self {
495+
fork_name,
496+
historical_summaries_with_proof,
497+
}
492498
}
499+
}
493500

494-
pub fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
495-
let fork_digest = ForkDigest::try_from(&buf[0..4]).map_err(|err| {
496-
DecodeError::BytesInvalid(format!("Unable to decode fork digest: {err:?}"))
497-
})?;
498-
let fork_name = ForkName::try_from(fork_digest).map_err(|_| {
499-
DecodeError::BytesInvalid(format!("Unable to decode fork name: {fork_digest:?}"))
500-
})?;
501-
let summaries_with_proof = HistoricalSummariesWithProof::from_ssz_bytes(&buf[4..])?;
502-
503-
Ok(Self {
504-
fork_name,
505-
historical_summaries_with_proof: summaries_with_proof,
506-
})
501+
impl ForkVersionedHistoricalSummariesWithProof {
502+
pub fn epoch(&self) -> u64 {
503+
*self.historical_summaries_with_proof.epoch()
507504
}
508505
}
509506

510-
impl Decode for ForkVersionedHistoricalSummariesWithProof {
507+
impl Encode for ForkVersionedHistoricalSummariesWithProof {
511508
fn is_ssz_fixed_len() -> bool {
512509
false
513510
}
514511

515-
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
516-
Self::decode(bytes)
512+
fn ssz_append(&self, buf: &mut Vec<u8>) {
513+
self.fork_name.as_fork_digest().ssz_append(buf);
514+
self.historical_summaries_with_proof.ssz_append(buf);
515+
}
516+
517+
fn ssz_fixed_len() -> usize {
518+
ssz::BYTES_PER_LENGTH_OFFSET
519+
}
520+
521+
fn ssz_bytes_len(&self) -> usize {
522+
self.fork_name.as_fork_digest().ssz_bytes_len()
523+
+ self.historical_summaries_with_proof.ssz_bytes_len()
517524
}
518525
}
519526

520-
impl Encode for ForkVersionedHistoricalSummariesWithProof {
527+
impl Decode for ForkVersionedHistoricalSummariesWithProof {
521528
fn is_ssz_fixed_len() -> bool {
522529
false
523530
}
524531

525-
fn ssz_append(&self, buf: &mut Vec<u8>) {
526-
buf.extend_from_slice(&self.encode());
532+
fn ssz_fixed_len() -> usize {
533+
ssz::BYTES_PER_LENGTH_OFFSET
527534
}
528535

529-
fn ssz_bytes_len(&self) -> usize {
530-
self.encode().len()
536+
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
537+
let fork_digest_len = <ForkDigest as Decode>::ssz_fixed_len();
538+
let Some((fork_digest_bytes, historical_summaries_with_proof_bytes)) =
539+
bytes.split_at_checked(fork_digest_len)
540+
else {
541+
return Err(DecodeError::InvalidByteLength {
542+
len: bytes.len(),
543+
expected: fork_digest_len,
544+
});
545+
};
546+
547+
let fork_digest = ForkDigest::from_ssz_bytes(fork_digest_bytes)?;
548+
let fork_name = match ForkName::try_from(fork_digest) {
549+
Ok(fork_name) => fork_name,
550+
Err(err) => return Err(DecodeError::BytesInvalid(err.to_string())),
551+
};
552+
553+
let historical_summaries_with_proof = match fork_name {
554+
ForkName::Bellatrix | ForkName::Capella => {
555+
return Err(DecodeError::BytesInvalid(format!(
556+
"HistoricalSummariesWithProof not supported for fork digest: {fork_digest:?}"
557+
)))
558+
}
559+
ForkName::Deneb => HistoricalSummariesWithProof::Deneb(
560+
HistoricalSummariesWithProofDeneb::from_ssz_bytes(
561+
historical_summaries_with_proof_bytes,
562+
)?,
563+
),
564+
ForkName::Electra => HistoricalSummariesWithProof::Electra(
565+
HistoricalSummariesWithProofElectra::from_ssz_bytes(
566+
historical_summaries_with_proof_bytes,
567+
)?,
568+
),
569+
};
570+
Ok(Self {
571+
fork_name,
572+
historical_summaries_with_proof,
573+
})
531574
}
532575
}
533576

@@ -710,11 +753,14 @@ mod test {
710753

711754
match &beacon_content {
712755
BeaconContentValue::HistoricalSummariesWithProof(content) => {
713-
assert_eq!(
714-
expected_epoch,
715-
content.historical_summaries_with_proof.epoch
716-
);
717756
assert_eq!(ForkName::Deneb, content.fork_name);
757+
758+
let Ok(historical_summaries_with_proof) =
759+
content.historical_summaries_with_proof.as_deneb()
760+
else {
761+
panic!("Expected Deneb historical summaries, actual: {content:?}");
762+
};
763+
assert_eq!(expected_epoch, historical_summaries_with_proof.epoch);
718764
}
719765
_ => panic!("Invalid beacon content type!"),
720766
}

crates/subnetworks/beacon/src/storage.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -717,11 +717,12 @@ fn prune_old_bootstrap_data(
717717
#[allow(clippy::unwrap_used)]
718718
mod test {
719719
use ethportal_api::{
720+
consensus::historical_summaries::HistoricalSummariesWithProof,
720721
types::content_key::beacon::{
721722
HistoricalSummariesWithProofKey, LightClientFinalityUpdateKey,
722723
LightClientOptimisticUpdateKey,
723724
},
724-
LightClientBootstrapKey, LightClientUpdatesByRangeKey,
725+
BeaconContentValue, ContentValue, LightClientBootstrapKey, LightClientUpdatesByRangeKey,
725726
};
726727
use tree_hash::TreeHash;
727728
use trin_storage::test_utils::create_test_portal_storage_config_with_capacity;
@@ -872,14 +873,18 @@ mod test {
872873
fn test_beacon_storage_get_put_historical_summaries() {
873874
let (_temp_dir, config) = create_test_portal_storage_config_with_capacity(10).unwrap();
874875
let mut storage = BeaconStorage::new(config).unwrap();
875-
let (value, _) = test_utils::get_history_summaries_with_proof();
876-
let epoch = value.historical_summaries_with_proof.epoch;
876+
let (historical_summaries_with_proof, _) =
877+
test_utils::get_deneb_historical_summaries_with_proof();
878+
let epoch = historical_summaries_with_proof.epoch;
879+
let value = BeaconContentValue::HistoricalSummariesWithProof(
880+
HistoricalSummariesWithProof::Deneb(historical_summaries_with_proof).into(),
881+
);
877882
let key = BeaconContentKey::HistoricalSummariesWithProof(HistoricalSummariesWithProofKey {
878883
epoch,
879884
});
880-
storage.put(key.clone(), value.as_ssz_bytes()).unwrap();
885+
storage.put(key.clone(), value.encode()).unwrap();
881886
let result = storage.get(&key).unwrap().unwrap();
882-
assert_eq!(result, value.as_ssz_bytes());
887+
assert_eq!(result, value.encode());
883888

884889
// Test is_key_within_radius_and_unavailable for the same epoch
885890
let should_store_content = storage.is_key_within_radius_and_unavailable(&key).unwrap();
@@ -911,7 +916,7 @@ mod test {
911916
epoch: 0,
912917
});
913918
let result = storage.get(&key).unwrap().unwrap();
914-
assert_eq!(result, value.as_ssz_bytes());
919+
assert_eq!(result, value.encode());
915920
}
916921

917922
#[test]

0 commit comments

Comments
 (0)