Skip to content

Commit

Permalink
feat: add and refactor post-merge proofs
Browse files Browse the repository at this point in the history
  • Loading branch information
ogenev committed Apr 23, 2024
1 parent 82b420a commit e575121
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 210 deletions.
2 changes: 1 addition & 1 deletion ethportal-api/src/types/consensus/beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::consensus::{
},
fork::ForkName,
header::BeaconBlockHeader,
header_proof::HistoricalSummary,
historical_summaries::HistoricalSummary,
participation_flags::ParticipationFlags,
pubkey::PubKey,
sync_committee::SyncCommittee,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,6 @@ use ssz_derive::{Decode, Encode};
use ssz_types::{typenum, VariableList};
use tree_hash_derive::TreeHash;

/// Types sourced from Fluffy:
/// https://github.com/status-im/nimbus-eth1/blob/77135e70015de77d9ca46b196d99dc260ed3e364/fluffy/network/history/experimental/beacon_chain_block_proof.nim
//BeaconBlockBodyProof* = array[8, Digest]
pub type BeaconBlockBodyProof = [B256; 8];

//BeaconBlockHeaderProof* = array[3, Digest]
pub type BeaconBlockHeaderProof = [B256; 3];

//HistoricalRootsProof* = array[14, Digest]
pub type BeaconBlockHistoricalRootsProof = [B256; 14];

//# Total size (8 + 1 + 3 + 1 + 14) * 32 bytes + 4 bytes = 868 bytes
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BeaconChainBlockProof {
pub beacon_block_body_proof: BeaconBlockBodyProof,
pub beacon_block_body_root: B256,
pub beacon_block_header_proof: BeaconBlockHeaderProof,
pub beacon_block_header_root: B256,
pub historical_roots_proof: BeaconBlockHistoricalRootsProof,
pub slot: u64,
}

/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch`
/// making the two hash_tree_root-compatible. This struct is introduced into the beacon state
/// in the Capella hard fork.
Expand All @@ -43,19 +20,19 @@ type HistoricalSummaries = VariableList<HistoricalSummary, typenum::U16777216>;

/// Proof against the beacon state root hash
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HistoricalSummariesProof {
pub struct HistoricalSummariesStateProof {
pub proof: [B256; 5],
}

impl Default for HistoricalSummariesProof {
impl Default for HistoricalSummariesStateProof {
fn default() -> Self {
Self {
proof: [B256::ZERO; 5],
}
}
}

impl ssz::Decode for HistoricalSummariesProof {
impl ssz::Decode for HistoricalSummariesStateProof {
fn is_ssz_fixed_len() -> bool {
true
}
Expand All @@ -75,7 +52,7 @@ impl ssz::Decode for HistoricalSummariesProof {
}
}

impl ssz::Encode for HistoricalSummariesProof {
impl ssz::Encode for HistoricalSummariesStateProof {
fn is_ssz_fixed_len() -> bool {
true
}
Expand Down Expand Up @@ -104,7 +81,7 @@ impl ssz::Encode for HistoricalSummariesProof {
pub struct HistoricalSummariesWithProof {
pub epoch: u64,
pub historical_summaries: HistoricalSummaries,
pub proof: HistoricalSummariesProof,
pub proof: HistoricalSummariesStateProof,
}

// TODO: Add test vectors for HistoricalSummariesWithProof
2 changes: 1 addition & 1 deletion ethportal-api/src/types/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod body;
pub mod execution_payload;
pub mod fork;
pub mod header;
pub mod header_proof;
pub mod historical_summaries;
pub mod kzg_commitment;
pub mod light_client;
pub mod participation_flags;
Expand Down
2 changes: 1 addition & 1 deletion ethportal-api/src/types/content_value/beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
types::{
consensus::{
fork::{ForkDigest, ForkName},
header_proof::HistoricalSummariesWithProof,
historical_summaries::HistoricalSummariesWithProof,
light_client::{
bootstrap::{
LightClientBootstrap, LightClientBootstrapBellatrix,
Expand Down
7 changes: 5 additions & 2 deletions ethportal-api/src/types/content_value/history.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::{
types::{content_value::ContentValue, execution::accumulator::EpochAccumulator},
types::{
content_value::ContentValue,
execution::{accumulator::EpochAccumulator, header_with_proof::HeaderWithProof},
},
utils::bytes::hex_encode,
BlockBody, ContentValueError, HeaderWithProof, Receipts,
BlockBody, ContentValueError, Receipts,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use ssz::{Decode, Encode};
Expand Down
170 changes: 2 additions & 168 deletions ethportal-api/src/types/execution/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ use alloy_primitives::{keccak256, Address, Bloom, Bytes, B256, B64, U256, U64};
use alloy_rlp::{Decodable, Encodable, Header as RlpHeader};
use reth_rpc_types::Header as RpcHeader;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use ssz::{Encode, SszDecoderBuilder, SszEncoder};
use ssz_derive::{Decode, Encode};

use crate::{
types::bytes::ByteList2048,
utils::bytes::{hex_decode, hex_encode},
};
use crate::utils::bytes::{hex_decode, hex_encode};

/// A block header.
#[derive(Debug, Clone, Eq, Deserialize, Serialize)]
Expand Down Expand Up @@ -307,156 +302,13 @@ impl<'de> Deserialize<'de> for TxHashes {
}
}

/// A block header with accumulator proof.
/// Type definition:
/// https://github.com/status-im/nimbus-eth1/blob/master/fluffy/network/history/history_content.nim#L136
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct HeaderWithProof {
pub header: Header,
pub proof: BlockHeaderProof,
}

impl ssz::Encode for HeaderWithProof {
fn is_ssz_fixed_len() -> bool {
false
}

fn ssz_append(&self, buf: &mut Vec<u8>) {
let header = alloy_rlp::encode(&self.header);
let header = ByteList2048::from(header);
let offset = <ByteList2048 as Encode>::ssz_fixed_len()
+ <AccumulatorProof as Encode>::ssz_fixed_len();
let mut encoder = SszEncoder::container(buf, offset);
encoder.append(&header);
encoder.append(&self.proof);
encoder.finalize();
}

fn ssz_bytes_len(&self) -> usize {
let header = alloy_rlp::encode(&self.header);
let header = ByteList2048::from(header);
header.len() + self.proof.ssz_bytes_len()
}
}

#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize)]
#[ssz(enum_behaviour = "union")]
// Ignore clippy here, since "box"-ing the accumulator proof breaks the Decode trait
#[allow(clippy::large_enum_variant)]
pub enum BlockHeaderProof {
None(SszNone),
AccumulatorProof(AccumulatorProof),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccumulatorProof {
pub proof: [B256; 15],
}

impl ssz::Decode for HeaderWithProof {
fn is_ssz_fixed_len() -> bool {
false
}

fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
let mut builder = SszDecoderBuilder::new(bytes);

builder.register_type::<ByteList2048>()?;
builder.register_type::<BlockHeaderProof>()?;

let mut decoder = builder.build()?;

let header_rlp: Vec<u8> = decoder.decode_next()?;
let proof = decoder.decode_next()?;
let header: Header = Decodable::decode(&mut header_rlp.as_slice()).map_err(|_| {
ssz::DecodeError::BytesInvalid("Unable to decode bytes into header.".to_string())
})?;

Ok(Self { header, proof })
}
}

impl ssz::Decode for AccumulatorProof {
fn is_ssz_fixed_len() -> bool {
true
}

fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
let vec: Vec<[u8; 32]> = Vec::from_ssz_bytes(bytes)?;
let mut proof: [B256; 15] = [B256::ZERO; 15];
let raw_proof: [[u8; 32]; 15] = vec
.try_into()
.map_err(|_| ssz::DecodeError::BytesInvalid("Invalid proof length".to_string()))?;
for (idx, val) in raw_proof.iter().enumerate() {
proof[idx] = B256::from_slice(val);
}
Ok(Self { proof })
}
}

impl ssz::Encode for AccumulatorProof {
fn is_ssz_fixed_len() -> bool {
true
}

fn ssz_append(&self, buf: &mut Vec<u8>) {
let offset = self.ssz_bytes_len();
let mut encoder = SszEncoder::container(buf, offset);

for proof in self.proof {
encoder.append(&proof);
}
encoder.finalize();
}

fn ssz_bytes_len(&self) -> usize {
<B256 as Encode>::ssz_fixed_len() * 15
}
}

/// Struct to represent encodable/decodable None value for an SSZ enum
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
pub struct SszNone {
// In rust, None is a variant not a type,
// so we must use Option here to represent a None value
pub value: Option<()>,
}

impl ssz::Decode for SszNone {
fn is_ssz_fixed_len() -> bool {
true
}

fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
match bytes.len() {
0 => Ok(Self { value: None }),
_ => Err(ssz::DecodeError::BytesInvalid(
"Expected None value to be empty, found bytes.".to_string(),
)),
}
}
}

impl ssz::Encode for SszNone {
fn is_ssz_fixed_len() -> bool {
true
}

fn ssz_append(&self, _buf: &mut Vec<u8>) {}

fn ssz_bytes_len(&self) -> usize {
0
}
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::{fs, str::FromStr};
use std::str::FromStr;

use serde_json::{json, Value};
use ssz::Decode;

#[test_log::test]
fn decode_and_encode_header() {
Expand Down Expand Up @@ -538,24 +390,6 @@ mod tests {
assert_eq!(header.base_fee_per_gas, None);
}

#[test_log::test]
fn decode_encode_header_with_proofs() {
let file =
fs::read_to_string("../trin-validation/src/assets/fluffy/header_with_proofs.json")
.unwrap();
let json: Value = serde_json::from_str(&file).unwrap();
let hwps = json.as_object().unwrap();
for (block_number, obj) in hwps {
let _content_key = obj.get("content_key").unwrap();
let block_number: u64 = block_number.parse().unwrap();
let proof = obj.get("value").unwrap().as_str().unwrap();
let hwp = HeaderWithProof::from_ssz_bytes(&hex_decode(proof).unwrap()).unwrap();
assert_eq!(block_number, hwp.header.number);
let encoded = hex_encode(hwp.as_ssz_bytes());
assert_eq!(encoded, proof);
}
}

#[test_log::test]
fn post_shanghai_header() {
let body =
Expand Down
Loading

0 comments on commit e575121

Please sign in to comment.