Skip to content

Commit

Permalink
feat(derive): blob testdata and provider tests
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell committed Apr 17, 2024
1 parent 5773554 commit 6cf0563
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ proptest = "1.4.0"
tracing-subscriber = "0.3.18"
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07", default-features = false }
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07", default-features = false }
serde_json = { version = "1.0.68", default-features = false }

[features]
default = ["serde", "k256"]
Expand Down
41 changes: 41 additions & 0 deletions crates/derive/src/online/blob_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,47 @@ mod tests {
types::{APIConfigResponse, APIGenesisResponse, APIGetBlobSidecarsResponse},
};
use alloc::vec;
use alloy_primitives::b256;

#[tokio::test]
async fn test_get_blobs() {
let (provider, _anvil) = spawn_anvil();
let json_bytes = include_bytes!("testdata/eth_v1_beacon_sidecars_goerli.json");
let sidecars: APIGetBlobSidecarsResponse = serde_json::from_slice(json_bytes).unwrap();
let blob_hashes = vec![
IndexedBlobHash {
index: 0,
hash: b256!("011075cbb20f3235b3179a5dff22689c410cd091692180f4b6a12be77ea0f586"),
},
IndexedBlobHash {
index: 1,
hash: b256!("010a9e10aab79bab62e10a5b83c164a91451b6ef56d31ac95a9514ffe6d6b4e6"),
},
IndexedBlobHash {
index: 2,
hash: b256!("016122c8e41c69917b688240707d107aa6d2a480343e4e323e564241769a6b4a"),
},
IndexedBlobHash {
index: 3,
hash: b256!("01df1f9ae707f5847513c9c430b683182079edf2b1f94ee12e4daae7f3c8c309"),
},
IndexedBlobHash {
index: 4,
hash: b256!("01e5ee2f6cbbafb3c03f05f340e795fe5b5a8edbcc9ac3fc7bd3d1940b99ef3c"),
},
];
let beacon_client = MockBeaconClient {
beacon_genesis: Some(APIGenesisResponse::new(10)),
config_spec: Some(APIConfigResponse::new(12)),
blob_sidecars: Some(sidecars),
..Default::default()
};
let mut blob_provider: OnlineBlobProvider<_, _, SimpleSlotDerivation> =
OnlineBlobProvider::new(provider, true, beacon_client, None, None);
let block_ref = BlockInfo { timestamp: 15, ..Default::default() };
let blobs = blob_provider.get_blobs(&block_ref, blob_hashes).await.unwrap();
assert_eq!(blobs.len(), 5);
}

#[tokio::test]
async fn test_get_blob_sidecars_empty_hashes() {
Expand Down

Large diffs are not rendered by default.

76 changes: 74 additions & 2 deletions crates/derive/src/online/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use crate::types::{Blob, BlobSidecar, IndexedBlobHash};
use alloc::vec::Vec;
use alloy_primitives::B256;

/// Constructs a list of [Blob]s from [BlobSidecar]s and the specified [IndexedBlobHash]es.
pub(crate) fn blobs_from_sidecars(
Expand Down Expand Up @@ -30,10 +31,10 @@ pub(crate) fn blobs_from_sidecars(
// Ensure the blob's kzg commitment hashes to the expected value.
if sidecar.to_kzg_versioned_hash() != hash.hash {
return Err(anyhow::anyhow!(
"expected hash {} for blob at index {} but got {:#?}",
"expected hash {} for blob at index {} but got {}",
hash.hash,
hash.index,
sidecar.to_kzg_versioned_hash()
B256::from(sidecar.to_kzg_versioned_hash())
));
}

Expand All @@ -56,7 +57,9 @@ pub(crate) fn blobs_from_sidecars(
#[cfg(test)]
mod tests {
use super::*;
use crate::types::APIGetBlobSidecarsResponse;
use alloc::{string::ToString, vec};
use alloy_primitives::{b256, FixedBytes};

#[test]
fn test_blobs_from_sidecars_length_mismatch() {
Expand All @@ -65,4 +68,73 @@ mod tests {
let err = blobs_from_sidecars(&sidecars, &hashes).unwrap_err();
assert_eq!(err.to_string(), "blob sidecars and hashes length mismatch, 1 != 2");
}

#[test]
fn test_blobs_from_sidecars_invalid_ordering() {
let sidecars = vec![BlobSidecar::default()];
let hashes = vec![IndexedBlobHash { index: 1, ..Default::default() }];
let err = blobs_from_sidecars(&sidecars, &hashes).unwrap_err();
assert_eq!(
err.to_string(),
"invalid sidecar ordering, blob hash index 1 does not match sidecar index 0"
);
}

#[test]
fn test_blobs_from_sidecars_invalid_hash() {
let sidecars = vec![BlobSidecar::default()];
let hashes =
vec![IndexedBlobHash { hash: FixedBytes::from([1; 32]), ..Default::default() }];
let err = blobs_from_sidecars(&sidecars, &hashes).unwrap_err();
assert_eq!(
err.to_string(),
"expected hash 0x0101010101010101010101010101010101010101010101010101010101010101 for blob at index 0 but got 0x01b0761f87b081d5cf10757ccc89f12be355c70e2e29df288b65b30710dcbcd1"
);
}

#[test]
fn test_blobs_from_sidecars_failed_verification() {
let sidecars = vec![BlobSidecar::default()];
let hashes = vec![IndexedBlobHash {
hash: b256!("01b0761f87b081d5cf10757ccc89f12be355c70e2e29df288b65b30710dcbcd1"),
..Default::default()
}];
let err = blobs_from_sidecars(&sidecars, &hashes).unwrap_err();
assert_eq!(err.to_string(), "blob at index 0 failed verification");
}

#[test]
fn test_blobs_from_sidecars_succeeds() {
// Read in the test data
let json_bytes = include_bytes!("testdata/eth_v1_beacon_sidecars_goerli.json");
let sidecars: APIGetBlobSidecarsResponse = serde_json::from_slice(json_bytes).unwrap();
let hashes = vec![
IndexedBlobHash {
index: 0,
hash: b256!("011075cbb20f3235b3179a5dff22689c410cd091692180f4b6a12be77ea0f586"),
},
IndexedBlobHash {
index: 1,
hash: b256!("010a9e10aab79bab62e10a5b83c164a91451b6ef56d31ac95a9514ffe6d6b4e6"),
},
IndexedBlobHash {
index: 2,
hash: b256!("016122c8e41c69917b688240707d107aa6d2a480343e4e323e564241769a6b4a"),
},
IndexedBlobHash {
index: 3,
hash: b256!("01df1f9ae707f5847513c9c430b683182079edf2b1f94ee12e4daae7f3c8c309"),
},
IndexedBlobHash {
index: 4,
hash: b256!("01e5ee2f6cbbafb3c03f05f340e795fe5b5a8edbcc9ac3fc7bd3d1940b99ef3c"),
},
];
let blob_sidecars = sidecars.data.into_iter().map(|s| s.inner).collect::<Vec<_>>();
let blobs = blobs_from_sidecars(&blob_sidecars, &hashes).unwrap();
assert_eq!(blobs.len(), 5);
for (i, blob) in blobs.iter().enumerate() {
assert_eq!(blob.len(), 131072, "blob {} has incorrect length", i);
}
}
}
20 changes: 20 additions & 0 deletions crates/derive/src/types/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ use sha2::{Digest, Sha256};
#[cfg(feature = "online")]
use tracing::warn;

#[cfg(feature = "serde")]
use serde::de::Deserialize;

#[cfg(feature = "serde")]
use core::str::FromStr;

/// KZG Proof Size
pub const KZG_PROOF_SIZE: usize = 48;

Expand All @@ -22,13 +28,23 @@ pub const KZG_COMMITMENT_SIZE: usize = 48;
#[cfg(feature = "online")]
pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

#[cfg(feature = "serde")]
fn parse_u64_string<'de, T, D>(de: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: FromStr,
<T as FromStr>::Err: core::fmt::Display,
{
String::deserialize(de)?.parse().map_err(serde::de::Error::custom)
}
/// A blob sidecar.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobSidecar {
/// The blob.
pub blob: Blob,
/// The index.
#[cfg_attr(feature = "serde", serde(deserialize_with = "parse_u64_string"))]
pub index: u64,
/// The KZG commitment.
#[cfg_attr(feature = "serde", serde(rename = "kzg_commitment"))]
Expand Down Expand Up @@ -94,8 +110,10 @@ pub struct SignedBeaconBlockHeader {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BeaconBlockHeader {
/// The slot.
#[cfg_attr(feature = "serde", serde(deserialize_with = "parse_u64_string"))]
pub slot: u64,
/// The proposer index.
#[cfg_attr(feature = "serde", serde(deserialize_with = "parse_u64_string"))]
pub proposer_index: u64,
/// The parent root.
#[cfg_attr(feature = "serde", serde(rename = "parent_root"))]
Expand Down Expand Up @@ -132,6 +150,7 @@ impl Clone for APIGetBlobSidecarsResponse {
pub struct ReducedGenesisData {
/// The genesis time.
#[cfg_attr(feature = "serde", serde(rename = "genesis_time"))]
#[cfg_attr(feature = "serde", serde(deserialize_with = "parse_u64_string"))]
pub genesis_time: u64,
}

Expand All @@ -156,6 +175,7 @@ impl APIGenesisResponse {
pub struct ReducedConfigData {
/// The seconds per slot.
#[cfg_attr(feature = "serde", serde(rename = "SECONDS_PER_SLOT"))]
#[cfg_attr(feature = "serde", serde(deserialize_with = "parse_u64_string"))]
pub seconds_per_slot: u64,
}

Expand Down

0 comments on commit 6cf0563

Please sign in to comment.