diff --git a/Cargo.lock b/Cargo.lock index 59e7bda170..516d0df358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1235,10 +1235,12 @@ dependencies = [ "eth2", "ethereum_ssz", "lighthouse_version", + "mockito", "reqwest 0.11.27", "sensitive_url", "serde", "serde_json", + "tokio", ] [[package]] diff --git a/beacon_node/builder_client/Cargo.toml b/beacon_node/builder_client/Cargo.toml index 1920bd0ebb..9b1f86360d 100644 --- a/beacon_node/builder_client/Cargo.toml +++ b/beacon_node/builder_client/Cargo.toml @@ -12,3 +12,7 @@ reqwest = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } + +[dev-dependencies] +mockito = { workspace = true } +tokio = { workspace = true } diff --git a/beacon_node/builder_client/src/lib.rs b/beacon_node/builder_client/src/lib.rs index 2c83e34755..6b993542f3 100644 --- a/beacon_node/builder_client/src/lib.rs +++ b/beacon_node/builder_client/src/lib.rs @@ -155,15 +155,7 @@ impl BuilderHttpClient { } ContentType::Json => { self.ssz_available.store(false, Ordering::SeqCst); - let mut de = serde_json::Deserializer::from_slice(&response_bytes); - let data = - T::context_deserialize(&mut de, fork_name).map_err(Error::InvalidJson)?; - - Ok(ForkVersionedResponse { - version: fork_name, - metadata: EmptyMetadata {}, - data, - }) + serde_json::from_slice(&response_bytes).map_err(Error::InvalidJson) } } } @@ -546,6 +538,12 @@ impl BuilderHttpClient { #[cfg(test)] mod tests { use super::*; + use eth2::types::builder_bid::{BuilderBid, BuilderBidFulu}; + use eth2::types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; + use eth2::types::{MainnetEthSpec, Signature}; + use mockito::{Matcher, Server, ServerGuard}; + + type E = MainnetEthSpec; #[test] fn test_headers_no_panic() { @@ -556,4 +554,146 @@ mod tests { assert!(HeaderValue::from_str(JSON_ACCEPT_VALUE).is_ok()); assert!(HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER).is_ok()); } + + #[tokio::test] + async fn test_get_builder_header_ssz_response() { + // Set up mock server + let mut server = Server::new_async().await; + let mock_response_body = fulu_signed_builder_bid(); + mock_get_header_response( + &mut server, + Some("fulu"), + ContentType::Ssz, + mock_response_body.clone(), + ); + + let builder_client = BuilderHttpClient::new( + SensitiveUrl::from_str(&server.url()).unwrap(), + None, + None, + false, + ) + .unwrap(); + + let response = builder_client + .get_builder_header( + Slot::new(1), + ExecutionBlockHash::repeat_byte(1), + &PublicKeyBytes::empty(), + ) + .await + .expect("should succeed in get_builder_header") + .expect("should have response body"); + + assert_eq!(response, mock_response_body); + } + + #[tokio::test] + async fn test_get_builder_header_json_response() { + // Set up mock server + let mut server = Server::new_async().await; + let mock_response_body = fulu_signed_builder_bid(); + mock_get_header_response( + &mut server, + None, + ContentType::Json, + mock_response_body.clone(), + ); + + let builder_client = BuilderHttpClient::new( + SensitiveUrl::from_str(&server.url()).unwrap(), + None, + None, + false, + ) + .unwrap(); + + let response = builder_client + .get_builder_header( + Slot::new(1), + ExecutionBlockHash::repeat_byte(1), + &PublicKeyBytes::empty(), + ) + .await + .expect("should succeed in get_builder_header") + .expect("should have response body"); + + assert_eq!(response, mock_response_body); + } + + #[tokio::test] + async fn test_get_builder_header_no_version_header_fallback_json() { + // Set up mock server + let mut server = Server::new_async().await; + let mock_response_body = fulu_signed_builder_bid(); + mock_get_header_response( + &mut server, + Some("fulu"), + ContentType::Json, + mock_response_body.clone(), + ); + + let builder_client = BuilderHttpClient::new( + SensitiveUrl::from_str(&server.url()).unwrap(), + None, + None, + false, + ) + .unwrap(); + + let response = builder_client + .get_builder_header( + Slot::new(1), + ExecutionBlockHash::repeat_byte(1), + &PublicKeyBytes::empty(), + ) + .await + .expect("should succeed in get_builder_header") + .expect("should have response body"); + + assert_eq!(response, mock_response_body); + } + + fn mock_get_header_response( + server: &mut ServerGuard, + header_version_opt: Option<&str>, + content_type: ContentType, + response_body: ForkVersionedResponse>, + ) { + let mut mock = server.mock( + "GET", + Matcher::Regex(r"^/eth/v1/builder/header/\d+/.+/.+$".to_string()), + ); + + if let Some(version) = header_version_opt { + mock = mock.with_header(CONSENSUS_VERSION_HEADER, version); + } + + match content_type { + ContentType::Json => { + mock = mock + .with_header(CONTENT_TYPE_HEADER, JSON_CONTENT_TYPE_HEADER) + .with_body(serde_json::to_string(&response_body).unwrap()); + } + ContentType::Ssz => { + mock = mock + .with_header(CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER) + .with_body(response_body.data.as_ssz_bytes()); + } + } + + mock.with_status(200).create(); + } + + fn fulu_signed_builder_bid() -> ForkVersionedResponse> { + let rng = &mut XorShiftRng::from_seed([42; 16]); + ForkVersionedResponse { + version: ForkName::Fulu, + metadata: EmptyMetadata {}, + data: SignedBuilderBid { + message: BuilderBid::Fulu(BuilderBidFulu::random_for_test(rng)), + signature: Signature::empty(), + }, + } + } }