Giter Club home page Giter Club logo

mev-boost-relay's Introduction

MEV-Boost Relay

Goreport status Test status Docker hub

MEV-Boost Relay for Ethereum proposer/builder separation (PBS).

Currently live at:

Alternatives (not audited or endorsed): blocknative/dreamboat, manifold/mev-freelay

See also

Components

The relay consists of three main components, which are designed to run and scale independently, and to be as simple as possible:

  1. API: Services that provide APIs for (a) proposers, (b) block builders, (c) data.
  2. Website: Serving the website requests (information is pulled from Redis and database).
  3. Housekeeper: Updates known validators, proposer duties, and more in the background. Only a single instance of this should run.

Dependencies

  1. Redis
  2. PostgreSQL
  3. one or more beacon nodes
  4. block submission validation nodes
  5. [optional] Memcached

Beacon nodes / CL clients

  • The relay services need access to one or more beacon node for event subscriptions (in particular the head and payload_attributes topics).
  • You can specify multiple beacon nodes by providing a comma separated list of beacon node URIs.
  • The beacon nodes need to support the payload_attributes SSE event.
  • Support the v2 CL publish block endpoint in the current main branch, since August 2. This is still experimental and may or may not fully work. It requires at least one of these CL clients
  • The latest release (v0.26) still uses the old V1 broadcast endpoint using CL clients with custom validate-before-broadcast patches (see README of the release for more details)

Relays are strongly advised to run multiple beacon nodes!

  • The reason is that on getPayload, the block has to be validated and broadcast by a local beacon node before it is returned to the proposer.
  • If the local beacon nodes don't accept it (i.e. because it's down), the block won't be returned to the proposer, which leads to the proposer missing the slot.
  • The relay makes the validate+broadcast request to all beacon nodes concurrently, and returns as soon as the first request is successful.

Security

A security assessment for the relay was conducted on 2022-08-22 by lotusbumi. Additional information can be found in the Security section of this repository.

If you find a security vulnerability on this project or any other initiative related to Flashbots, please let us know sending an email to [email protected].


Background

MEV is a centralizing force on Ethereum. Unattended, the competition for MEV opportunities leads to consensus security instability and permissioned communication infrastructure between traders and block producers. This erodes neutrality, transparency, decentralization, and permissionlessness.

Flashbots is a research and development organization working on mitigating the negative externalities of MEV. Flashbots started as a builder specializing in MEV extraction in proof-of-work Ethereum to democratize access to MEV and make the most profitable blocks available to all miners. >90% of miners are outsourcing some of their block construction to Flashbots today.

The mev-boost relay is a trusted mediator between block producers and block builders. It enables all Ethereum proof-of-stake validators to offer their blockspace to not just Flashbots but other builders as well. This opens up the market to more builders and creates competition between them, leading to more revenue and choice for validators, and better censorship-resistance for Ethereum.

In the future, proposer/builder separation will be enshrined in the Ethereum protocol itself to further harden its trust model.

Read more in Why run mev-boost? and in the Frequently Asked Questions.


Usage

Running Postgres, Redis and Memcached

# Start PostgreSQL & Redis individually:
docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres
docker run -d -p 6379:6379 redis

# [optional] Start Memcached
docker run -d -p 11211:11211 memcached

# Or with docker-compose:
docker-compose up

Note: docker-compose also runs an Adminer (a web frontend for Postgres) on http://localhost:8093/?username=postgres (db: postgres, username: postgres, password: postgres)

Now start the services:

# The housekeeper sets up the validators, and does various housekeeping
go run . housekeeper --network sepolia --db postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable

# Run APIs for sepolia (using a dummy BLS secret key)
go run . api --network sepolia --secret-key 0x607a11b45a7219cc61a3d9c5fd08c7eebd602a6a19a977f8d3771d5711a550f2 --db postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable

# Run Website for sepolia
go run . website --network sepolia --db postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable

# Query status
curl localhost:9062/eth/v1/builder/status

# Send test validator registrations
curl -X POST -H'Content-Encoding: gzip' localhost:9062/eth/v1/builder/validators --data-binary @testdata/valreg2.json.gz

# Delete previous registrations
redis-cli DEL boost-relay/sepolia:validators-registration boost-relay/sepolia:validators-registration-timestamp

Environment variables

General

  • ACTIVE_VALIDATOR_HOURS - number of hours to track active proposers in redis (default: 3)
  • API_MAX_HEADER_BYTES - http maximum header bytes (default: 60_000)
  • API_TIMEOUT_READ_MS - http read timeout in milliseconds (default: 1_500)
  • API_TIMEOUT_READHEADER_MS - http read header timeout in milliseconds (default: 600)
  • API_TIMEOUT_WRITE_MS - http write timeout in milliseconds (default: 10_000)
  • API_TIMEOUT_IDLE_MS - http idle timeout in milliseconds (default: 3_000)
  • API_SHUTDOWN_WAIT_SEC - how long to wait on shutdown before stopping server, to allow draining of requests (default: 30)
  • API_SHUTDOWN_STOP_SENDING_BIDS - whether API should stop sending bids during shutdown (nly useful in single-instance/testnet setups, default: false)
  • BLOCKSIM_MAX_CONCURRENT - maximum number of concurrent block-sim requests (0 for no maximum, default: 4)
  • BLOCKSIM_TIMEOUT_MS - builder block submission validation request timeout (default: 3000)
  • BROADCAST_MODE - which broadcast mode to use for block publishing (default: consensus_and_equivocation)
  • DB_DONT_APPLY_SCHEMA - disable applying DB schema on startup (useful for connecting data API to read-only replica)
  • DB_TABLE_PREFIX - prefix to use for db tables (default uses dev)
  • GETPAYLOAD_RETRY_TIMEOUT_MS - getPayload retry getting a payload if first try failed (default: 100)
  • MEMCACHED_URIS - optional comma separated list of memcached endpoints, typically used as secondary storage alongside Redis
  • MEMCACHED_EXPIRY_SECONDS - item expiry timeout when using memcache (default: 45)
  • MEMCACHED_CLIENT_TIMEOUT_MS - client timeout in milliseconds (default: 250)
  • MEMCACHED_MAX_IDLE_CONNS - client max idle conns (default: 10)
  • NUM_ACTIVE_VALIDATOR_PROCESSORS - proposer API - number of goroutines to listen to the active validators channel
  • NUM_VALIDATOR_REG_PROCESSORS - proposer API - number of goroutines to listen to the validator registration channel
  • NO_HEADER_USERAGENTS - proposer API - comma separated list of user agents for which no bids should be returned
  • ENABLE_BUILDER_CANCELLATIONS - whether to enable block builder cancellations
  • REDIS_URI - main redis URI (default: localhost:6379)
  • REDIS_READONLY_URI - optional, a secondary redis instance for heavy read operations

Feature Flags

  • DISABLE_PAYLOAD_DATABASE_STORAGE - builder API - disable storing execution payloads in the database (i.e. when using memcached as data availability redundancy)
  • DISABLE_LOWPRIO_BUILDERS - reject block submissions by low-prio builders
  • FORCE_GET_HEADER_204 - force 204 as getHeader response
  • ENABLE_IGNORABLE_VALIDATION_ERRORS - enable ignorable validation errors
  • USE_V1_PUBLISH_BLOCK_ENDPOINT - uses the v1 publish block endpoint on the beacon node
  • USE_SSZ_ENCODING_PUBLISH_BLOCK - uses the SSZ encoding for the publish block endpoint

Development Environment Variables

  • RUN_DB_TESTS - when set to "1" enables integration tests with Postgres using endpoint specified by environment variable TEST_DB_DSN
  • RUN_INTEGRATION_TESTS - when set to "1" enables integration tests, currently used for testing Memcached using comma separated list of endpoints specified by MEMCACHED_URIS
  • TEST_DB_DSN - specifies connection string using Data Source Name (DSN) for Postgres (default: postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable)

Redis Tuning

  • REDIS_CONNECTION_POOL_SIZE, REDIS_MIN_IDLE_CONNECTIONS, REDIS_READ_TIMEOUT_SEC, REDIS_POOL_TIMEOUT_SEC, REDIS_WRITE_TIMEOUT_SEC (see also the code here)

Website

  • LINK_BEACONCHAIN - url for beaconcha.in (default: https://beaconcha.in)
  • LINK_DATA_API - origin url for data api (https://domain:port)
  • LINK_ETHERSCAN - url for etherscan (default: https://etherscan.io)
  • LISTEN_ADDR - listen address for webserver (default: localhost:9060)
  • RELAY_URL - full url for the relay (https://pubkey@host)
  • SHOW_CONFIG_DETAILS - when set to "1", logs configuration details

Updating the website

  • Edit the HTML in services/website/website.html
  • Edit template values in testdata/website-htmldata.json
  • Generate a static version of the website with go run scripts/website-staticgen/main.go

This builds a local copy of the template and saves it in website-index.html

The website is using:


Technical Notes

See ARCHITECTURE.md and Running MEV-Boost-Relay at scale for more technical details!

Storing execution payloads and redundant data availability

By default, the execution payloads for all block submission are stored in Redis and also in the Postgres database, to provide redundant data availability for getPayload responses. But the database table is not pruned automatically, because it takes a lot of resources to rebuild the indexes (and a better option is using TRUNCATE).

Storing all the payloads in the database can lead to terabytes of data in this particular table. Now it's also possible to use memcached as a second data availability layer. Using memcached is optional and disabled by default.

To enable memcached, you just need to supply the memcached URIs either via environment variable (i.e. MEMCACHED_URIS=localhost:11211) or through command line flag (--memcached-uris).

You can disable storing the execution payloads in the database with this environment variable: DISABLE_PAYLOAD_DATABASE_STORAGE=1.

Builder submission validation nodes

You can use the builder project to validate block builder submissions: https://github.com/flashbots/builder

Here's an example systemd config:

/etc/systemd/system/geth.service
[Unit]
Description=mev-boost
Wants=network-online.target
After=network-online.target

[Service]
User=ubuntu
Group=ubuntu
Environment=HOME=/home/ubuntu
Type=simple
KillMode=mixed
KillSignal=SIGINT
TimeoutStopSec=90
Restart=on-failure
RestartSec=10s
ExecStart=/home/ubuntu/builder/build/bin/geth \
    --syncmode=snap \
    --datadir /var/lib/goethereum \
    --metrics \
    --metrics.expensive \
    --http \
    --http.api="engine,eth,web3,net,debug,flashbots" \
    --http.corsdomain "*" \
    --http.addr "0.0.0.0" \
    --http.port 8545 \
    --http.vhosts '*' \
    --ws \
    --ws.api="engine,eth,web3,net,debug" \
    --ws.addr 0.0.0.0 \
    --ws.port 8546 \
    --ws.api engine,eth,net,web3 \
    --ws.origins '*' \
    --graphql \
    --graphql.corsdomain '*' \
    --graphql.vhosts '*' \
    --authrpc.addr="0.0.0.0" \
    --authrpc.jwtsecret=/var/lib/goethereum/jwtsecret \
    --authrpc.vhosts '*' \
    --cache=8192

[Install]
WantedBy=multi-user.target

Sending blocks to the validation node:

  • The built-in blocksim-ratelimiter is a simple example queue implementation.
  • By default, BLOCKSIM_MAX_CONCURRENT is set to 4, which allows 4 concurrent block simulations per API node
  • For production use, use the prio-load-balancer project for a single priority queue, and disable the internal concurrency limit (set BLOCKSIM_MAX_CONCURRENT to 0).

Beacon node setup

Lighthouse

  • Lighthouse with validation and equivocaation check before broadcast: sigp/lighthouse#4168
  • with --always-prepare-payload and --prepare-payload-lookahead 12000 flags, and some junk feeRecipeint

Here's a quick guide for setting up Lighthouse.

Here's an example Lighthouse systemd config:

/etc/systemd/system/lighthouse.service
[Unit]
Description=Lighthouse
After=network.target
Wants=network.target

[Service]
User=ubuntu
Group=ubuntu
Type=simple
Restart=always
RestartSec=5
TimeoutStopSec=180
ExecStart=/home/ubuntu/.cargo/bin/lighthouse bn \
        --network mainnet \
        --checkpoint-sync-url=https://mainnet-checkpoint-sync.attestant.io \
        --eth1 \
        --http \
        --http-address "0.0.0.0" \
        --http-port 3500 \
        --datadir=/mnt/data/lighthouse \
        --http-allow-sync-stalled \
        --execution-endpoints=http://localhost:8551 \
        --jwt-secrets=/var/lib/goethereum/jwtsecret \
        --disable-deposit-contract-sync \
        --always-prepare-payload \
        --prepare-payload-lookahead 12000

[Install]
WantedBy=default.target

Prysm

  • Prysm with validation and equivocaation check before broadcast: prysmaticlabs/prysm#12335
  • use --grpc-max-msg-size 104857600, because by default the getAllValidators response is too big and fails

Here's an example Prysm systemd config:

/etc/systemd/system/prysm.service
[Unit]
Description=Prysm
After=network.target
Wants=network.target

[Service]
User=ubuntu
Group=ubuntu
Type=simple
Restart=always
RestartSec=5
TimeoutStopSec=180
ExecStart=/home/ubuntu/prysm/bazel-bin/cmd/beacon-chain/beacon-chain_/beacon-chain \
        --accept-terms-of-use \
        --enable-debug-rpc-endpoints \
        --checkpoint-sync-url=https://mainnet-checkpoint-sync.attestant.io \
        --genesis-beacon-api-url=https://mainnet-checkpoint-sync.attestant.io \
        --grpc-gateway-host "0.0.0.0" \
        --datadir=/mnt/data/prysm \
        --p2p-max-peers 100 \
        --execution-endpoint=http://localhost:8551 \
        --jwt-secret=/var/lib/goethereum/jwtsecret \
        --min-sync-peers=1 \
        --grpc-max-msg-size 104857600 \
        --prepare-all-payloads \
        --disable-reorg-late-blocks

[Install]
WantedBy=default.target

Bid Cancellations

Block builders can opt into cancellations by submitting blocks to /relay/v1/builder/blocks?cancellations=1. This may incur a performance penalty (i.e. validation of submissions taking significantly longer). See also #348


Maintainers

Contributing

Flashbots is a research and development collective working on mitigating the negative externalities of decentralized economies. We contribute with the larger free software community to illuminate the dark forest.

You are welcome here <3.

  • If you have a question, feedback or a bug report for this project, please open a new Issue.
  • If you would like to contribute with code, check the CONTRIBUTING file for further info about the development environment.
  • We just ask you to be nice. Read our code of conduct.

Security

If you find a security vulnerability on this project or any other initiative related to Flashbots, please let us know sending an email to [email protected].

Audits

License

The code in this project is free software under the AGPL License version 3 or later.


Made with ☀️ by the ⚡🤖 collective.

mev-boost-relay's People

Contributors

0x416e746f6e avatar 0xpanoramix avatar alextes avatar asanso avatar austonst avatar avalonche avatar bertmiller avatar bhakiyakalimuthu avatar blombern avatar come-maiz avatar dependabot[bot] avatar eliasiofir avatar franciscodiazydiaz avatar freddmannen avatar jtraglia avatar kailinr avatar metachris avatar michaelneuder avatar paulopontesm avatar rkrasiuk avatar ruteri avatar sukoneck avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mev-boost-relay's Issues

[spec proposal] improved API to get validators

Context: https://collective.flashbots.net/t/privacy-concerns-with-data-api/568

We'll update the API for getting validators (https://boost-relay.flashbots.net/relay/v1/data/validator_registration?pubkey=) to make it more private, and to allow bulk queries.

Changes:

  1. Instead of only providing pubkey for the request, it will also require fee_recipient (and optionally gas_limit). This is to maintain privacy for proposers (you can only query them if you know the fee_recipient).
  2. Allow single queries (like currently), as well as bulk queries with POST requests.
  3. Return a response only for active proposers (currently the API returns the result if a proposer registered at any time in the past)

Single query:

https://boost-relay.flashbots.net/relay/v1/data/validator_registration
    ?pubkey=<pubkey>
    ?fee_recipient=<feeRecipient>
    [?gas_limit=<gasLimit>]

Bulk query:

[
  {
    "pubkey": "<a_pubkey>",
    "fee_recipient": "<a_fee_recipient>"
  },
  {
    "pubkey": "<b_pubkey>",
    "fee_recipient": "<b_fee_recipient>",
    "gas_limit": "<b_gas_limit>"
  }
]

Response includes a list of found entries:

[
  "<a_pubkey>",
  "<b_pubkey>"
]

Possible extensions (please comment if you have a need for that, could be added now or in the future):

  1. return not only the pubkey but also fee_recipient and gas_limit (enable this response format with another query arg?)
  2. return not the found entries, but those that are not found (could also be enabled with another query arg).

Please comment with any remaining thoughts. Will start implementation soon 🙏

registerValidator fails with "error response: 403 / error code: 1020"

This are the error messages:

time="2022-09-23T10:30:47Z" level=warning msg="error calling registerValidator on relay" error="HTTP error response: 403 / error code: 1020" method=registerValidator module=service numRegistrations=25 ua=Go-http-client/1.1 url="https://boost-relay.flashbots.net/eth/v1/builder/validators"
time="2022-09-23T10:30:47Z" level=info msg="http: POST /eth/v1/builder/validators 502" duration=0.017662955 method=POST module=service path=/eth/v1/builder/validators status=502
time="2022-09-23T10:30:59Z" level=debug msg="Checking relay status" module=service url="https://boost-relay.flashbots.net/eth/v1/builder/status"
time="2022-09-23T10:30:59Z" level=error msg="failed to retrieve relay status" error="HTTP error response: 403 / error code: 1020" module=service url="https://boost-relay.flashbots.net/eth/v1/builder/status"
time="2022-09-23T10:30:59Z" level=info msg="http: GET /eth/v1/builder/status 503" duration=0.011533301 method=GET module=service path=/eth/v1/builder/status status=503

Restrict bids received data API to past slots

Thoughts on restricting /relay/v1/data/bidtraces/builder_blocks_received to past slots?

I think a builder could:

  1. Wait as long as it can before submitting a bid
  2. Use the data API to grab all of the bids for that slot
  3. Submit a bid that just slightly beats the highest bid

This should be a blind auction, right?

Upcoming release: v0.16.0

New release coming up: mev-boost-relay v0.16.0

Latest alpha release

Notable changes

Important: this Capella-ready version of the relay requires a custom Prysm fork as CL client (the develop-boost-capella branch of github.com/flashbots/prysm), which includes a new getWithdrawals endpoint the relay needs to verify withdrawals of block builder submissions. This is expected to be a temporary requirement, as there is a new ethereum/beacon-APIs#244 (comment) specified to provide all the required details, and once that is implemented in CL clients we will update the relay accordingly.

See this writeup for more information about the Capella changes across the MEV-Boost ecosystem.

Registration handler is missing negative time diff check

A validator could bypass the timestamp checks and potentially DoS the relay. The problem lies with this check:

https://github.com/flashbots/boost-relay/blob/2137374990edeafcdb18776d1acf40c60f26cefc/services/api/service.go#L450-L454

Actually, there are two ways of exploiting this, both the same root issue:

Timestamps less than startTimestamp

The check above will not throw an error if the time difference (td) is negative. With a new validator that has not previously registered with the relay, it could send a billion+ registrations to the relay and it would update the datastore for all of them.

for t in range(0, now()):
    send(registration_with_timestamp(t))

Timestamps greater than math.MaxInt64

In the check above, it will cast the registration timestamp from uint64 to int64. If the timestamp is greater than math.MaxInt64 the result will be negative. Similarly to the previous method of exploiting this, a validator could send virtually unlimited number of registrations (9,223,372,036,854,775,808+) to the relay and it would accept all of them.

The solution

Check that the time difference (td) isn't negative. But it might not be quite that simple. If the validator uses the current time when constructing the registration, the timestamp could be less than startTimestamp if the time between constructing the registration and its processing is greater than 1 second. Could happen with slower internet speeds. With this in mind, I believe the better solution is to check that the registration is within 10 seconds of the start time in either direction.

Something like:

 td := int64(registration.Message.Timestamp) - startTimestamp 
 if td > 10 { 
 	respondError(http.StatusBadRequest, "timestamp too far in the future") 
 	return 
 } else if td < -10 { 
 	respondError(http.StatusBadRequest, "timestamp too old") 
 	return 
 }

Write validator registrations from DB to Redis

Currently redis is used as source of truth, and validator registrations are only saved to the database, but not read.

Housekeeper should probably periodically write these from DB to Redis.

Add tests

Start adding tests for the service

Support full redis URLs in command line options

The --redis-uri command line option, used by all three relay programs, only supports begin given the hostname and port of the redis server. Given a Redis server with TLS and a password, using the full URL rediss://<user>:<pass>@<host>:<port> as the argument to --redis-uri produces the following (with details e.g. hostname and password edited out):

time="2022-11-19T21:26:12Z" level=fatal msg="Failed to connect to Redis at rediss://<user>:<pass>@<host>:<port>" error="dial tcp: address rediss://<user>:<pass>@<host>:<port>: too many colons in address" service=relay/website

Passing in only the Redis hostname and port, excluding the protocol and credentials, also expectedly fails:

time="2022-11-19T21:23:09Z" level=fatal msg="Failed to connect to Redis at <host>:<port>" error="write: broken pipe" service=relay/website


This happens because the provided URL is passed as the Addr in redis.Options without parsing out the various pieces.

The solution I propose uses redis.ParseURL to populate the options for creating a new client. ParseURL enforces that a protocol be present, so to ensure backwards-compatability, addresses provided without a protocol are prepended with redis:// to maintain the same behavior as before.

Start integrating Prysm

Prysm is a consensus client software written in Golang: https://github.com/prysmaticlabs/prysm

We have a private prysm fork here: https://github.com/flashbots/prysm-private/

The relay and the builder will use Prysm to get data from the beacon chain.

There's a couple of tasks and goals inside:

  1. Update our local Prysm fork with the recent upstream changes
  2. Run a local Sepolia setup (because it syncs very fast, geth in ~10 minutes) with geth + Prysm
  3. The relay needs to know the current slot. CL clients send this to the validator with an API call named forkchoiceUpdated (fcU). The goal here is to update Prysm to send every single forkchoiceUpdated event to a redis pubsub channel, so the relay can pick that up.

Document every step along the way in a Notion document. Here's instructions for running geth on Sepolia:

geth \
    --sepolia \
    --http \
    --http.addr "0.0.0.0" \
    --http.api="engine,eth,web3,net,debug" \
    --http.corsdomain "*" \
    --ws \
    --ws.api="engine,eth,web3,net,debug" \
    --override.terminaltotaldifficulty 17000000000000000 \
    --bootnodes "enode://9246d00bc8fd1742e5ad2428b80fc4dc45d786283e05ef6edbd9002cbc335d40998444732fbe921cb88e1d2c73d1b1de53bae6a2237996e9bfe14f871baf7066@18.168.182.86:30303,enode://ec66ddcf1a974950bd4c782789a7e04f8aa7110a72569b6e65fcd51e937e74eed303b1ea734e4d19cfaec9fbff9b6ee65bf31dcb50ba79acce9dd63a6aca61c7@52.14.151.177:30303"

[spec proposal] Default BidTrace Updated to Support Timestamp and ExtraData

Context:

Currently, the relay/v1/data/bidtraces/proposer_payload_delivered endpoint returns a BidTraceV2 object that does not include a timestamp. This becomes a problem when one tries to analyze the impact of time on block proposals. As discussed in [1] and [2], time impacts proposers' incentives, and correct analysis can help us better understand how the game is played.

I'm currently working on finding the unrealized value (UNREV) in payloads delivered through mev-boost. Since it is not always the case that proposers pick the (latest-delivered -> as only the latest submitted block of each builder matters) most valuable block available in any relay, UNREV emerges.

Although one reason for UNREV can be a proposer being irrational (in the sense that not always aiming to maximize his utility, e.g., because he only wants to work with a specific relay), another reason is the timing/availability of blocks. It is highly possible that a proposer had accepted a block too early, exposing UNREV. For example, in slot 4,849,684, the delivered payload paid 0.06 ETH. One second later, a block offered 465 ETH (0x18ce78328850d8de2f9d4c6f3fbafdd3ae143b97a9860f8b8b78ad5ac7606167).

With the current endpoints (proposer_payload_delivered and builder_blocks_received), one cannot do a complete UNREV analysis for each block as:

  • builder_blocks_received only returns a subset of blocks received. If every relay offers their complete block data like Flashbots does, then this issue can be resolved.
  • proposer_payload_delivered returns a BidTraceV2 object that does not include a timestamp (unlike BidTraceV2WithTimestamp). One can try to find the same block through builder_blocks_received endpoint and use the timestamp there but I'm not sure if the timestamp of the delivered payload would be the same as the submitted builder block's.

Changes:

  1. Introduces BidTraceV3 which inherits from BidTraceV2WithTimestamp and adds an extra_data field as proposed by @metachris .
  2. relay/v1/data/bidtraces/proposer_payload_delivered and relay/v1/data/bidtraces/builder_blocks_received endpoints both return a BidTraceV3 now. However, it is open to discussion whether both should return the same timestamp for a given block.

Data API ProposerPayloadsDelivered implementation

Hello,

I'm opening this issue because of how ProposerPayloadsDelivered data API is implemented. Currently, in the Data API specs of notion this is how the cursor parameter is described:

cursor: a slot cursor, where limit number of entries up until the cursor slot is returned (note only slot or cursor can be used)

So, this means that delivered payloads are returned up until the cursor, meaning that the cursor is the upper limit. Therefore, the returned entries should start at the bottom and go up until the cursor is hit or we reach limit amount of entries. But, in the current implementation the entries start at cursor, and go downwards until limit amount of entries are found.

So, is this a bug in the relay code or a change in the spec? Thank you!

⚡ Enable dependabot to manage dependencies

Description

I wanted to have your opinion on the following : should we enable dependabot to manage golang dependencies automatically ?

If yes:

  • What should be the interval ? Daily or weekly ?
  • Who should be assigned as a reviewer for the dependabot PRs ? I'm thinking @metachris and @Ruteri but I don't know who else is working / responsible for the development of this project on Flashbot's side.

Note: I've already created a branch and the configuration locally.

feat: database migrations

We want to have database migrations.

One consideration is that the base table name is configurable (with env var), and this doesn't seem to be supported by existing migration tooling (i.e. go-migrate).

What's the easiest and best way to add migrations?

  • We could just manually add migrations as golang files
  • These could be manually applied, without needing to store the migration status in the database itself

Discussion: support payout tx payee being different than fee recipient

As it stands now, block validation requires the to field of the payout transaction to be that of the fee recipient.

This is problematic as reorgs still do happen in eth PoS. The payout tx can make its way into the mempool, and into a future block. Even though one would set a max priority of 0 it can still be picked up by clients who've configured their nodes min priority to be zero.

A builder would be especially prone to this if they're seldomly building blocks. That payout tx won't be invalidated until their nonce is bumped (i.e. until they get another block in and perform another payout) so it can sit in the pool for e.g. hours. waiting for a builder with their configured max priority = 0 to pick it up.

This isn't theoretical, I've seen it happen a few times already.

One workaround is to do a 2-step payout. Step 1 - create a tx that funds a contract; step 2 - have this contract address pay the fee recipient with a block number and/or base fee check (to catch re-org).

However, my suggestion is to change the validation rules to remove the to == fee_recipient check, and instead validate that the total payout to the fee recipient is >= bid.value.

Possible race condition due to memory aliasing of variable

📝 Summary

I ran semgrep (static analysis tool) on the relay and it pointed out one thing that I think is a valid issue:

services/api/service.go
  trailofbits.go.anonymous-race-condition.anonymous-race-condition
     Possible race condition due to memory aliasing of variable `registration`
     Details: https://sg.run/BL22

     458for _, registration := range payload {
     459if registration.Message == nil {
     460respondError(http.StatusBadRequest, "registration without message")
     461return
     462┆ 	}
     463464pubkey := registration.Message.Pubkey.PubkeyHex()
     465regLog := api.log.WithFields(logrus.Fields{
     466"pubkey": pubkey,
     467┆ 	})
        [hid 54 additional lines, adjust with --max-lines-per-finding]

This is a big loop, but this is the important section of the loop:

go func() {
err := api.datastore.SetValidatorRegistration(registration)
if err != nil {
regLog.WithError(err).Error("Failed to set validator registration")
}
}()

This is in a go func and we're referencing the aliased variable, it may not be saving the registration that we think it is.

📚 References

Potential SQL injection in Data API endpoint

I believe I've identified a potential SQL injection vulnerability in the Data API (proposer_payload_delivered). The problem is with the block_hash filter. The value provided in the request is directly copied into the filters structure without any checks.

filters := database.GetPayloadsFilters{
IncludeBidTrace: true,
Limit: 100,
BlockHash: args.Get("block_hash"),
}

Then an argument map is created.

arg := map[string]interface{}{
"limit": filters.Limit,
"slot": filters.Slot,
"cursor": filters.Cursor,
"block_hash": filters.BlockHash,
"block_number": filters.BlockNumber,
}

As long as it's not empty, the value will be in the query string.

if filters.BlockHash != "" {
whereConds = append(whereConds, "block_hash = :block_hash")
}

And then it's executed.

nstmt, err := s.DB.PrepareNamed(fmt.Sprintf("SELECT %s FROM %s %s ORDER BY id DESC LIMIT :limit", fields, TableDeliveredPayload, where))
if err != nil {
return nil, err
}
// fmt.Println("nstmt", nstmt.QueryString)
err = nstmt.Select(&tasks, arg)
return tasks, err

Show build version on website

I think it would be really cool if we showed some type of hash on the relay's website that could be used to verify the relay matches what is found in this repository. For example, the sha256sum of the mev-boost-relay binary. This would only work if the build is reproducible though. Reproducible Builds is a good resource for getting that setup. Thoughts?

feat: move housekeeper to cronjob or make redundant deployment possible

Ideally the housekeeper could run as cronjob.

Currently the housekeeper runs as single instance, and syncs a lot of state from database to Redis. It also keeps some state around to not perform the expensive operations multiple times if not necessary.

The expensive operations are mostly

  1. writing all validators from the beacon node to redis
  2. writing all validator registrations from the database to redis

These are currently a redis command per entry, but could possibly be made batch requests.

Storing historical validator registrations

Currently we overwrite validator registrations but to create historic metrics (such as "how many blocks on mainnet were validated by validators registered with the relay?") that can also be backfilled, we need to add the history of all known fee_recipients instead of overwriting the entries in the validator registrations table

Upcoming feature: allowing block builders to cancel previous submissions

We will allow builders to cancel previous block submissions. This will work by always using the latest submission of a builder, even if it's less valuable.

Currently it works like this:

  • Once a builder submissions is validated and the highest bid for a slot+parent_hash combination, a new getHeader response is generated and saved to Redis, which will be used for the next getHeader responses:
    // Check if there's already a bid
    prevBid, err := api.datastore.GetGetHeaderResponse(payload.Message.Slot, payload.Message.ParentHash.String(), payload.Message.ProposerPubkey.String())
    if err != nil {
    log.WithError(err).Error("error getting previous bid")
    api.RespondError(w, http.StatusInternalServerError, err.Error())
    return
    }
    // Only proceed if this bid is higher than previous one
    isMostProfitableBlock = prevBid == nil || payload.Message.Value.Cmp(&prevBid.Data.Message.Value) == 1
    if !isMostProfitableBlock {
    log.Debug("block submission with same or lower value")
    w.WriteHeader(http.StatusOK)
    return
    }
    // Prepare the response data
    signedBuilderBid, err := BuilderSubmitBlockRequestToSignedBuilderBid(payload, api.blsSk, api.publicKey, api.opts.EthNetDetails.DomainBuilder)
    if err != nil {
    log.WithError(err).Error("could not sign builder bid")
    api.RespondError(w, http.StatusBadRequest, err.Error())
    return
    }
    getHeaderResponse := types.GetHeaderResponse{
    Version: VersionBellatrix,
    Data: signedBuilderBid,
    }
    getPayloadResponse := types.GetPayloadResponse{
    Version: VersionBellatrix,
    Data: payload.ExecutionPayload,
    }
    bidTrace := common.BidTraceV2{
    BidTrace: *payload.Message,
    BlockNumber: payload.ExecutionPayload.BlockNumber,
    NumTx: uint64(len(payload.ExecutionPayload.Transactions)),
    }
    err = api.datastore.SaveBid(&bidTrace, &getHeaderResponse, &getPayloadResponse)
    if err != nil {
    log.WithError(err).Error("could not save bid and block")
    api.RespondError(w, http.StatusBadRequest, err.Error())
    return
    }

Changes needed:

  1. Save the latest bid of every builder
  2. On every valid submission, recalculate the current best bid
  3. If bid value is different from the last cached getHeader response, then update it

Notes:

github.com/btcsuite/btcd/chaincfg/chainhash: ambiguous import: found package github.com/btcsuite/btcd/chaincfg/chainhash in multiple modules:

Documentation of an "ambiguous import error" with go mod tidy:

test imports
        github.com/ethereum/go-ethereum imports
        github.com/ethereum/go-ethereum/core/types imports
        github.com/ethereum/go-ethereum/crypto imports
        github.com/btcsuite/btcd/btcec/v2/ecdsa tested by
        github.com/btcsuite/btcd/btcec/v2/ecdsa.test imports
        github.com/btcsuite/btcd/chaincfg/chainhash: ambiguous import: found package github.com/btcsuite/btcd/chaincfg/chainhash in multiple modules:
        github.com/btcsuite/btcd v0.22.0-beta (/Users/metachris/go/pkg/mod/github.com/btcsuite/[email protected]/chaincfg/chainhash)
        github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 (/Users/metachris/go/pkg/mod/github.com/btcsuite/btcd/chaincfg/[email protected])

See also: [1]

Workaround: Manually import and require the latest github.com/btcsuite/btcd/btcutil (currently v1.1.2), both in go.mod and requiring it in a file (else a subsequent go mod tidy will lead to the error again.

Make the relay free software

  • Announce the plan to open source
  • Choose a date right before or right after the merge
  • #30
  • Clean up the README #72
  • Publish the test coverage report
  • Make sure that the test coverage is reasonable
  • Define the data that a trusted relay must publish
  • Prepare the source code for the audit
  • Choose the auditors
  • Audit the source code
  • Fix the findings
  • Publish a post on the risks of running an untrusted relay
  • Make this repository public
  • Celebrate

Question about running services

I have cloned the repo and ran the docker-compose. But I'm having some questions

1.- Do I need to run a local PRYSM ?
2.- When I Run go run . housekeeper --network sepolia --db postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable

I'm getting the terminal error : zsh: no matches found: postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.