Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Block Building #1923

Closed
metachris opened this issue Mar 23, 2023 · 11 comments
Closed

Support for Block Building #1923

metachris opened this issue Mar 23, 2023 · 11 comments
Labels
A-block-building Related to block building C-enhancement New feature or request M-prevent-stale Prevents old inactive issues/PRs from being closed due to inactivity

Comments

@metachris
Copy link

metachris commented Mar 23, 2023

Describe the feature

It would be great if reth supports building blocks, and allows submitting to multiple mev-boost relays.

Based on early reth metrics, it looks like it could be several times as performant as typical geth-based block builders, and perhaps provide some nice features on top!

At Flashbots we maintain a block builder based on geth, and would be happy to support the development of this functionality in reth with our experience and learnings.

Additional context

References

Goals

  1. Build blocks
    1. using transactions from mempool as well as directly sent transactions and bundles
    2. according proposer preferences: gasLimit, feeRecipient
    3. collect gas fees to the builder’s own block.coinbase address
    4. at end of the block, have a payment transaction from the builder (block.coinbase) to proposer feeRecipient
  2. Submit the block to one or more mev-boost relays
  3. Additional APIs to sending private transactions and bundles to the builder: eth_callBundle, eth_sendBundle, eth_sendPrivateTransaction ([1] [2] [3]) / eth_sendPrivateRawTransaction ([1])

How it works

  1. Builder polls relay for the proposer registrations for the next epoch when block building is triggered
    1. https://flashbots.github.io/relay-specs/#/Builder/getValidators
    2. https://boost-relay.flashbots.net/relay/v1/builder/validators
    3. Note that multiple relays should be supported
  2. The builder uses the payload_attributes SSE event subscription to a CL client to receive information about the current slot and the required payload attributes (timestamp, withdrawals, prevRandao) which are required for the produced block.
    1. See also PayloadAttributesV2 structure and the SSE event docs
  3. Blocks are submitted to the relay(s) on an ongoing basis
    1. Note that (1) relays might employ some rate-limiting for block submissions, and (2) blocks are becoming more valuable towards end of the slot, and may be submitted more often then.
    2. Therefore a smart scheduling might be advisable, sending only few blocks early on in the slot and sending more frequently in the second half of a slot.

Additional notes

  • Geth makes some things very hard to do, and there's some possible strong improvements over geth based on our experience:
    • Fast multi-transaction simulation with cheap reverts, in particular reverting ethereum state to one from before a list of transactions
    • Support for multiple merging algorithms is important, perhaps they can even be made somewhat modular?
    • A pain-point with geth is the slow calculation of the state root and block hash.
  • Bundles require revert protection and cannot be unbundled. (bundles are a list of transactions that can either be included together or not at all, and which must not be included if one of the transactions reverts)
  • See also the Flashbots builder README for inspiration on command-line arguments
  • The original Flashbots builder implementation uses a custom Prysm fork to send payload attributes to the builder. We are currently in the process of phasing this out and switching to the newly standardized SSE events: Add SSE subscription to builder flashbots/builder#53
  • We are happy to collaborate, feel free reach out through Discord, Telegram or the Flashbots forum.
@metachris metachris added C-enhancement New feature or request S-needs-triage This issue needs to be labelled labels Mar 23, 2023
@onbjerg
Copy link
Member

onbjerg commented Mar 23, 2023

Duplicate of #1013, but since this is more fleshed out I will close the other issue

@onbjerg onbjerg added A-block-building Related to block building and removed S-needs-triage This issue needs to be labelled labels Mar 23, 2023
@gakonst
Copy link
Member

gakonst commented Mar 23, 2023

Thank you Chris. Excited to start working on this soon...pinging searchers/builders to weigh in as well.

@jasalper
Copy link

My 2c: I don't think reth should implement a builder, but rather reth by default should provide RPC calls that make it possible to build a block without explicitly accessing the EL database. If builders want to modify reth the way geth is modified, then they're welcome to do so as well.

A possible barebones API:

reth_buildBlock

inputs: 
- txns [signed & rlp encoded]
- block_number
- fee_recipient 
- mix_hash
- extra_data

returns:
- execution_block

A few notes: mix_hash is passed in to avoid reth needing to make any consensus-client calls (to determine the randao reveal from the previous block). Could also pass in gas_limit.

The returned type is an execution payload (or a Block ethers-rs style).

Based on the builder_api (https://flashbots.github.io/relay-specs/#/Builder/submitBlock), builders would then only need to fill in a few more fields easily accessible from a CL client.

Beyond this, you can offer additional functionality either through separate rpc calls or flags to reth_buildBlock. I'm including these as separate rpc calls here for clarity (and naming can certainly be improved), but flags would allow mixing and matching and potentially save computation.

reth_buildBlockWithTxnpool: Same as reth_buildBlock, but reth's txnpool is used to pack extra transactions at the bottom of the submitted txns.

reth_buildBlockWithTrace: Returns a parity-style trace in addition to the execution payload

reth_buildBlockWithoutStateRoot: Returns only a parity style trace, without computing the state root or block hash. Similar to trace_callMany, if implemented with accurate gas counters, correct randomness, and nextblock behavior. (if trace_callMany handles that, then this function is not necessary).

reth_buildBlockWithProfitCounter: same as reth_buildBlock, except it also takes in an additional parameter (the proposer's desired fee address), and returns the additional value received at that address.

@gakonst
Copy link
Member

gakonst commented Mar 23, 2023

Makes a ton of sense, thanks @jasalper. Should there also be extra parameters supplied which allow for state overrides?

@joroshiba
Copy link

@jasalper this would certainly fit our needs, and fits relatively nicely with my suggested interface in #1013 (here).

To implement the Engine API, however, I believe Reth will require the ability to build a block from its mempool? Both v1 and v2 of eth_forkchoiceUpdate accept payload attributes and then start the building process. See the spec here, notably point number 8 of the requirements, as well as the details on payload building.

I think the key here is that as opposed to Geth, we can make Reth easy for people with alternative transaction selection criteria to build their own payloads (and thus be more usable as a library).

@jasalper
Copy link

@gakonst - I don't think the state overrides would be relevant for us specifically, but I could see reth_buildBlockWithoutStateRoot perhaps using them. It doesn't make much sense though to have state overrides but also compute state root/block hash.

@metachris
Copy link
Author

metachris commented Mar 24, 2023

Just an API call with transactions as input and returning a block seems entirely insufficient for building blocks that have a chance to land on mainnet.

Building blocks for a slot is not an one-off task, but an ongoing effort to find the highest value block by trying (simulating) combinations of transactions and bundles (that aren't allowed to be unbundled), including new ones that arrive over time (from mempool, through eth_sendBundle, etc.), and continuously submitting them to the relays.

The block building process would also often have more transactions available than fit into the block, and needs to decide which transactions to include or skip based on simulation results and block value.

It's also important to point out that bundles need to be supported, which are a list of transactions that can either be included all together or none at all, and which must not be included if one of the transactions reverts.

If an external service would need to go through this API call, it would need to be connected to the mempool, and receive bundles/private transactions through additional APIs, and would be very inefficient because of the request/response pattern, even if the API would allow for revert protection, transactions, private transactions and bundles, and even if reth would try to find the most valuable block out of all the supplied inputs while adhering to the expressed preferences.

@rakita
Copy link
Collaborator

rakita commented Mar 24, 2023

Duplicate of #1013, but since this is more fleshed out I will close the other issue

Let us leave it open. This issue is more discussion on API and that issue is for implementation and how it gets integrated, so there is a difference.

@jasalper
Copy link

jasalper commented Mar 24, 2023

@metachris - I don't think reth should be making a block builder - just adding support for external builders. I think there is a misunderstanding of what reth does in my proposal. Here, reth does not try to find the most valuable block. reth simply exposes a call that computes the block sealing values for builders that have already computed the most valuable block, and are submitting this block via a list of transactions.

"Building blocks for a slot is not an one-off task, but an ongoing effort to find the highest value block by trying (simulating) combinations of transactions and bundles (that aren't allowed to be unbundled), including new ones that arrive over time (from mempool, through eth_sendBundle, etc.), and continuously submitting them to the relays."

The RPC call doesn't broadcast the block anywhere. The RPC call is used to compute the state root (+ logs bloom, transactions root, etc) for a given list of transactions. An efficient block builder will not be computing these cryptographic values for any potential block, only the ones they wish to submit.

In this model, builders would do their simulation via something more efficient like trace_callMany or direct revm db bindings, and would continuously update their blocks with incoming transactions outside of reth. reth_buildBlock is only called once a potential block is ready to be sent to the relay.

"It's also important to point out that bundles need to be supported, which are a list of transactions that can either be included all together or none at all, and which must not be included if one of the transactions reverts."

I don't think reth_buildBlock needs to care about whether something is a bundle or a mempool transaction. Once something becomes part of the EL and lands on chain, these distinctions disappear. Ensuring that bundle transactions do not revert and that private transactions remain private should be the responsibility of the block builder. (Although perhaps returning an additional data structure which indicates which transactions reverted might be useful for these purposes?)

"If an external service would need to go through this API call, it would need to be connected to the mempool, and receive bundles/private transactions through additional APIs, and would be very inefficient because of the request/response pattern, even if the API would allow for revert protection, transactions, private transactions and bundles, and even if reth would try to find the most valuable block out of all the supplied inputs while adhering to the expressed preferences."

Having external builders subscribe to the txnpool to pack the block doesn't seem like a particularly big ask - everybody is doing it anyways, and if they don't wish to do it, the reth_buildBlockWithTxnpool call would fill the rest of the block with transactions that reth has seen in the default geth-style gas price ordering. (Not optimal compared to other packings, but if a builder wants efficient packings, they should do it themselves).

The builder already receives bundles/private transactions through additional APIs, so I'm not sure what the distinction is.

===

All of this being said, while I do like the separation of responsibilities, I do see a couple potential advantages to doing building with direct access to the db, such as the one you mentioned: "Fast multi-transaction simulation with cheap reverts, in particular reverting ethereum state to one from before a list of transactions".

But, I think that stuff like this is already exposed via reth revm db bindings, and anybody who wants this level of customization/optimization in their builder will be modifying their builder extensively themselves to the point where it doesn't make sense to ask the reth team to write their builder for them.

@github-actions
Copy link
Contributor

This issue is stale because it has been open for 14 days with no activity.

@github-actions github-actions bot added the S-stale This issue/PR is stale and will close with no further activity label Aug 27, 2023
@mattsse mattsse added M-prevent-stale Prevents old inactive issues/PRs from being closed due to inactivity and removed S-stale This issue/PR is stale and will close with no further activity labels Aug 27, 2023
@gakonst
Copy link
Member

gakonst commented Aug 30, 2023

I think we can close this given the CliExt trait which allows extending things and given we have the PayloadBuilder trait now.

also see integrations:

https://github.com/ralexstokes/mev-rs/pull/112/files
https://github.com/jacobkaufmann/evangelion/

@gakonst gakonst closed this as completed Aug 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-block-building Related to block building C-enhancement New feature or request M-prevent-stale Prevents old inactive issues/PRs from being closed due to inactivity
Projects
Archived in project
Development

No branches or pull requests

7 participants