Skip to content

feat: allow bundles to target storage slots for blind backrunning#933

Open
MavenRain wants to merge 1 commit into
flashbots:developfrom
MavenRain:feat/bundle-target-storage-slots
Open

feat: allow bundles to target storage slots for blind backrunning#933
MavenRain wants to merge 1 commit into
flashbots:developfrom
MavenRain:feat/bundle-target-storage-slots

Conversation

@MavenRain

Copy link
Copy Markdown

📝 Summary

Closes #27. Lets a bundle declare the storage slots it targets via a new optional
targetStorageSlots field on eth_sendBundle (bundle v2+). The builder only attempts to
include such a bundle after an order that writes one of those slots has been added to the
block. This enables blind backrunning (backrun any transaction that touches, e.g., a pool
reserve slot, without knowing its hash) and reduces the wasted inclusion attempts / spam that
searchers otherwise generate by submitting a bundle for every candidate position.

An empty targetStorageSlots (the default) leaves a bundle unconditional, so existing bundles
are completely unaffected.

Example request:

{
  "version": "v2",
  "txs": ["0x..."],
  "targetStorageSlots": [
    { "address": "0x1f98431c8ad98523631ae4a59f267346ea31f984", "slot": "0x0000...0008" }
  ]
}

💡 Motivation and Context

Blind backrunning is a common searcher pattern: a bundle wants to land immediately after any
transaction that mutates some piece of state (a DEX pool reserve, an oracle slot, ...), but the
searcher does not know the triggering transaction's hash ahead of time. Today the only way to
express this is to spam many speculative bundles, most of which the builder simulates and
discards. Letting the bundle name the storage slots it depends on lets the builder attempt it
exactly once, and only when the precondition is actually met.

How it works

The change deliberately reuses machinery that already exists, so it adds no cost to the hot path:

  • rbuilder already records each order's read/write storage sets during simulation
    (simulate_order runs an AccumulatorSimulationTracer, so every SimulatedOrder carries a
    populated used_state_trace).
  • PrioritizedOrderStore already gates "attempt-after" dependencies for nonces: an order whose
    required nonce is not yet reached is parked in pending_orders (keyed by AccountNonce) and
    released by update_onchain_nonces when a committed order satisfies it.

Target storage slots mirror that exact pattern, keyed by SlotKey instead of AccountNonce:

  • A bundle with target slots that have not yet been written this block is parked in a new
    pending_on_slot map instead of entering the ready queue.
  • After each order commits, the greedy fill_orders loop feeds the slots that order wrote
    (taken from its already-computed simulation trace) to a new notify_slots_written, which
    releases any bundle waiting on one of those slots back into the normal priority queue.

Because the slot writes come from the existing simulation trace, no change is made to the
block-building commit tracer
and there is no added EVM instrumentation. Bundles that do not
use the feature see identical behavior.

Notes / scope

  • Version gating: targetStorageSlots is only accepted on bundle version >= v2; a v1 bundle
    that sets it is rejected with FieldNotSupportedByVersion, consistent with the other v2 fields.
  • OR semantics: a bundle listing several target slots becomes eligible as soon as any one
    of them is written.
  • Builder scope: this is wired through the sequential ordering builder. The parallel builder
    does not currently gate on target slots (documented on the field).
  • Nonce marker over-report: the simulation trace records a synthetic write to the signer's
    slot 0 for nonce tracking, so a bundle that explicitly targets {eoa, 0x0} may be released by
    any transaction from that account. This is a benign over-report (it can only release a bundle
    early, never miss a release) and is noted in a code comment.

🗂️ Changes

  • rbuilder-primitives
    • Bundle::target_storage_slots: Vec<SlotKey> and an Order::target_storage_slots() accessor.
    • targetStorageSlots wire field (RawTargetStorageSlot { address, slot }) on
      RawBundleMetadata, with decode/encode round-trip and the v2 version gate.
    • The field is intentionally excluded from the bundle uuid/hash to preserve replacement-id
      compatibility (same as signer / external_hash).
  • rbuilder
    • PrioritizedOrderStore: pending_on_slot + written_slots, slot gating in insert_order,
      and notify_slots_written.
    • ordering_builder::fill_orders: releases waiting bundles using each committed order's
      written slots.

🧪 Testing

  • rbuilder-primitives: targetStorageSlots v2 parse + wire round-trip, and v1 rejection.
  • rbuilder: test_block_orders_target_storage_slots — a slot-targeting bundle stays pending,
    is not released by an unrelated slot write, and becomes includable once its target slot is
    written.

✅ I have completed the following steps:

  • Run make lint
  • Run make test
  • Added tests (if applicable)

Implements flashbots#27. A bundle can declare the storage slots it targets via the
optional `targetStorageSlots` field on eth_sendBundle (v2+).  The builder only
attempts to include such a bundle after an order that writes one of those slots
has been added to the block, enabling blind backrunning and reducing wasted
inclusion attempts.

- rbuilder-primitives: add target_storage_slots to Bundle, the targetStorageSlots
  wire field + round-trip, v2 gating, and Order::target_storage_slots().
- PrioritizedOrderStore: park target-slot bundles until one of their slots is
  written (mirrors the nonce pending/release machinery) via notify_slots_written().
- ordering_builder: after each committed order, feed the slots it wrote (from the
  order's simulation trace) to the store so waiting bundles are released.

Adds unit tests for parsing/round-trip, v1 rejection, and store parking/release.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain
MavenRain requested review from ZanCorDX and dvush as code owners July 17, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: allow bundles to specify storage slots they target

1 participant