diff --git a/README.md b/README.md index 0bb5ecd7..ce9053ea 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,7 @@ When the deadline is reached, the simulator is stopped, and all open simulation ### ✨ Submit Task -If Flashbots endpoint has been configured the Flashbots submit task will prepare a Flashbots bundle out of that Signet block, and then submits that bundle to the Flashbots endpoint. - -If a Flashbots endpoint has _not_ been configured, the Builder uses the [builder helper contract] and to craft a rollup block transaction and submits that to the default mempool. This mode of operation is only for testing on private networks and should not be used in production, since it can leak sensitive transaction data from the Signet block. +The Flashbots submit task prepares a Flashbots bundle out of the Signet block and its host transactions and then submits that bundle to the Flashbots endpoint. It sends the hash of the rollup block transaction for to the Metrics task for further tracking. If the block received from simulation is empty, the submit task will ignore it. @@ -88,32 +86,29 @@ Finally, if it's non-empty, the submit task attempts to get a signature for the The Builder is configured via environment variables. The following values are supported for configuration. Key | Required | Description ------------------------------ | -------- | ---------------------------------------------------------------------------------------- -`HOST_CHAIN_ID` | Yes | Host-chain ID (e.g. `3151908`) -`RU_CHAIN_ID` | Yes | Rollup-chain ID (e.g. `14174`) +----------------------------- | -------- | ------------------------------------------------------------------------------ +`RUST_LOG` | No | The log level of the builder +`CHAIN_NAME` | No | The chain name ("pecorino", or the corresponding name) `HOST_RPC_URL` | Yes | RPC endpoint for the host chain `ROLLUP_RPC_URL` | Yes | RPC endpoint for the rollup chain -`TX_POOL_URL` | Yes | Transaction pool URL (must end with `/`) -`TX_BROADCAST_URLS` | No | Additional endpoints for blob txs (comma-separated, slash required) -`FLASHBOTS_ENDPOINT` | No | Flashbots API to submit blocks to. Defaults to the BuilderHelper submit task if not set. -`ZENITH_ADDRESS` | Yes | Zenith contract address -`BUILDER_HELPER_ADDRESS` | Yes | Builder helper contract address `QUINCEY_URL` | Yes | Remote sequencer signing endpoint -`BUILDER_PORT` | Yes | HTTP port for the Builder (default: `8080`) -`SEQUENCER_KEY` | Yes | AWS KMS key ID _or_ local private key for sequencer signing +`SEQUENCER_KEY` | No | AWS Key ID _OR_ local private key for the Sequencer; set IFF using local Sequencer signing instead of remote (via `QUINCEY_URL`) Quincey signing +`TX_POOL_URL` | Yes | Transaction pool URL (must end with `/`) +`FLASHBOTS_ENDPOINT` | No | Flashbots API to submit blocks to +`ROLLUP_BLOCK_GAS_LIMIT` | No | Override for rollup block gas limit +`MAX_HOST_GAS_COEFFICIENT` | No | Optional maximum host gas coefficient, as a percentage, to use when building blocks `BUILDER_KEY` | Yes | AWS KMS key ID _or_ local private key for builder signing +`AWS_ACCESS_KEY_ID` | No | AWS secret access key ID (required if not using `BUILDER_KEY`) +`AWS_SECRET_ACCESS_KEY` | No | AWS secret access key (required if not using `BUILDER_KEY`) +`AWS_DEFAULT_REGION` | No | AWS region for the KMS key in question (required if not using `BUILDER_KEY`) +`BUILDER_PORT` | Yes | HTTP port for the Builder (default: `8080`) `BUILDER_REWARDS_ADDRESS` | Yes | Address receiving builder rewards -`ROLLUP_BLOCK_GAS_LIMIT` | No | Override for block gas limit -`CONCURRENCY_LIMIT` | No | Max concurrent tasks the simulator uses +`CONCURRENCY_LIMIT` | No | Optional max number of concurrent tasks the simulator uses. Defaults to a system call to determine optimal parallelism `OAUTH_CLIENT_ID` | Yes | Oauth client ID for the builder `OAUTH_CLIENT_SECRET` | Yes | Oauth client secret for the builder `OAUTH_AUTHENTICATE_URL` | Yes | Oauth authenticate URL for the builder for performing OAuth logins `OAUTH_TOKEN_URL` | Yes | Oauth token URL for the builder to get an Oauth2 access token `AUTH_TOKEN_REFRESH_INTERVAL` | Yes | The OAuth token refresh interval in seconds. -`CHAIN_NAME` | No | The chain name ("pecorino", or the corresponding name) -`SLOT_OFFSET` | No | Slot timing offset in seconds. Required if `CHAIN_NAME` is not present -`SLOT_DURATION` | No | Slot duration in seconds. Required if `CHAIN_NAME` is not present -`START_TIMESTAMP` | No | UNIX timestamp for slot 0\. Required if `CHAIN_NAME` is not present -------------------------------------------------------------------------------- @@ -188,5 +183,3 @@ The previous header's basefee is tracked through the build loop and used for gas ## 🪪 License This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). - -[builder helper contract]: https://github.com/init4tech/helper-contracts/blob/main/src/BuilderHelper.sol diff --git a/src/tasks/submit/flashbots.rs b/src/tasks/submit/flashbots.rs index 83edd792..7ec68a2a 100644 --- a/src/tasks/submit/flashbots.rs +++ b/src/tasks/submit/flashbots.rs @@ -16,8 +16,8 @@ use init4_bin_base::{deps::metrics::counter, utils::signer::LocalOrAws}; use tokio::{sync::mpsc, task::JoinHandle}; use tracing::{Instrument, debug, debug_span}; -/// Handles construction, simulation, and submission of rollup blocks to the -/// Flashbots network. +/// Handles preparation and submission of simulated rollup blocks to the +/// Flashbots relay as MEV bundles. #[derive(Debug)] pub struct FlashbotsTask { /// Builder configuration for the task. @@ -31,12 +31,14 @@ pub struct FlashbotsTask { /// The key used to sign requests to the Flashbots relay. signer: LocalOrAws, /// Channel for sending hashes of outbound transactions. - _outbound: mpsc::UnboundedSender, + outbound: mpsc::UnboundedSender, } impl FlashbotsTask { - /// Returns a new `FlashbotsTask` instance that receives `SimResult` types from the given - /// channel and handles their preparation, submission to the Flashbots network. + /// Creates a new `FlashbotsTask` instance with initialized providers and connections. + /// + /// Sets up Quincey for block signing, host provider for transaction submission, + /// Flashbots provider for bundle submission, and Zenith instance for contract interactions. pub async fn new( config: BuilderConfig, outbound: mpsc::UnboundedSender, @@ -50,28 +52,56 @@ impl FlashbotsTask { let zenith = config.connect_zenith(host_provider); - Ok(Self { config, quincey, zenith, flashbots, signer: builder_key, _outbound: outbound }) + Ok(Self { config, quincey, zenith, flashbots, signer: builder_key, outbound }) } - /// Returns a reference to the inner `HostProvider` - pub fn host_provider(&self) -> HostProvider { - self.zenith.provider().clone() - } - - /// Returns a reference to the inner `FlashbotsProvider` - pub const fn flashbots(&self) -> &FlashbotsProvider { - &self.flashbots - } - - /// Prepares a MEV bundle with the configured submit call + /// Prepares a MEV bundle from a simulation result. + /// + /// This function serves as an entry point for bundle preparation and is left + /// for forward compatibility when adding different bundle preparation methods. pub async fn prepare(&self, sim_result: &SimResult) -> eyre::Result { // This function is left for forwards compatibility when we want to add // different bundle preparation methods in the future. - self.prepare_bundle_helper(sim_result).await + self.prepare_bundle(sim_result).await + } + + /// Prepares a MEV bundle containing the host transactions and the rollup block. + /// + /// This method orchestrates the bundle preparation by: + /// 1. Preparing and signing the submission transaction + /// 2. Tracking the transaction hash for monitoring + /// 3. Encoding the transaction for bundle inclusion + /// 4. Constructing the complete bundle body + async fn prepare_bundle(&self, sim_result: &SimResult) -> eyre::Result { + // Prepare and sign the transaction + let block_tx = self.prepare_signed_transaction(sim_result).await?; + + // Track the outbound transaction + self.track_outbound_tx(&block_tx); + + // Encode the transaction + let tx_bytes = block_tx.encoded_2718().into(); + + // Build the bundle body with the block_tx bytes as the last transaction in the bundle. + let bundle_body = self.build_bundle_body(sim_result, tx_bytes); + + // Create the MEV bundle (valid only in the specific host block) + Ok(MevSendBundle::new( + sim_result.host_block_number(), + Some(sim_result.host_block_number()), + ProtocolVersion::V0_1, + bundle_body, + )) } - /// Prepares a BundleHelper call containing the rollup block and corresponding fills into a MEV bundle. - async fn prepare_bundle_helper(&self, sim_result: &SimResult) -> eyre::Result { + /// Prepares and signs the submission transaction for the rollup block. + /// + /// Creates a `SubmitPrep` instance to build the transaction, then fills + /// and signs it using the host provider. + async fn prepare_signed_transaction( + &self, + sim_result: &SimResult, + ) -> eyre::Result { let prep = SubmitPrep::new( &sim_result.block, self.host_provider(), @@ -80,34 +110,48 @@ impl FlashbotsTask { ); let tx = prep.prep_transaction(sim_result.prev_host()).await?; - let sendable = self.host_provider().fill(tx.into_request()).await?; - let tx_bytes = sendable - .as_envelope() - .ok_or_eyre("failed to get envelope from filled tx")? - .encoded_2718() - .into(); + sendable.as_envelope().ok_or_eyre("failed to get envelope from filled tx").cloned() + } + + /// Tracks the outbound transaction hash and increments submission metrics. + /// + /// Sends the transaction hash to the outbound channel for monitoring. + /// Logs a debug message if the channel is closed. + fn track_outbound_tx(&self, envelope: &alloy::consensus::TxEnvelope) { + counter!("signet.builder.flashbots.").increment(1); + let hash = *envelope.tx_hash(); + if self.outbound.send(hash).is_err() { + debug!("outbound channel closed, could not track tx hash"); + } + } - let bundle_body = sim_result + /// Constructs the MEV bundle body from host transactions and the submission transaction. + /// + /// Combines all host transactions from the rollup block with the prepared rollup block + /// submission transaction, wrapping each as a non-revertible bundle item. + /// + /// The rollup block transaction is placed last in the bundle. + fn build_bundle_body( + &self, + sim_result: &SimResult, + tx_bytes: alloy::primitives::Bytes, + ) -> Vec { + sim_result .block .host_transactions() .iter() .cloned() .chain(std::iter::once(tx_bytes)) .map(|tx| BundleItem::Tx { tx, can_revert: false }) - .collect::>(); - - // Only valid in the specific host block - Ok(MevSendBundle::new( - sim_result.host_block_number(), - Some(sim_result.host_block_number()), - ProtocolVersion::V0_1, - bundle_body, - )) + .collect() } - /// Task future that runs the Flashbots submission loop. + /// Main task loop that processes simulation results and submits bundles to Flashbots. + /// + /// Receives `SimResult`s from the inbound channel, prepares MEV bundles, and submits + /// them to the Flashbots relay. Skips empty blocks and continues processing on errors. async fn task_future(self, mut inbound: mpsc::UnboundedReceiver) { debug!("starting flashbots task"); @@ -172,7 +216,19 @@ impl FlashbotsTask { } } - /// Spawns the Flashbots task that handles incoming `SimResult`s. + /// Returns a clone of the host provider for transaction operations. + fn host_provider(&self) -> HostProvider { + self.zenith.provider().clone() + } + + /// Returns a reference to the Flashbots provider. + const fn flashbots(&self) -> &FlashbotsProvider { + &self.flashbots + } + + /// Spawns the Flashbots task in a new Tokio task. + /// + /// Returns a channel sender for submitting `SimResult`s and a join handle for the task. pub fn spawn(self) -> (mpsc::UnboundedSender, JoinHandle<()>) { let (sender, inbound) = mpsc::unbounded_channel::(); let handle = tokio::spawn(self.task_future(inbound));