Repository navigation
Stream Gloas execution payload bids over a websocket - #682
Open
JasonVranek wants to merge 2 commits into
Open
JasonVranek wants to merge 2 commits into
JasonVranek wants to merge 2 commits into
Conversation
Moves header_stream's schedule (handshake, window, start, end, interval) into StreamSchedule, and its timed polling loop into StreamSchedule::run, which calls a ProposerStream for each tick. build_frame takes any ssz bid. The getExecutionPayloadBid request auth check (the auth's slot, then its BLS signature) moves into verify_request_auth. No behaviour changes: the terminating check moves from send_bid to the top of the loop, where it ran before every send_bid call anyway.
GET /eth/v1/builder/execution_payload_bid_stream/{slot}/{parent_hash}/{parent_root}/{proposer_pubkey}
upgrades to a websocket and streams the proposer's Gloas bid as it
improves, the way header_stream does for get_header. Each update is one
binary frame, [kind = 1][fork = gloas][ssz SignedExecutionPayloadBid].
The stream ends with a close frame: a client cannot tell a dropped
socket from a failed stream.
A GET carries no body, so the SignedBuilderRequestAuth arrives in the
X-Request-Auth header as standard padded base64 of its ssz. Every check
runs before the upgrade, so a refused request is a plain HTTP status and
no socket opens. The slot must be a Gloas slot by the chain's schedule,
and the path pubkey must be the slot's registered proposer. The auth
header is length-capped and decoded only after these cheap checks, and
its BLS signature is verified last.
The stream reuses header_stream's config and admit_header_stream. An
ApiProvider decides admission from the same inputs for both streams and
cannot tell them apart, so the two share one admission policy. The
route joins the condensed ProposerApi set.
Each tick passes the bid the stream last sent, as its block hash and
execution payment. When the top bid still matches it, the auctioneer
answers BidUnchanged without signing, so a stream that polls every few
ms costs one signature per new bid. The POST endpoint passes nothing and
is unchanged. Each frame carries the auctioneer's signed bid as it is.
The stream ends early when the auction has moved past the request: a
later slot, another proposer, or a payload already being delivered.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces #670, which was stacked on #635 and closed when #635 merged and its branch was deleted. This is the same change rebased on main, with the auctioneer part adapted to #635's prebuilt, auctioneer-signed bids. Commit-Boost's ePBS bid stream (commit-boost-client#509, released in v0.12.0-rc1) is a client of this endpoint.
What this does
Before Gloas, a proposer client can open
header_streamand receive the relay's improving bid until its deadline, instead of making oneget_headerrequest. Gloas replacesget_headerwithgetExecutionPayloadBid, a POST, and has no stream. This PR adds the Gloas equivalent:The request upgrades to a websocket. Each time the top bid changes, the relay sends one binary frame,
[kind = 1][fork = 7 (gloas)][ssz SignedExecutionPayloadBid], the same layout as the header stream. When the stream ends, the relay sends a close frame. Without it, a client cannot tell a stream that ended from one that failed.A GET has no body, so the client sends its
SignedBuilderRequestAuthin anX-Request-Authheader, as standard padded base64 of the ssz.Admission
Every check runs before the upgrade, so a refused request gets an HTTP status and no socket opens. The cheap checks run first, and the BLS verify runs last:
Eth-Consensus-Versionheader is not trusted for this.validate_header_requestchecks asheader_stream: the slot being bid for, a registered proposer, timing.admit_header_streamdecides whether this client may stream.X-Request-Authis length-capped at the base64 size of the largest valid auth (5612 characters), then decoded.Implementation notes
header_stream's schedule and polling loop intoStreamSchedule, without behaviour changes, so both streams run the same loop. The second commit adds the bid stream on top.header_streamconfig andadmit_header_streamon purpose. AnApiProvidersees the same inputs for both streams and cannot tell PBS admission from Gloas admission, so the two streams share one admission policy.ExecutionPayloadBidStreamjoins the condensedProposerApiroute set, so a relay servingProposerApiserves the stream.admit_header_streamstill gates every connection.BidUnchangedwithout signing, so a stream that polls every few ms costs one signature per new bid. The POST endpoint passes nothing, and its behaviour does not change.header_streamdoes not send a close frame yet. That is left for a separate change.Tested
On a Kurtosis Gloas devnet (mainnet preset, 12 s slots, a Lodestar beacon node), running this change with #681, and Commit-Boost v0.12.0-rc1 with two stream relay entries: this relay and a Lodestar builder (nflaig/lodestar#26) serving the same endpoint. The run predates the rebase over #680, which touches only
crates/operator.400 proposer not registeredbefore the proposer had registered.Without #681 the stream worked the same way, and helix's streamed bids won 15 slots, but helix could not publish to the Lodestar beacon node, so no payload was revealed. #681 has the details.
Known limitation
The stream sends until the client's deadline,
Date-Milliseconds + X-Timeout-Ms.getExecutionPayloadBidasks the builder to respond by that time, and the client discards anything later, so a frame sent in the last one-way network trip before the deadline arrives too late. If the one-way latency is longer than the streaming window (300 ms by default), no frame arrives in time at all. Ending the stream one return trip early would fix this, but the relay would first have to measure that trip, for example with a ping over the open stream.header_streamuses the same schedule and has the same gap, so both are left for a separate change.