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

ePBS specs #1

Open
wants to merge 23 commits into
base: dev
Choose a base branch
from
Open

ePBS specs #1

wants to merge 23 commits into from

Conversation

terencechain
Copy link
Owner

@terencechain terencechain commented Aug 1, 2023

No description provided.

specs/_features/ePBS/p2p-interface.md Outdated Show resolved Hide resolved
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
Copy link

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

Copy link
Owner Author

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

Copy link

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)

Copy link

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`.
Copy link

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

Copy link
Owner Author

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

Copy link

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

Copy link

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/

Copy link

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/p2p-interface.md Outdated Show resolved Hide resolved
* 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:
Copy link

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?

Copy link
Owner Author

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?

Copy link

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


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`
Copy link

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.
Copy link

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?

Copy link
Owner Author

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


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.

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.

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.

Copy link

@potuz potuz left a 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.


#### `get_inclusion_list`

Given the `payload_id`, `get_payload` returns `GetInclusionListResponse` with the most recent version of
Copy link

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.

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:
Copy link

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


#### `get_inclusion_list`

Given the `payload_id`, `get_payload` returns `GetInclusionListResponse` with the most recent version of
Copy link

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.

Copy link
Owner Author

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


## 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`.
Copy link

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

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.
Copy link

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.


#### 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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)


### 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)`.
Copy link

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?

#### 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),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,
Copy link

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)?


```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))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


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`.

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.

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
Copy link

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.

specs/_features/ePBS/builder.md Outdated Show resolved Hide resolved


```python
def prepare_execution_payload(state: BeaconState,
Copy link

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.


#### Broadcast execution payload header envelope

Finally, the validator broadcasts `signed_execution_payload_header_envelope` to the global `execution_payload_header_envelope` pubsub topic.
Copy link

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?

Copy link
Owner Author

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


#### 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.
Copy link

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.

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.
Copy link

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

specs/_features/ePBS/builder.md Outdated Show resolved Hide resolved

- 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?
Copy link

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?

Copy link
Owner Author

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.
Copy link

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?
Copy link

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):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?

Copy link
Owner Author

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.
Copy link

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.
Copy link

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).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- _[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)`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- _[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
Copy link

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)`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- _[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)`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants