Skip to content

Commit 111de00

Browse files
authored
feat: updates bundle prep to track the rollup tx hash (#176)
# feat: updates bundle prep to track the rollup tx hash This PR updates the bundle preparation functions to track the rollup block's transaction hash through to landing on chain. There are also some drive-by refactors and doc updates to the bundle prep loop and documentation surrounding env var configuration.
1 parent bc2ba25 commit 111de00

File tree

2 files changed

+108
-59
lines changed

2 files changed

+108
-59
lines changed

README.md

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,7 @@ When the deadline is reached, the simulator is stopped, and all open simulation
7373

7474
### ✨ Submit Task
7575

76-
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.
77-
78-
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.
76+
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.
7977

8078
If the block received from simulation is empty, the submit task will ignore it.
8179

@@ -88,32 +86,29 @@ Finally, if it's non-empty, the submit task attempts to get a signature for the
8886
The Builder is configured via environment variables. The following values are supported for configuration.
8987

9088
Key | Required | Description
91-
----------------------------- | -------- | ----------------------------------------------------------------------------------------
92-
`HOST_CHAIN_ID` | Yes | Host-chain ID (e.g. `3151908`)
93-
`RU_CHAIN_ID` | Yes | Rollup-chain ID (e.g. `14174`)
89+
----------------------------- | -------- | ------------------------------------------------------------------------------
90+
`RUST_LOG` | No | The log level of the builder
91+
`CHAIN_NAME` | No | The chain name ("pecorino", or the corresponding name)
9492
`HOST_RPC_URL` | Yes | RPC endpoint for the host chain
9593
`ROLLUP_RPC_URL` | Yes | RPC endpoint for the rollup chain
96-
`TX_POOL_URL` | Yes | Transaction pool URL (must end with `/`)
97-
`TX_BROADCAST_URLS` | No | Additional endpoints for blob txs (comma-separated, slash required)
98-
`FLASHBOTS_ENDPOINT` | No | Flashbots API to submit blocks to. Defaults to the BuilderHelper submit task if not set.
99-
`ZENITH_ADDRESS` | Yes | Zenith contract address
100-
`BUILDER_HELPER_ADDRESS` | Yes | Builder helper contract address
10194
`QUINCEY_URL` | Yes | Remote sequencer signing endpoint
102-
`BUILDER_PORT` | Yes | HTTP port for the Builder (default: `8080`)
103-
`SEQUENCER_KEY` | Yes | AWS KMS key ID _or_ local private key for sequencer signing
95+
`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
96+
`TX_POOL_URL` | Yes | Transaction pool URL (must end with `/`)
97+
`FLASHBOTS_ENDPOINT` | No | Flashbots API to submit blocks to
98+
`ROLLUP_BLOCK_GAS_LIMIT` | No | Override for rollup block gas limit
99+
`MAX_HOST_GAS_COEFFICIENT` | No | Optional maximum host gas coefficient, as a percentage, to use when building blocks
104100
`BUILDER_KEY` | Yes | AWS KMS key ID _or_ local private key for builder signing
101+
`AWS_ACCESS_KEY_ID` | No | AWS secret access key ID (required if not using `BUILDER_KEY`)
102+
`AWS_SECRET_ACCESS_KEY` | No | AWS secret access key (required if not using `BUILDER_KEY`)
103+
`AWS_DEFAULT_REGION` | No | AWS region for the KMS key in question (required if not using `BUILDER_KEY`)
104+
`BUILDER_PORT` | Yes | HTTP port for the Builder (default: `8080`)
105105
`BUILDER_REWARDS_ADDRESS` | Yes | Address receiving builder rewards
106-
`ROLLUP_BLOCK_GAS_LIMIT` | No | Override for block gas limit
107-
`CONCURRENCY_LIMIT` | No | Max concurrent tasks the simulator uses
106+
`CONCURRENCY_LIMIT` | No | Optional max number of concurrent tasks the simulator uses. Defaults to a system call to determine optimal parallelism
108107
`OAUTH_CLIENT_ID` | Yes | Oauth client ID for the builder
109108
`OAUTH_CLIENT_SECRET` | Yes | Oauth client secret for the builder
110109
`OAUTH_AUTHENTICATE_URL` | Yes | Oauth authenticate URL for the builder for performing OAuth logins
111110
`OAUTH_TOKEN_URL` | Yes | Oauth token URL for the builder to get an Oauth2 access token
112111
`AUTH_TOKEN_REFRESH_INTERVAL` | Yes | The OAuth token refresh interval in seconds.
113-
`CHAIN_NAME` | No | The chain name ("pecorino", or the corresponding name)
114-
`SLOT_OFFSET` | No | Slot timing offset in seconds. Required if `CHAIN_NAME` is not present
115-
`SLOT_DURATION` | No | Slot duration in seconds. Required if `CHAIN_NAME` is not present
116-
`START_TIMESTAMP` | No | UNIX timestamp for slot 0\. Required if `CHAIN_NAME` is not present
117112

118113
--------------------------------------------------------------------------------
119114

@@ -188,5 +183,3 @@ The previous header's basefee is tracked through the build loop and used for gas
188183
## 🪪 License
189184

190185
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
191-
192-
[builder helper contract]: https://github.com/init4tech/helper-contracts/blob/main/src/BuilderHelper.sol

src/tasks/submit/flashbots.rs

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use init4_bin_base::{deps::metrics::counter, utils::signer::LocalOrAws};
1616
use tokio::{sync::mpsc, task::JoinHandle};
1717
use tracing::{Instrument, debug, debug_span};
1818

19-
/// Handles construction, simulation, and submission of rollup blocks to the
20-
/// Flashbots network.
19+
/// Handles preparation and submission of simulated rollup blocks to the
20+
/// Flashbots relay as MEV bundles.
2121
#[derive(Debug)]
2222
pub struct FlashbotsTask {
2323
/// Builder configuration for the task.
@@ -31,12 +31,14 @@ pub struct FlashbotsTask {
3131
/// The key used to sign requests to the Flashbots relay.
3232
signer: LocalOrAws,
3333
/// Channel for sending hashes of outbound transactions.
34-
_outbound: mpsc::UnboundedSender<TxHash>,
34+
outbound: mpsc::UnboundedSender<TxHash>,
3535
}
3636

3737
impl FlashbotsTask {
38-
/// Returns a new `FlashbotsTask` instance that receives `SimResult` types from the given
39-
/// channel and handles their preparation, submission to the Flashbots network.
38+
/// Creates a new `FlashbotsTask` instance with initialized providers and connections.
39+
///
40+
/// Sets up Quincey for block signing, host provider for transaction submission,
41+
/// Flashbots provider for bundle submission, and Zenith instance for contract interactions.
4042
pub async fn new(
4143
config: BuilderConfig,
4244
outbound: mpsc::UnboundedSender<TxHash>,
@@ -50,28 +52,56 @@ impl FlashbotsTask {
5052

5153
let zenith = config.connect_zenith(host_provider);
5254

53-
Ok(Self { config, quincey, zenith, flashbots, signer: builder_key, _outbound: outbound })
55+
Ok(Self { config, quincey, zenith, flashbots, signer: builder_key, outbound })
5456
}
5557

56-
/// Returns a reference to the inner `HostProvider`
57-
pub fn host_provider(&self) -> HostProvider {
58-
self.zenith.provider().clone()
59-
}
60-
61-
/// Returns a reference to the inner `FlashbotsProvider`
62-
pub const fn flashbots(&self) -> &FlashbotsProvider {
63-
&self.flashbots
64-
}
65-
66-
/// Prepares a MEV bundle with the configured submit call
58+
/// Prepares a MEV bundle from a simulation result.
59+
///
60+
/// This function serves as an entry point for bundle preparation and is left
61+
/// for forward compatibility when adding different bundle preparation methods.
6762
pub async fn prepare(&self, sim_result: &SimResult) -> eyre::Result<MevSendBundle> {
6863
// This function is left for forwards compatibility when we want to add
6964
// different bundle preparation methods in the future.
70-
self.prepare_bundle_helper(sim_result).await
65+
self.prepare_bundle(sim_result).await
66+
}
67+
68+
/// Prepares a MEV bundle containing the host transactions and the rollup block.
69+
///
70+
/// This method orchestrates the bundle preparation by:
71+
/// 1. Preparing and signing the submission transaction
72+
/// 2. Tracking the transaction hash for monitoring
73+
/// 3. Encoding the transaction for bundle inclusion
74+
/// 4. Constructing the complete bundle body
75+
async fn prepare_bundle(&self, sim_result: &SimResult) -> eyre::Result<MevSendBundle> {
76+
// Prepare and sign the transaction
77+
let block_tx = self.prepare_signed_transaction(sim_result).await?;
78+
79+
// Track the outbound transaction
80+
self.track_outbound_tx(&block_tx);
81+
82+
// Encode the transaction
83+
let tx_bytes = block_tx.encoded_2718().into();
84+
85+
// Build the bundle body with the block_tx bytes as the last transaction in the bundle.
86+
let bundle_body = self.build_bundle_body(sim_result, tx_bytes);
87+
88+
// Create the MEV bundle (valid only in the specific host block)
89+
Ok(MevSendBundle::new(
90+
sim_result.host_block_number(),
91+
Some(sim_result.host_block_number()),
92+
ProtocolVersion::V0_1,
93+
bundle_body,
94+
))
7195
}
7296

73-
/// Prepares a BundleHelper call containing the rollup block and corresponding fills into a MEV bundle.
74-
async fn prepare_bundle_helper(&self, sim_result: &SimResult) -> eyre::Result<MevSendBundle> {
97+
/// Prepares and signs the submission transaction for the rollup block.
98+
///
99+
/// Creates a `SubmitPrep` instance to build the transaction, then fills
100+
/// and signs it using the host provider.
101+
async fn prepare_signed_transaction(
102+
&self,
103+
sim_result: &SimResult,
104+
) -> eyre::Result<alloy::consensus::TxEnvelope> {
75105
let prep = SubmitPrep::new(
76106
&sim_result.block,
77107
self.host_provider(),
@@ -80,34 +110,48 @@ impl FlashbotsTask {
80110
);
81111

82112
let tx = prep.prep_transaction(sim_result.prev_host()).await?;
83-
84113
let sendable = self.host_provider().fill(tx.into_request()).await?;
85114

86-
let tx_bytes = sendable
87-
.as_envelope()
88-
.ok_or_eyre("failed to get envelope from filled tx")?
89-
.encoded_2718()
90-
.into();
115+
sendable.as_envelope().ok_or_eyre("failed to get envelope from filled tx").cloned()
116+
}
117+
118+
/// Tracks the outbound transaction hash and increments submission metrics.
119+
///
120+
/// Sends the transaction hash to the outbound channel for monitoring.
121+
/// Logs a debug message if the channel is closed.
122+
fn track_outbound_tx(&self, envelope: &alloy::consensus::TxEnvelope) {
123+
counter!("signet.builder.flashbots.").increment(1);
124+
let hash = *envelope.tx_hash();
125+
if self.outbound.send(hash).is_err() {
126+
debug!("outbound channel closed, could not track tx hash");
127+
}
128+
}
91129

92-
let bundle_body = sim_result
130+
/// Constructs the MEV bundle body from host transactions and the submission transaction.
131+
///
132+
/// Combines all host transactions from the rollup block with the prepared rollup block
133+
/// submission transaction, wrapping each as a non-revertible bundle item.
134+
///
135+
/// The rollup block transaction is placed last in the bundle.
136+
fn build_bundle_body(
137+
&self,
138+
sim_result: &SimResult,
139+
tx_bytes: alloy::primitives::Bytes,
140+
) -> Vec<BundleItem> {
141+
sim_result
93142
.block
94143
.host_transactions()
95144
.iter()
96145
.cloned()
97146
.chain(std::iter::once(tx_bytes))
98147
.map(|tx| BundleItem::Tx { tx, can_revert: false })
99-
.collect::<Vec<_>>();
100-
101-
// Only valid in the specific host block
102-
Ok(MevSendBundle::new(
103-
sim_result.host_block_number(),
104-
Some(sim_result.host_block_number()),
105-
ProtocolVersion::V0_1,
106-
bundle_body,
107-
))
148+
.collect()
108149
}
109150

110-
/// Task future that runs the Flashbots submission loop.
151+
/// Main task loop that processes simulation results and submits bundles to Flashbots.
152+
///
153+
/// Receives `SimResult`s from the inbound channel, prepares MEV bundles, and submits
154+
/// them to the Flashbots relay. Skips empty blocks and continues processing on errors.
111155
async fn task_future(self, mut inbound: mpsc::UnboundedReceiver<SimResult>) {
112156
debug!("starting flashbots task");
113157

@@ -172,7 +216,19 @@ impl FlashbotsTask {
172216
}
173217
}
174218

175-
/// Spawns the Flashbots task that handles incoming `SimResult`s.
219+
/// Returns a clone of the host provider for transaction operations.
220+
fn host_provider(&self) -> HostProvider {
221+
self.zenith.provider().clone()
222+
}
223+
224+
/// Returns a reference to the Flashbots provider.
225+
const fn flashbots(&self) -> &FlashbotsProvider {
226+
&self.flashbots
227+
}
228+
229+
/// Spawns the Flashbots task in a new Tokio task.
230+
///
231+
/// Returns a channel sender for submitting `SimResult`s and a join handle for the task.
176232
pub fn spawn(self) -> (mpsc::UnboundedSender<SimResult>, JoinHandle<()>) {
177233
let (sender, inbound) = mpsc::unbounded_channel::<SimResult>();
178234
let handle = tokio::spawn(self.task_future(inbound));

0 commit comments

Comments
 (0)