From 96f785e84be320229ab921476dc83108105505e3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 20 May 2020 13:23:59 -0600 Subject: [PATCH] ensure only forward progress with eth1data voting --- setup.py | 5 +- specs/phase0/validator.md | 14 ++++-- .../test/validator/test_validator_unittest.py | 46 +++++++++++++++++-- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index da56175af..6d7d47767 100644 --- a/setup.py +++ b/setup.py @@ -150,7 +150,10 @@ def get_eth1_data(block: Eth1Block) -> Eth1Data: """ A stub function return mocking Eth1Data. """ - return Eth1Data(block_hash=hash_tree_root(block)) + return Eth1Data( + deposit_root=block.deposit_root, + deposit_count=block.deposit_count, + block_hash=hash_tree_root(block)) def hash(x: bytes) -> Bytes32: # type: ignore diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 80e9d48c5..5e8ddc977 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -252,11 +252,13 @@ The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 d ###### `Eth1Block` -Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` field available. +Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` and depost contract data available. ```python class Eth1Block(Container): timestamp: uint64 + deposit_root: Root + deposit_count: uint64 # All other eth1 block fields ``` @@ -289,8 +291,14 @@ def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: period_start = voting_period_start_time(state) # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height - votes_to_consider = [get_eth1_data(block) for block in eth1_chain if - is_candidate_block(block, period_start)] + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] # Valid votes already cast during this period valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index 5bb246ed5..9ef499313 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -190,8 +190,14 @@ def test_get_eth1_vote_consensus_vote(spec, state): assert votes_length >= 3 # We need to have the majority vote state.eth1_data_votes = () - block_1 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE - 1) - block_2 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE) + block_1 = spec.Eth1Block( + timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE - 1, + deposit_count=state.eth1_data.deposit_count, + ) + block_2 = spec.Eth1Block( + timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE, + deposit_count=state.eth1_data.deposit_count, + ) eth1_chain = [block_1, block_2] eth1_data_votes = [] @@ -218,8 +224,14 @@ def test_get_eth1_vote_tie(spec, state): assert votes_length > 0 and votes_length % 2 == 0 state.eth1_data_votes = () - block_1 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE - 1) - block_2 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE) + block_1 = spec.Eth1Block( + timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE - 1, + deposit_count=state.eth1_data.deposit_count, + ) + block_2 = spec.Eth1Block( + timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE, + deposit_count=state.eth1_data.deposit_count, + ) eth1_chain = [block_1, block_2] eth1_data_votes = [] # Half votes are for block_1, another half votes are for block_2 @@ -237,6 +249,32 @@ def test_get_eth1_vote_tie(spec, state): assert eth1_data.block_hash == eth1_chain[0].hash_tree_root() +@with_all_phases +@spec_state_test +def test_get_eth1_vote_chain_in_past(spec, state): + min_new_period_epochs = get_min_new_period_epochs(spec) + for _ in range(min_new_period_epochs + 1): + next_epoch(spec, state) + + period_start = spec.voting_period_start_time(state) + votes_length = spec.get_current_epoch(state) % spec.EPOCHS_PER_ETH1_VOTING_PERIOD + assert votes_length > 0 and votes_length % 2 == 0 + + state.eth1_data_votes = () + block_1 = spec.Eth1Block( + timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE, + deposit_count=state.eth1_data.deposit_count - 1, # Chain prior to current eth1data + ) + eth1_chain = [block_1] + eth1_data_votes = [] + + state.eth1_data_votes = eth1_data_votes + eth1_data = spec.get_eth1_vote(state, eth1_chain) + + # Should be default vote + assert eth1_data == state.eth1_data + + @with_all_phases @spec_state_test def test_compute_new_state_root(spec, state):