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

Exclude empty requests in requests list #3998

Merged
merged 17 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions specs/electra/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Constants](#constants)
- [Misc](#misc)
- [Withdrawal prefixes](#withdrawal-prefixes)
- [Execution layer triggered requests](#execution-layer-triggered-requests)
- [Preset](#preset)
- [Gwei values](#gwei-values)
- [Rewards and penalties](#rewards-and-penalties)
Expand Down Expand Up @@ -137,6 +138,14 @@ The following values are (non-configurable) constants used throughout the specif
| - | - |
| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` |

### Execution layer triggered requests

| Name | Value |
| - | - |
| `DEPOSIT_REQUEST_TYPE` | `Bytes1('0x00')` |
| `WITHDRAWAL_REQUEST_TYPE` | `Bytes1('0x01')` |
| `CONSOLIDATION_REQUEST_TYPE` | `Bytes1('0x02')` |
Comment on lines +145 to +147
Copy link
Contributor

Choose a reason for hiding this comment

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

For CL specs, they should be named with REQUEST_TYPE_*, but I figured out they are EIP constants... 😭

@lightclient is it too late to change?


## Preset

### Gwei values
Expand Down Expand Up @@ -1146,11 +1155,17 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:

```python
def get_execution_requests_list(execution_requests: ExecutionRequests) -> Sequence[bytes]:
deposit_bytes = ssz_serialize(execution_requests.deposits)
withdrawal_bytes = ssz_serialize(execution_requests.withdrawals)
consolidation_bytes = ssz_serialize(execution_requests.consolidations)

return [deposit_bytes, withdrawal_bytes, consolidation_bytes]
requests = [
(DEPOSIT_REQUEST_TYPE, execution_requests.deposits),
(WITHDRAWAL_REQUEST_TYPE, execution_requests.withdrawals),
(CONSOLIDATION_REQUEST_TYPE, execution_requests.consolidations),
]

return [
request_type + ssz_serialize(request_data)
for request_type, request_data in requests
if len(request_data) != 0
]
```

##### Modified `process_execution_payload`
Expand Down
55 changes: 46 additions & 9 deletions specs/electra/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,55 @@ def prepare_execution_payload(state: BeaconState,

*[New in Electra]*

1. The execution payload is obtained from the execution engine as defined above using `payload_id`. The response also includes a `execution_requests` entry containing a list of bytes. Each element on the list corresponds to one SSZ list of requests as defined
in [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). The index of each element in the array determines the type of request.
1. The execution payload is obtained from the execution engine as defined above using `payload_id`. The response also includes a `execution_requests` entry containing a list of bytes. Each element on the list corresponds to one SSZ list of requests as defined in [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). The first byte of each request is used to determine the request type. Requests must be ordered by request type in ascending order. As a result, there can only be at most one instance of each request type.
2. Set `block.body.execution_requests = get_execution_requests(execution_requests)`, where:

```python
def get_execution_requests(execution_requests: Sequence[bytes]) -> ExecutionRequests:
deposits = ssz_deserialize(List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD], execution_requests[0])
withdrawals = ssz_deserialize(List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD], execution_requests[1])
consolidations = ssz_deserialize(List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
execution_requests[2])

return ExecutionRequests(deposits, withdrawals, consolidations)
def get_execution_requests(execution_requests_list: Sequence[bytes]) -> ExecutionRequests:
deposits = []
withdrawals = []
consolidations = []

request_types = [
DEPOSIT_REQUEST_TYPE,
WITHDRAWAL_REQUEST_TYPE,
CONSOLIDATION_REQUEST_TYPE,
]

prev_request_type = None
for request in execution_requests_list:
request_type, request_data = request[0:1], request[1:]

# Check that the request type is valid
assert request_type in request_types
# Check that the request data is not empty
assert len(request_data) != 0
# Check that requests are in strictly ascending order
# Each successive type must be greater than the last with no duplicates
assert prev_request_type is None or prev_request_type < request_type
prev_request_type = request_type

if request_type == DEPOSIT_REQUEST_TYPE:
deposits = ssz_deserialize(
List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
request_data
)
elif request_type == WITHDRAWAL_REQUEST_TYPE:
withdrawals = ssz_deserialize(
List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
request_data
)
elif request_type == CONSOLIDATION_REQUEST_TYPE:
consolidations = ssz_deserialize(
List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
request_data
)

return ExecutionRequests(
deposits=deposits,
withdrawals=withdrawals,
consolidations=consolidations,
)
```

## Attesting
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from eth2spec.test.context import (
single_phase,
spec_test,
with_electra_and_later,
)


@with_electra_and_later
@spec_test
@single_phase
def test_requests_serialization_round_trip__empty(spec):
execution_requests = spec.ExecutionRequests()
serialized_execution_requests = spec.get_execution_requests_list(execution_requests)
deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests)
assert deserialized_execution_requests == execution_requests


@with_electra_and_later
@spec_test
@single_phase
def test_requests_serialization_round_trip__one_request(spec):
execution_requests = spec.ExecutionRequests(
deposits=[spec.DepositRequest()],
)
serialized_execution_requests = spec.get_execution_requests_list(execution_requests)
deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests)
assert deserialized_execution_requests == execution_requests


@with_electra_and_later
@spec_test
@single_phase
def test_requests_serialization_round_trip__multiple_requests(spec):
execution_requests = spec.ExecutionRequests(
deposits=[spec.DepositRequest()],
withdrawals=[spec.WithdrawalRequest()],
consolidations=[spec.ConsolidationRequest()],
)
serialized_execution_requests = spec.get_execution_requests_list(execution_requests)
deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests)
assert deserialized_execution_requests == execution_requests


@with_electra_and_later
@spec_test
@single_phase
def test_requests_serialization_round_trip__one_request_with_real_data(spec):
execution_requests = spec.ExecutionRequests(
deposits=[
spec.DepositRequest(
pubkey=spec.BLSPubkey(48 * "aa"),
withdrawal_credentials=spec.Bytes32(32 * "bb"),
amount=spec.Gwei(11111111),
signature=spec.BLSSignature(96 * "cc"),
index=spec.uint64(22222222),
),
]
)
serialized_execution_requests = spec.get_execution_requests_list(execution_requests)
deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests)
assert deserialized_execution_requests == execution_requests


@with_electra_and_later
@spec_test
@single_phase
def test_requests_deserialize__reject_duplicate_request(spec):
serialized_withdrawal = 76 * b"\x0a"
serialized_execution_requests = [
spec.WITHDRAWAL_REQUEST_TYPE + serialized_withdrawal,
spec.WITHDRAWAL_REQUEST_TYPE + serialized_withdrawal,
]
try:
spec.get_execution_requests(serialized_execution_requests)
assert False, "expected exception"
except Exception:
pass


@with_electra_and_later
@spec_test
@single_phase
def test_requests_deserialize__reject_out_of_order_requests(spec):
serialized_execution_requests = [
spec.WITHDRAWAL_REQUEST_TYPE + 76 * b"\x0a",
spec.DEPOSIT_REQUEST_TYPE + 192 * b"\x0b",
]
assert int(serialized_execution_requests[0][0]) > int(serialized_execution_requests[1][0])
try:
spec.get_execution_requests(serialized_execution_requests)
assert False, "expected exception"
except Exception:
pass


@with_electra_and_later
@spec_test
@single_phase
def test_requests_deserialize__reject_empty_request(spec):
serialized_execution_requests = [b"\x01"]
try:
spec.get_execution_requests(serialized_execution_requests)
assert False, "expected exception"
except Exception:
pass


@with_electra_and_later
@spec_test
@single_phase
def test_requests_deserialize__reject_unexpected_request_type(spec):
serialized_execution_requests = [
b"\x03\xff\xff\xff",
]
try:
spec.get_execution_requests(serialized_execution_requests)
assert False, "expected exception"
except Exception:
pass
43 changes: 36 additions & 7 deletions tests/core/pyspec/eth2spec/test/helpers/execution_payload.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from eth_hash.auto import keccak
from hashlib import sha256
from trie import HexaryTrie
from rlp import encode
from rlp.sedes import big_endian_int, Binary, List
Expand All @@ -7,7 +8,12 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.debug.random_value import get_random_bytes_list
from eth2spec.test.helpers.withdrawals import get_expected_withdrawals
from eth2spec.test.helpers.forks import is_post_capella, is_post_deneb, is_post_eip7732
from eth2spec.test.helpers.forks import (
is_post_capella,
is_post_deneb,
is_post_electra,
is_post_eip7732,
)


def get_execution_payload_header(spec, execution_payload):
Expand Down Expand Up @@ -59,13 +65,23 @@ def compute_trie_root_from_indexed_data(data):
return t.root_hash


# https://eips.ethereum.org/EIPS/eip-7685
def compute_requests_hash(block_requests):
m = sha256()
for r in block_requests:
if len(r) > 1:
m.update(sha256(r))
return m.digest()


# https://eips.ethereum.org/EIPS/eip-4895
# https://eips.ethereum.org/EIPS/eip-4844
def compute_el_header_block_hash(spec,
payload_header,
transactions_trie_root,
withdrawals_trie_root=None,
parent_beacon_block_root=None):
parent_beacon_block_root=None,
requests_root=None):
"""
Computes the RLP execution block hash described by an `ExecutionPayloadHeader`.
"""
Expand Down Expand Up @@ -116,6 +132,9 @@ def compute_el_header_block_hash(spec,
execution_payload_header_rlp.append((big_endian_int, payload_header.excess_blob_gas))
# parent_beacon_root
execution_payload_header_rlp.append((Binary(32, 32), parent_beacon_block_root))
if is_post_electra(spec):
# requests_root
execution_payload_header_rlp.append((Binary(32, 32), requests_root))

sedes = List([schema for schema, _ in execution_payload_header_rlp])
values = [value for _, value in execution_payload_header_rlp]
Expand Down Expand Up @@ -191,7 +210,7 @@ def get_consolidation_request_rlp_bytes(consolidation_request):
return b"\x02" + encode(values, sedes)


def compute_el_block_hash_with_parent_root(spec, payload, parent_beacon_block_root):
def compute_el_block_hash_with_new_fields(spec, payload, parent_beacon_block_root, requests_root):
if payload == spec.ExecutionPayload():
return spec.Hash32()

Expand All @@ -213,25 +232,35 @@ def compute_el_block_hash_with_parent_root(spec, payload, parent_beacon_block_ro
transactions_trie_root,
withdrawals_trie_root,
parent_beacon_block_root,
requests_root,
)


def compute_el_block_hash(spec, payload, pre_state):
parent_beacon_block_root = None
requests_root = None

if is_post_deneb(spec):
previous_block_header = pre_state.latest_block_header.copy()
if previous_block_header.state_root == spec.Root():
previous_block_header.state_root = pre_state.hash_tree_root()
parent_beacon_block_root = previous_block_header.hash_tree_root()
if is_post_electra(spec):
requests_root = compute_requests_hash([])

return compute_el_block_hash_with_parent_root(
spec, payload, parent_beacon_block_root)
return compute_el_block_hash_with_new_fields(
spec, payload, parent_beacon_block_root, requests_root)


def compute_el_block_hash_for_block(spec, block):
return compute_el_block_hash_with_parent_root(
spec, block.body.execution_payload, block.parent_root)
requests_root = None

if is_post_electra(spec):
requests_list = spec.get_execution_requests_list(block.body.execution_requests)
requests_root = compute_requests_hash(requests_list)

return compute_el_block_hash_with_new_fields(
spec, block.body.execution_payload, block.parent_root, requests_root)


def build_empty_post_eip7732_execution_payload_header(spec, state):
Expand Down
5 changes: 5 additions & 0 deletions tests/core/pyspec/eth2spec/test/helpers/genesis.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from hashlib import sha256
from eth2spec.test.helpers.constants import (
PHASE0,
PREVIOUS_FORK_OF,
Expand Down Expand Up @@ -66,18 +67,22 @@ def get_sample_genesis_execution_payload_header(spec,
transactions_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
withdrawals_trie_root = None
parent_beacon_block_root = None
requests_root = None

if is_post_capella(spec):
withdrawals_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
if is_post_deneb(spec):
parent_beacon_block_root = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")
if is_post_electra(spec):
requests_root = sha256(b"").digest()

payload_header.block_hash = compute_el_header_block_hash(
spec,
payload_header,
transactions_trie_root,
withdrawals_trie_root,
parent_beacon_block_root,
requests_root,
)
return payload_header

Expand Down