From 5476b8e6d3803fc7ff107f8f7697172233a405d3 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Fri, 29 Sep 2023 03:16:11 +0000 Subject: [PATCH] Use only lighthouse types in the mock builder (#4793) ## Proposed Changes - only use LH types to avoid build issues - use warp instead of axum for the server to avoid importing the dep ## Additional Info - wondering if we can move the `execution_layer/test_utils` to its own crate and import it as a dev dependency - this would be made easier by separating out our engine API types into their own crate so we can use them in the test crate - or maybe we can look into using reth types for the engine api if they are in their own crate Co-authored-by: realbigsean --- Cargo.lock | 216 +----- Cargo.toml | 2 +- beacon_node/beacon_chain/src/test_utils.rs | 14 +- beacon_node/execution_layer/Cargo.toml | 5 - .../src/test_utils/mock_builder.rs | 729 +++++++++--------- .../execution_layer/src/test_utils/mod.rs | 2 +- beacon_node/http_api/tests/tests.rs | 6 +- consensus/types/src/builder_bid.rs | 2 +- .../types/src/validator_registration_data.rs | 14 + validator_client/Cargo.toml | 2 +- 10 files changed, 410 insertions(+), 582 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90a5373dbb5..510c633a908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,15 +195,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "anvil-rpc" -version = "0.1.0" -source = "git+https://github.com/foundry-rs/foundry?rev=b45456717ffae1af65acdc71099f8cb95e6683a0#b45456717ffae1af65acdc71099f8cb95e6683a0" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "anyhow" version = "1.0.75" @@ -320,28 +311,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", -] - [[package]] name = "async-trait" version = "0.1.73" @@ -518,24 +487,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "beacon-api-client" -version = "0.1.0" -source = "git+https://github.com/ralexstokes/beacon-api-client?rev=93d7e8c#93d7e8c38fe9782c4862909663e7b57c44f805a9" -dependencies = [ - "ethereum-consensus", - "http", - "itertools", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-subscriber", - "url", -] - [[package]] name = "beacon_chain" version = "0.2.0" @@ -1758,7 +1709,7 @@ dependencies = [ "aes-gcm", "arrayvec", "delay_map", - "enr 0.9.0", + "enr", "fnv", "futures", "hashlink 0.7.0", @@ -1904,7 +1855,6 @@ dependencies = [ "ff 0.12.1", "generic-array", "group 0.12.1", - "pkcs8 0.9.0", "rand_core 0.6.4", "sec1 0.3.0", "subtle", @@ -1940,25 +1890,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enr" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fa0a0be8915790626d5759eb51fe47435a8eac92c2f212bd2da9aa7f30ea56" -dependencies = [ - "base64 0.13.1", - "bs58 0.4.0", - "bytes", - "hex", - "k256 0.11.6", - "log", - "rand", - "rlp", - "serde", - "sha3 0.10.8", - "zeroize", -] - [[package]] name = "enr" version = "0.9.0" @@ -2314,30 +2245,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "ethereum-consensus" -version = "0.1.1" -source = "git+https://github.com/ralexstokes/ethereum-consensus?rev=e380108#e380108d15fcc40349927fdf3d11c71f9edb67c2" -dependencies = [ - "async-stream", - "blst", - "bs58 0.4.0", - "enr 0.6.2", - "hex", - "integer-sqrt", - "multiaddr 0.14.0", - "multihash 0.16.3", - "rand", - "serde", - "serde_json", - "serde_yaml", - "sha2 0.9.9", - "ssz_rs", - "thiserror", - "tokio", - "tokio-stream", -] - [[package]] name = "ethereum-types" version = "0.12.1" @@ -2575,12 +2482,10 @@ version = "0.1.0" dependencies = [ "arc-swap", "async-trait", - "axum", "builder_client", "bytes", "environment", "eth2", - "ethereum-consensus", "ethereum_serde_utils", "ethereum_ssz", "ethers-core", @@ -2590,13 +2495,11 @@ dependencies = [ "hash-db", "hash256-std-hasher", "hex", - "hyper", "jsonwebtoken", "keccak-hash", "lazy_static", "lighthouse_metrics", "lru 0.7.8", - "mev-rs", "parking_lot 0.12.1", "pretty_reqwest_error", "rand", @@ -2606,7 +2509,6 @@ dependencies = [ "serde_json", "slog", "slot_clock", - "ssz_rs", "ssz_types", "state_processing", "strum", @@ -4017,7 +3919,7 @@ dependencies = [ "libp2p-swarm", "libp2p-tcp", "libp2p-yamux", - "multiaddr 0.18.0", + "multiaddr", "pin-project", ] @@ -4058,8 +3960,8 @@ dependencies = [ "instant", "libp2p-identity", "log", - "multiaddr 0.18.0", - "multihash 0.19.1", + "multiaddr", + "multihash", "multistream-select", "once_cell", "parking_lot 0.12.1", @@ -4153,7 +4055,7 @@ dependencies = [ "ed25519-dalek", "libsecp256k1", "log", - "multihash 0.19.1", + "multihash", "p256", "quick-protobuf", "rand", @@ -4232,8 +4134,8 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "multiaddr 0.18.0", - "multihash 0.19.1", + "multiaddr", + "multihash", "once_cell", "quick-protobuf", "rand", @@ -4819,27 +4721,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "mev-rs" -version = "0.3.0" -source = "git+https://github.com/ralexstokes/mev-rs?rev=216657016d5c0889b505857c89ae42c7aa2764af#216657016d5c0889b505857c89ae42c7aa2764af" -dependencies = [ - "anvil-rpc", - "async-trait", - "axum", - "beacon-api-client", - "ethereum-consensus", - "hyper", - "parking_lot 0.12.1", - "reqwest", - "serde", - "serde_json", - "ssz_rs", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "migrations_internals" version = "2.1.0" @@ -4941,24 +4822,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" -[[package]] -name = "multiaddr" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" -dependencies = [ - "arrayref", - "bs58 0.4.0", - "byteorder", - "data-encoding", - "multihash 0.16.3", - "percent-encoding", - "serde", - "static_assertions", - "unsigned-varint 0.7.2", - "url", -] - [[package]] name = "multiaddr" version = "0.18.0" @@ -4970,7 +4833,7 @@ dependencies = [ "data-encoding", "libp2p-identity", "multibase", - "multihash 0.19.1", + "multihash", "percent-encoding", "serde", "static_assertions", @@ -4989,19 +4852,6 @@ dependencies = [ "data-encoding-macro", ] -[[package]] -name = "multihash" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc" -dependencies = [ - "core2", - "digest 0.10.7", - "multihash-derive", - "sha2 0.10.7", - "unsigned-varint 0.7.2", -] - [[package]] name = "multihash" version = "0.19.1" @@ -5012,20 +4862,6 @@ dependencies = [ "unsigned-varint 0.7.2", ] -[[package]] -name = "multihash-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" -dependencies = [ - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure", -] - [[package]] name = "multistream-select" version = "0.13.0" @@ -5442,15 +5278,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.1.3+3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd2c101a165fff9935e34def4669595ab1c7847943c42be86e21503e482be107" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.93" @@ -5459,7 +5286,6 @@ checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -7385,31 +7211,6 @@ dependencies = [ "der 0.7.8", ] -[[package]] -name = "ssz_rs" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057291e5631f280978fa9c8009390663ca4613359fc1318e36a8c24c392f6d1f" -dependencies = [ - "bitvec 1.0.1", - "hex", - "num-bigint", - "serde", - "sha2 0.9.9", - "ssz_rs_derive", -] - -[[package]] -name = "ssz_rs_derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "ssz_types" version = "0.5.4" @@ -7917,7 +7718,6 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2 0.5.4", diff --git a/Cargo.toml b/Cargo.toml index 62c0e7bd20a..bb100d5a042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,7 +156,7 @@ superstruct = "0.6" syn = "1" sysinfo = "0.26" tempfile = "3" -tokio = { version = "1", features = ["rt-multi-thread", "sync"] } +tokio = { version = "1", features = ["rt-multi-thread", "sync", "signal"] } tokio-stream = { version = "0.1", features = ["sync"] } tokio-util = { version = "0.6", features = ["codec", "compat", "time"] } tree_hash = "0.5" diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 5e54b1194d4..ed54af7abec 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -17,8 +17,8 @@ use bls::get_withdrawal_credentials; use execution_layer::{ auth::JwtKey, test_utils::{ - ExecutionBlockGenerator, MockBuilder, MockBuilderServer, MockExecutionLayer, - DEFAULT_JWT_SECRET, DEFAULT_TERMINAL_BLOCK, + ExecutionBlockGenerator, MockBuilder, MockExecutionLayer, DEFAULT_JWT_SECRET, + DEFAULT_TERMINAL_BLOCK, }, ExecutionLayer, }; @@ -595,7 +595,10 @@ where .execution_block_generator() } - pub fn set_mock_builder(&mut self, beacon_url: SensitiveUrl) -> MockBuilderServer { + pub fn set_mock_builder( + &mut self, + beacon_url: SensitiveUrl, + ) -> impl std::future::Future { let mock_el = self .mock_execution_layer .as_ref() @@ -604,7 +607,7 @@ where let mock_el_url = SensitiveUrl::parse(mock_el.server.url().as_str()).unwrap(); // Create the builder, listening on a free port. - let (mock_builder, mock_builder_server) = MockBuilder::new_for_testing( + let (mock_builder, (addr, mock_builder_server)) = MockBuilder::new_for_testing( mock_el_url, beacon_url, self.spec.clone(), @@ -612,8 +615,7 @@ where ); // Set the builder URL in the execution layer now that its port is known. - let builder_listen_addr = mock_builder_server.local_addr(); - let port = builder_listen_addr.port(); + let port = addr.port(); mock_el .el .set_builder_url( diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 3651e371883..257ff945f89 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -41,11 +41,6 @@ lazy_static = { workspace = true } ethers-core = { workspace = true } builder_client = { path = "../builder_client" } fork_choice = { workspace = true } -mev-rs = { git = "https://github.com/ralexstokes/mev-rs", rev = "216657016d5c0889b505857c89ae42c7aa2764af" } -axum = "0.6" -hyper = "0.14" -ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "e380108" } -ssz_rs = "0.9.0" tokio-stream = { workspace = true } strum = { workspace = true } keccak-hash = "0.10.0" diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index c9dd196fabc..2d3cc27eb1d 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -1,49 +1,28 @@ use crate::test_utils::{DEFAULT_BUILDER_PAYLOAD_VALUE_WEI, DEFAULT_JWT_SECRET}; use crate::{Config, ExecutionLayer, PayloadAttributes}; -use async_trait::async_trait; use eth2::types::{BlockId, StateId, ValidatorId}; use eth2::{BeaconNodeHttpClient, Timeouts}; -pub use ethereum_consensus::state_transition::Context; -use ethereum_consensus::{ - crypto::{SecretKey, Signature}, - primitives::{BlsPublicKey, BlsSignature, ExecutionAddress, Hash32, Root, U256}, - state_transition::Error, -}; use fork_choice::ForkchoiceUpdateParameters; -use mev_rs::{ - blinded_block_provider::Server as BlindedBlockProviderServer, - signing::{sign_builder_message, verify_signed_builder_message}, - types::{ - bellatrix::{ - BuilderBid as BuilderBidBellatrix, SignedBuilderBid as SignedBuilderBidBellatrix, - }, - capella::{BuilderBid as BuilderBidCapella, SignedBuilderBid as SignedBuilderBidCapella}, - BidRequest, BuilderBid, ExecutionPayload as ServerPayload, SignedBlindedBeaconBlock, - SignedBuilderBid, SignedValidatorRegistration, - }, - Error as MevError, -}; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; -use ssz::{Decode, Encode}; -use ssz_rs::{Merkleized, SimpleSerialize}; use std::collections::HashMap; use std::fmt::Debug; -use std::net::Ipv4Addr; +use std::future::Future; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::sync::Arc; use std::time::Duration; use task_executor::TaskExecutor; use tempfile::NamedTempFile; use tree_hash::TreeHash; +use types::builder_bid::{BuilderBid, SignedBuilderBid}; +use types::payload::BlindedPayloadRefMut; use types::{ - Address, BeaconState, BlindedPayload, ChainSpec, EthSpec, ExecPayload, ForkName, Hash256, Slot, - Uint256, + Address, BeaconState, BlindedPayload, ChainSpec, EthSpec, ExecPayload, ForkName, + ForkVersionedResponse, Hash256, PublicKeyBytes, Signature, SignedBlindedBeaconBlock, + SignedRoot, SignedValidatorRegistrationData, Slot, Uint256, }; - -pub type MockBuilderServer = axum::Server< - hyper::server::conn::AddrIncoming, - axum::routing::IntoMakeService, ->; +use types::{ExecutionBlockHash, SecretKey}; +use warp::{Filter, Rejection}; #[derive(Clone)] pub enum Operation { @@ -58,119 +37,128 @@ pub enum Operation { } impl Operation { - fn apply(self, bid: &mut B) -> Result<(), MevError> { + fn apply>(self, bid: &mut B) { match self { - Operation::FeeRecipient(fee_recipient) => { - *bid.fee_recipient_mut() = to_ssz_rs(&fee_recipient)? - } - Operation::GasLimit(gas_limit) => *bid.gas_limit_mut() = gas_limit as u64, - Operation::Value(value) => *bid.value_mut() = to_ssz_rs(&value)?, - Operation::ParentHash(parent_hash) => *bid.parent_hash_mut() = to_ssz_rs(&parent_hash)?, - Operation::PrevRandao(prev_randao) => *bid.prev_randao_mut() = to_ssz_rs(&prev_randao)?, - Operation::BlockNumber(block_number) => *bid.block_number_mut() = block_number as u64, - Operation::Timestamp(timestamp) => *bid.timestamp_mut() = timestamp as u64, - Operation::WithdrawalsRoot(root) => *bid.withdrawals_root_mut()? = to_ssz_rs(&root)?, + Operation::FeeRecipient(fee_recipient) => bid.set_fee_recipient(fee_recipient), + Operation::GasLimit(gas_limit) => bid.set_gas_limit(gas_limit as u64), + Operation::Value(value) => bid.set_value(value), + Operation::ParentHash(parent_hash) => bid.set_parent_hash(parent_hash), + Operation::PrevRandao(prev_randao) => bid.set_prev_randao(prev_randao), + Operation::BlockNumber(block_number) => bid.set_block_number(block_number as u64), + Operation::Timestamp(timestamp) => bid.set_timestamp(timestamp as u64), + Operation::WithdrawalsRoot(root) => bid.set_withdrawals_root(root), } - Ok(()) } } +#[derive(Debug)] +struct Custom(String); + +impl warp::reject::Reject for Custom {} + // contains functions we need for BuilderBids.. not sure what to call this -pub trait BidStuff { - fn fee_recipient_mut(&mut self) -> &mut ExecutionAddress; - fn gas_limit_mut(&mut self) -> &mut u64; - fn value_mut(&mut self) -> &mut U256; - fn parent_hash_mut(&mut self) -> &mut Hash32; - fn prev_randao_mut(&mut self) -> &mut Hash32; - fn block_number_mut(&mut self) -> &mut u64; - fn timestamp_mut(&mut self) -> &mut u64; - fn withdrawals_root_mut(&mut self) -> Result<&mut Root, MevError>; - - fn sign_builder_message( - &mut self, - signing_key: &SecretKey, - context: &Context, - ) -> Result; - - fn to_signed_bid(self, signature: BlsSignature) -> SignedBuilderBid; +pub trait BidStuff { + fn set_fee_recipient(&mut self, fee_recipient_address: Address); + fn set_gas_limit(&mut self, gas_limit: u64); + fn set_value(&mut self, value: Uint256); + fn set_parent_hash(&mut self, parent_hash: Hash256); + fn set_prev_randao(&mut self, randao: Hash256); + fn set_block_number(&mut self, block_number: u64); + fn set_timestamp(&mut self, timestamp: u64); + fn set_withdrawals_root(&mut self, withdrawals_root: Hash256); + + fn sign_builder_message(&mut self, sk: &SecretKey, spec: &ChainSpec) -> Signature; + + fn to_signed_bid(self, signature: Signature) -> SignedBuilderBid>; } -impl BidStuff for BuilderBid { - fn fee_recipient_mut(&mut self) -> &mut ExecutionAddress { - match self { - Self::Bellatrix(bid) => &mut bid.header.fee_recipient, - Self::Capella(bid) => &mut bid.header.fee_recipient, +impl BidStuff for BuilderBid> { + fn set_fee_recipient(&mut self, fee_recipient: Address) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(payload) => { + payload.execution_payload_header.fee_recipient = fee_recipient; + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.fee_recipient = fee_recipient; + } } } - - fn gas_limit_mut(&mut self) -> &mut u64 { - match self { - Self::Bellatrix(bid) => &mut bid.header.gas_limit, - Self::Capella(bid) => &mut bid.header.gas_limit, + fn set_gas_limit(&mut self, gas_limit: u64) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(payload) => { + payload.execution_payload_header.gas_limit = gas_limit; + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.gas_limit = gas_limit; + } } } - - fn value_mut(&mut self) -> &mut U256 { - match self { - Self::Bellatrix(bid) => &mut bid.value, - Self::Capella(bid) => &mut bid.value, - } + fn set_value(&mut self, value: Uint256) { + self.value = value; } - - fn parent_hash_mut(&mut self) -> &mut Hash32 { - match self { - Self::Bellatrix(bid) => &mut bid.header.parent_hash, - Self::Capella(bid) => &mut bid.header.parent_hash, + fn set_parent_hash(&mut self, parent_hash: Hash256) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(payload) => { + payload.execution_payload_header.parent_hash = + ExecutionBlockHash::from_root(parent_hash); + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.parent_hash = + ExecutionBlockHash::from_root(parent_hash); + } } } - - fn prev_randao_mut(&mut self) -> &mut Hash32 { - match self { - Self::Bellatrix(bid) => &mut bid.header.prev_randao, - Self::Capella(bid) => &mut bid.header.prev_randao, + fn set_prev_randao(&mut self, prev_randao: Hash256) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(payload) => { + payload.execution_payload_header.prev_randao = prev_randao; + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.prev_randao = prev_randao; + } } } - - fn block_number_mut(&mut self) -> &mut u64 { - match self { - Self::Bellatrix(bid) => &mut bid.header.block_number, - Self::Capella(bid) => &mut bid.header.block_number, + fn set_block_number(&mut self, block_number: u64) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(payload) => { + payload.execution_payload_header.block_number = block_number; + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.block_number = block_number; + } } } - - fn timestamp_mut(&mut self) -> &mut u64 { - match self { - Self::Bellatrix(bid) => &mut bid.header.timestamp, - Self::Capella(bid) => &mut bid.header.timestamp, + fn set_timestamp(&mut self, timestamp: u64) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(payload) => { + payload.execution_payload_header.timestamp = timestamp; + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.timestamp = timestamp; + } } } - - fn withdrawals_root_mut(&mut self) -> Result<&mut Root, MevError> { - match self { - Self::Bellatrix(_) => Err(MevError::InvalidFork), - Self::Capella(bid) => Ok(&mut bid.header.withdrawals_root), + fn set_withdrawals_root(&mut self, withdrawals_root: Hash256) { + match self.header.to_mut() { + BlindedPayloadRefMut::Merge(_) => { + panic!("no withdrawals before capella") + } + BlindedPayloadRefMut::Capella(payload) => { + payload.execution_payload_header.withdrawals_root = withdrawals_root; + } } } - fn sign_builder_message( - &mut self, - signing_key: &SecretKey, - context: &Context, - ) -> Result { - match self { - Self::Bellatrix(message) => sign_builder_message(message, signing_key, context), - Self::Capella(message) => sign_builder_message(message, signing_key, context), - } + fn sign_builder_message(&mut self, sk: &SecretKey, spec: &ChainSpec) -> Signature { + let domain = spec.get_builder_domain(); + let message = self.signing_root(domain); + sk.sign(message) } - fn to_signed_bid(self, signature: Signature) -> SignedBuilderBid { - match self { - Self::Bellatrix(message) => { - SignedBuilderBid::Bellatrix(SignedBuilderBidBellatrix { message, signature }) - } - Self::Capella(message) => { - SignedBuilderBid::Capella(SignedBuilderBidCapella { message, signature }) - } + fn to_signed_bid(self, signature: Signature) -> SignedBuilderBid> { + SignedBuilderBid { + message: self, + signature, } } } @@ -180,8 +168,7 @@ pub struct MockBuilder { el: ExecutionLayer, beacon_client: BeaconNodeHttpClient, spec: ChainSpec, - context: Arc, - val_registration_cache: Arc>>, + val_registration_cache: Arc>>, builder_sk: SecretKey, operations: Arc>>, invalidate_signatures: Arc>, @@ -193,7 +180,7 @@ impl MockBuilder { beacon_url: SensitiveUrl, spec: ChainSpec, executor: TaskExecutor, - ) -> (Self, MockBuilderServer) { + ) -> (Self, (SocketAddr, impl Future)) { let file = NamedTempFile::new().unwrap(); let path = file.path().into(); std::fs::write(&path, hex::encode(DEFAULT_JWT_SECRET)).unwrap(); @@ -209,23 +196,14 @@ impl MockBuilder { let el = ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap(); - // This should probably be done for all fields, we only update ones we are testing with so far. - let mut context = Context::for_mainnet(); - context.terminal_total_difficulty = to_ssz_rs(&spec.terminal_total_difficulty).unwrap(); - context.terminal_block_hash = to_ssz_rs(&spec.terminal_block_hash).unwrap(); - context.terminal_block_hash_activation_epoch = - to_ssz_rs(&spec.terminal_block_hash_activation_epoch).unwrap(); - let builder = MockBuilder::new( el, BeaconNodeHttpClient::new(beacon_url, Timeouts::set_all(Duration::from_secs(1))), spec, - context, ); let host: Ipv4Addr = Ipv4Addr::LOCALHOST; let port = 0; - let provider = BlindedBlockProviderServer::new(host, port, builder.clone()); - let server = provider.serve(); + let server = serve(host, port, builder.clone()).expect("mock builder server should start"); (builder, server) } @@ -233,15 +211,13 @@ impl MockBuilder { el: ExecutionLayer, beacon_client: BeaconNodeHttpClient, spec: ChainSpec, - context: Context, ) -> Self { - let sk = SecretKey::random(&mut rand::thread_rng()).unwrap(); + let sk = SecretKey::random(); Self { el, beacon_client, // Should keep spec and context consistent somehow spec, - context: Arc::new(context), val_registration_cache: Arc::new(RwLock::new(HashMap::new())), builder_sk: sk, operations: Arc::new(RwLock::new(vec![])), @@ -263,237 +239,282 @@ impl MockBuilder { *self.invalidate_signatures.write() = false; } - fn apply_operations(&self, bid: &mut B) -> Result<(), MevError> { + fn apply_operations>(&self, bid: &mut B) { let mut guard = self.operations.write(); while let Some(op) = guard.pop() { - op.apply(bid)?; + op.apply(bid); } - Ok(()) } } -#[async_trait] -impl mev_rs::BlindedBlockProvider for MockBuilder { - async fn register_validators( - &self, - registrations: &mut [SignedValidatorRegistration], - ) -> Result<(), MevError> { - for registration in registrations { - let pubkey = registration.message.public_key.clone(); - let message = &mut registration.message; - verify_signed_builder_message( - message, - ®istration.signature, - &pubkey, - &self.context, - )?; - self.val_registration_cache.write().insert( - registration.message.public_key.clone(), - registration.clone(), - ); - } - - Ok(()) - } - - async fn fetch_best_bid(&self, bid_request: &BidRequest) -> Result { - let slot = Slot::new(bid_request.slot); - let fork = self.spec.fork_name_at_slot::(slot); - let signed_cached_data = self - .val_registration_cache - .read() - .get(&bid_request.public_key) - .ok_or_else(|| convert_err("missing registration"))? - .clone(); - let cached_data = signed_cached_data.message; - - let head = self - .beacon_client - .get_beacon_blocks::(BlockId::Head) - .await - .map_err(convert_err)? - .ok_or_else(|| convert_err("missing head block"))?; - - let block = head.data.message(); - let head_block_root = block.tree_hash_root(); - let head_execution_hash = block - .body() - .execution_payload() - .map_err(convert_err)? - .block_hash(); - if head_execution_hash != from_ssz_rs(&bid_request.parent_hash)? { - return Err(custom_err(format!( - "head mismatch: {} {}", - head_execution_hash, bid_request.parent_hash - ))); - } - - let finalized_execution_hash = self - .beacon_client - .get_beacon_blocks::(BlockId::Finalized) - .await - .map_err(convert_err)? - .ok_or_else(|| convert_err("missing finalized block"))? - .data - .message() - .body() - .execution_payload() - .map_err(convert_err)? - .block_hash(); - - let justified_execution_hash = self - .beacon_client - .get_beacon_blocks::(BlockId::Justified) - .await - .map_err(convert_err)? - .ok_or_else(|| convert_err("missing finalized block"))? - .data - .message() - .body() - .execution_payload() - .map_err(convert_err)? - .block_hash(); - - let val_index = self - .beacon_client - .get_beacon_states_validator_id( - StateId::Head, - &ValidatorId::PublicKey(from_ssz_rs(&cached_data.public_key)?), - ) - .await - .map_err(convert_err)? - .ok_or_else(|| convert_err("missing validator from state"))? - .data - .index; - let fee_recipient = from_ssz_rs(&cached_data.fee_recipient)?; - let slots_since_genesis = slot.as_u64() - self.spec.genesis_slot.as_u64(); - - let genesis_time = self - .beacon_client - .get_beacon_genesis() - .await - .map_err(convert_err)? - .data - .genesis_time; - let timestamp = (slots_since_genesis * self.spec.seconds_per_slot) + genesis_time; - - let head_state: BeaconState = self - .beacon_client - .get_debug_beacon_states(StateId::Head) - .await - .map_err(convert_err)? - .ok_or_else(|| custom_err("missing head state".to_string()))? - .data; - let prev_randao = head_state - .get_randao_mix(head_state.current_epoch()) - .map_err(convert_err)?; - - let payload_attributes = match fork { - ForkName::Merge => PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None), - // the withdrawals root is filled in by operations - ForkName::Capella => { - PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, Some(vec![])) - } - ForkName::Base | ForkName::Altair => { - return Err(MevError::InvalidFork); - } - }; - - self.el - .insert_proposer(slot, head_block_root, val_index, payload_attributes.clone()) - .await; - - let forkchoice_update_params = ForkchoiceUpdateParameters { - head_root: Hash256::zero(), - head_hash: None, - justified_hash: Some(justified_execution_hash), - finalized_hash: Some(finalized_execution_hash), - }; - - let payload = self - .el - .get_full_payload_caching::>( - head_execution_hash, - &payload_attributes, - forkchoice_update_params, - fork, - ) - .await - .map_err(convert_err)? - .to_payload() - .to_execution_payload_header(); - - let json_payload = serde_json::to_string(&payload).map_err(convert_err)?; - let mut message = match fork { - ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { - header: serde_json::from_str(json_payload.as_str()).map_err(convert_err)?, - value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?, - public_key: self.builder_sk.public_key(), - }), - ForkName::Merge => BuilderBid::Bellatrix(BuilderBidBellatrix { - header: serde_json::from_str(json_payload.as_str()).map_err(convert_err)?, - value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?, - public_key: self.builder_sk.public_key(), - }), - ForkName::Base | ForkName::Altair => return Err(MevError::InvalidFork), - }; - *message.gas_limit_mut() = cached_data.gas_limit; - - self.apply_operations(&mut message)?; - let mut signature = - message.sign_builder_message(&self.builder_sk, self.context.as_ref())?; - - if *self.invalidate_signatures.read() { - signature = Signature::default(); - } - - Ok(message.to_signed_bid(signature)) - } - - async fn open_bid( - &self, - signed_block: &mut SignedBlindedBeaconBlock, - ) -> Result { - let node = match signed_block { - SignedBlindedBeaconBlock::Bellatrix(block) => { - block.message.body.execution_payload_header.hash_tree_root() - } - SignedBlindedBeaconBlock::Capella(block) => { - block.message.body.execution_payload_header.hash_tree_root() - } - } - .map_err(convert_err)?; - - let payload = self - .el - .get_payload_by_root(&from_ssz_rs(&node)?) - .ok_or_else(|| convert_err("missing payload for tx root"))?; +pub fn serve( + listen_addr: Ipv4Addr, + listen_port: u16, + builder: MockBuilder, +) -> Result<(SocketAddr, impl Future), crate::test_utils::Error> { + let inner_ctx = builder.clone(); + let ctx_filter = warp::any().map(move || inner_ctx.clone()); + + let prefix = warp::path("eth") + .and(warp::path("v1")) + .and(warp::path("builder")); + + let validators = prefix + .and(warp::path("validators")) + .and(warp::body::json()) + .and(warp::path::end()) + .and(ctx_filter.clone()) + .and_then( + |registrations: Vec, builder: MockBuilder| async move { + for registration in registrations { + if !registration.verify_signature(&builder.spec) { + return Err(reject("invalid signature")); + } + builder + .val_registration_cache + .write() + .insert(registration.message.pubkey, registration); + } + Ok(warp::reply()) + }, + ); - let json_payload = serde_json::to_string(&payload).map_err(convert_err)?; - serde_json::from_str(json_payload.as_str()).map_err(convert_err) - } -} + let blinded_block = prefix + .and(warp::path("blinded_blocks")) + .and(warp::body::json()) + .and(warp::path::end()) + .and(ctx_filter.clone()) + .and_then( + |block: SignedBlindedBeaconBlock, builder: MockBuilder| async move { + let slot = block.slot(); + let root = match block { + SignedBlindedBeaconBlock::Base(_) | types::SignedBeaconBlock::Altair(_) => { + return Err(reject("invalid fork")); + } + SignedBlindedBeaconBlock::Merge(block) => { + block.message.body.execution_payload.tree_hash_root() + } + SignedBlindedBeaconBlock::Capella(block) => { + block.message.body.execution_payload.tree_hash_root() + } + }; + + let fork_name = builder.spec.fork_name_at_slot::(slot); + let payload = builder + .el + .get_payload_by_root(&root) + .ok_or_else(|| reject("missing payload for tx root"))?; + let resp = ForkVersionedResponse { + version: Some(fork_name), + data: payload, + }; + + let json_payload = serde_json::to_string(&resp) + .map_err(|_| reject("coudn't serialize response"))?; + Ok::<_, warp::reject::Rejection>( + warp::http::Response::builder() + .status(200) + .body( + serde_json::to_string(&json_payload) + .map_err(|_| reject("nvalid JSON"))?, + ) + .unwrap(), + ) + }, + ); -pub fn from_ssz_rs(ssz_rs_data: &T) -> Result { - U::from_ssz_bytes( - ssz_rs::serialize(ssz_rs_data) - .map_err(convert_err)? - .as_ref(), - ) - .map_err(convert_err) -} + let status = prefix + .and(warp::path("status")) + .then(|| async { warp::reply() }); + + let header = prefix + .and(warp::path("header")) + .and(warp::path::param::().or_else(|_| async { Err(reject("Invalid slot")) })) + .and( + warp::path::param::() + .or_else(|_| async { Err(reject("Invalid parent hash")) }), + ) + .and( + warp::path::param::() + .or_else(|_| async { Err(reject("Invalid pubkey")) }), + ) + .and(warp::path::end()) + .and(ctx_filter.clone()) + .and_then( + |slot: Slot, + parent_hash: ExecutionBlockHash, + pubkey: PublicKeyBytes, + builder: MockBuilder| async move { + let fork = builder.spec.fork_name_at_slot::(slot); + let signed_cached_data = builder + .val_registration_cache + .read() + .get(&pubkey) + .ok_or_else(|| reject("missing registration"))? + .clone(); + let cached_data = signed_cached_data.message; + + let head = builder + .beacon_client + .get_beacon_blocks::(BlockId::Head) + .await + .map_err(|_| reject("couldn't get head"))? + .ok_or_else(|| reject("missing head block"))?; + + let block = head.data.message(); + let head_block_root = block.tree_hash_root(); + let head_execution_hash = block + .body() + .execution_payload() + .map_err(|_| reject("pre-merge block"))? + .block_hash(); + if head_execution_hash != parent_hash { + return Err(reject("head mismatch")); + } + + let finalized_execution_hash = builder + .beacon_client + .get_beacon_blocks::(BlockId::Finalized) + .await + .map_err(|_| reject("couldn't get finalized block"))? + .ok_or_else(|| reject("missing finalized block"))? + .data + .message() + .body() + .execution_payload() + .map_err(|_| reject("pre-merge block"))? + .block_hash(); + + let justified_execution_hash = builder + .beacon_client + .get_beacon_blocks::(BlockId::Justified) + .await + .map_err(|_| reject("couldn't get justified block"))? + .ok_or_else(|| reject("missing justified block"))? + .data + .message() + .body() + .execution_payload() + .map_err(|_| reject("pre-merge block"))? + .block_hash(); + + let val_index = builder + .beacon_client + .get_beacon_states_validator_id(StateId::Head, &ValidatorId::PublicKey(pubkey)) + .await + .map_err(|_| reject("couldn't get validator"))? + .ok_or_else(|| reject("missing validator"))? + .data + .index; + let fee_recipient = cached_data.fee_recipient; + let slots_since_genesis = slot.as_u64() - builder.spec.genesis_slot.as_u64(); + + let genesis_data = builder + .beacon_client + .get_beacon_genesis() + .await + .map_err(|_| reject("couldn't get beacon genesis"))? + .data; + let genesis_time = genesis_data.genesis_time; + let timestamp = + (slots_since_genesis * builder.spec.seconds_per_slot) + genesis_time; + + let head_state: BeaconState = builder + .beacon_client + .get_debug_beacon_states(StateId::Head) + .await + .map_err(|_| reject("couldn't get state"))? + .ok_or_else(|| reject("missing state"))? + .data; + let prev_randao = head_state + .get_randao_mix(head_state.current_epoch()) + .map_err(|_| reject("couldn't get prev randao"))?; + + let payload_attributes = match fork { + ForkName::Merge => { + PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None) + } + // the withdrawals root is filled in by operations + ForkName::Capella => { + PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, Some(vec![])) + } + ForkName::Base | ForkName::Altair => { + return Err(reject("invalid fork")); + } + }; + + builder + .el + .insert_proposer(slot, head_block_root, val_index, payload_attributes.clone()) + .await; + + let forkchoice_update_params = ForkchoiceUpdateParameters { + head_root: Hash256::zero(), + head_hash: None, + justified_hash: Some(justified_execution_hash), + finalized_hash: Some(finalized_execution_hash), + }; + + let payload = builder + .el + .get_full_payload_caching::>( + head_execution_hash, + &payload_attributes, + forkchoice_update_params, + fork, + ) + .await + .map_err(|_| reject("couldn't get payload"))? + .to_payload() + .to_execution_payload_header(); + + let mut message = BuilderBid { + header: BlindedPayload::from(payload), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + _phantom_data: std::marker::PhantomData, + }; + message.set_gas_limit(cached_data.gas_limit); + + builder.apply_operations(&mut message); + + let mut signature = + message.sign_builder_message(&builder.builder_sk, &builder.spec); + + if *builder.invalidate_signatures.read() { + signature = Signature::empty(); + } + + let fork_name = builder + .spec + .fork_name_at_epoch(slot.epoch(E::slots_per_epoch())); + let signed_bid = SignedBuilderBid { message, signature }; + let resp = ForkVersionedResponse { + version: Some(fork_name), + data: signed_bid, + }; + let json_bid = serde_json::to_string(&resp) + .map_err(|_| reject("coudn't serialize signed bid"))?; + Ok::<_, Rejection>( + warp::http::Response::builder() + .status(200) + .body(json_bid) + .unwrap(), + ) + }, + ); -pub fn to_ssz_rs(ssz_data: &T) -> Result { - ssz_rs::deserialize::(&ssz_data.as_ssz_bytes()).map_err(convert_err) -} + let routes = warp::post() + .and(validators.or(blinded_block)) + .or(warp::get().and(status).or(header)) + .map(|reply| warp::reply::with_header(reply, "Server", "lighthouse-mock-builder-server")); -fn convert_err(e: E) -> MevError { - custom_err(format!("{e:?}")) + let (listening_socket, server) = warp::serve(routes) + .try_bind_ephemeral(SocketAddrV4::new(listen_addr, listen_port)) + .expect("mock builder server should start"); + Ok((listening_socket, server)) } -// This is a bit of a hack since the `Custom` variant was removed from `mev_rs::Error`. -fn custom_err(s: String) -> MevError { - MevError::Consensus(ethereum_consensus::state_transition::Error::Io( - std::io::Error::new(std::io::ErrorKind::Other, s), - )) +fn reject(msg: &'static str) -> Rejection { + warp::reject::custom(Custom(msg.to_string())) } diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 059c0275bba..f7f82781228 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -25,7 +25,7 @@ use warp::{http::StatusCode, Filter, Rejection}; use crate::EngineCapabilities; pub use execution_block_generator::{generate_pow_block, Block, ExecutionBlockGenerator}; pub use hook::Hook; -pub use mock_builder::{Context as MockBuilderContext, MockBuilder, MockBuilderServer, Operation}; +pub use mock_builder::{MockBuilder, Operation}; pub use mock_execution_layer::MockExecutionLayer; pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400; diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 1fbdab07ccd..6a8bd4e61ba 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -265,11 +265,7 @@ impl ApiTester { // Start the mock builder service prior to building the chain out. harness.runtime.task_executor.spawn( - async move { - if let Err(e) = mock_builder_server.await { - panic!("error in mock builder server: {e:?}"); - } - }, + async move { mock_builder_server.await }, "mock_builder_server", ); diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index 8723c2afed9..a7355b68350 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -21,7 +21,7 @@ pub struct BuilderBid> { pub pubkey: PublicKeyBytes, #[serde(skip)] #[tree_hash(skip_hashing)] - _phantom_data: PhantomData, + pub _phantom_data: PhantomData, } impl> SignedRoot for BuilderBid {} diff --git a/consensus/types/src/validator_registration_data.rs b/consensus/types/src/validator_registration_data.rs index de7f26cc632..174014df8ec 100644 --- a/consensus/types/src/validator_registration_data.rs +++ b/consensus/types/src/validator_registration_data.rs @@ -21,3 +21,17 @@ pub struct ValidatorRegistrationData { } impl SignedRoot for ValidatorRegistrationData {} + +impl SignedValidatorRegistrationData { + pub fn verify_signature(&self, spec: &ChainSpec) -> bool { + self.message + .pubkey + .decompress() + .map(|pubkey| { + let domain = spec.get_builder_domain(); + let message = self.message.signing_root(domain); + self.signature.verify(&pubkey, message) + }) + .unwrap_or(false) + } +} diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 46f523c9905..18b71afc364 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -56,7 +56,7 @@ itertools = { workspace = true } monitoring_api = { workspace = true } sensitive_url = { workspace = true } task_executor = { workspace = true } -reqwest = { workspace = true } +reqwest = { workspace = true, features = ["native-tls"] } url = { workspace = true } malloc_utils = { workspace = true } sysinfo = { workspace = true }