From f46dc218179451c3d38762ce8a421381749524e9 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 10 Nov 2023 19:03:29 +0100 Subject: [PATCH 1/2] integrate payload builder directly from reth --- Cargo.lock | 1 + Cargo.toml | 1 + bin/mev/src/cmd/config.rs | 6 +- mev-build-rs/Cargo.toml | 1 + mev-build-rs/src/reth_builder/build.rs | 21 +- mev-build-rs/src/reth_builder/builder.rs | 31 +-- mev-build-rs/src/reth_builder/cancelled.rs | 18 -- mev-build-rs/src/reth_builder/error.rs | 3 + mev-build-rs/src/reth_builder/mod.rs | 1 - .../src/reth_builder/payload_builder.rs | 186 ++++++++++-------- mev-build-rs/src/reth_builder/service.rs | 4 +- 11 files changed, 148 insertions(+), 125 deletions(-) delete mode 100644 mev-build-rs/src/reth_builder/cancelled.rs diff --git a/Cargo.lock b/Cargo.lock index 67995733..ca53dae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4347,6 +4347,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project", "reth", + "reth-basic-payload-builder", "reth-interfaces", "reth-payload-builder", "reth-primitives", diff --git a/Cargo.toml b/Cargo.toml index 4f707713..a4b0fb85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus" beacon-api-client = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "fc049504a200926c8bd5f0fbd3f9696c6c6f699d" } reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "0a3884ba81579a775a0305be3a6621cd6782176a" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "0a3884ba81579a775a0305be3a6621cd6782176a" } reth-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "0a3884ba81579a775a0305be3a6621cd6782176a" } reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "0a3884ba81579a775a0305be3a6621cd6782176a" } reth-rpc-types = { git = "https://github.com/paradigmxyz/reth", rev = "0a3884ba81579a775a0305be3a6621cd6782176a" } diff --git a/bin/mev/src/cmd/config.rs b/bin/mev/src/cmd/config.rs index d4ab18d3..daa03e21 100644 --- a/bin/mev/src/cmd/config.rs +++ b/bin/mev/src/cmd/config.rs @@ -3,8 +3,6 @@ use ethereum_consensus::networks::Network; use eyre::WrapErr; #[cfg(feature = "boost")] use mev_boost_rs::Config as BoostConfig; -#[cfg(feature = "build")] -use mev_build_rs::reth_builder::Config as BuildConfig; #[cfg(feature = "relay")] use mev_relay_rs::Config as RelayConfig; use mev_rs::config::from_toml_file; @@ -17,9 +15,7 @@ pub struct Config { pub network: Network, #[cfg(feature = "boost")] pub boost: Option, - #[cfg(feature = "build")] - #[serde(rename = "builder")] - pub build: Option, + // NOTE: builder config is handled in reth cli extension #[cfg(feature = "relay")] pub relay: Option, } diff --git a/mev-build-rs/Cargo.toml b/mev-build-rs/Cargo.toml index b60c0409..48a55e2e 100644 --- a/mev-build-rs/Cargo.toml +++ b/mev-build-rs/Cargo.toml @@ -25,6 +25,7 @@ mev-rs = { path = "../mev-rs" } revm = { workspace = true } reth-payload-builder = { workspace = true } +reth-basic-payload-builder = { workspace = true } reth-primitives = { workspace = true } reth-transaction-pool = { workspace = true } reth-provider = { workspace = true } diff --git a/mev-build-rs/src/reth_builder/build.rs b/mev-build-rs/src/reth_builder/build.rs index 22f36a8f..22fc69c4 100644 --- a/mev-build-rs/src/reth_builder/build.rs +++ b/mev-build-rs/src/reth_builder/build.rs @@ -14,6 +14,7 @@ use mev_rs::{ types::{BidTrace, SignedBidSubmission}, Relay, }; +use reth_payload_builder::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{Bytes, ChainSpec, SealedBlock, Withdrawal, B256, U256}; use revm::primitives::{BlockEnv, CfgEnv}; use std::sync::{Arc, Mutex}; @@ -62,11 +63,14 @@ pub struct BuildContext { pub builder_wallet: LocalWallet, // Amount of gas to reserve after building a payload // e.g. used for end-of-block proposer payments - pub gas_reserve: u64, + pub _gas_reserve: u64, // Amount of the block's value to bid to the proposer pub bid_percent: f64, // Amount to add to the block's value to bid to the proposer pub subsidy: U256, + // TODO: refactor these w/ the above fields + pub parent_block: Arc, + pub payload_attributes: PayloadBuilderAttributes, } pub fn compute_build_id(slot: Slot, parent_hash: B256, proposer: &BlsPublicKey) -> BuildIdentifier { @@ -113,9 +117,9 @@ impl Build { Self { context, state: Mutex::new(Default::default()) } } - pub fn value(&self) -> U256 { + pub fn payload(&self) -> Option> { let state = self.state.lock().unwrap(); - state.payload_with_payments.proposer_payment + state.payload_with_payments.payload.clone() } pub fn prepare_bid( @@ -134,7 +138,14 @@ impl Build { let payment = &payload_with_payments.proposer_payment; let builder_payment = payload_with_payments.builder_payment; Ok(( - make_submission(secret_key, public_key, context, build_context, payload, payment)?, + make_submission( + secret_key, + public_key, + context, + build_context, + payload.block(), + payment, + )?, builder_payment, )) } @@ -142,7 +153,7 @@ impl Build { #[derive(Debug, Default)] pub struct PayloadWithPayments { - pub payload: Option, + pub payload: Option>, pub proposer_payment: U256, pub builder_payment: U256, } diff --git a/mev-build-rs/src/reth_builder/builder.rs b/mev-build-rs/src/reth_builder/builder.rs index 5b78291f..ebc9afdb 100644 --- a/mev-build-rs/src/reth_builder/builder.rs +++ b/mev-build-rs/src/reth_builder/builder.rs @@ -1,6 +1,5 @@ use crate::reth_builder::{ - auction_schedule::AuctionSchedule, build::*, cancelled::Cancelled, error::Error, - payload_builder::*, + auction_schedule::AuctionSchedule, build::*, error::Error, payload_builder::*, }; use ethereum_consensus::{ clock::SystemClock, @@ -10,6 +9,7 @@ use ethereum_consensus::{ }; use ethers::signers::{LocalWallet, Signer}; use mev_rs::{blinded_block_relayer::BlindedBlockRelayer, compute_preferred_gas_limit, Relay}; +use reth_basic_payload_builder::Cancelled; use reth_payload_builder::PayloadBuilderAttributes; use reth_primitives::{Address, BlockNumberOrTag, Bytes, ChainSpec, B256, U256}; use reth_provider::{BlockReaderIdExt, BlockSource, StateProviderFactory}; @@ -22,6 +22,7 @@ use std::{ }; use tokio::sync::mpsc; use tokio_stream::{wrappers::ReceiverStream, Stream}; +use tracing::debug; // The amount to let the build progress into its target slot. // The build will stop early if stopped by an outside process. @@ -231,7 +232,9 @@ pub enum PayloadAttributesProcessingOutcome { Duplicate(PayloadBuilderAttributes), } -impl Builder { +impl + Builder +{ // TODO: clean up argument set #[allow(clippy::too_many_arguments)] // NOTE: this is held inside a lock currently, minimize work here @@ -289,9 +292,11 @@ impl Bui extra_data: self.extra_data.clone(), builder_wallet: self.builder_wallet.clone(), // TODO: handle smart contract payments to fee recipient - gas_reserve: 21000, + _gas_reserve: 21000, bid_percent: self.bid_percent, subsidy: subsidy_in_wei, + parent_block: Arc::new(parent_block), + payload_attributes: payload_attributes.clone(), }; Ok(context) } @@ -334,11 +339,14 @@ impl Bui let build = Arc::new(Build::new(context)); // TODO: encapsulate these details - let current_value = build.value(); let cancel = Cancelled::default(); - if let Ok(BuildOutcome::BetterOrEqual(payload_with_payments)) = - build_payload(&build.context, current_value, &self.client, &self.pool, &cancel) - { + if let Ok(BuildOutcome::BetterOrEqual(payload_with_payments)) = build_payload( + &build.context, + None, + self.client.clone(), + self.pool.clone(), + cancel.clone(), + ) { let mut state = build.state.lock().unwrap(); state.payload_with_payments = payload_with_payments; } @@ -379,13 +387,14 @@ impl Bui return Ok(()) } _ = interval.tick() => { - let current_value = build.value(); - match build_payload(&build.context, current_value, &self.client, &self.pool, &cancel) { + match build_payload(&build.context, build.payload(), self.client.clone(), self.pool.clone(), cancel.clone()) { Ok(BuildOutcome::BetterOrEqual(payload_with_payments)) => { let mut state = build.state.lock().unwrap(); state.payload_with_payments = payload_with_payments; } - Ok(BuildOutcome::Worse { .. }) => continue, + Ok(BuildOutcome::Worse { threshold, provided }) => { + debug!(%threshold, %provided, "did not build a better payload"); + } Ok(BuildOutcome::Cancelled) => { tracing::trace!(%id, "build cancelled"); return Ok(()) diff --git a/mev-build-rs/src/reth_builder/cancelled.rs b/mev-build-rs/src/reth_builder/cancelled.rs deleted file mode 100644 index c543425d..00000000 --- a/mev-build-rs/src/reth_builder/cancelled.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::sync::{atomic::AtomicBool, Arc}; - -// NOTE: cribbed from https://github.com/paradigmxyz/reth/blob/900ada5aaa4b5d4a633df78764e7dd7169a13405/crates/payload/basic/src/lib.rs#L514 -#[derive(Default, Clone, Debug)] -pub struct Cancelled(Arc); - -impl Cancelled { - /// Returns true if the job was cancelled. - pub fn is_cancelled(&self) -> bool { - self.0.load(std::sync::atomic::Ordering::Relaxed) - } -} - -impl Drop for Cancelled { - fn drop(&mut self) { - self.0.store(true, std::sync::atomic::Ordering::Relaxed); - } -} diff --git a/mev-build-rs/src/reth_builder/error.rs b/mev-build-rs/src/reth_builder/error.rs index dd757fc5..3ba765f8 100644 --- a/mev-build-rs/src/reth_builder/error.rs +++ b/mev-build-rs/src/reth_builder/error.rs @@ -1,6 +1,7 @@ use crate::reth_builder::build::BuildIdentifier; use ethereum_consensus::{primitives::Slot, Error as ConsensusError}; use reth_interfaces::RethError; +use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::B256; use revm::primitives::EVMError; use thiserror::Error; @@ -19,6 +20,8 @@ pub enum Error { Consensus(#[from] ConsensusError), #[error(transparent)] Reth(#[from] RethError), + #[error(transparent)] + RethPayloadBuilder(#[from] PayloadBuilderError), #[error("evm execution error: {0:?}")] Execution(#[from] EVMError), #[error("{0}")] diff --git a/mev-build-rs/src/reth_builder/mod.rs b/mev-build-rs/src/reth_builder/mod.rs index 7e371724..40d014ea 100644 --- a/mev-build-rs/src/reth_builder/mod.rs +++ b/mev-build-rs/src/reth_builder/mod.rs @@ -4,7 +4,6 @@ mod auction_schedule; mod bidder; mod build; mod builder; -mod cancelled; mod error; mod payload_builder; mod reth_compat; diff --git a/mev-build-rs/src/reth_builder/payload_builder.rs b/mev-build-rs/src/reth_builder/payload_builder.rs index 27919aa1..fb4c1a6e 100644 --- a/mev-build-rs/src/reth_builder/payload_builder.rs +++ b/mev-build-rs/src/reth_builder/payload_builder.rs @@ -2,7 +2,6 @@ /// the `reth-basic-payload-builder` package in the `reth` codebase. use crate::reth_builder::{ build::{BuildContext, PayloadWithPayments}, - cancelled::Cancelled, error::Error, }; use ethers::{ @@ -12,13 +11,17 @@ use ethers::{ U256 as ethers_U256, }, }; +use reth_basic_payload_builder::{ + default_payload_builder, BuildArguments, BuildOutcome as RethOutcome, Cancelled, PayloadConfig, +}; use reth_interfaces::RethError; +use reth_payload_builder::{error::PayloadBuilderError, BuiltPayload, PayloadId}; use reth_primitives::{ constants::{BEACON_NONCE, EMPTY_OMMER_ROOT_HASH}, proofs, revm::{compat::into_reth_log, env::tx_env_with_recovered}, - Address, Block, Bytes, ChainSpec, Header, IntoRecoveredTransaction, Receipt, Receipts, - TransactionSigned, TransactionSignedEcRecovered, Withdrawal, B256, U256, + Address, Block, Bytes, ChainSpec, Header, Receipt, Receipts, TransactionSigned, + TransactionSignedEcRecovered, Withdrawal, B256, U256, }; use reth_provider::{BundleStateWithReceipts, StateProvider, StateProviderFactory}; use reth_revm::{ @@ -26,10 +29,45 @@ use reth_revm::{ }; use revm::{ db::{states::bundle_state::BundleRetention, WrapDatabaseRef}, - primitives::{EVMError, Env, InvalidTransaction, ResultAndState}, + primitives::{Env, ResultAndState}, Database, DatabaseCommit, State, }; -use std::fmt; +use std::{fmt, sync::Arc}; + +pub struct RethPayloadBuilder { + build_arguments: BuildArguments, +} + +impl RethPayloadBuilder +where + Client: reth_provider::StateProviderFactory, + Pool: reth_transaction_pool::TransactionPool, +{ + pub fn new( + context: &BuildContext, + client: Client, + pool: Pool, + cancel: Cancelled, + best_payload: Option>, + ) -> Self { + let cached_reads = Default::default(); + let config = PayloadConfig::new( + context.parent_block.clone(), + context.extra_data.clone(), + context.payload_attributes.clone(), + context.chain_spec.clone(), + ); + + let build_arguments = + BuildArguments::new(client, pool, cached_reads, config, cancel, best_payload); + + Self { build_arguments } + } + + pub fn build(self) -> Result { + default_payload_builder(self.build_arguments) + } +} fn process_withdrawals>( withdrawals: &[Withdrawal], @@ -51,48 +89,9 @@ pub enum BuildOutcome { Cancelled, } -fn assemble_txs_from_pool( - context: &mut ExecutionContext, - pool: &Pool, -) -> Result<(), Error> { - let base_fee = context.build.base_fee(); - let block_gas_limit = context.build.gas_limit(); - - let mut best_txs = pool.best_transactions_with_base_fee(base_fee); - - let effective_gas_limit = block_gas_limit - context.build.gas_reserve; - while let Some(pool_tx) = best_txs.next() { - if context.is_cancelled() { - return Ok(()) - } - - // NOTE: we withhold the `gas_reserve` so the "bidder" has some guaranteed room - // to play with the payload after it is built. - if context.cumulative_gas_used + pool_tx.gas_limit() > effective_gas_limit { - best_txs.mark_invalid(&pool_tx); - continue - } - - let tx = pool_tx.to_recovered_transaction(); - - if let Err(err) = context.extend_transaction(tx) { - match err { - Error::Execution(EVMError::Transaction(err)) => { - if !matches!(err, InvalidTransaction::NonceTooLow { .. }) { - best_txs.mark_invalid(&pool_tx); - } - continue - } - _ => return Err(err), - } - } - } - Ok(()) -} - -fn assemble_payload_with_payments( +fn assemble_payload_with_payments( mut context: ExecutionContext, - client: &dyn StateProviderFactory, + client: P, ) -> Result { let base_fee = context.build.base_fee(); let block_number = context.build.number(); @@ -152,8 +151,14 @@ fn assemble_payload_with_payments( withdrawals: Some(context.build.withdrawals.clone()), }; + let payload = BuiltPayload::new( + PayloadId::new(Default::default()), + payload.seal_slow(), + context.total_fees, + ); + let payload_with_payments = PayloadWithPayments { - payload: Some(payload.seal_slow()), + payload: Some(Arc::new(payload)), proposer_payment: context.total_payment, builder_payment: context.revenue, }; @@ -197,7 +202,7 @@ fn construct_payment_tx( struct ExecutionContext<'a> { build: &'a BuildContext, - cancel: &'a Cancelled, + cancel: Cancelled, db: revm::State>>>, receipts: Vec>, cumulative_gas_used: u64, @@ -219,23 +224,23 @@ impl<'a> fmt::Debug for ExecutionContext<'a> { } } +type DB<'a> = revm::State>>>; + impl<'a> ExecutionContext<'a> { - fn try_from( + fn try_from( context: &'a BuildContext, - cancel: &'a Cancelled, - provider: &'a P, + cancel: Cancelled, + db: DB<'a>, + payload: BuiltPayload, ) -> Result { - let state_provider = provider.state_by_block_hash(context.parent_hash)?; - let state = StateProviderDatabase::new(state_provider); - let db = State::builder().with_database_ref(state).with_bundle_update().build(); Ok(ExecutionContext { build: context, cancel, db, receipts: Default::default(), cumulative_gas_used: 0, - total_fees: U256::ZERO, - executed_txs: Default::default(), + total_fees: payload.fees(), + executed_txs: payload.block().body.clone(), total_payment: U256::ZERO, revenue: U256::ZERO, }) @@ -289,40 +294,53 @@ impl<'a> ExecutionContext<'a> { } pub fn build_payload< - Provider: reth_provider::StateProviderFactory, + Provider: reth_provider::StateProviderFactory + Clone, Pool: reth_transaction_pool::TransactionPool, >( context: &BuildContext, - threshold_value: U256, - provider: &Provider, - pool: &Pool, - cancel: &Cancelled, + best_payload: Option>, + client: Provider, + pool: Pool, + cancel: Cancelled, ) -> Result { - let mut context = ExecutionContext::try_from(context, cancel, provider)?; - - if context.is_cancelled() { - return Ok(BuildOutcome::Cancelled) - } - assemble_txs_from_pool(&mut context, pool)?; - - if context.total_fees < threshold_value { - return Ok(BuildOutcome::Worse { threshold: threshold_value, provided: context.total_fees }) - } - - context.compute_payment_from_fees(); - - let payment_tx = construct_payment_tx(&mut context)?; + let payload_builder = RethPayloadBuilder::new( + context, + client.clone(), + pool, + cancel.clone(), + best_payload.clone(), + ); + match payload_builder.build() { + Ok(RethOutcome::Aborted { fees, .. }) => Ok(BuildOutcome::Worse { + threshold: best_payload.map(|p| p.fees()).unwrap_or_default(), + provided: fees, + }), + // TODO: leverage cached reads + Ok(RethOutcome::Better { payload, .. }) => { + let client_handle = client.clone(); + let state_provider = client_handle.state_by_block_hash(context.parent_hash)?; + let state = StateProviderDatabase::new(state_provider); + let db = State::builder().with_database_ref(state).with_bundle_update().build(); + let mut context = ExecutionContext::try_from(context, cancel, db, payload)?; + + context.compute_payment_from_fees(); + + let payment_tx = construct_payment_tx(&mut context)?; + + if context.is_cancelled() { + return Ok(BuildOutcome::Cancelled) + } - if context.is_cancelled() { - return Ok(BuildOutcome::Cancelled) - } + // NOTE: assume payment transaction always succeeds + context.extend_transaction(payment_tx)?; - // NOTE: assume payment transaction always succeeds - context.extend_transaction(payment_tx)?; + if context.is_cancelled() { + return Ok(BuildOutcome::Cancelled) + } - if context.is_cancelled() { - return Ok(BuildOutcome::Cancelled) + assemble_payload_with_payments(context, client) + } + Ok(RethOutcome::Cancelled) => Ok(BuildOutcome::Cancelled), + Err(err) => Err(err.into()), } - - assemble_payload_with_payments(context, provider) } diff --git a/mev-build-rs/src/reth_builder/service.rs b/mev-build-rs/src/reth_builder/service.rs index 9d4413fd..7f3d72cb 100644 --- a/mev-build-rs/src/reth_builder/service.rs +++ b/mev-build-rs/src/reth_builder/service.rs @@ -199,7 +199,9 @@ impl< }); } } - Ok(PayloadAttributesProcessingOutcome::Duplicate(_)) => continue, + Ok(PayloadAttributesProcessingOutcome::Duplicate(payload_attributes)) => { + info!(?payload_attributes, "duplicate payload attributes encountered"); + } Err(BuilderError::NoProposals(_)) => continue, Err(err) => { tracing::warn!(err = ?err, "could not process payload attributes"); From 99ef526e3bb992c41d73f49e0416885dbbb9ab46 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 19 Apr 2024 11:13:32 -0600 Subject: [PATCH 2/2] need to update `ahash` for lint --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca53dae9..7339e137 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom 0.2.12",