From 97d7cf519063f306039fa0e9e4958e4033932aa0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 Nov 2019 10:58:45 -0700 Subject: [PATCH] further test bounce attack --- configs/mainnet.yaml | 5 ++ configs/minimal.yaml | 6 ++ specs/core/0_fork-choice.md | 2 +- .../test/fork_choice/test_on_block.py | 71 +++++++++++++++++++ .../eth2spec/test/fork_choice/test_on_tick.py | 62 ++++++++++++++-- 5 files changed, 141 insertions(+), 5 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index af446d575..e1b0faa3c 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -23,6 +23,11 @@ MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 65536 MIN_GENESIS_TIME: 1578009600 +# Fork Choice +# --------------------------------------------------------------- +# 2**3 (= 8) +SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8 + # Deposit contract # --------------------------------------------------------------- diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 53599e83a..fbc961ab1 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -21,6 +21,12 @@ MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64 # Jan 3, 2020 MIN_GENESIS_TIME: 1578009600 +# +# +# Fork Choice +# --------------------------------------------------------------- +# 2**1 (= 1) +SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2 # Deposit contract diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 722695f23..f2bb64f2a 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -150,7 +150,7 @@ def get_head(store: Store) -> Hash: def should_update_justified_checkpoint(store: Store, justified_checkpoint: Checkpoint) -> bool: current_epoch = compute_epoch_at_slot(get_current_slot(store)) - if get_current_slot(store) - compute_start_slot_at_epoch(current_epoch) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: + if get_current_slot(store) % SLOTS_PER_EPOCH < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: return True justified_block = store.blocks[justified_checkpoint.root] diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py index 918c0f79e..36e61f0b5 100644 --- a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py +++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py @@ -132,3 +132,74 @@ def test_on_block_before_finalized(spec, state): block = build_empty_block_for_next_slot(spec, state) state_transition_and_sign_block(spec, state, block) run_on_block(spec, store, block, False) + + +@with_all_phases +@spec_state_test +def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): + # Initialization + store = spec.get_genesis_store(state) + time = 100 + spec.on_tick(store, time) + + next_epoch(spec, state) + spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT) + state, store, last_block = apply_next_epoch_with_attestations(spec, state, store) + next_epoch(spec, state) + spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT) + last_block_root = signing_root(last_block) + + # Mock the justified checkpoint + just_state = store.block_states[last_block_root] + new_justified = spec.Checkpoint( + epoch=just_state.current_justified_checkpoint.epoch + 1, + root=b'\x77' * 32, + ) + just_state.current_justified_checkpoint = new_justified + + block = build_empty_block_for_next_slot(spec, just_state) + state_transition_and_sign_block(spec, deepcopy(just_state), block) + assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + run_on_block(spec, store, block) + + assert store.justified_checkpoint == new_justified + + +@with_all_phases +@spec_state_test +def test_on_block_outside_safe_slots_and_old_block(spec, state): + # Initialization + store = spec.get_genesis_store(state) + time = 100 + spec.on_tick(store, time) + + next_epoch(spec, state) + spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT) + state, store, last_block = apply_next_epoch_with_attestations(spec, state, store) + next_epoch(spec, state) + spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT) + last_block_root = signing_root(last_block) + + # Mock justified block in store + just_block = build_empty_block_for_next_slot(spec, state) + # Slot is same as justified checkpoint so does not trigger an override in the store + just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) + store.blocks[just_block.hash_tree_root()] = just_block + + # Mock the justified checkpoint + just_state = store.block_states[last_block_root] + new_justified = spec.Checkpoint( + epoch=just_state.current_justified_checkpoint.epoch + 1, + root=just_block.hash_tree_root(), + ) + just_state.current_justified_checkpoint = new_justified + + block = build_empty_block_for_next_slot(spec, just_state) + state_transition_and_sign_block(spec, deepcopy(just_state), block) + + spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.SECONDS_PER_SLOT) + assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + run_on_block(spec, store, block) + + assert store.justified_checkpoint != new_justified + assert store.queued_justified_checkpoints[0] == new_justified diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_tick.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_tick.py index cd0d20abb..2ba89ebca 100644 --- a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_tick.py +++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_tick.py @@ -32,7 +32,6 @@ def test_update_justified_single(spec, state): epoch=store.justified_checkpoint.epoch + 1, root=b'\x55' * 32, ) - store.queued_justified_checkpoints.append(new_justified) run_on_tick(spec, store, store.time + seconds_per_epoch, new_justified) @@ -57,7 +56,7 @@ def test_update_justified_multiple(spec, state): @with_all_phases @spec_state_test -def test_no_update_(spec, state): +def test_no_update_same_slot_at_epoch_boundary(spec, state): store = spec.get_genesis_store(state) seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH @@ -65,8 +64,63 @@ def test_no_update_(spec, state): epoch=store.justified_checkpoint.epoch + 1, root=b'\x55' * 32, ) - store.queued_justified_checkpoints.append(new_justified) - run_on_tick(spec, store, store.time + seconds_per_epoch, new_justified) + # set store time to already be at epoch boundary + store.time = seconds_per_epoch + run_on_tick(spec, store, store.time + 1) + + +@with_all_phases +@spec_state_test +def test_no_update_not_epoch_boundary(spec, state): + store = spec.get_genesis_store(state) + + new_justified = spec.Checkpoint( + epoch=store.justified_checkpoint.epoch + 1, + root=b'\x55' * 32, + ) + store.queued_justified_checkpoints.append(new_justified) + + run_on_tick(spec, store, store.time + spec.SECONDS_PER_SLOT) + + +@with_all_phases +@spec_state_test +def test_no_update_new_justified_equal_epoch(spec, state): + store = spec.get_genesis_store(state) + seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + + new_justified = spec.Checkpoint( + epoch=store.justified_checkpoint.epoch + 1, + root=b'\x55' * 32, + ) + store.queued_justified_checkpoints.append(new_justified) + + store.justified_checkpoint = spec.Checkpoint( + epoch=new_justified.epoch, + root=b'\44' * 32, + ) + + run_on_tick(spec, store, store.time + seconds_per_epoch) + + +@with_all_phases +@spec_state_test +def test_no_update_new_justified_later_epoch(spec, state): + store = spec.get_genesis_store(state) + seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + + new_justified = spec.Checkpoint( + epoch=store.justified_checkpoint.epoch + 1, + root=b'\x55' * 32, + ) + store.queued_justified_checkpoints.append(new_justified) + + store.justified_checkpoint = spec.Checkpoint( + epoch=new_justified.epoch + 1, + root=b'\44' * 32, + ) + + run_on_tick(spec, store, store.time + seconds_per_epoch)