From 1ade07d705d3dd2ad8168923f96d1188d5bcd996 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 9 Jun 2021 16:31:20 -0600 Subject: [PATCH 01/22] fix mismatched proposer test names --- .../test_process_proposer_slashing.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py index b9becc1fd..48bc5ba3d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py @@ -35,6 +35,14 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True) @with_all_phases @spec_state_test def test_success(spec, state): + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + + yield from run_proposer_slashing_processing(spec, state, proposer_slashing) + + +@with_all_phases +@spec_state_test +def test_success_slashed_and_proposer_index_the_same(spec, state): # Get proposer for next slot block = build_empty_block_for_next_slot(spec, state) proposer_index = block.proposer_index @@ -47,14 +55,6 @@ def test_success(spec, state): yield from run_proposer_slashing_processing(spec, state, proposer_slashing) -@with_all_phases -@spec_state_test -def test_success_slashed_and_proposer_index_the_same(spec, state): - proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) - - yield from run_proposer_slashing_processing(spec, state, proposer_slashing) - - @with_all_phases @spec_state_test @always_bls From a5762cbad8502945d762cffaaa5a927dec656c43 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 9 Jun 2021 16:56:32 -0600 Subject: [PATCH 02/22] add attestesr slashing for proposer, and some future slashing tests woo --- .../test/helpers/proposer_slashings.py | 5 ++- .../test_process_attester_slashing.py | 39 ++++++++++++++++++- .../test_process_proposer_slashing.py | 8 ++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index a783d2517..ac0a1cce2 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -73,12 +73,13 @@ def check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block= def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32, - slashed_index=None, signed_1=False, signed_2=False): + slashed_index=None, slot=None, signed_1=False, signed_2=False): if slashed_index is None: current_epoch = spec.get_current_epoch(state) slashed_index = spec.get_active_validator_indices(state, current_epoch)[-1] privkey = pubkey_to_privkey[state.validators[slashed_index].pubkey] - slot = state.slot + if slot is None: + slot = state.slot header_1 = spec.BeaconBlockHeader( slot=slot, diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 940bc47fb..b620a7342 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -6,8 +6,10 @@ from eth2spec.test.context import ( low_balances, misc_balances, ) from eth2spec.test.helpers.attestations import sign_indexed_attestation -from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \ - get_indexed_attestation_participants, get_attestation_2_data, get_attestation_1_data +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing, get_valid_attester_slashing_by_indices, + get_indexed_attestation_participants, get_attestation_2_data, get_attestation_1_data, +) from eth2spec.test.helpers.proposer_slashings import get_min_slashing_penalty_quotient from eth2spec.test.helpers.state import ( get_balance, @@ -126,6 +128,39 @@ def test_success_already_exited_recent(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing) +@with_all_phases +@spec_state_test +@always_bls +def test_success_proposer_index_slashed(spec, state): + # Transition past genesis slot because generally doesn't have a proposer + next_epoch_via_block(spec, state) + + proposer_index = spec.get_beacon_proposer_index(state) + attester_slashing = get_valid_attester_slashing_by_indices( + spec, state, + [proposer_index], + signed_1=True, signed_2=True, + ) + + yield from run_attester_slashing_processing(spec, state, attester_slashing) + + +@with_all_phases +@spec_state_test +def test_success_attestation_from_future(spec, state): + # Transition state to future to enable generation of a "future" attestation + future_state = state.copy() + next_epoch_via_block(spec, future_state) + # Generate slashing using the future state + attester_slashing = get_valid_attester_slashing( + spec, future_state, + slot=state.slot + 5, # Slot is in the future wrt `state` + signed_1=True, signed_2=True + ) + + yield from run_attester_slashing_processing(spec, state, attester_slashing) + + @with_all_phases @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py index b9becc1fd..3f01bc841 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py @@ -55,6 +55,14 @@ def test_success_slashed_and_proposer_index_the_same(spec, state): yield from run_proposer_slashing_processing(spec, state, proposer_slashing) +@with_all_phases +@spec_state_test +def test_success_block_header_from_future(spec, state): + proposer_slashing = get_valid_proposer_slashing(spec, state, slot=state.slot + 5, signed_1=True, signed_2=True) + + yield from run_proposer_slashing_processing(spec, state, proposer_slashing) + + @with_all_phases @spec_state_test @always_bls From 95775e1b904b1f97d19ec69669bd6ddc1114aef4 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 11 Jun 2021 16:05:19 +0600 Subject: [PATCH 03/22] Add randao to execution payload --- specs/merge/beacon-chain.md | 10 +++- specs/merge/validator.md | 25 ++++++--- .../pyspec/eth2spec/test/helpers/block.py | 11 +++- .../test/helpers/execution_payload.py | 7 +-- .../test_process_execution_payload.py | 51 +++++++++---------- 5 files changed, 66 insertions(+), 38 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 697bd0c96..c19a406f0 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -105,6 +105,7 @@ class ExecutionPayload(Container): timestamp: uint64 receipt_root: Bytes32 logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + randao: Bytes32 # 'difficulty' in the yellow paper transactions: List[OpaqueTransaction, MAX_EXECUTION_TRANSACTIONS] ``` @@ -126,6 +127,7 @@ class ExecutionPayloadHeader(Container): timestamp: uint64 receipt_root: Bytes32 logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + randao: Bytes32 transactions_root: Root ``` @@ -198,7 +200,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_operations(state, block.body) # Pre-merge, skip execution payload processing if is_execution_enabled(state, block): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + # [New in Merge] + randao_mix = get_randao_mix(state, get_current_epoch(state)) + process_execution_payload(state, block.body.execution_payload, randao_mix, EXECUTION_ENGINE) ``` #### Execution payload processing @@ -208,6 +212,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ```python def process_execution_payload(state: BeaconState, execution_payload: ExecutionPayload, + randao_mix: Bytes32, execution_engine: ExecutionEngine) -> None: """ Note: This function is designed to be able to be run in parallel with the other `process_block` sub-functions @@ -215,6 +220,7 @@ def process_execution_payload(state: BeaconState, if is_transition_completed(state): assert execution_payload.parent_hash == state.latest_execution_payload_header.block_hash assert execution_payload.number == state.latest_execution_payload_header.number + 1 + assert execution_payload.randao == randao_mix assert execution_payload.timestamp == compute_time_at_slot(state, state.slot) @@ -231,6 +237,7 @@ def process_execution_payload(state: BeaconState, timestamp=execution_payload.timestamp, receipt_root=execution_payload.receipt_root, logs_bloom=execution_payload.logs_bloom, + randao=execution_payload.randao, transactions_root=hash_tree_root(execution_payload.transactions), ) ``` @@ -289,6 +296,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, timestamp=eth1_timestamp, receipt_root=Bytes32(), logs_bloom=ByteVector[BYTES_PER_LOGS_BLOOM](), + randao=eth1_block_hash, transactions_root=Root(), ) diff --git a/specs/merge/validator.md b/specs/merge/validator.md index c5a7a4c78..6b86aae75 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -49,7 +49,7 @@ The body of this function is implementation dependent. The Consensus API may be used to implement this with an external execution engine. ```python -def assemble_block(self: ExecutionEngine, block_hash: Hash32, timestamp: uint64) -> ExecutionPayload: +def assemble_block(self: ExecutionEngine, block_hash: Hash32, timestamp: uint64, randao: Bytes32) -> ExecutionPayload: ... ``` @@ -70,8 +70,23 @@ Let `get_pow_chain_head() -> PowBlock` be the function that returns the head of * Set `block.body.execution_payload = get_execution_payload(state, transition_store, execution_engine)` where: ```python +def compute_randao_mix(state: BeaconState, randao_reveal: BLSSignature) -> Bytes32: + epoch = get_current_epoch(state) + return xor(get_randao_mix(state, epoch), hash(randao_reveal)) + + +def produce_execution_payload(state: BeaconState, + parent_hash: Hash32, + randao_reveal: BLSSignature, + execution_engine: ExecutionEngine) -> ExecutionPayload: + timestamp = compute_time_at_slot(state, state.slot) + randao = compute_randao_mix(state, randao_reveal) + return execution_engine.assemble_block(parent_hash, timestamp, randao) + + def get_execution_payload(state: BeaconState, transition_store: TransitionStore, + randao_reveal: BLSSignature, execution_engine: ExecutionEngine) -> ExecutionPayload: if not is_transition_completed(state): pow_block = get_pow_chain_head() @@ -80,11 +95,9 @@ def get_execution_payload(state: BeaconState, return ExecutionPayload() else: # Signify merge via producing on top of the last PoW block - timestamp = compute_time_at_slot(state, state.slot) - return execution_engine.assemble_block(pow_block.block_hash, timestamp) + return produce_execution_payload(state, pow_block.block_hash, timestamp, randao_reveal) # Post-merge, normal payload - execution_parent_hash = state.latest_execution_payload_header.block_hash - timestamp = compute_time_at_slot(state, state.slot) - return execution_engine.assemble_block(execution_parent_hash, timestamp) + parent_hash = state.latest_execution_payload_header.block_hash + return produce_execution_payload(state, parent_hash, timestamp, randao_reveal) ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index 0dd9dc98e..3781f0d50 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -35,6 +35,11 @@ def apply_randao_reveal(spec, state, block, proposer_index=None): block.body.randao_reveal = bls.Sign(privkey, signing_root) +def compute_randao_mix(spec, state, randao_reveal): + epoch = spec.get_current_epoch(state) + return spec.xor(spec.get_randao_mix(state, epoch), spec.hash(randao_reveal)) + + # Fully ignore the function if BLS is off, beacon-proposer index calculation is slow. @only_with_bls() def apply_sig(spec, state, signed_block, proposer_index=None): @@ -93,13 +98,15 @@ def build_empty_block(spec, state, slot=None): empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index empty_block.parent_root = parent_block_root + apply_randao_reveal(spec, state, empty_block) + if is_post_altair(spec): empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY if is_post_merge(spec): - empty_block.body.execution_payload = build_empty_execution_payload(spec, state) + randao_mix = compute_randao_mix(spec, state, empty_block.body.randao_reveal) + empty_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix) - apply_randao_reveal(spec, state, empty_block) return empty_block diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 7774aa4d9..661abe964 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,4 +1,4 @@ -def build_empty_execution_payload(spec, state): +def build_empty_execution_payload(spec, state, randao_mix): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. """ @@ -17,6 +17,7 @@ def build_empty_execution_payload(spec, state): timestamp=timestamp, receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? + randao=randao_mix, transactions=empty_txs, ) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. @@ -24,7 +25,6 @@ def build_empty_execution_payload(spec, state): return payload - def get_execution_payload_header(spec, execution_payload): return spec.ExecutionPayloadHeader( block_hash=execution_payload.block_hash, @@ -37,6 +37,7 @@ def get_execution_payload_header(spec, execution_payload): timestamp=execution_payload.timestamp, receipt_root=execution_payload.receipt_root, logs_bloom=execution_payload.logs_bloom, + randao=execution_payload.randao, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) @@ -46,7 +47,7 @@ def build_state_with_incomplete_transition(spec, state): def build_state_with_complete_transition(spec, state): - pre_state_payload = build_empty_execution_payload(spec, state) + pre_state_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) payload_header = get_execution_payload_header(spec, pre_state_payload) return build_state_with_execution_payload_header(spec, state, payload_header) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index 5edd31960..04fb0df30 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -7,8 +7,7 @@ from eth2spec.test.helpers.execution_payload import ( from eth2spec.test.context import spec_state_test, expect_assertion_error, with_merge_and_later from eth2spec.test.helpers.state import next_slot - -def run_execution_payload_processing(spec, state, execution_payload, valid=True, execution_valid=True): +def run_execution_payload_processing(spec, state, execution_payload, randao_mix, valid=True, execution_valid=True): """ Run ``process_execution_payload``, yielding: - pre-state ('pre') @@ -32,11 +31,11 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, return execution_valid if not valid: - expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload, TestEngine())) + expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload, randao_mix, TestEngine())) yield 'post', None return - spec.process_execution_payload(state, execution_payload, TestEngine()) + spec.process_execution_payload(state, execution_payload, randao_mix, TestEngine()) # Make sure we called the engine assert called_new_block @@ -54,9 +53,9 @@ def test_success_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) - yield from run_execution_payload_processing(spec, state, execution_payload) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) @with_merge_and_later @@ -67,9 +66,9 @@ def test_success_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) - yield from run_execution_payload_processing(spec, state, execution_payload) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) @with_merge_and_later @@ -81,9 +80,9 @@ def test_success_first_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) - yield from run_execution_payload_processing(spec, state, execution_payload) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) @with_merge_and_later @@ -95,9 +94,9 @@ def test_success_regular_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) - yield from run_execution_payload_processing(spec, state, execution_payload) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) @with_merge_and_later @@ -110,9 +109,9 @@ def test_bad_execution_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False, execution_valid=False) @with_merge_and_later @@ -125,9 +124,9 @@ def test_bad_execution_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False, execution_valid=False) @with_merge_and_later @@ -138,10 +137,10 @@ def test_bad_parent_hash_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) execution_payload.parent_hash = spec.Hash32() - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) @with_merge_and_later @@ -152,10 +151,10 @@ def test_bad_number_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) execution_payload.number = execution_payload.number + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) @with_merge_and_later @@ -166,11 +165,11 @@ def test_bad_everything_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) execution_payload.parent_hash = spec.Hash32() execution_payload.number = execution_payload.number + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) @with_merge_and_later @@ -181,10 +180,10 @@ def test_bad_timestamp_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) execution_payload.timestamp = execution_payload.timestamp + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) @with_merge_and_later @@ -195,7 +194,7 @@ def test_bad_timestamp_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state) + execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) execution_payload.timestamp = execution_payload.timestamp + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) From 7d617bc7c0351ce581726c6ea5a5dcc9ea0e9936 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 11 Jun 2021 22:46:24 +0600 Subject: [PATCH 04/22] Fix timestamp passed to produce_execution_payload --- specs/merge/validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/merge/validator.md b/specs/merge/validator.md index 6b86aae75..d196d8fae 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -95,9 +95,9 @@ def get_execution_payload(state: BeaconState, return ExecutionPayload() else: # Signify merge via producing on top of the last PoW block - return produce_execution_payload(state, pow_block.block_hash, timestamp, randao_reveal) + return produce_execution_payload(state, pow_block.block_hash, randao_reveal) # Post-merge, normal payload parent_hash = state.latest_execution_payload_header.block_hash - return produce_execution_payload(state, parent_hash, timestamp, randao_reveal) + return produce_execution_payload(state, parent_hash, randao_reveal) ``` From 2e87a6b44e71a0f399c1aaf8fd4c94cea44adea9 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 11 Jun 2021 22:50:11 +0600 Subject: [PATCH 05/22] Apply review comments --- specs/merge/beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index c19a406f0..2265844e6 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -198,9 +198,8 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_randao(state, block.body) process_eth1_data(state, block.body) process_operations(state, block.body) - # Pre-merge, skip execution payload processing + # [New in Merge] Pre-merge, skip execution payload processing if is_execution_enabled(state, block): - # [New in Merge] randao_mix = get_randao_mix(state, get_current_epoch(state)) process_execution_payload(state, block.body.execution_payload, randao_mix, EXECUTION_ENGINE) ``` @@ -252,6 +251,7 @@ This helper function is only for initializing the state for pure Merge testnets def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, eth1_timestamp: uint64, deposits: Sequence[Deposit]) -> BeaconState: + randao_seed = eth1_block_hash fork = Fork( previous_version=GENESIS_FORK_VERSION, current_version=MERGE_FORK_VERSION, # [Modified in Merge] @@ -262,7 +262,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, fork=fork, eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + randao_mixes=[randao_seed] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy ) # Process deposits @@ -296,7 +296,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, timestamp=eth1_timestamp, receipt_root=Bytes32(), logs_bloom=ByteVector[BYTES_PER_LOGS_BLOOM](), - randao=eth1_block_hash, + randao=randao_seed, transactions_root=Root(), ) From cc20b80103c8d55c05cb624e28b631eb852d9644 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 15 Jun 2021 14:55:06 +0600 Subject: [PATCH 06/22] Fix linter, add more test helpers --- setup.py | 2 +- specs/merge/validator.md | 4 +- .../test/helpers/execution_payload.py | 5 ++ .../test_process_execution_payload.py | 53 ++++++++++--------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/setup.py b/setup.py index 9ecf8261f..24ac6dd0c 100644 --- a/setup.py +++ b/setup.py @@ -532,7 +532,7 @@ class NoopExecutionEngine(ExecutionEngine): def finalize_block(self, block_hash: Hash32) -> bool: return True - def assemble_block(self, block_hash: Hash32, timestamp: uint64) -> ExecutionPayload: + def assemble_block(self, block_hash: Hash32, timestamp: uint64, randao: Bytes32) -> ExecutionPayload: raise NotImplementedError("no default block production") diff --git a/specs/merge/validator.md b/specs/merge/validator.md index d196d8fae..adbf63e84 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -95,9 +95,9 @@ def get_execution_payload(state: BeaconState, return ExecutionPayload() else: # Signify merge via producing on top of the last PoW block - return produce_execution_payload(state, pow_block.block_hash, randao_reveal) + return produce_execution_payload(state, pow_block.block_hash, randao_reveal, execution_engine) # Post-merge, normal payload parent_hash = state.latest_execution_payload_header.block_hash - return produce_execution_payload(state, parent_hash, randao_reveal) + return produce_execution_payload(state, parent_hash, randao_reveal, execution_engine) ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 661abe964..a84494e74 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,3 +1,7 @@ +def build_empty_execution_payload_with_randao(spec, state): + return build_empty_execution_payload(spec, state, spec.Bytes32()) + + def build_empty_execution_payload(spec, state, randao_mix): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. @@ -25,6 +29,7 @@ def build_empty_execution_payload(spec, state, randao_mix): return payload + def get_execution_payload_header(spec, execution_payload): return spec.ExecutionPayloadHeader( block_hash=execution_payload.block_hash, diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index 04fb0df30..d5e3a796e 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.execution_payload import ( - build_empty_execution_payload, + build_empty_execution_payload_with_randao, get_execution_payload_header, build_state_with_incomplete_transition, build_state_with_complete_transition, @@ -7,7 +7,8 @@ from eth2spec.test.helpers.execution_payload import ( from eth2spec.test.context import spec_state_test, expect_assertion_error, with_merge_and_later from eth2spec.test.helpers.state import next_slot -def run_execution_payload_processing(spec, state, execution_payload, randao_mix, valid=True, execution_valid=True): + +def run_execution_payload_processing(spec, state, execution_payload, valid=True, execution_valid=True): """ Run ``process_execution_payload``, yielding: - pre-state ('pre') @@ -21,6 +22,7 @@ def run_execution_payload_processing(spec, state, execution_payload, randao_mix, yield 'execution', {'execution_valid': execution_valid} yield 'execution_payload', execution_payload + randao_mix = spec.Bytes32() called_new_block = False class TestEngine(spec.NoopExecutionEngine): @@ -31,7 +33,8 @@ def run_execution_payload_processing(spec, state, execution_payload, randao_mix, return execution_valid if not valid: - expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload, randao_mix, TestEngine())) + expect_assertion_error( + lambda: spec.process_execution_payload(state, execution_payload, randao_mix, TestEngine())) yield 'post', None return @@ -53,9 +56,9 @@ def test_success_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) + yield from run_execution_payload_processing(spec, state, execution_payload) @with_merge_and_later @@ -66,9 +69,9 @@ def test_success_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) + yield from run_execution_payload_processing(spec, state, execution_payload) @with_merge_and_later @@ -80,9 +83,9 @@ def test_success_first_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) + yield from run_execution_payload_processing(spec, state, execution_payload) @with_merge_and_later @@ -94,9 +97,9 @@ def test_success_regular_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32()) + yield from run_execution_payload_processing(spec, state, execution_payload) @with_merge_and_later @@ -109,9 +112,9 @@ def test_bad_execution_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False, execution_valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) @with_merge_and_later @@ -124,9 +127,9 @@ def test_bad_execution_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False, execution_valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) @with_merge_and_later @@ -137,10 +140,10 @@ def test_bad_parent_hash_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) execution_payload.parent_hash = spec.Hash32() - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @with_merge_and_later @@ -151,10 +154,10 @@ def test_bad_number_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) execution_payload.number = execution_payload.number + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @with_merge_and_later @@ -165,11 +168,11 @@ def test_bad_everything_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) execution_payload.parent_hash = spec.Hash32() execution_payload.number = execution_payload.number + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @with_merge_and_later @@ -180,10 +183,10 @@ def test_bad_timestamp_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) execution_payload.timestamp = execution_payload.timestamp + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @with_merge_and_later @@ -194,7 +197,7 @@ def test_bad_timestamp_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + execution_payload = build_empty_execution_payload_with_randao(spec, state) execution_payload.timestamp = execution_payload.timestamp + 1 - yield from run_execution_payload_processing(spec, state, execution_payload, spec.Bytes32(), valid=False) + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) From 27889860f098f92a60b7e543d61c5c156aeecb44 Mon Sep 17 00:00:00 2001 From: ericsson Date: Wed, 16 Jun 2021 12:03:20 +0300 Subject: [PATCH 07/22] fix typo: sometimes `change()` invoked on `ShardWork` itself, should be invoked on its `status` field --- specs/sharding/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 98feba22b..d57e97752 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -576,12 +576,12 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N # TODO In Altair: set participation bit flag for voters of this early winning header if pending_header.commitment == DataCommitment(): # The committee voted to not confirm anything - state.shard_buffer[buffer_index][attestation_shard].change( + state.shard_buffer[buffer_index][attestation_shard].status.change( selector=SHARD_WORK_UNCONFIRMED, value=None, ) else: - state.shard_buffer[buffer_index][attestation_shard].change( + state.shard_buffer[buffer_index][attestation_shard].status.change( selector=SHARD_WORK_CONFIRMED, value=pending_header.commitment, ) @@ -785,7 +785,7 @@ def reset_pending_shard_work(state: BeaconState) -> None: shard = (start_shard + committee_index) % active_shards # a committee is available, initialize a pending shard-header list committee_length = len(get_beacon_committee(state, slot, committee_index)) - state.shard_buffer[buffer_index][shard].change( + state.shard_buffer[buffer_index][shard].status.change( selector=SHARD_WORK_PENDING, value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD]( PendingShardHeader( From d83ca352d5b89a1b11aedeb290f3c3730766d331 Mon Sep 17 00:00:00 2001 From: ericsson Date: Wed, 16 Jun 2021 13:33:56 +0300 Subject: [PATCH 08/22] Fix typing problem: `append` is invoked on a `ShardWork` instance --- specs/sharding/beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 98feba22b..e6137748c 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -608,7 +608,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade assert committee_work.status.selector == SHARD_WORK_PENDING # Check that this header is not yet in the pending list - current_headers: Sequence[PendingShardHeader] = committee_work.status.value + current_headers: MutableSequence[PendingShardHeader] = committee_work.status.value header_root = hash_tree_root(header) assert header_root not in [pending_header.root for pending_header in current_headers] @@ -640,7 +640,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade ) # Include it in the pending list - state.shard_buffer[header.slot % SHARD_STATE_MEMORY_SLOTS][header.shard].append(pending_header) + current_headers.append(pending_header) ``` The degree proof works as follows. For a block `B` with length `l` (so `l` values in `[0...l - 1]`, seen as a polynomial `B(X)` which takes these values), From 5b4f89875c26d4a15b3a83bc8eb5d255a328179e Mon Sep 17 00:00:00 2001 From: ericsson Date: Wed, 16 Jun 2021 14:06:17 +0300 Subject: [PATCH 09/22] use `List[PendingShardHeader,...]` instead of `MutableSequence`, since `remerkleable.List` does not implement the latter --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index e6137748c..6a6772690 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -608,7 +608,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade assert committee_work.status.selector == SHARD_WORK_PENDING # Check that this header is not yet in the pending list - current_headers: MutableSequence[PendingShardHeader] = committee_work.status.value + current_headers: List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] = committee_work.status.value header_root = hash_tree_root(header) assert header_root not in [pending_header.root for pending_header in current_headers] From 22b2a73615591cc60be95e85033d89ba4ded32ec Mon Sep 17 00:00:00 2001 From: ericsson Date: Wed, 16 Jun 2021 19:07:24 +0300 Subject: [PATCH 10/22] convert `int` to `CommitteeIndex` when passing to `get_beacon_committee` --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 98feba22b..56d18b0e7 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -784,7 +784,7 @@ def reset_pending_shard_work(state: BeaconState) -> None: for committee_index in range(committees_per_slot): shard = (start_shard + committee_index) % active_shards # a committee is available, initialize a pending shard-header list - committee_length = len(get_beacon_committee(state, slot, committee_index)) + committee_length = len(get_beacon_committee(state, slot, CommitteeIndex(committee_index))) state.shard_buffer[buffer_index][shard].change( selector=SHARD_WORK_PENDING, value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD]( From 67f1c2c2bd9ba1d408c66a7938c527752fdd585a Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Thu, 17 Jun 2021 23:05:23 +0800 Subject: [PATCH 11/22] Update validator.md --- specs/altair/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/validator.md b/specs/altair/validator.md index f193e3591..53ef2ec60 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -265,7 +265,7 @@ This process occurs each slot. ##### Prepare sync committee message -If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. +If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This would mean that a validator assigned to `slot` would have to prepare and broadcast their `SyncCommitteeMessage` in `slot-1 ` instead of `slot`. This logic is triggered upon the same conditions as when producing an attestation. Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of the slot) -- whichever comes first. From 6e86d8a6968ab705ec95e16fc43e7dc20ec6edbb Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 17 Jun 2021 21:20:17 +0600 Subject: [PATCH 12/22] Rename randao->random, other fixes as per review --- setup.py | 2 +- specs/merge/beacon-chain.md | 10 ++++---- specs/merge/validator.md | 6 ++--- .../pyspec/eth2spec/test/helpers/block.py | 7 +----- .../test/helpers/execution_payload.py | 8 +++---- .../test_process_execution_payload.py | 24 +++++++++---------- 6 files changed, 26 insertions(+), 31 deletions(-) diff --git a/setup.py b/setup.py index 24ac6dd0c..2abd363bf 100644 --- a/setup.py +++ b/setup.py @@ -532,7 +532,7 @@ class NoopExecutionEngine(ExecutionEngine): def finalize_block(self, block_hash: Hash32) -> bool: return True - def assemble_block(self, block_hash: Hash32, timestamp: uint64, randao: Bytes32) -> ExecutionPayload: + def assemble_block(self, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload: raise NotImplementedError("no default block production") diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 2265844e6..b1bbb57e6 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -105,7 +105,7 @@ class ExecutionPayload(Container): timestamp: uint64 receipt_root: Bytes32 logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - randao: Bytes32 # 'difficulty' in the yellow paper + random: Bytes32 # 'difficulty' in the yellow paper transactions: List[OpaqueTransaction, MAX_EXECUTION_TRANSACTIONS] ``` @@ -127,7 +127,7 @@ class ExecutionPayloadHeader(Container): timestamp: uint64 receipt_root: Bytes32 logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - randao: Bytes32 + random: Bytes32 transactions_root: Root ``` @@ -219,7 +219,7 @@ def process_execution_payload(state: BeaconState, if is_transition_completed(state): assert execution_payload.parent_hash == state.latest_execution_payload_header.block_hash assert execution_payload.number == state.latest_execution_payload_header.number + 1 - assert execution_payload.randao == randao_mix + assert execution_payload.random == randao_mix assert execution_payload.timestamp == compute_time_at_slot(state, state.slot) @@ -236,7 +236,7 @@ def process_execution_payload(state: BeaconState, timestamp=execution_payload.timestamp, receipt_root=execution_payload.receipt_root, logs_bloom=execution_payload.logs_bloom, - randao=execution_payload.randao, + random=execution_payload.random, transactions_root=hash_tree_root(execution_payload.transactions), ) ``` @@ -296,7 +296,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, timestamp=eth1_timestamp, receipt_root=Bytes32(), logs_bloom=ByteVector[BYTES_PER_LOGS_BLOOM](), - randao=randao_seed, + random=randao_seed, transactions_root=Root(), ) diff --git a/specs/merge/validator.md b/specs/merge/validator.md index adbf63e84..7910132e6 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -49,7 +49,7 @@ The body of this function is implementation dependent. The Consensus API may be used to implement this with an external execution engine. ```python -def assemble_block(self: ExecutionEngine, block_hash: Hash32, timestamp: uint64, randao: Bytes32) -> ExecutionPayload: +def assemble_block(self: ExecutionEngine, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload: ... ``` @@ -80,8 +80,8 @@ def produce_execution_payload(state: BeaconState, randao_reveal: BLSSignature, execution_engine: ExecutionEngine) -> ExecutionPayload: timestamp = compute_time_at_slot(state, state.slot) - randao = compute_randao_mix(state, randao_reveal) - return execution_engine.assemble_block(parent_hash, timestamp, randao) + randao_mix = compute_randao_mix(state, randao_reveal) + return execution_engine.assemble_block(parent_hash, timestamp, randao_mix) def get_execution_payload(state: BeaconState, diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index 3781f0d50..b8f7c4bcb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -35,11 +35,6 @@ def apply_randao_reveal(spec, state, block, proposer_index=None): block.body.randao_reveal = bls.Sign(privkey, signing_root) -def compute_randao_mix(spec, state, randao_reveal): - epoch = spec.get_current_epoch(state) - return spec.xor(spec.get_randao_mix(state, epoch), spec.hash(randao_reveal)) - - # Fully ignore the function if BLS is off, beacon-proposer index calculation is slow. @only_with_bls() def apply_sig(spec, state, signed_block, proposer_index=None): @@ -104,7 +99,7 @@ def build_empty_block(spec, state, slot=None): empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY if is_post_merge(spec): - randao_mix = compute_randao_mix(spec, state, empty_block.body.randao_reveal) + randao_mix = spec.compute_randao_mix(state, empty_block.body.randao_reveal) empty_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix) return empty_block diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index a84494e74..d14bdfa88 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,8 +1,8 @@ -def build_empty_execution_payload_with_randao(spec, state): +def build_empty_execution_payload_with_zeroed_random(spec, state): return build_empty_execution_payload(spec, state, spec.Bytes32()) -def build_empty_execution_payload(spec, state, randao_mix): +def build_empty_execution_payload(spec, state, random): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. """ @@ -21,7 +21,7 @@ def build_empty_execution_payload(spec, state, randao_mix): timestamp=timestamp, receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? - randao=randao_mix, + random=random, transactions=empty_txs, ) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. @@ -42,7 +42,7 @@ def get_execution_payload_header(spec, execution_payload): timestamp=execution_payload.timestamp, receipt_root=execution_payload.receipt_root, logs_bloom=execution_payload.logs_bloom, - randao=execution_payload.randao, + random=execution_payload.random, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index d5e3a796e..e2a2bf663 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.execution_payload import ( - build_empty_execution_payload_with_randao, + build_empty_execution_payload_with_zeroed_random, get_execution_payload_header, build_state_with_incomplete_transition, build_state_with_complete_transition, @@ -56,7 +56,7 @@ def test_success_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -69,7 +69,7 @@ def test_success_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -83,7 +83,7 @@ def test_success_first_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -97,7 +97,7 @@ def test_success_regular_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -112,7 +112,7 @@ def test_bad_execution_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) @@ -127,7 +127,7 @@ def test_bad_execution_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) @@ -140,7 +140,7 @@ def test_bad_parent_hash_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) execution_payload.parent_hash = spec.Hash32() yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -154,7 +154,7 @@ def test_bad_number_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) execution_payload.number = execution_payload.number + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -168,7 +168,7 @@ def test_bad_everything_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) execution_payload.parent_hash = spec.Hash32() execution_payload.number = execution_payload.number + 1 @@ -183,7 +183,7 @@ def test_bad_timestamp_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) execution_payload.timestamp = execution_payload.timestamp + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -197,7 +197,7 @@ def test_bad_timestamp_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_randao(spec, state) + execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) execution_payload.timestamp = execution_payload.timestamp + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) From 878b15df6ab8d56dd4499c45e2ddec4faa570910 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 18 Jun 2021 11:09:30 +0100 Subject: [PATCH 13/22] polish merge/beacon-chain.md (#2472) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Polish `merge/beacon-chain.md` with mostly non-substantive changes. **Non-substantive changes** * rename `MAX_EXECUTION_TRANSACTIONS` to `MAX_TRANSACTIONS_PER_PAYLOAD` - rename "execution transaction" to just "transaction" as per discussion with Danny * rename `compute_time_at_slot` to `compute_timestamp_at_slot` - the function returns a Unix timestamp - "timestamp" matches `execution_payload.timestamp` * be explicit about `ExecutionEngine.execution_state` for clarity * rename `ExecutionPayload.number` to `ExecutionPayload.block_number` - more specific ("number" is pretty vague) - consistent with `ExecutionPayload.block_hash` * rename `new_block` to `on_payload` - the `on_` prefix is consistent with other event handlers (e.g. see `on_tick`, `on_block`, `on_attestation` [here](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/fork-choice.md#handlers)) - the `_payload` suffix is more to the point given the function accepts an `execution_payload` - avoids conflict with `on_block` which is already used in the fork choice * rework the table of contents for consistency * order `is_execution_enabled` after `is_transition_completed` and `is_transition_block` - `is_execution_enabled` refers to `is_transition_completed` and `is_transition_block` * rename "transition" to "merge" - "transition" is a bit vague—we will have other transitions at future hard forks - there is no need for two words to refer to the same concept * add a bunch of inline comments, e.g. in `process_execution_payload` * make the `process_execution_payload` signature consistent with the other `process_` functions in `process_block` which take as arguments `state` and `block.body` * remove `TRANSITION_TOTAL_DIFFICULTY` - to be put in `merge/fork-choice.md` where it is used * various misc cleanups **Substantive changes** * reorder `ExecutionPayload` fields - for consistency with yellow paper and Eth1 - same for `ExecutionPayloadHeader` - added comments separating out the execution block header fields from the extra fields (cosmetic) --- setup.py | 18 +- specs/merge/beacon-chain.md | 233 ++++++++---------- specs/merge/fork-choice.md | 2 +- specs/merge/validator.md | 2 +- .../test/helpers/execution_payload.py | 18 +- .../test_process_execution_payload.py | 6 +- 6 files changed, 129 insertions(+), 150 deletions(-) diff --git a/setup.py b/setup.py index 9ecf8261f..4430ccec0 100644 --- a/setup.py +++ b/setup.py @@ -223,7 +223,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> if not _is_constant_id(name): # Check for short type declarations - if value.startswith("uint") or value.startswith("Bytes") or value.startswith("ByteList"): + if value.startswith("uint") or value.startswith("Bytes") or value.startswith("ByteList") or value.startswith("Union"): custom_types[name] = value continue @@ -495,7 +495,7 @@ class MergeSpecBuilder(Phase0SpecBuilder): return super().imports(preset_name) + f''' from typing import Protocol from eth2spec.phase0 import {preset_name} as phase0 -from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256 +from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256, Union ''' @classmethod @@ -523,7 +523,7 @@ def get_pow_chain_head() -> PowBlock: class NoopExecutionEngine(ExecutionEngine): - def new_block(self, execution_payload: ExecutionPayload) -> bool: + def on_payload(self, execution_payload: ExecutionPayload) -> bool: return True def set_head(self, block_hash: Hash32) -> bool: @@ -553,6 +553,10 @@ spec_builders = { } +def is_spec_defined_type(value: str) -> bool: + return value.startswith('ByteList') or value.startswith('Union') + + def objects_to_spec(preset_name: str, spec_object: SpecObject, builder: SpecBuilder, @@ -565,15 +569,15 @@ def objects_to_spec(preset_name: str, [ f"class {key}({value}):\n pass\n" for key, value in spec_object.custom_types.items() - if not value.startswith('ByteList') + if not is_spec_defined_type(value) ] ) - + ('\n\n' if len([key for key, value in spec_object.custom_types.items() if value.startswith('ByteList')]) > 0 else '') + + ('\n\n' if len([key for key, value in spec_object.custom_types.items() if is_spec_defined_type(value)]) > 0 else '') + '\n\n'.join( [ f"{key} = {value}\n" for key, value in spec_object.custom_types.items() - if value.startswith('ByteList') + if is_spec_defined_type(value) ] ) ) @@ -1020,7 +1024,7 @@ setup( "py_ecc==5.2.0", "milagro_bls_binding==1.6.3", "dataclasses==0.6", - "remerkleable==0.1.20", + "remerkleable==0.1.21", RUAMEL_YAML_VERSION, "lru-dict==1.1.6", MARKO_VERSION, diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 697bd0c96..562adaab4 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -1,6 +1,6 @@ # Ethereum 2.0 The Merge -**Warning:** This document is currently based on [Phase 0](../phase0/beacon-chain.md) but will be rebased to [Altair](../altair/beacon-chain.md) once the latter is shipped. +**Warning**: This document is currently based on [Phase 0](../phase0/beacon-chain.md) and will be rebased on [Altair](../altair/beacon-chain.md). **Notice**: This document is a work-in-progress for researchers and implementers. @@ -21,35 +21,36 @@ - [New containers](#new-containers) - [`ExecutionPayload`](#executionpayload) - [`ExecutionPayloadHeader`](#executionpayloadheader) -- [Protocols](#protocols) - - [`ExecutionEngine`](#executionengine) - - [`new_block`](#new_block) - [Helper functions](#helper-functions) - - [Misc](#misc) + - [Predicates](#predicates) + - [`is_merge_complete`](#is_merge_complete) + - [`is_merge_block`](#is_merge_block) - [`is_execution_enabled`](#is_execution_enabled) - - [`is_transition_completed`](#is_transition_completed) - - [`is_transition_block`](#is_transition_block) - - [`compute_time_at_slot`](#compute_time_at_slot) + - [Misc](#misc) + - [`compute_timestamp_at_slot`](#compute_timestamp_at_slot) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Execution engine](#execution-engine) + - [`on_payload`](#on_payload) - [Block processing](#block-processing) - - [Execution payload processing](#execution-payload-processing) - - [`process_execution_payload`](#process_execution_payload) -- [Initialize state for pure Merge testnets and test vectors](#initialize-state-for-pure-merge-testnets-and-test-vectors) + - [Execution payload processing](#execution-payload-processing) + - [`process_execution_payload`](#process_execution_payload) +- [Testing](#testing) ## Introduction -This is a patch implementing the executable beacon chain proposal. -It enshrines transaction execution and validity as a first class citizen at the core of the beacon chain. +This patch adds transaction execution to the beacon chain as part of the Merge fork. ## Custom types -We define the following Python custom types for type hinting and readability: +*Note*: The `Transaction` type is a stub which is not final. | Name | SSZ equivalent | Description | | - | - | - | -| `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a byte-list containing a single [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | +| `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | +| `Transaction` | `Union[OpaqueTransaction]` | a transaction | ## Constants @@ -58,32 +59,26 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | -| `MAX_EXECUTION_TRANSACTIONS` | `uint64(2**14)` (= 16,384) | +| `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | ## Containers ### Extended containers -*Note*: Extended SSZ containers inherit all fields from the parent in the original -order and append any additional fields to the end. - #### `BeaconBlockBody` -*Note*: `BeaconBlockBody` fields remain unchanged other than the addition of `execution_payload`. - ```python class BeaconBlockBody(phase0.BeaconBlockBody): + # Execution execution_payload: ExecutionPayload # [New in Merge] ``` #### `BeaconState` -*Note*: `BeaconState` fields remain unchanged other than addition of `latest_execution_payload_header`. - ```python class BeaconState(phase0.BeaconState): - # Execution-layer + # Execution latest_execution_payload_header: ExecutionPayloadHeader # [New in Merge] ``` @@ -91,103 +86,100 @@ class BeaconState(phase0.BeaconState): #### `ExecutionPayload` -The execution payload included in a `BeaconBlockBody`. - ```python class ExecutionPayload(Container): - block_hash: Hash32 # Hash of execution block + # Execution block header fields parent_hash: Hash32 - coinbase: Bytes20 + coinbase: Bytes20 # 'beneficiary' in the yellow paper state_root: Bytes32 - number: uint64 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + receipt_root: Bytes32 # 'receipts root' in the yellow paper + block_number: uint64 # 'number' in the yellow paper gas_limit: uint64 gas_used: uint64 timestamp: uint64 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - transactions: List[OpaqueTransaction, MAX_EXECUTION_TRANSACTIONS] + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] ``` #### `ExecutionPayloadHeader` -The execution payload header included in a `BeaconState`. - -*Note:* Holds execution payload data without transaction bodies. - ```python class ExecutionPayloadHeader(Container): - block_hash: Hash32 # Hash of execution block + # Execution block header fields parent_hash: Hash32 coinbase: Bytes20 state_root: Bytes32 - number: uint64 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + receipt_root: Bytes32 + block_number: uint64 gas_limit: uint64 gas_used: uint64 timestamp: uint64 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + # Extra payload fields + block_hash: Hash32 # Hash of execution block transactions_root: Root ``` -## Protocols - -### `ExecutionEngine` - -The `ExecutionEngine` protocol separates the consensus and execution sub-systems. -The consensus implementation references an instance of this sub-system with `EXECUTION_ENGINE`. - -The following methods are added to the `ExecutionEngine` protocol for use in the state transition: - -#### `new_block` - -Verifies the given `execution_payload` with respect to execution state transition, and persists changes if valid. - -The body of this function is implementation dependent. -The Consensus API may be used to implement this with an external execution engine. - -```python -def new_block(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - """ - Returns True if the ``execution_payload`` was verified and processed successfully, False otherwise. - """ - ... -``` - ## Helper functions -### Misc +### Predicates + +#### `is_merge_complete` + +```python +def is_merge_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() +``` + +#### `is_merge_block` + +```python +def is_merge_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_complete(state) and body.execution_payload != ExecutionPayload() +``` #### `is_execution_enabled` ```python -def is_execution_enabled(state: BeaconState, block: BeaconBlock) -> bool: - return is_transition_completed(state) or is_transition_block(state, block) +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_block(state, body) or is_merge_complete(state) ``` -#### `is_transition_completed` +### Misc -```python -def is_transition_completed(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() -``` - -#### `is_transition_block` - -```python -def is_transition_block(state: BeaconState, block: BeaconBlock) -> bool: - return not is_transition_completed(state) and block.body.execution_payload != ExecutionPayload() -``` - -#### `compute_time_at_slot` +#### `compute_timestamp_at_slot` *Note*: This function is unsafe with respect to overflows and underflows. ```python -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: +def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: slots_since_genesis = slot - GENESIS_SLOT return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) ``` +## Beacon chain state transition function + +### Execution engine + +The implementation-dependent `ExecutionEngine` protocol encapsulates the execution sub-system logic via: + +* a state object `self.execution_state` of type `ExecutionState` +* a state transition function `self.on_payload` which mutates `self.execution_state` + +#### `on_payload` + +```python +def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + """ + Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... +``` + +The above function is accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol. + ### Block processing ```python @@ -196,50 +188,45 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_randao(state, block.body) process_eth1_data(state, block.body) process_operations(state, block.body) - # Pre-merge, skip execution payload processing - if is_execution_enabled(state, block): + if is_execution_enabled(state, block.body): process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] ``` -#### Execution payload processing +### Execution payload processing -##### `process_execution_payload` +#### `process_execution_payload` ```python -def process_execution_payload(state: BeaconState, - execution_payload: ExecutionPayload, - execution_engine: ExecutionEngine) -> None: - """ - Note: This function is designed to be able to be run in parallel with the other `process_block` sub-functions - """ - if is_transition_completed(state): - assert execution_payload.parent_hash == state.latest_execution_payload_header.block_hash - assert execution_payload.number == state.latest_execution_payload_header.number + 1 - - assert execution_payload.timestamp == compute_time_at_slot(state, state.slot) - - assert execution_engine.new_block(execution_payload) - +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash and block number + if is_merge_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.on_payload(payload) + # Cache execution payload state.latest_execution_payload_header = ExecutionPayloadHeader( - block_hash=execution_payload.block_hash, - parent_hash=execution_payload.parent_hash, - coinbase=execution_payload.coinbase, - state_root=execution_payload.state_root, - number=execution_payload.number, - gas_limit=execution_payload.gas_limit, - gas_used=execution_payload.gas_used, - timestamp=execution_payload.timestamp, - receipt_root=execution_payload.receipt_root, - logs_bloom=execution_payload.logs_bloom, - transactions_root=hash_tree_root(execution_payload.transactions), + parent_hash=payload.parent_hash, + coinbase=payload.coinbase, + state_root=payload.state_root, + logs_bloom=payload.logs_bloom, + receipt_root=payload.receipt_root, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), ) ``` -## Initialize state for pure Merge testnets and test vectors +## Testing -This helper function is only for initializing the state for pure Merge testnets and tests. +*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Merge testing only. -*Note*: The function `initialize_beacon_state_from_eth1` is modified: (1) using `MERGE_FORK_VERSION` as the current fork version, (2) utilizing the Merge `BeaconBlockBody` when constructing the initial `latest_block_header`, and (3) adding initial `latest_execution_payload_header`. +*Note*: The function `initialize_beacon_state_from_eth1` is modified to use `MERGE_FORK_VERSION` and initialize `latest_execution_payload_header`. ```python def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, @@ -276,21 +263,9 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, # Set genesis validators root for domain separation and chain versioning state.genesis_validators_root = hash_tree_root(state.validators) - # [New in Merge] Construct execution payload header - # Note: initialized with zero block height - state.latest_execution_payload_header = ExecutionPayloadHeader( - block_hash=eth1_block_hash, - parent_hash=Hash32(), - coinbase=Bytes20(), - state_root=Bytes32(), - number=uint64(0), - gas_limit=uint64(0), - gas_used=uint64(0), - timestamp=eth1_timestamp, - receipt_root=Bytes32(), - logs_bloom=ByteVector[BYTES_PER_LOGS_BLOOM](), - transactions_root=Root(), - ) + # [New in Merge] Initialize the execution payload header (with block number set to 0) + state.latest_execution_payload_header.block_hash = eth1_block_hash + state.latest_execution_payload_header.timestamp = eth1_timestamp return state ``` diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index 56345dd90..d0f327137 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -127,7 +127,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: Tr assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root # [New in Merge] - if (transition_store is not None) and is_transition_block(pre_state, block): + if (transition_store is not None) and is_merge_block(pre_state, block): # Delay consideration of block until PoW block is processed by the PoW node pow_block = get_pow_block(block.body.execution_payload.parent_hash) assert pow_block.is_processed diff --git a/specs/merge/validator.md b/specs/merge/validator.md index c5a7a4c78..21668b327 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -73,7 +73,7 @@ Let `get_pow_chain_head() -> PowBlock` be the function that returns the head of def get_execution_payload(state: BeaconState, transition_store: TransitionStore, execution_engine: ExecutionEngine) -> ExecutionPayload: - if not is_transition_completed(state): + if not is_merge_complete(state): pow_block = get_pow_chain_head() if not is_valid_terminal_pow_block(transition_store, pow_block): # Pre-merge, empty payload diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 7774aa4d9..238e2e00f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -4,19 +4,19 @@ def build_empty_execution_payload(spec, state): """ latest = state.latest_execution_payload_header timestamp = spec.compute_time_at_slot(state, state.slot) - empty_txs = spec.List[spec.OpaqueTransaction, spec.MAX_EXECUTION_TRANSACTIONS]() + empty_txs = spec.List[spec.Transaction, spec.MAX_TRANSACTIONS_PER_PAYLOAD]() payload = spec.ExecutionPayload( - block_hash=spec.Hash32(), parent_hash=latest.block_hash, coinbase=spec.Bytes20(), state_root=latest.state_root, # no changes to the state - number=latest.number + 1, + logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? + receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. + block_number=latest.block_number + 1, gas_limit=latest.gas_limit, # retain same limit gas_used=0, # empty block, 0 gas timestamp=timestamp, - receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. - logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? + block_hash=spec.Hash32(), transactions=empty_txs, ) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. @@ -27,16 +27,16 @@ def build_empty_execution_payload(spec, state): def get_execution_payload_header(spec, execution_payload): return spec.ExecutionPayloadHeader( - block_hash=execution_payload.block_hash, parent_hash=execution_payload.parent_hash, coinbase=execution_payload.coinbase, state_root=execution_payload.state_root, - number=execution_payload.number, + logs_bloom=execution_payload.logs_bloom, + receipt_root=execution_payload.receipt_root, + block_number=execution_payload.block_number, gas_limit=execution_payload.gas_limit, gas_used=execution_payload.gas_used, timestamp=execution_payload.timestamp, - receipt_root=execution_payload.receipt_root, - logs_bloom=execution_payload.logs_bloom, + block_hash=execution_payload.block_hash, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index 5edd31960..4c68034d4 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -25,7 +25,7 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, called_new_block = False class TestEngine(spec.NoopExecutionEngine): - def new_block(self, payload) -> bool: + def on_payload(self, payload) -> bool: nonlocal called_new_block, execution_valid called_new_block = True assert payload == execution_payload @@ -153,7 +153,7 @@ def test_bad_number_regular_payload(spec, state): # execution payload execution_payload = build_empty_execution_payload(spec, state) - execution_payload.number = execution_payload.number + 1 + execution_payload.block_number = execution_payload.block_number + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -168,7 +168,7 @@ def test_bad_everything_regular_payload(spec, state): # execution payload execution_payload = build_empty_execution_payload(spec, state) execution_payload.parent_hash = spec.Hash32() - execution_payload.number = execution_payload.number + 1 + execution_payload.block_number = execution_payload.block_number + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) From a61aa7968d87c13489f026a64f9510d4392fa36c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 21 Jun 2021 12:48:44 -0600 Subject: [PATCH 14/22] clean up --- specs/altair/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/validator.md b/specs/altair/validator.md index 53ef2ec60..9de78638b 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -265,7 +265,7 @@ This process occurs each slot. ##### Prepare sync committee message -If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This would mean that a validator assigned to `slot` would have to prepare and broadcast their `SyncCommitteeMessage` in `slot-1 ` instead of `slot`. +If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to a `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`. This logic is triggered upon the same conditions as when producing an attestation. Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of the slot) -- whichever comes first. From fc1012b6326b76dec3ba7ee87daae212e16f0812 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 21 Jun 2021 12:49:41 -0600 Subject: [PATCH 15/22] typo --- specs/altair/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/validator.md b/specs/altair/validator.md index 9de78638b..49146da00 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -265,7 +265,7 @@ This process occurs each slot. ##### Prepare sync committee message -If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to a `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`. +If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`. This logic is triggered upon the same conditions as when producing an attestation. Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of the slot) -- whichever comes first. From ac19aa3e2d1533d933d504661b39341b18f086ad Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 22 Jun 2021 14:23:26 +0600 Subject: [PATCH 16/22] Accept polishing suggested by Justin --- specs/merge/beacon-chain.md | 18 +++++------- .../test/helpers/execution_payload.py | 11 ++++--- .../test_process_execution_payload.py | 29 +++++++++---------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 47e69e918..7823a664d 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -191,25 +191,22 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_eth1_data(state, block.body) process_operations(state, block.body) if is_execution_enabled(state, block.body): - # [New in Merge] - randao_mix = get_randao_mix(state, get_current_epoch(state)) - process_execution_payload(state, block.body.execution_payload, randao_mix, EXECUTION_ENGINE) + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] ``` ### Execution payload processing #### `process_execution_payload` +*Note:* This function depends on `process_randao` function call as it retrieves the most recent randao mix from the `state`. Implementations that are considering parallel processing of execution payload with respect to beacon chain state transition function should work around this dependency. + ```python -def process_execution_payload(state: BeaconState, - payload: ExecutionPayload, - randao_mix: Bytes32, - execution_engine: ExecutionEngine) -> None: +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: # Verify consistency of the parent hash, block number and random if is_merge_complete(state): assert payload.parent_hash == state.latest_execution_payload_header.block_hash assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) - assert payload.random == randao_mix + assert payload.random == get_randao_mix(state, get_current_epoch(state)) # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify the execution payload is valid @@ -241,7 +238,6 @@ def process_execution_payload(state: BeaconState, def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, eth1_timestamp: uint64, deposits: Sequence[Deposit]) -> BeaconState: - randao_seed = eth1_block_hash fork = Fork( previous_version=GENESIS_FORK_VERSION, current_version=MERGE_FORK_VERSION, # [Modified in Merge] @@ -252,7 +248,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, fork=fork, eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[randao_seed] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy ) # Process deposits @@ -276,7 +272,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, # [New in Merge] Initialize the execution payload header (with block number set to 0) state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.timestamp = eth1_timestamp - state.latest_execution_payload_header.random = randao_seed + state.latest_execution_payload_header.random = eth1_block_hash return state ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 41fa3117d..1ef63eaaf 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,8 +1,4 @@ -def build_empty_execution_payload_with_zeroed_random(spec, state): - return build_empty_execution_payload(spec, state, spec.Bytes32()) - - -def build_empty_execution_payload(spec, state, random): +def build_empty_execution_payload(spec, state, randao_mix=None): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. """ @@ -10,6 +6,9 @@ def build_empty_execution_payload(spec, state, random): timestamp = spec.compute_time_at_slot(state, state.slot) empty_txs = spec.List[spec.Transaction, spec.MAX_TRANSACTIONS_PER_PAYLOAD]() + if randao_mix is None: + randao_mix = spec.get_randao_mix(state, spec.get_current_epoch(state)) + payload = spec.ExecutionPayload( parent_hash=latest.block_hash, coinbase=spec.Bytes20(), @@ -17,7 +16,7 @@ def build_empty_execution_payload(spec, state, random): receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? block_number=latest.block_number + 1, - random=random, + random=randao_mix, gas_limit=latest.gas_limit, # retain same limit gas_used=0, # empty block, 0 gas timestamp=timestamp, diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index 4f6c98705..15c69f2dd 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.execution_payload import ( - build_empty_execution_payload_with_zeroed_random, + build_empty_execution_payload, get_execution_payload_header, build_state_with_incomplete_transition, build_state_with_complete_transition, @@ -22,7 +22,6 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, yield 'execution', {'execution_valid': execution_valid} yield 'execution_payload', execution_payload - randao_mix = spec.Bytes32() called_new_block = False class TestEngine(spec.NoopExecutionEngine): @@ -34,11 +33,11 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, if not valid: expect_assertion_error( - lambda: spec.process_execution_payload(state, execution_payload, randao_mix, TestEngine())) + lambda: spec.process_execution_payload(state, execution_payload, TestEngine())) yield 'post', None return - spec.process_execution_payload(state, execution_payload, randao_mix, TestEngine()) + spec.process_execution_payload(state, execution_payload, TestEngine()) # Make sure we called the engine assert called_new_block @@ -56,7 +55,7 @@ def test_success_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -69,7 +68,7 @@ def test_success_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -83,7 +82,7 @@ def test_success_first_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -97,7 +96,7 @@ def test_success_regular_payload_with_gap_slot(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload) @@ -112,7 +111,7 @@ def test_bad_execution_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) @@ -127,7 +126,7 @@ def test_bad_execution_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) @@ -140,7 +139,7 @@ def test_bad_parent_hash_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) execution_payload.parent_hash = spec.Hash32() yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -154,7 +153,7 @@ def test_bad_number_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) execution_payload.block_number = execution_payload.block_number + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -168,7 +167,7 @@ def test_bad_everything_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) execution_payload.parent_hash = spec.Hash32() execution_payload.block_number = execution_payload.block_number + 1 @@ -183,7 +182,7 @@ def test_bad_timestamp_first_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) execution_payload.timestamp = execution_payload.timestamp + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -197,7 +196,7 @@ def test_bad_timestamp_regular_payload(spec, state): next_slot(spec, state) # execution payload - execution_payload = build_empty_execution_payload_with_zeroed_random(spec, state) + execution_payload = build_empty_execution_payload(spec, state) execution_payload.timestamp = execution_payload.timestamp + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) From e5c01061af230ad2651097576c5900cb719b1fbd Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 22 Jun 2021 14:26:38 +0600 Subject: [PATCH 17/22] Add couple of cosmetic fixes --- tests/core/pyspec/eth2spec/test/helpers/execution_payload.py | 2 +- .../merge/block_processing/test_process_execution_payload.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 1ef63eaaf..c41a05079 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -51,7 +51,7 @@ def build_state_with_incomplete_transition(spec, state): def build_state_with_complete_transition(spec, state): - pre_state_payload = build_empty_execution_payload(spec, state, spec.Bytes32()) + pre_state_payload = build_empty_execution_payload(spec, state) payload_header = get_execution_payload_header(spec, pre_state_payload) return build_state_with_execution_payload_header(spec, state, payload_header) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index 15c69f2dd..4c68034d4 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -32,8 +32,7 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, return execution_valid if not valid: - expect_assertion_error( - lambda: spec.process_execution_payload(state, execution_payload, TestEngine())) + expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload, TestEngine())) yield 'post', None return From c61eeb8a27370fbb98c317404fcfc770ba29dd0d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Jun 2021 12:31:02 -0600 Subject: [PATCH 18/22] optimize sync committee message gossip and caches --- specs/altair/p2p-interface.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/specs/altair/p2p-interface.md b/specs/altair/p2p-interface.md index fc0a8a35f..6945704f4 100644 --- a/specs/altair/p2p-interface.md +++ b/specs/altair/p2p-interface.md @@ -137,13 +137,14 @@ def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64 return sync_committee.pubkeys[i:i + sync_subcommittee_size] ``` -- _[IGNORE]_ The contribution's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance), i.e. `contribution.slot == current_slot`. -- _[IGNORE]_ The block being signed over (`contribution.beacon_block_root`) has been seen (via both gossip and non-gossip sources). +- _[IGNORE]_ The contribution's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `contribution.slot == current_slot`. - _[REJECT]_ The subcommittee index is in the allowed range, i.e. `contribution.subcommittee_index < SYNC_COMMITTEE_SUBNET_COUNT`. -- _[IGNORE]_ The sync committee contribution is the first valid contribution received for the aggregator with index `contribution_and_proof.aggregator_index` for the slot `contribution.slot` and subcommittee index `contribution.subcommittee_index`. - _[REJECT]_ `contribution_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_sync_committee_aggregator(contribution_and_proof.selection_proof)` returns `True`. - _[REJECT]_ The aggregator's validator index is in the declared subcommittee of the current sync committee -- i.e. `state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index)`. +- _[IGNORE]_ The sync committee contribution is the first valid contribution received for the aggregator with index `contribution_and_proof.aggregator_index` + for the slot `contribution.slot` and subcommittee index `contribution.subcommittee_index` + (this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE` for this topic that can be flushed after each slot). - _[REJECT]_ The `contribution_and_proof.selection_proof` is a valid signature of the `SyncAggregatorSelectionData` derived from the `contribution` by the validator with index `contribution_and_proof.aggregator_index`. - _[REJECT]_ The aggregator signature, `signed_contribution_and_proof.signature`, is valid. - _[REJECT]_ The aggregate signature is valid for the message `beacon_block_root` and aggregate pubkey derived from the participation info in `aggregation_bits` for the subcommittee specified by the `contribution.subcommittee_index`. @@ -158,12 +159,12 @@ The `sync_committee_{subnet_id}` topics are used to propagate unaggregated sync The following validations MUST pass before forwarding the `sync_committee_message` on the network: -- _[IGNORE]_ The signature's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance), i.e. `sync_committee_message.slot == current_slot`. -- _[IGNORE]_ The block being signed over (`sync_committee_message.beacon_block_root`) has been seen (via both gossip and non-gossip sources). -- _[IGNORE]_ There has been no other valid sync committee signature for the declared `slot` for the validator referenced by `sync_committee_message.validator_index`. - Note this validation is _per topic_ so that for a given `slot`, multiple messages could be forwarded with the same `validator_index` as long as the `subnet_id`s are distinct. +- _[IGNORE]_ The signature's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `sync_committee_message.slot == current_slot`. - _[REJECT]_ The `subnet_id` is valid for the given validator, i.e. `subnet_id in compute_subnets_for_sync_committee(state, sync_committee_message.validator_index)`. Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee. +- _[IGNORE]_ There has been no other valid sync committee signature for the declared `slot` for the validator referenced by `sync_committee_message.validator_index` + (this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT` for each subnet that can be flushed after each slot). + Note this validation is _per topic_ so that for a given `slot`, multiple messages could be forwarded with the same `validator_index` as long as the `subnet_id`s are distinct. - _[REJECT]_ The `signature` is valid for the message `beacon_block_root` for the validator referenced by `validator_index`. #### Sync committees and aggregation From e8136cb62ceb2985fdb454b7746ecf26135de12f Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 22 Jun 2021 18:15:56 -0500 Subject: [PATCH 19/22] Update merge description to signal more progress --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a9a840bde..d1a0b2876 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,7 @@ The current features are: ### Merge -The merge is still actively in R&D. The specifications outline a general direction for engineering work, -while the details are in review and may change. +The merge is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change. * Background material: * An [ethresear.ch](https://ethresear.ch) post [describing the basic mechanism](https://ethresear.ch/t/the-eth1-eth2-transition/6265) From 989c9620c7185da64537b7b6bea57021000a754c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Jun 2021 08:32:52 -0600 Subject: [PATCH 20/22] bump VERSION.txt to 1.1.0-alpha.8 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index e79b1b4ae..a40920e99 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.0-alpha.7 \ No newline at end of file +1.1.0-alpha.8 \ No newline at end of file From dbf7fbd3d041717f606052424d06e60b933f9843 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 24 Jun 2021 17:13:36 +0200 Subject: [PATCH 21/22] encode, decode and randomize ssz Union types --- tests/core/pyspec/eth2spec/debug/decode.py | 13 ++++++++++++- tests/core/pyspec/eth2spec/debug/encode.py | 8 +++++++- .../core/pyspec/eth2spec/debug/random_value.py | 18 +++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/debug/decode.py b/tests/core/pyspec/eth2spec/debug/decode.py index 871748d54..30bfd487b 100644 --- a/tests/core/pyspec/eth2spec/debug/decode.py +++ b/tests/core/pyspec/eth2spec/debug/decode.py @@ -2,7 +2,7 @@ from typing import Any from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( uint, Container, List, boolean, - Vector, ByteVector, ByteList + Vector, ByteVector, ByteList, Union, View ) @@ -27,5 +27,16 @@ def decode(data: Any, typ): assert (data["hash_tree_root"][2:] == hash_tree_root(ret).hex()) return ret + elif issubclass(typ, Union): + selector = int(data["selector"]) + options = typ.options() + value_typ = options[selector] + value: View + if value_typ is None: # handle the "nil" type case + assert data["value"] is None + value = None + else: + value = decode(data["value"], value_typ) + return typ(selector=selector, value=value) else: raise Exception(f"Type not recognized: data={data}, typ={typ}") diff --git a/tests/core/pyspec/eth2spec/debug/encode.py b/tests/core/pyspec/eth2spec/debug/encode.py index 10bf4bedd..d93f7cf5e 100644 --- a/tests/core/pyspec/eth2spec/debug/encode.py +++ b/tests/core/pyspec/eth2spec/debug/encode.py @@ -1,7 +1,7 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root, serialize from eth2spec.utils.ssz.ssz_typing import ( uint, boolean, - Bitlist, Bitvector, Container, Vector, List + Bitlist, Bitvector, Container, Vector, List, Union ) @@ -31,5 +31,11 @@ def encode(value, include_hash_tree_roots=False): if include_hash_tree_roots: ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex() return ret + elif isinstance(value, Union): + inner_value = value.value() + return { + 'selector': int(value.selector()), + 'value': None if inner_value is None else encode(inner_value, include_hash_tree_roots) + } else: raise Exception(f"Type not recognized: value={value}, typ={type(value)}") diff --git a/tests/core/pyspec/eth2spec/debug/random_value.py b/tests/core/pyspec/eth2spec/debug/random_value.py index 33eb25c0c..f8cb5701e 100644 --- a/tests/core/pyspec/eth2spec/debug/random_value.py +++ b/tests/core/pyspec/eth2spec/debug/random_value.py @@ -5,7 +5,7 @@ from typing import Type from eth2spec.utils.ssz.ssz_typing import ( View, BasicView, uint, Container, List, boolean, - Vector, ByteVector, ByteList, Bitlist, Bitvector + Vector, ByteVector, ByteList, Bitlist, Bitvector, Union ) # in bytes @@ -115,6 +115,22 @@ def get_random_ssz_object(rng: Random, get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos) for field_name, field_type in fields.items() }) + elif issubclass(typ, Union): + options = typ.options() + selector: int + if mode == RandomizationMode.mode_zero: + selector = 0 + elif mode == RandomizationMode.mode_max: + selector = len(options)-1 + else: + selector = rng.randrange(0, len(options)) + elem_type = options[selector] + elem: View + if elem_type is None: + elem = None + else: + elem = get_random_ssz_object(rng, elem_type, max_bytes_length, max_list_length, mode, chaos) + return typ(selector=selector, value=elem) else: raise Exception(f"Type not recognized: typ={typ}") From 44a3113ce176358ab69a0648aed883a23e7039a6 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 24 Jun 2021 09:24:58 -0700 Subject: [PATCH 22/22] fix spacing for linter --- tests/core/pyspec/eth2spec/debug/random_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/debug/random_value.py b/tests/core/pyspec/eth2spec/debug/random_value.py index f8cb5701e..ff80ee0f4 100644 --- a/tests/core/pyspec/eth2spec/debug/random_value.py +++ b/tests/core/pyspec/eth2spec/debug/random_value.py @@ -121,7 +121,7 @@ def get_random_ssz_object(rng: Random, if mode == RandomizationMode.mode_zero: selector = 0 elif mode == RandomizationMode.mode_max: - selector = len(options)-1 + selector = len(options) - 1 else: selector = rng.randrange(0, len(options)) elem_type = options[selector]