-
Notifications
You must be signed in to change notification settings - Fork 1
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
ePBS specs #1
base: dev
Are you sure you want to change the base?
ePBS specs #1
Conversation
See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for | ||
details on how to handle transitioning gossip topics for this upgrade. | ||
|
||
### The Req/Resp domain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Block by root and by range will propagate full blocks (SignedBeaconBlock
) or blinded blocks SignedBeaconBlockWithBid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good question and I admit I haven't thought much on.
I think by range requires no changes. It's applied to finalized blocks anyway
By root is tricky, we might want to add a signed_blinded_block_by_root
method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure why would signed by root be required for blinded except for those on the tip, but should be pickedup from gossip, anything missed from gossip can't be synced next slot by the time the payload is revealed (assuming that this ePBS scheme has payload reveal before last interval of the slot)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the notion of "full block" dissappears on ePBS. You request blocks and payloads separated, there's very little need to assemble a full block except for validation in which you get the two lists anyway as they perform separate state transitions on the beacon state.
- _[IGNORE]_ The signed builder bid value, `bid.value`, is less than builder's balance in state. | ||
- _[IGNORE]_ The signed builder header timestamp is correct with respect to next slot -- i.e. `header.timestamp == compute_timestamp_at_slot(state, current_slot + 1)`. | ||
- _[IGNORE]_ The signed builder header parent block has matches one of the chain tip in the fork choice store. Builder may submit multiple bids with respect to forks. | ||
- _[REJECT]_ The builder signature, `signed_bid.signature`, is valid with respect to the `bid.pubkey`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
each builder can publish an unbounded number of bids. How can we bound it? I would start by requiring increasing bids amounts to be at least % higher than previous
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am tempted to say we only allow one bid per builder per fork
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 reject bids on same fork
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If ePBS does not allow all features wanted by builders they will bypass ePBS and use side-channels. Re-bidding is key IMO, see current usage of mev-boost https://payload.de/data/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wow pretty interesting visualization, i guess re-bids with at least 10% increase would makes sense similar to tx acceptance
specs/_features/ePBS/validator.md
Outdated
* Set `execution_attestation.slot = slot` where `slot` is the assigned slot. | ||
* Set `execution_attestation.block_hash = block_hash` where `block_hash` is the block hash seen from the block builder reveal at `SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT` | ||
* Set `execution_attestation.validator_index = validator_index` where `validator_index` is the validator chosen to submit. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the payload timeliness attestation. | ||
* Set `signed_execution_attestation = SignedExecution(message=execution_attestation, signature=execution_attestation_signature)`, where `execution_attestation_signature` is obtained from: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PTC should cast a vote for either full or empty, does this data-structure allow empty votes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps empty could be leveraged as zero hashes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You still need to indicate that the payload for specifically header block_hash was not seen so that field must be set
specs/_features/ePBS/validator.md
Outdated
|
||
Next, the validator creates `signed_execution_attestation` | ||
* Set `execution_attestation.slot = slot` where `slot` is the assigned slot. | ||
* Set `execution_attestation.block_hash = block_hash` where `block_hash` is the block hash seen from the block builder reveal at `SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here block_hash refers to the SignedBeaconBlockWithBid
hash tree root which is head at the builder reveal deadline?
|
||
- _[REJECT]_ The block's execution header timestamp is correct with respect to the slot -- i.e. `header.timestamp == compute_timestamp_at_slot(state, block.slot)`. | ||
- _[REJECT]_ The bid pubkey is a valid builder in state. | ||
- _[REJECT]_ The builder has sufficient balances to pay for the bid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would this be validated? won't this require access to execution state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bid payment is happening at consensus, not execution. @potuz should have a consensus spec soon
Co-authored-by: g11tech <develop@g11tech.io>
|
||
For the `value` check. A node must drop a bid if the builder does not have enough balance to pay for the bid plus maintain a self-min balance. I think `REJECT` makes more sense here. A node should not gossip about this because it's no use. | ||
|
||
For the `chain_extension` check. Suppose a chain has multiple tips. Builders maintain the same view. Competitive builders will be building simultaneously on all the tips. One paradigm shift is builder bid cancellation is no longer viable. This differs from the current mev-boost model. A node should `IGNORE` builder bids that are not top-of-the-chain tips. Worrying about DOS, a node can accept multiple bids of the same builder on the same tip, but the value has to be incremental. We need to think about this more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How feasible it is to have a PriceBump
field for bids similar to what we have in transaction pool? Only accept if the new bid for same tip is x%
more than previous one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really sure if it has to be enforced by the protocol or can be made configurable. Configurable might be favourable for some proposers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First superficial pass, many comments.
specs/_features/ePBS/validator.md
Outdated
|
||
#### `get_inclusion_list` | ||
|
||
Given the `payload_id`, `get_payload` returns `GetInclusionListResponse` with the most recent version of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure you mean get_payload
here? Also what do you mean by " the execution payload that has been built since the last FCU call"?
get_inclusion_list
should simply return a current valid inclusion list.
specs/_features/ePBS/validator.md
Outdated
the execution payload that has been built since the corresponding call to `notify_forkchoice_updated` method. | ||
|
||
```python | ||
def get_inclusion_list(self: ExecutionEngine, payload_id: PayloadId) -> GetInclusionListResponse: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type GetInclusionListResponse
needs to be defined
specs/_features/ePBS/validator.md
Outdated
|
||
#### `get_inclusion_list` | ||
|
||
Given the `payload_id`, `get_payload` returns `GetInclusionListResponse` with the most recent version of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a payload_id
here? More generally, we need to be explicit that calls to FCU should not be sending the payload attributes anymore since proposers are not builders. And in the honest builder spec this should be specified. It's important to note that the typical validator should not be triggering block creation on their EL when proposing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, I'll note this for the engine API spec. cc @naviechan
specs/_features/ePBS/validator.md
Outdated
|
||
## Validator assignment | ||
|
||
A validator can get PTC committee assignments for a given epoch using the following helper via `get_ptc_committee_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTC assignments are by slot currently
specs/_features/ePBS/validator.md
Outdated
A validator can get PTC committee assignments for a given epoch using the following helper via `get_ptc_committee_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`. | ||
|
||
A validator can use the following function to see if they are supposed to submit payload attestation message during a slot. | ||
This function can only be run with a `state` of the slot in question. PTC committee selection is only stable within the context of the current and next epoch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be a state, slot
pair and allow this function to be called with any state.
specs/_features/ePBS/validator.md
Outdated
|
||
#### Aggregation selection | ||
|
||
A validator is selected to aggregate based upon the return value of `is_aggregator()`. [New in MaxEB]. Taken from [PR](https://github.com/michaelneuder/consensus-specs/pull/3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A validator is selected to aggregate based upon the return value of `is_aggregator()`. [New in MaxEB]. Taken from [PR](https://github.com/michaelneuder/consensus-specs/pull/3) | |
A validator is selected to aggregate based upon the return value of `is_aggregator()`. [Modified in ePBS]. Taken from [PR](https://github.com/michaelneuder/consensus-specs/pull/3) |
specs/_features/ePBS/validator.md
Outdated
|
||
### Payload timeliness attestation | ||
|
||
Some validators are selected to submit payload timeliness attestation. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_execution_committee(state, slot)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this function get_execution_committee
?
specs/_features/ePBS/validator.md
Outdated
#### Constructing payload attestation | ||
|
||
##### Prepare payload attestation message | ||
If a validator is in the payload attestation committee (i.e. `is_assigned_to_payload_committee()` above returns True), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a validator is in the payload attestation committee (i.e. `is_assigned_to_payload_committee()` above returns True), | |
If a validator is in the payload attestation committee (i.e. `is_assigned_to_payload_committee()` below returns True), |
according to the logic in `get_payload_attestation_message` at `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` interval. | ||
|
||
```python | ||
def is_assigned_to_payload_committee(state: BeaconState, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need a helper for validator_index in get_ptc(state, slot)
?
specs/_features/ePBS/validator.md
Outdated
|
||
```python | ||
def get_payload_attestation_message_signature(state: BeaconState, attestation: PayloadAttestationMessage, privkey: int) -> BLSSignature: | ||
domain = get_domain(state, DOMAIN_PAYLOAD_TIMELINESS_COMMITTEE, compute_epoch_at_slot(attestation.slot)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in https://github.com/potuz/consensus-specs/blob/epbs/specs/_features/epbs/beacon-chain.md#domain-types there's DOMAIN_PTC_ATTESTER
specs/_features/ePBS/validator.md
Outdated
|
||
To obtain an inclusion list, a block proposer building a block on top of a `state` must take the following actions: | ||
|
||
1. Retrieve inclusion list from execution layer by calling `get_execution_inclusion_list`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think proposer should also check if previous slot's payload was empty or not. If it was, the proposer shouldn't send a new inclusion list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I think this info has been added into the fork choice section by Potuz in a code snippet so not sure if we need it here.
A builder is an entity that participates in the block building of the Ethereum proof-of-stake protocol. This is an optional role for users in which they can post ETH as collateral and build blocks to see financial returns in exchange for building the protocol. | ||
In this design, builder is an extension of validator using a different withdrawal prefix `0x0b`. The protocol onboards builder by upgrading validators into builders if minimal staked balance is reached. | ||
|
||
## Becoming a builder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A builder must keep a minimum balance of BUILDER_MIN_BALANCE
in order to continue performing building duties.
|
||
|
||
```python | ||
def prepare_execution_payload(state: BeaconState, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The payload attributes should contain the inclusion list summary at the very least, these are the txs that the payload will need to satisfy. The EL should have already cached the transactions when the builder notified of the IL before.
specs/_features/ePBS/builder.md
Outdated
|
||
#### Broadcast execution payload header envelope | ||
|
||
Finally, the validator broadcasts `signed_execution_payload_header_envelope` to the global `execution_payload_header_envelope` pubsub topic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a global topic? only the proposer is interested in receiving this information, can this be made a smaller subset of the P2P network?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. We can make this like the sync_committee_message
subnet and only subscribe if you are proposing with an epoch look ahead
specs/_features/ePBS/builder.md
Outdated
|
||
#### Determine if it is safe to reveal | ||
|
||
The builder will call `get_head_block`. If the head block contains the payload header, then it is safe to reveal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what the right wording is here. In principle the builder should reveal if he has seen any beacon block which is valid and included his payload. Head or not. The problem is that if the call to get_head_block
returns another block, it will not have his header. If later the attestations switch back to the intended head, the builder will lose his bid.
specs/_features/ePBS/builder.md
Outdated
1. Build `ExecutionPayloadEnvelope` | ||
- Set `payload` to the saved `execution_payload` from constructing the header. | ||
- Set `builder_index` to the index of the builder. | ||
- Set `beacon_block_root` to the head block root. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not necessarily the head block root, but rather the block root of the block that included his bid
|
||
- Builder to proposer payment is unconditional after processing the signed execution payload header and the block remains head and canonical throughout. The builder will not lose the bid if the block is reorged. | ||
|
||
### Should the builder worry about same slot unbundling? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need a section about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the current specs, most have design rational sections rather than a separate design doc. I felt like if we were going to production this and get accepted by the upstream authors, a design rational section under respected domain may be the way to go. I don't feel strongly about this though
|
||
### Should the builder worry about same slot unbundling? | ||
|
||
- There are cases to consider for same slot unbundling. the common case is proposer equivocate and broadcast the equivocated block to the network after builder has revealed the payload. In this case, the next slot proposer will also have to collude and build on the equivocated block. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not only the next slot proposer but also a vast majority of the committee since attesters would have voted for the previous head.
|
||
- There are cases to consider for same slot unbundling. the common case is proposer equivocate and broadcast the equivocated block to the network after builder has revealed the payload. In this case, the next slot proposer will also have to collude and build on the equivocated block. | ||
|
||
### What if builder sees proposer equivocate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this section specified?
*[New in ePBS]* | ||
|
||
```python | ||
class SignedNetworkBlock(Container): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For syncing. It's a network wrapper. I'll add a design rationale for this today
The following validations MUST pass before forwarding the `signed_execution_payload_envelope` on the network, assuming the alias `payload_envelope = signed_execution_payload_envelope.message`, `payload = payload_envelope.message.payload`: | ||
|
||
- _[IGNORE]_ The envelope's block root `payload_envelope.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue payload for processing once the block is retrieved). | ||
- _[IGNORE]_ The hash tree root of the `payload` in `payload_envelope` matches the hash tree root of a payload header received previously on the `beacon_block` topic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be a reject
|
||
- _[IGNORE]_ The envelope's block root `payload_envelope.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue payload for processing once the block is retrieved). | ||
- _[IGNORE]_ The hash tree root of the `payload` in `payload_envelope` matches the hash tree root of a payload header received previously on the `beacon_block` topic. | ||
- _[IGNORE]_ The builder index of the `payload_envelope` matches the payload header received previously on the `beacon_block` topic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be a reject
The following validations MUST pass before forwarding the `payload_attestation_message` on the network, assuming the alias `data = payload_attestation_message.data`: | ||
|
||
- _[IGNORE]_ `data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. `data.slot` + `ATTESTATION_PROPAGATION_SLOT_RANGE` >= `current_slot` >= `data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). | ||
- _[IGNORE]_ The attestation's `data.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- _[IGNORE]_ The attestation's `data.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). | |
- _[IGNORE]_ The attestation's `data.beacon_block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). |
|
||
- _[IGNORE]_ `data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. `data.slot` + `ATTESTATION_PROPAGATION_SLOT_RANGE` >= `current_slot` >= `data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). | ||
- _[IGNORE]_ The attestation's `data.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). | ||
- _[REJECT]_ The validator index is within the payload committee in `get_ptc(state, slot)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- _[REJECT]_ The validator index is within the payload committee in `get_ptc(state, slot)`. | |
- _[REJECT]_ The validator index is within the payload committee in `get_ptc(state, data.slot)`. |
See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for | ||
details on how to handle transitioning gossip topics for this upgrade. | ||
|
||
### The Req/Resp domain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the notion of "full block" dissappears on ePBS. You request blocks and payloads separated, there's very little need to assemble a full block except for validation in which you get the two lists anyway as they perform separate state transitions on the beacon state.
The following validations MUST pass before forwarding the `inclusion_list` on the network, assuming the alias `signed_summary = inclusion_list.summary`, `summary = signed_summary.message`: | ||
|
||
- _[REJECT]_ The inclusion list transactions `inclusion_list.transactions` length is within upperbound `MAX_TRANSACTIONS_PER_INCLUSION_LIST`. | ||
- _[REJECT]_ The inclusion list summery has the same length of transactions `len(summary.summary) == len(inclusion_list.transactions)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- _[REJECT]_ The inclusion list summery has the same length of transactions `len(summary.summary) == len(inclusion_list.transactions)`. | |
- _[REJECT]_ The inclusion list summary has the same length of transactions `len(summary.summary) == len(inclusion_list.transactions)`. |
Co-authored-by: Potuz <potuz@potuz.net>
No description provided.