Add sandwich test scenarios
This commit is contained in:
parent
c3c24fb13a
commit
dc9db78a67
|
@ -60,7 +60,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state):
|
|||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
|
@ -89,7 +89,7 @@ def test_ex_ante_scenario_1_with_boost(spec, state):
|
|||
|
||||
# Attestation_1 received at N+2 — C is head
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
@ -188,7 +188,7 @@ def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_ro
|
|||
|
||||
|
||||
@with_all_phases
|
||||
@with_presets([MAINNET], reason="to create larger committee")
|
||||
@with_presets([MAINNET], reason="to create non-duplicate committee")
|
||||
@spec_state_test
|
||||
def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state):
|
||||
"""
|
||||
|
@ -258,7 +258,7 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, st
|
|||
@spec_configured_state_test({
|
||||
'PROPOSER_SCORE_BOOST': 0,
|
||||
})
|
||||
@with_presets([MAINNET], reason="to create larger committee")
|
||||
@with_presets([MAINNET], reason="to create non-duplicate committee")
|
||||
def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec, state):
|
||||
"""
|
||||
Adversarial attestations > proposer boost
|
||||
|
@ -327,3 +327,386 @@ def test_ex_ante_attestations_is_greater_than_proposer_boost_without_boost(spec,
|
|||
assert spec.get_head(store) == signed_block_b.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ex_ante_sandwich_without_attestations_with_boost(spec, state):
|
||||
"""
|
||||
Simple Sandwich test with boost and no attestations.
|
||||
Obejcts:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head (with boost)
|
||||
Block D received at N+3 — D is head (with boost)
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||
yield 'anchor_state', state
|
||||
yield 'anchor_block', anchor_block
|
||||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||
assert store.time == current_time
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - D is head, it has proposer score boost
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_d.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
@with_all_phases
|
||||
@spec_configured_state_test({
|
||||
'PROPOSER_SCORE_BOOST': 0,
|
||||
})
|
||||
def test_ex_ante_sandwich_without_attestations_without_boost(spec, state):
|
||||
"""
|
||||
Simple Sandwich test with no boost and no attestations.
|
||||
Obejcts:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — B or C is head (chosen lexicographically; without boost)
|
||||
Block D received at N+3 — D or C is head (chosen lexicographically; without boost)
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||
yield 'anchor_state', state
|
||||
yield 'anchor_block', anchor_block
|
||||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||
assert store.time == current_time
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2
|
||||
# Block B and C have the same score 0. Use a lexicographical order for tie-breaking.
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root():
|
||||
assert spec.get_head(store) == signed_block_b.message.hash_tree_root()
|
||||
else:
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3
|
||||
# Block D and C have the same score 0. Use a lexicographical order for tie-breaking.
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
if signed_block_d.message.hash_tree_root() >= signed_block_c.message.hash_tree_root():
|
||||
assert spec.get_head(store) == signed_block_d.message.hash_tree_root()
|
||||
else:
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ex_ante_sandwich_with_honest_attestation_with_boost(spec, state):
|
||||
"""
|
||||
Boosting necessary to sandwich attack.
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Attestation_1 (Block C); size 1 - slot N+2 (honest)
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head
|
||||
Attestation_1 received at N+3 — C is head
|
||||
Block D received at N+3 — D is head
|
||||
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||
yield 'anchor_state', state
|
||||
yield 'anchor_block', anchor_block
|
||||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||
assert store.time == current_time
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Attestation_1 at N+2 voting for block C
|
||||
def _filter_participant_set(participants):
|
||||
return [next(iter(participants))]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root()
|
||||
assert len([i for i in attestation.aggregation_bits if i == 1]) == 1
|
||||
sign_attestation(spec, state_c, attestation)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_1 received at N+3 — C is head
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - D is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_d.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
@with_all_phases
|
||||
@spec_configured_state_test({
|
||||
'PROPOSER_SCORE_BOOST': 0,
|
||||
})
|
||||
def test_ex_ante_sandwich_with_honest_attestation_without_boost(spec, state):
|
||||
"""
|
||||
Boost necessary to sandwich attack: no boost, so not successful here.
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Attestation_1 (Block C); size 1 - slot N+2 (honest)
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — B or C is head (chosen lexicographically)
|
||||
Attestation_1 received at N+3 — C is head
|
||||
Block D received at N+3 — C is head
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||
yield 'anchor_state', state
|
||||
yield 'anchor_block', anchor_block
|
||||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||
assert store.time == current_time
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2
|
||||
# Block B and C have the same score, 0. Use a lexicographical order for tie-breaking.
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
if signed_block_b.message.hash_tree_root() >= signed_block_c.message.hash_tree_root():
|
||||
assert spec.get_head(store) == signed_block_b.message.hash_tree_root()
|
||||
else:
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_1 at N+2 voting for block C
|
||||
def _filter_participant_set(participants):
|
||||
return [next(iter(participants))]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root()
|
||||
assert len([i for i in attestation.aggregation_bits if i == 1]) == 1
|
||||
sign_attestation(spec, state_c, attestation)
|
||||
|
||||
# Attestation_1 received at N+3 - C is head
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - C is head, because block D has no proposer boost
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
@with_all_phases
|
||||
@with_presets([MAINNET], reason="to create non-duplicate committee")
|
||||
@spec_state_test
|
||||
def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state):
|
||||
"""
|
||||
Boost not sufficient to sandwich attack.
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Attestation_set_1 (Block C); size proposer_boost + 1 - slot N+2
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head
|
||||
Attestation_set_1 received — C is head
|
||||
Block D received at N+3 — C is head
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||
yield 'anchor_state', state
|
||||
yield 'anchor_block', anchor_block
|
||||
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, current_time, test_steps)
|
||||
assert store.time == current_time
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_set_1 at N+2 voting for block C
|
||||
proposer_boost_root = signed_block_c.message.hash_tree_root()
|
||||
root = signed_block_c.message.hash_tree_root()
|
||||
participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root)
|
||||
|
||||
def _filter_participant_set(participants):
|
||||
return [index for i, index in enumerate(participants) if i < participant_num]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root()
|
||||
sign_attestation(spec, state_c, attestation)
|
||||
|
||||
# Attestation_1 received at N+3 — B is head because B's attestation_score > C's proposer_score.
|
||||
# (B's proposer_score = C's attestation_score = 0)
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - C is head, D's boost not sufficient!
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
|
Loading…
Reference in New Issue