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 5 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
38 changes: 29 additions & 9 deletions specs/electra/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,38 @@ 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. There can only be one instance of each request type per execution requests.
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:
requests = {
DEPOSIT_REQUEST_TYPE: {
"value": None,
"field": "deposits",
"type": List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
},
WITHDRAWAL_REQUEST_TYPE: {
"value": None,
"field": "withdrawals",
"type": List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
},
CONSOLIDATION_REQUEST_TYPE: {
"value": None,
"field": "consolidations",
"type": List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
},
}

execution_requests = ExecutionRequests()
for request in execution_requests_list:
request_type, request_data = request[0:1], request[1:]
assert request_type in requests, "unexpected request type"
jtraglia marked this conversation as resolved.
Show resolved Hide resolved
assert len(request_data) != 0, "empty request data"
assert requests[request_type]["value"] is None, "duplicate request"
requests[request_type]["value"] = ssz_deserialize(requests[request_type]["type"], request_data)
setattr(execution_requests, requests[request_type]["field"], requests[request_type]["value"])
return execution_requests
```

## Attesting
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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__allow_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])
spec.get_execution_requests(serialized_execution_requests)


@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 as e:
assert "empty request data" in str(e)


@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 as e:
assert "duplicate request" in str(e)


@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 as e:
assert "unexpected request type" in str(e)