-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update HeaderWithProof
with latest spec changes
#1672
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c93ca1b
fix: update BlockHeaderProof type to match new specs
njgheorghita f1fc079
refactor: rename PreMergeAccumulatorProof -> HistoricalHashesAccumula…
njgheorghita e21806d
refactor: move updated hwp types to separate module
njgheorghita 2f3bcc5
refactor: apply milos pr suggestions
njgheorghita a828b0c
fix: final pr comments
njgheorghita File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
crates/ethportal-api/src/types/content_value/history_new.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use ssz::{Decode, Encode}; | ||
|
||
use crate::{ | ||
types::{ | ||
content_value::ContentValue, execution::header_with_proof_new::HeaderWithProof, | ||
network::Subnetwork, | ||
}, | ||
utils::bytes::hex_encode, | ||
BlockBody, ContentValueError, HistoryContentKey, RawContentValue, Receipts, | ||
}; | ||
|
||
/// A Portal History content value. | ||
#[derive(Clone, Debug, PartialEq)] | ||
#[allow(clippy::large_enum_variant)] | ||
pub enum HistoryContentValue { | ||
BlockHeaderWithProof(HeaderWithProof), | ||
BlockBody(BlockBody), | ||
Receipts(Receipts), | ||
} | ||
|
||
impl ContentValue for HistoryContentValue { | ||
type TContentKey = HistoryContentKey; | ||
|
||
fn encode(&self) -> RawContentValue { | ||
match self { | ||
Self::BlockHeaderWithProof(value) => value.as_ssz_bytes().into(), | ||
Self::BlockBody(value) => value.as_ssz_bytes().into(), | ||
Self::Receipts(value) => value.as_ssz_bytes().into(), | ||
} | ||
} | ||
|
||
fn decode(key: &Self::TContentKey, buf: &[u8]) -> Result<Self, ContentValueError> { | ||
match key { | ||
HistoryContentKey::BlockHeaderByHash(_) | HistoryContentKey::BlockHeaderByNumber(_) => { | ||
if let Ok(value) = HeaderWithProof::from_ssz_bytes(buf) { | ||
return Ok(Self::BlockHeaderWithProof(value)); | ||
} | ||
} | ||
HistoryContentKey::BlockBody(_) => { | ||
if let Ok(value) = BlockBody::from_ssz_bytes(buf) { | ||
return Ok(Self::BlockBody(value)); | ||
} | ||
} | ||
HistoryContentKey::BlockReceipts(_) => { | ||
if let Ok(value) = Receipts::from_ssz_bytes(buf) { | ||
return Ok(Self::Receipts(value)); | ||
} | ||
} | ||
} | ||
|
||
Err(ContentValueError::UnknownContent { | ||
bytes: hex_encode(buf), | ||
subnetwork: Subnetwork::History, | ||
}) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use crate::HistoryContentValue; | ||
|
||
#[test] | ||
fn content_value_deserialization_failure_displays_debuggable_data() { | ||
let key = HistoryContentKey::random().unwrap(); | ||
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; | ||
let item_result = HistoryContentValue::decode(&key, &data); | ||
let error = item_result.unwrap_err(); | ||
// Test the error Debug representation | ||
assert_eq!( | ||
error, | ||
ContentValueError::UnknownContent { | ||
bytes: "0x010203040506070809".to_string(), | ||
subnetwork: Subnetwork::History, | ||
} | ||
); | ||
// Test the error Display representation. | ||
assert_eq!( | ||
error.to_string(), | ||
"could not determine content type of 0x010203040506070809 from History subnetwork" | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
222 changes: 222 additions & 0 deletions
222
crates/ethportal-api/src/types/execution/header_with_proof_new.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
use alloy::primitives::B256; | ||
use jsonrpsee::core::Serialize; | ||
use serde::Deserialize; | ||
use ssz::SszDecoderBuilder; | ||
use ssz_derive::{Decode, Encode}; | ||
use ssz_types::{typenum, FixedVector, VariableList}; | ||
|
||
use crate::{ | ||
types::{ | ||
bytes::ByteList1024, | ||
execution::block_body::{MERGE_TIMESTAMP, SHANGHAI_TIMESTAMP}, | ||
}, | ||
Header, | ||
}; | ||
|
||
/// The accumulator proof for the pre-merge blocks. | ||
pub type HistoricalHashesAccumulatorProof = FixedVector<B256, typenum::U15>; | ||
|
||
/// Proof that execution header root is part of BeaconBlock, post-Merge and pre-Capella | ||
pub type BeaconBlockProofPreCapella = FixedVector<B256, typenum::U11>; | ||
/// Proof that execution header root is part of BeaconBlock, post-Capella | ||
pub type BeaconBlockProof = VariableList<B256, typenum::U12>; | ||
/// Proof that BeaconBlockHeader root is part of HistoricalRoots | ||
pub type HistoricalRootsProof = FixedVector<B256, typenum::U14>; | ||
/// Proof that BeaconBlockHeader root is part of HistoricalSummaries | ||
pub type HistoricalSummariesProof = FixedVector<B256, typenum::U13>; | ||
|
||
/// 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, Encode, Deserialize)] | ||
pub struct HeaderWithProof { | ||
#[ssz(with = "ssz_header")] | ||
pub header: Header, | ||
pub proof: BlockHeaderProof, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] | ||
pub enum BlockHeaderProof { | ||
HistoricalHashesAccumulatorProof(HistoricalHashesAccumulatorProof), | ||
HistoricalRootsBlockProof(HistoricalRootsBlockProof), | ||
HistoricalSummariesBlockProof(HistoricalSummariesBlockProof), | ||
} | ||
|
||
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_anonymous_variable_length_item()?; | ||
builder.register_anonymous_variable_length_item()?; | ||
|
||
let mut decoder = builder.build()?; | ||
|
||
let header = decoder.decode_next_with(ssz_header::decode::from_ssz_bytes)?; | ||
|
||
let proof = decoder.decode_next::<ByteList1024>()?; | ||
let proof = if header.timestamp <= MERGE_TIMESTAMP { | ||
BlockHeaderProof::HistoricalHashesAccumulatorProof( | ||
HistoricalHashesAccumulatorProof::from_ssz_bytes(&proof)?, | ||
) | ||
} else if header.number <= SHANGHAI_TIMESTAMP { | ||
BlockHeaderProof::HistoricalRootsBlockProof(HistoricalRootsBlockProof::from_ssz_bytes( | ||
&proof, | ||
)?) | ||
} else { | ||
BlockHeaderProof::HistoricalSummariesBlockProof( | ||
HistoricalSummariesBlockProof::from_ssz_bytes(&proof)?, | ||
) | ||
}; | ||
Ok(Self { header, proof }) | ||
} | ||
} | ||
|
||
impl ssz::Encode for BlockHeaderProof { | ||
fn is_ssz_fixed_len() -> bool { | ||
false | ||
} | ||
|
||
fn ssz_append(&self, buf: &mut Vec<u8>) { | ||
match self { | ||
BlockHeaderProof::HistoricalHashesAccumulatorProof(proof) => { | ||
proof.ssz_append(buf); | ||
} | ||
BlockHeaderProof::HistoricalRootsBlockProof(proof) => { | ||
proof.ssz_append(buf); | ||
} | ||
BlockHeaderProof::HistoricalSummariesBlockProof(proof) => { | ||
proof.ssz_append(buf); | ||
} | ||
} | ||
} | ||
|
||
fn ssz_bytes_len(&self) -> usize { | ||
match self { | ||
BlockHeaderProof::HistoricalHashesAccumulatorProof(proof) => proof.ssz_bytes_len(), | ||
BlockHeaderProof::HistoricalRootsBlockProof(proof) => proof.ssz_bytes_len(), | ||
BlockHeaderProof::HistoricalSummariesBlockProof(proof) => proof.ssz_bytes_len(), | ||
} | ||
} | ||
} | ||
|
||
/// The struct holds a chain of proofs. This chain of proofs allows for verifying that an EL | ||
/// `BlockHeader` is part of the canonical chain. The only requirement is having access to the | ||
/// beacon chain `historical_roots`. | ||
// Total size (8 + 1 + 3 + 1 + 14) * 32 bytes + 4 bytes = 868 bytes | ||
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)] | ||
pub struct HistoricalRootsBlockProof { | ||
pub beacon_block_proof: BeaconBlockProofPreCapella, | ||
pub beacon_block_root: B256, | ||
pub historical_roots_proof: HistoricalRootsProof, | ||
pub slot: u64, | ||
} | ||
|
||
/// The struct holds a chain of proofs. This chain of proofs allows for verifying that an EL | ||
/// `BlockHeader` is part of the canonical chain. The only requirement is having access to the | ||
/// beacon chain `historical_summaries`. | ||
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)] | ||
pub struct HistoricalSummariesBlockProof { | ||
pub beacon_block_proof: BeaconBlockProof, | ||
pub beacon_block_root: B256, | ||
pub historical_summaries_proof: HistoricalSummariesProof, | ||
pub slot: u64, | ||
} | ||
|
||
pub mod ssz_header { | ||
|
||
use crate::{types::bytes::ByteList2048, Header}; | ||
|
||
pub mod encode { | ||
use ssz::Encode; | ||
|
||
use super::*; | ||
|
||
pub fn is_ssz_fixed_len() -> bool { | ||
ByteList2048::is_ssz_fixed_len() | ||
} | ||
|
||
pub fn ssz_append(header: &Header, buf: &mut Vec<u8>) { | ||
let header = alloy::rlp::encode(header); | ||
ByteList2048::from(header).ssz_append(buf); | ||
} | ||
|
||
pub fn ssz_fixed_len() -> usize { | ||
ByteList2048::ssz_fixed_len() | ||
} | ||
|
||
pub fn ssz_bytes_len(header: &Header) -> usize { | ||
// The ssz encoded length is the same as rlp encoded length. | ||
alloy_rlp::Encodable::length(header) | ||
} | ||
} | ||
|
||
pub mod decode { | ||
use alloy_rlp::Decodable; | ||
use ssz::Decode; | ||
|
||
use super::*; | ||
|
||
pub fn is_ssz_fixed_len() -> bool { | ||
ByteList2048::is_ssz_fixed_len() | ||
} | ||
|
||
pub fn ssz_fixed_len() -> usize { | ||
ByteList2048::ssz_fixed_len() | ||
} | ||
|
||
pub fn from_ssz_bytes(bytes: &[u8]) -> Result<Header, ssz::DecodeError> { | ||
let rlp_encoded_header = ByteList2048::from_ssz_bytes(bytes)?; | ||
Header::decode(&mut &*rlp_encoded_header).map_err(|_| { | ||
ssz::DecodeError::BytesInvalid("Unable to decode bytes into header.".to_string()) | ||
}) | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
#[allow(clippy::unwrap_used)] | ||
mod tests { | ||
use std::fs; | ||
|
||
use serde_json::Value; | ||
use ssz::Decode; | ||
|
||
use super::*; | ||
use crate::utils::bytes::{hex_decode, hex_encode}; | ||
|
||
#[test_log::test] | ||
fn decode_encode_headers_with_proof() { | ||
let file = | ||
fs::read_to_string("../validation/src/assets/fluffy/1000001-1000010.json").unwrap(); | ||
let json: Value = serde_json::from_str(&file).unwrap(); | ||
let hwps = json.as_object().unwrap(); | ||
for (block_number, obj) in hwps { | ||
let block_number: u64 = block_number.parse().unwrap(); | ||
let actual_hwp = obj.get("content_value").unwrap().as_str().unwrap(); | ||
let hwp = HeaderWithProof::from_ssz_bytes(&hex_decode(actual_hwp).unwrap()).unwrap(); | ||
assert_eq!(block_number, hwp.header.number); | ||
let encoded = hex_encode(ssz::Encode::as_ssz_bytes(&hwp)); | ||
assert_eq!(encoded, actual_hwp); | ||
} | ||
} | ||
|
||
#[rstest::rstest] | ||
#[case("1000010")] | ||
#[case("14764013")] | ||
#[case("15537392")] | ||
#[case("15537393")] | ||
fn decode_encode_more_headers_with_proofs(#[case] filename: &str) { | ||
let file = fs::read_to_string(format!("../validation/src/assets/fluffy/{filename}.yaml",)) | ||
.unwrap(); | ||
let yaml: serde_yaml::Value = serde_yaml::from_str(&file).unwrap(); | ||
let actual_hwp = yaml.get("content_value").unwrap().as_str().unwrap(); | ||
let hwp = HeaderWithProof::from_ssz_bytes(&hex_decode(actual_hwp).unwrap()).unwrap(); | ||
assert_eq!(hwp.header.number, filename.parse::<u64>().unwrap()); | ||
let encoded = hex_encode(ssz::Encode::as_ssz_bytes(&hwp)); | ||
assert_eq!(encoded, actual_hwp); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked the spec and it seems that
ExecutionBlockProof
is different forBlockProofHistoricalRoots
andBlockProofHistoricalSummaries
, while we still use the some type.Are all updated specs tests passing? Do we cover all these types in them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the updated specs tests are passing now. I actually don't think the test vectors cover all cases (aka, 1 pre-merge, 1 capella, 1 post-capella). This is worth addressing.
Also, looking at the spec, it looks like our naming convention is inverse of what's in the spec, which we should update to be 1-1. Though, if I'm reading everything correctly, I actually think our logic is sound. "our"
HistoricalSummariesProof
type andHistoricalRootsProof
type (which I think are the types you're referring to?) seem to be up to spec. But, honestly it's confusing with all the variable naming schemes. I don't quite consider this a blocker for this pr? I'd rather address it in a follow-up pr where I'll update all the names to be spec-compliant and add missing test vectors? But can do it here if you think that's worthwhile.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to do it in this PR.