A middleware server written in Go, that sits between an ethereum PoS consensus client and an execution client. It allows consensus clients to outsource block construction to third party block builders as well as fallback to execution clients. See ethresearch post for the high level architecture.
A summary of consensus client changes can be found here.
simple middleware logic with minimal consensus client changes, simple networking, no authentication, and manual safety mechanism
- middleware sends
feeRecipient
to relay with directengine_forkchoiceUpdatedV1
request at beginning of block - middleware fetches signed payloads from relay using unauthenticated
getPayloadHeader
request - middleware selects best payload that matches expected
payloadId
and requests signature from consensus client, this requires passing header object to the consensus client and flagging that it should be returned to the middleware once signed - middleware returns signed block + initial payload header to relay with direct request
- consensus client must implement blind transaction signing
cleanup consensus client and add security fallbacks
- middleware requests authenticated
feeRecipient
message from consensus client and gossips over p2p at regular interval - add middleware module for verifying previous relay payload validity and accuracy with hard or statistical blacklist (may require modifications to execution client)
- add middleware module for subscribing to 3rd party relay monitoring service
- in event of middleware crash, consensus client must be able to bypass the middleware to reach a local or remote execution client
- consensus client must implement Proposal Promises
add p2p comms mechanisms to prevent validator deanonymization
- middleware gossips signed block + initial payload header over p2p
- consensus client must implement New Gossipsub Topics
add optional configurations to provide alternative guarantees
- consider adding direct
relay_forkchoiceUpdatedV1
call to relay for syncing state - consider returning full payload directly to validator as optimization
- consider adding merkle proof of payment to shift verification requirements to the relay
Methods are prefixed using the following convention:
engine
prefix indicates calls made to the execution client. These methods are specified in the execution engine APIs.builder
prefix indicates calls made to the mev-boost middleware.relay
prefix indicates calls made to a relay.
See engine_forkchoiceUpdatedV1.
- method:
builder_proposeBlindedBlockV1
- params:
- result:
ExecutionPayloadV1
. - error: code and message set in case an exception happens while proposing the payload.
Technically, this call only needs to return the transactions
field of ExecutionPayloadV1
, but we return the full payload for simplicity.
- method:
builder_getPayloadHeaderV1
- params:
payloadId
:DATA
, 8 Bytes - Identifier of the payload build process
- result:
ExecutionPayloadHeaderV1
- error: code and message set in case an exception happens while getting the payload.
- method:
relay_getPayloadHeaderV1
- params:
payloadId
:DATA
, 8 Bytes - Identifier of the payload build process
- result:
SignedMEVPayloadHeader
- error: code and message set in case an exception happens while getting the payload.
- method:
relay_proposeBlindedBlockV1
- params:
- result:
ExecutionPayloadV1
- error: code and message set in case an exception happens while proposing the payload.
Technically, this call only needs to return the transactions
field of ExecutionPayloadV1
, but we return the full payload for simplicity.
See here for the definition of fields like BLSSignature
- message: MEVPayloadHeader
- signature: BLSSignature
- payloadHeader:
ExecutionPayloadHeaderV1
- feeRecipientDiff: Quantity, 256 Bits - the change in balance of the feeRecipient address
See here for the definition of fields like BLSSignature
- message: BlindedBeaconBlock
- signature: BLSSignature
This is forked from here with body
replaced with BlindedBeaconBlockBody
- slot: Slot
- proposer_index: ValidatorIndex
- parent_root: Root
- state_root: Root
- body: BlindedBeaconBlockBody
This is forked from here with execution_payload
replaced with execution_payload_header
- randao_reveal: BLSSignature
- eth1_data: Eth1Data
- graffiti: Bytes32
- proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
- attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
- attestations: List[Attestation, MAX_ATTESTATIONS]
- deposits: List[Deposit, MAX_DEPOSITS]
- voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
- sync_aggregate: SyncAggregate
- execution_payload_header: ExecutionPayloadHeader
make build
and then run it with:
./mev-boost
make test
We use revive
as a linter. You need to install it with go install github.com/mgechev/revive@latest
make lint
We are currently testing using a forked version of mergemock, see https://github.com/flashbots/mergemock
Make sure you've setup and built mergemock first, refer to its README but here's a quick setup guide:
git clone https://github.com/flashbots/mergemock.git
cd mergemock
go build . mergemock
wget https://gist.githubusercontent.com/lightclient/799c727e826483a2804fc5013d0d3e3d/raw/2e8824fa8d9d9b040f351b86b75c66868fb9b115/genesis.json
Then you can run an integration test with mergemock, spawning both a mergemock execution engine and a mergemock consensus client as well as mev-boost:
cd mev-boost
make run-mergemock-integration
The path to the mergemock repo is assumed to be ../mergemock
, you can override like so:
make MERGEMOCK_DIR=/PATH-TO-MERGEMOCK-REPO run-mergemock-integration
to run mergemock in dev mode:
make MERGEMOCK_BIN='go run .' run-mergemock-integration