diff --git a/bin/e2hs-writer/src/reader.rs b/bin/e2hs-writer/src/reader.rs index 1c3c2ff0c..560e6141f 100644 --- a/bin/e2hs-writer/src/reader.rs +++ b/bin/e2hs-writer/src/reader.rs @@ -282,15 +282,16 @@ impl EpochReader { #[cfg(test)] mod tests { - use ethportal_api::types::{ - consensus::{ - beacon_block::{BeaconBlockBellatrix, BeaconBlockCapella}, - beacon_state::BeaconState, - fork::ForkName, - }, - execution::header_with_proof::{ - build_historical_roots_proof, build_historical_summaries_proof, - BlockProofHistoricalRoots, BlockProofHistoricalSummaries, + use ethportal_api::{ + consensus::beacon_block::BeaconBlock, + types::{ + consensus::{ + beacon_block::BeaconBlockBellatrix, beacon_state::BeaconState, fork::ForkName, + }, + execution::header_with_proof::{ + build_block_proof_historical_roots, build_historical_summaries_proof, + BlockProofHistoricalRoots, BlockProofHistoricalSummaries, + }, }, }; use serde_yaml::Value; @@ -336,7 +337,7 @@ mod tests { let block_path = format!("{test_assets_dir}/block.ssz"); let block_raw = std::fs::read(block_path).unwrap(); let block = BeaconBlockBellatrix::from_ssz_bytes(&block_raw).unwrap(); - let proof = build_historical_roots_proof(slot, &historical_batch, block); + let proof = build_block_proof_historical_roots(slot, &historical_batch, &block); assert_eq!(actual_proof, proof); } @@ -388,11 +389,41 @@ mod tests { let state_path = format!("{test_assets_dir}/beacon_state.ssz"); let state_raw = std::fs::read(state_path).unwrap(); let beacon_state = BeaconState::from_ssz_bytes(&state_raw, ForkName::Capella).unwrap(); - let beacon_state = beacon_state.as_capella().unwrap(); let block_path = format!("{test_assets_dir}/block.ssz"); let block_raw = std::fs::read(block_path).unwrap(); - let block = BeaconBlockCapella::from_ssz_bytes(&block_raw).unwrap(); - let proof = build_historical_summaries_proof(slot, beacon_state, block); + let block = BeaconBlock::from_ssz_bytes(&block_raw, ForkName::Capella).unwrap(); + let proof = build_historical_summaries_proof(slot, &beacon_state, &block); + + assert_eq!(actual_proof, proof); + } + #[tokio::test] + async fn test_pre_pectra_proof_generation() { + let test_vector = std::fs::read_to_string( + "../../portal-spec-tests/tests/mainnet/history/headers_with_proof/block_proofs_deneb/beacon_block_proof-22162263.yaml" + ) + .unwrap(); + let test_vector: Value = serde_yaml::from_str(&test_vector).unwrap(); + let actual_proof = BlockProofHistoricalSummaries { + beacon_block_proof: serde_yaml::from_value(test_vector["beacon_block_proof"].clone()) + .unwrap(), + beacon_block_root: serde_yaml::from_value(test_vector["beacon_block_root"].clone()) + .unwrap(), + execution_block_proof: serde_yaml::from_value( + test_vector["execution_block_proof"].clone(), + ) + .unwrap(), + slot: serde_yaml::from_value(test_vector["slot"].clone()).unwrap(), + }; + + let test_assets_dir = + "../../portal-spec-tests/tests/mainnet/history/headers_with_proof/beacon_data/22162263"; + let state_path = format!("{test_assets_dir}/beacon_state.ssz"); + let state_raw = std::fs::read(state_path).unwrap(); + let beacon_state = BeaconState::from_ssz_bytes(&state_raw, ForkName::Deneb).unwrap(); + let block_path = format!("{test_assets_dir}/block.ssz"); + let block_raw = std::fs::read(block_path).unwrap(); + let block = BeaconBlock::from_ssz_bytes(&block_raw, ForkName::Deneb).unwrap(); + let proof = build_historical_summaries_proof(11378687, &beacon_state, &block); assert_eq!(actual_proof, proof); } diff --git a/crates/ethportal-api/src/types/consensus/beacon_block.rs b/crates/ethportal-api/src/types/consensus/beacon_block.rs index fe048e971..391ad7b14 100644 --- a/crates/ethportal-api/src/types/consensus/beacon_block.rs +++ b/crates/ethportal-api/src/types/consensus/beacon_block.rs @@ -69,31 +69,58 @@ impl BeaconBlock { } } +/// Helper function to build a body root proof for a beacon block +fn build_beacon_block_body_root_proof( + slot: u64, + proposer_index: u64, + parent_root: &B256, + state_root: &B256, + body_root: &B256, +) -> Vec { + let leaves = vec![ + slot.tree_hash_root().0, + proposer_index.tree_hash_root().0, + parent_root.tree_hash_root().0, + state_root.tree_hash_root().0, + body_root.tree_hash_root().0, + ]; + // We want to prove the body root, which is the 5th leaf + build_merkle_proof_for_index(leaves, 4) +} + impl BeaconBlockCapella { pub fn build_body_root_proof(&self) -> Vec { - let leaves = vec![ - self.slot.tree_hash_root().0, - self.proposer_index.tree_hash_root().0, - self.parent_root.tree_hash_root().0, - self.state_root.tree_hash_root().0, - self.body.tree_hash_root().0, - ]; - // We want to prove the body root, which is the 5th leaf - build_merkle_proof_for_index(leaves, 4) + build_beacon_block_body_root_proof( + self.slot, + self.proposer_index, + &self.parent_root, + &self.state_root, + &self.body.tree_hash_root(), + ) } } impl BeaconBlockBellatrix { pub fn build_body_root_proof(&self) -> Vec { - let leaves = vec![ - self.slot.tree_hash_root().0, - self.proposer_index.tree_hash_root().0, - self.parent_root.tree_hash_root().0, - self.state_root.tree_hash_root().0, - self.body.tree_hash_root().0, - ]; - // We want to prove the body root, which is the 5th leaf - build_merkle_proof_for_index(leaves, 4) + build_beacon_block_body_root_proof( + self.slot, + self.proposer_index, + &self.parent_root, + &self.state_root, + &self.body.tree_hash_root(), + ) + } +} + +impl BeaconBlockDeneb { + pub fn build_body_root_proof(&self) -> Vec { + build_beacon_block_body_root_proof( + self.slot, + self.proposer_index, + &self.parent_root, + &self.state_root, + &self.body.tree_hash_root(), + ) } } diff --git a/crates/ethportal-api/src/types/consensus/beacon_state.rs b/crates/ethportal-api/src/types/consensus/beacon_state.rs index a27471067..6b04384d9 100644 --- a/crates/ethportal-api/src/types/consensus/beacon_state.rs +++ b/crates/ethportal-api/src/types/consensus/beacon_state.rs @@ -162,19 +162,29 @@ impl BeaconState { } } +/// Helper function to build a block root proof from block roots +fn build_block_root_proof_from_roots( + block_roots: &FixedVector, + block_root_index: usize, +) -> Vec { + let leaves: Vec<[u8; 32]> = block_roots + .iter() + .map(|root| root.tree_hash_root().0) + .collect(); + build_merkle_proof_for_index(leaves, block_root_index) +} + impl BeaconStateCapella { pub fn build_block_root_proof(&self, block_root_index: usize) -> Vec { - // Build block hash proof for self.block_roots - let leaves: Vec<[u8; 32]> = self - .block_roots - .iter() - .map(|root| root.tree_hash_root().0) - .collect(); - build_merkle_proof_for_index(leaves, block_root_index) + build_block_root_proof_from_roots(&self.block_roots, block_root_index) } } impl BeaconStateDeneb { + pub fn build_block_root_proof(&self, block_root_index: usize) -> Vec { + build_block_root_proof_from_roots(&self.block_roots, block_root_index) + } + pub fn build_historical_summaries_proof(&self) -> Vec { let leaves = vec![ self.genesis_time.tree_hash_root().0, diff --git a/crates/ethportal-api/src/types/consensus/body.rs b/crates/ethportal-api/src/types/consensus/body.rs index c6f230045..961518d4b 100644 --- a/crates/ethportal-api/src/types/consensus/body.rs +++ b/crates/ethportal-api/src/types/consensus/body.rs @@ -87,6 +87,34 @@ impl BeaconBlockBody { } } +impl BeaconBlockBodyDeneb { + pub fn build_execution_payload_proof(&self) -> Vec { + let leaves = vec![ + self.randao_reveal.tree_hash_root().0, + self.eth1_data.tree_hash_root().0, + self.graffiti.tree_hash_root().0, + self.proposer_slashings.tree_hash_root().0, + self.attester_slashings.tree_hash_root().0, + self.attestations.tree_hash_root().0, + self.deposits.tree_hash_root().0, + self.voluntary_exits.tree_hash_root().0, + self.sync_aggregate.tree_hash_root().0, + self.execution_payload.tree_hash_root().0, + self.bls_to_execution_changes.tree_hash_root().0, + self.blob_kzg_commitments.tree_hash_root().0, + ]; + // We want to prove the 10th leaf + build_merkle_proof_for_index(leaves, 9) + } + + pub fn build_execution_block_hash_proof(&self) -> Vec { + let mut block_hash_proof = self.execution_payload.build_block_hash_proof(); + let execution_payload_proof = self.build_execution_payload_proof(); + block_hash_proof.extend(execution_payload_proof); + block_hash_proof + } +} + impl BeaconBlockBodyCapella { pub fn build_execution_payload_proof(&self) -> Vec { let leaves = vec![ diff --git a/crates/ethportal-api/src/types/consensus/execution_payload.rs b/crates/ethportal-api/src/types/consensus/execution_payload.rs index c24d6fb5d..a737e403c 100644 --- a/crates/ethportal-api/src/types/consensus/execution_payload.rs +++ b/crates/ethportal-api/src/types/consensus/execution_payload.rs @@ -92,6 +92,31 @@ impl ExecutionPayload { } } +impl ExecutionPayloadDeneb { + pub fn build_block_hash_proof(&self) -> Vec { + let leaves = vec![ + self.parent_hash.tree_hash_root().0, + self.fee_recipient.tree_hash_root().0, + self.state_root.tree_hash_root().0, + self.receipts_root.tree_hash_root().0, + self.logs_bloom.tree_hash_root().0, + self.prev_randao.tree_hash_root().0, + self.block_number.tree_hash_root().0, + self.gas_limit.tree_hash_root().0, + self.gas_used.tree_hash_root().0, + self.timestamp.tree_hash_root().0, + self.extra_data.tree_hash_root().0, + self.base_fee_per_gas.tree_hash_root().0, + self.block_hash.tree_hash_root().0, + self.transactions.tree_hash_root().0, + self.withdrawals.tree_hash_root().0, + self.blob_gas_used.tree_hash_root().0, + self.excess_blob_gas.tree_hash_root().0, + ]; + build_merkle_proof_for_index(leaves, 12) + } +} + impl ExecutionPayloadCapella { pub fn build_block_hash_proof(&self) -> Vec { let leaves = vec![ diff --git a/crates/ethportal-api/src/types/execution/header_with_proof.rs b/crates/ethportal-api/src/types/execution/header_with_proof.rs index 1b405a228..153c5763a 100644 --- a/crates/ethportal-api/src/types/execution/header_with_proof.rs +++ b/crates/ethportal-api/src/types/execution/header_with_proof.rs @@ -9,8 +9,8 @@ use tree_hash::TreeHash; use crate::types::{ bytes::ByteList1024, consensus::{ - beacon_block::{BeaconBlockBellatrix, BeaconBlockCapella}, - beacon_state::{BeaconStateCapella, HistoricalBatch}, + beacon_block::{BeaconBlock, BeaconBlockBellatrix}, + beacon_state::{BeaconState, HistoricalBatch}, proof::build_merkle_proof_for_index, }, execution::block_body::{MERGE_TIMESTAMP, SHANGHAI_TIMESTAMP}, @@ -199,74 +199,62 @@ pub mod ssz_header { } } -pub fn build_historical_roots_proof( +/// Helper function to create a historical summaries proof from block root proof and beacon block +fn create_historical_summaries_proof( slot: u64, - historical_batch: &HistoricalBatch, - beacon_block: BeaconBlockBellatrix, -) -> BlockProofHistoricalRoots { - let beacon_block_proof = historical_batch.build_block_root_proof(slot % 8192); - - // execution block proof - let mut execution_block_hash_proof = beacon_block.body.build_execution_block_hash_proof(); - let body_root_proof = beacon_block.build_body_root_proof(); - execution_block_hash_proof.extend(body_root_proof); - - BlockProofHistoricalRoots { - beacon_block_proof: beacon_block_proof.into(), - beacon_block_root: beacon_block.tree_hash_root(), - execution_block_proof: execution_block_hash_proof.into(), - slot, - } -} - -pub fn build_historical_summaries_proof( - slot: u64, - capella_state: &BeaconStateCapella, - beacon_block: BeaconBlockCapella, + block_root_proof: Vec, + beacon_block: &BeaconBlock, ) -> BlockProofHistoricalSummaries { - // beacon block proof - let block_root_proof = capella_state.build_block_root_proof(slot as usize % 8192); let beacon_block_proof: FixedVector = block_root_proof.into(); // execution block proof - let mut execution_block_hash_proof = beacon_block.body.build_execution_block_hash_proof(); - let body_root_proof = beacon_block.build_body_root_proof(); - execution_block_hash_proof.extend(body_root_proof); + let (execution_block_hash_proof, body_root_proof) = match beacon_block { + BeaconBlock::Capella(block) => { + let proof = block.body.build_execution_block_hash_proof(); + let body_proof = block.build_body_root_proof(); + (proof, body_proof) + } + BeaconBlock::Deneb(block) => { + let proof = block.body.build_execution_block_hash_proof(); + let body_proof = block.build_body_root_proof(); + (proof, body_proof) + } + _ => panic!("Only Capella and Deneb blocks are supported"), + }; + + let mut execution_proof = execution_block_hash_proof; + execution_proof.extend(body_root_proof); BlockProofHistoricalSummaries { beacon_block_proof, beacon_block_root: beacon_block.tree_hash_root(), - execution_block_proof: execution_block_hash_proof.into(), + execution_block_proof: execution_proof.into(), slot, } } -pub fn build_block_proof_historical_roots( +pub fn build_historical_summaries_proof( slot: u64, - historical_batch: HistoricalBatch, - beacon_block: BeaconBlockBellatrix, -) -> BlockProofHistoricalRoots { + state: &BeaconState, + beacon_block: &BeaconBlock, +) -> BlockProofHistoricalSummaries { // beacon block proof - let historical_batch_proof = historical_batch.build_block_root_proof(slot % 8192); - - // execution block proof - let mut execution_block_hash_proof = beacon_block.body.build_execution_block_hash_proof(); - let body_root_proof = beacon_block.build_body_root_proof(); - execution_block_hash_proof.extend(body_root_proof); + let block_root_proof = match state { + BeaconState::Capella(capella_state) => { + capella_state.build_block_root_proof(slot as usize % 8192) + } + BeaconState::Deneb(deneb_state) => deneb_state.build_block_root_proof(slot as usize % 8192), + _ => panic!("Only Capella and Deneb states are supported"), + }; - BlockProofHistoricalRoots { - beacon_block_proof: historical_batch_proof.into(), - beacon_block_root: beacon_block.tree_hash_root(), - execution_block_proof: execution_block_hash_proof.into(), - slot, - } + create_historical_summaries_proof(slot, block_root_proof, beacon_block) } pub fn build_block_proof_historical_summaries( slot: u64, // block roots fields from BeaconState block_roots: FixedVector, - beacon_block: BeaconBlockCapella, + beacon_block: &BeaconBlock, ) -> BlockProofHistoricalSummaries { // beacon block proof let leaves = block_roots @@ -275,15 +263,25 @@ pub fn build_block_proof_historical_summaries( .collect(); let slot_index = slot as usize % 8192; let block_root_proof = build_merkle_proof_for_index(leaves, slot_index); - let beacon_block_proof: FixedVector = block_root_proof.into(); + + create_historical_summaries_proof(slot, block_root_proof, beacon_block) +} + +pub fn build_block_proof_historical_roots( + slot: u64, + historical_batch: &HistoricalBatch, + beacon_block: &BeaconBlockBellatrix, +) -> BlockProofHistoricalRoots { + // beacon block proof + let historical_batch_proof = historical_batch.build_block_root_proof(slot % 8192); // execution block proof let mut execution_block_hash_proof = beacon_block.body.build_execution_block_hash_proof(); let body_root_proof = beacon_block.build_body_root_proof(); execution_block_hash_proof.extend(body_root_proof); - BlockProofHistoricalSummaries { - beacon_block_proof, + BlockProofHistoricalRoots { + beacon_block_proof: historical_batch_proof.into(), beacon_block_root: beacon_block.tree_hash_root(), execution_block_proof: execution_block_hash_proof.into(), slot, @@ -394,7 +392,7 @@ mod tests { let block_raw = read_bytes_from_tests_submodule(format!("{test_assets_dir}/block.ssz",)).unwrap(); let block = BeaconBlockBellatrix::from_ssz_bytes(&block_raw).unwrap(); - let actual_proof = build_block_proof_historical_roots(slot, historical_batch, block); + let actual_proof = build_block_proof_historical_roots(slot, &historical_batch, &block); assert_eq!(expected_proof, actual_proof); } @@ -435,8 +433,41 @@ mod tests { let block_roots = beacon_state.as_capella().unwrap().block_roots.clone(); let block_raw = read_bytes_from_tests_submodule(format!("{test_assets_dir}/block.ssz",)).unwrap(); - let block = BeaconBlockCapella::from_ssz_bytes(&block_raw).unwrap(); - let actual_proof = build_block_proof_historical_summaries(slot, block_roots, block); + let block = BeaconBlock::from_ssz_bytes(&block_raw, ForkName::Capella).unwrap(); + let actual_proof = build_block_proof_historical_summaries(slot, block_roots, &block); + + assert_eq!(expected_proof, actual_proof); + } + + #[tokio::test] + async fn pre_pectra_historical_summaries_generation() { + let test_vector = read_file_from_tests_submodule( + "tests/mainnet/history/headers_with_proof/block_proofs_deneb/beacon_block_proof-22162263.yaml", + ) + .unwrap(); + let test_vector: YamlValue = serde_yaml::from_str(&test_vector).unwrap(); + let expected_proof = BlockProofHistoricalSummaries { + beacon_block_proof: serde_yaml::from_value(test_vector["beacon_block_proof"].clone()) + .unwrap(), + beacon_block_root: serde_yaml::from_value(test_vector["beacon_block_root"].clone()) + .unwrap(), + execution_block_proof: serde_yaml::from_value( + test_vector["execution_block_proof"].clone(), + ) + .unwrap(), + slot: serde_yaml::from_value(test_vector["slot"].clone()).unwrap(), + }; + + let test_assets_dir = "tests/mainnet/history/headers_with_proof/beacon_data/22162263"; + let beacon_state_raw = + read_bytes_from_tests_submodule(format!("{test_assets_dir}/beacon_state.ssz",)) + .unwrap(); + let beacon_state = BeaconState::from_ssz_bytes(&beacon_state_raw, ForkName::Deneb).unwrap(); + let block_roots = beacon_state.as_deneb().unwrap().block_roots.clone(); + let block_raw = + read_bytes_from_tests_submodule(format!("{test_assets_dir}/block.ssz",)).unwrap(); + let block = BeaconBlock::from_ssz_bytes(&block_raw, ForkName::Deneb).unwrap(); + let actual_proof = build_block_proof_historical_summaries(11378687, block_roots, &block); assert_eq!(expected_proof, actual_proof); }