enhance fork choice testing
This commit is contained in:
parent
0e362d36b1
commit
751738f411
|
@ -58,7 +58,7 @@ The head block root associated with a `store` is defined as `get_head(store)`. A
|
||||||
#### `Checkpoint`
|
#### `Checkpoint`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass(eq=True, frozen=True)
|
||||||
class Checkpoint(object):
|
class Checkpoint(object):
|
||||||
epoch: Epoch
|
epoch: Epoch
|
||||||
root: Hash
|
root: Hash
|
||||||
|
@ -69,13 +69,13 @@ class Checkpoint(object):
|
||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class Store(object):
|
class Store(object):
|
||||||
|
time: int
|
||||||
|
justified_checkpoint: Checkpoint
|
||||||
|
finalized_checkpoint: Checkpoint
|
||||||
blocks: Dict[Hash, BeaconBlock] = field(default_factory=dict)
|
blocks: Dict[Hash, BeaconBlock] = field(default_factory=dict)
|
||||||
block_states: Dict[Hash, BeaconState] = field(default_factory=dict)
|
block_states: Dict[Hash, BeaconState] = field(default_factory=dict)
|
||||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||||
time: int
|
|
||||||
latest_targets: Dict[ValidatorIndex, Checkpoint] = field(default_factory=dict)
|
latest_targets: Dict[ValidatorIndex, Checkpoint] = field(default_factory=dict)
|
||||||
justified_checkpoint: Checkpoint
|
|
||||||
finalized_checkpoint: Checkpoint
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_genesis_store`
|
#### `get_genesis_store`
|
||||||
|
@ -87,12 +87,12 @@ def get_genesis_store(genesis_state: BeaconState) -> Store:
|
||||||
justified_checkpoint = Checkpoint(GENESIS_EPOCH, root)
|
justified_checkpoint = Checkpoint(GENESIS_EPOCH, root)
|
||||||
finalized_checkpoint = Checkpoint(GENESIS_EPOCH, root)
|
finalized_checkpoint = Checkpoint(GENESIS_EPOCH, root)
|
||||||
return Store(
|
return Store(
|
||||||
blocks={root: genesis_block},
|
|
||||||
block_states={root: genesis_state},
|
|
||||||
checkpoint_states={justified_checkpoint: genesis_state.copy()},
|
|
||||||
time=genesis_state.genesis_time,
|
time=genesis_state.genesis_time,
|
||||||
justified_checkpoint=justified_checkpoint,
|
justified_checkpoint=justified_checkpoint,
|
||||||
finalized_checkpoint=finalized_checkpoint,
|
finalized_checkpoint=finalized_checkpoint,
|
||||||
|
blocks={root: genesis_block},
|
||||||
|
block_states={root: genesis_state},
|
||||||
|
checkpoint_states={justified_checkpoint: genesis_state.copy()},
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -150,13 +150,13 @@ def on_tick(store: Store, time: int) -> None:
|
||||||
def on_block(store: Store, block: BeaconBlock) -> None:
|
def on_block(store: Store, block: BeaconBlock) -> None:
|
||||||
# Make a copy of the state to avoid mutability issues
|
# Make a copy of the state to avoid mutability issues
|
||||||
parent_block = store.blocks[block.parent_root]
|
parent_block = store.blocks[block.parent_root]
|
||||||
pre_state = store.block_states[parent_block.root].copy()
|
pre_state = store.block_states[block.parent_root].copy()
|
||||||
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
|
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
|
||||||
assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT
|
assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT
|
||||||
# Add new block to the store
|
# Add new block to the store
|
||||||
store.blocks[signing_root(block)] = block
|
store.blocks[signing_root(block)] = block
|
||||||
# Check block is a descendant of the finalized block
|
# Check block is a descendant of the finalized block
|
||||||
assert get_ancestor(store, signing_root(block), store.blocks[get_epoch_start_slot(store.finalized_checkpoint)].slot) == store.finalized_checkpoint.root
|
assert get_ancestor(store, signing_root(block), store.blocks[store.finalized_checkpoint.root].slot) == store.finalized_checkpoint.root
|
||||||
# Check that block is later than the finalized epoch slot
|
# Check that block is later than the finalized epoch slot
|
||||||
assert block.slot > get_epoch_start_slot(store.finalized_checkpoint.epoch)
|
assert block.slot > get_epoch_start_slot(store.finalized_checkpoint.epoch)
|
||||||
# Check the block is valid and compute the post-state
|
# Check the block is valid and compute the post-state
|
||||||
|
@ -167,7 +167,7 @@ def on_block(store: Store, block: BeaconBlock) -> None:
|
||||||
# Update justified checkpoint
|
# Update justified checkpoint
|
||||||
if state.current_justified_epoch > store.justified_checkpoint.epoch:
|
if state.current_justified_epoch > store.justified_checkpoint.epoch:
|
||||||
store.justified_checkpoint = Checkpoint(state.current_justified_epoch, state.current_justified_root)
|
store.justified_checkpoint = Checkpoint(state.current_justified_epoch, state.current_justified_root)
|
||||||
elif state.previous_justified_epoch > store.justified_epoch:
|
elif state.previous_justified_epoch > store.justified_checkpoint.epoch:
|
||||||
store.justified_checkpoint = Checkpoint(state.previous_justified_epoch, state.previous_justified_root)
|
store.justified_checkpoint = Checkpoint(state.previous_justified_epoch, state.previous_justified_root)
|
||||||
|
|
||||||
# Update finalized checkpoint
|
# Update finalized checkpoint
|
||||||
|
@ -185,11 +185,11 @@ def on_attestation(store: Store, attestation: Attestation) -> None:
|
||||||
assert target.root in store.blocks
|
assert target.root in store.blocks
|
||||||
|
|
||||||
# Attestations cannot be from future epochs. If they are, their consideration must be delayed until the are in the past.
|
# Attestations cannot be from future epochs. If they are, their consideration must be delayed until the are in the past.
|
||||||
assert store.time >= pre_state.genesis_time + get_epoch_start_slot(target.epoch) * SECONDS_PER_SLOT
|
base_state = store.block_states[target.root].copy()
|
||||||
|
assert store.time >= base_state.genesis_time + get_epoch_start_slot(target.epoch) * SECONDS_PER_SLOT
|
||||||
|
|
||||||
# Store target checkpoint state if not yet seen
|
# Store target checkpoint state if not yet seen
|
||||||
if target not in store.checkpoint_states:
|
if target not in store.checkpoint_states:
|
||||||
base_state = store.block_states[target.root].copy()
|
|
||||||
store.checkpoint_states[target] = process_slots(base_state, get_epoch_start_slot(target.epoch))
|
store.checkpoint_states[target] = process_slots(base_state, get_epoch_start_slot(target.epoch))
|
||||||
target_state = store.checkpoint_states[target]
|
target_state = store.checkpoint_states[target]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
|
|
||||||
|
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||||
|
|
||||||
|
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||||
|
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||||
|
from eth2spec.test.helpers.state import next_slot
|
||||||
|
|
||||||
|
|
||||||
|
def run_on_attestation(spec, state, store, attestation, valid=True):
|
||||||
|
if not valid:
|
||||||
|
try:
|
||||||
|
spec.on_attestation(store, attestation)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
indexed_attestation = spec.convert_to_indexed(state, attestation)
|
||||||
|
spec.on_attestation(store, attestation)
|
||||||
|
assert (
|
||||||
|
store.latest_targets[indexed_attestation.custody_bit_0_indices[0]] ==
|
||||||
|
spec.Checkpoint(
|
||||||
|
epoch=attestation.data.target_epoch,
|
||||||
|
root=attestation.data.target_root,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_attestation(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 100
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||||
|
|
||||||
|
# store block in store
|
||||||
|
spec.on_block(store, block)
|
||||||
|
|
||||||
|
next_slot(spec, state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||||
|
run_on_attestation(spec, state, store, attestation)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_attestation_target_not_in_store(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 100
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
# move to next epoch to make block new target
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||||
|
|
||||||
|
# do not add block to store
|
||||||
|
|
||||||
|
next_slot(spec, state)
|
||||||
|
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||||
|
run_on_attestation(spec, state, store, attestation, False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_attestation_future_epoch(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 3 * spec.SECONDS_PER_SLOT
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||||
|
|
||||||
|
# store block in store
|
||||||
|
spec.on_block(store, block)
|
||||||
|
next_slot(spec, state)
|
||||||
|
|
||||||
|
# move state forward but not store
|
||||||
|
attestation_slot = block.slot + spec.SLOTS_PER_EPOCH
|
||||||
|
state.slot = attestation_slot
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, slot=state.slot)
|
||||||
|
run_on_attestation(spec, state, store, attestation, False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_attestation_same_slot(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 1 * spec.SECONDS_PER_SLOT
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||||
|
|
||||||
|
spec.on_block(store, block)
|
||||||
|
next_slot(spec, state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||||
|
run_on_attestation(spec, state, store, attestation, False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_attestation_invalid_attestation(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 3 * spec.SECONDS_PER_SLOT
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||||
|
|
||||||
|
spec.on_block(store, block)
|
||||||
|
next_slot(spec, state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||||
|
# make attestation invalid
|
||||||
|
attestation.custody_bitfield = b'\xf0' + attestation.custody_bitfield[1:]
|
||||||
|
run_on_attestation(spec, state, store, attestation, False)
|
|
@ -0,0 +1,95 @@
|
||||||
|
from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
|
||||||
|
|
||||||
|
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||||
|
|
||||||
|
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||||
|
|
||||||
|
|
||||||
|
def run_on_block(spec, state, store, block, valid=True):
|
||||||
|
if not valid:
|
||||||
|
try:
|
||||||
|
spec.on_block(store, block)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
spec.on_block(store, block)
|
||||||
|
assert store.blocks[signing_root(block)] == block
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_basic(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 100
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
assert store.time == time
|
||||||
|
|
||||||
|
# On receiving a block of `GENESIS_SLOT + 1` slot
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
run_on_block(spec, state, store, block)
|
||||||
|
|
||||||
|
# On receiving a block of next epoch
|
||||||
|
store.time = time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
block.slot += spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
run_on_block(spec, state, store, block)
|
||||||
|
|
||||||
|
# TODO: add tests for justified_root and finalized_root
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_block_future_block(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
|
||||||
|
# do not tick time
|
||||||
|
|
||||||
|
# Fail receiving block of `GENESIS_SLOT + 1` slot
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
run_on_block(spec, state, store, block, False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_block_bad_parent_root(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 100
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
# Fail receiving block of `GENESIS_SLOT + 1` slot
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
block.parent_root = b'\x45' * 32
|
||||||
|
run_on_block(spec, state, store, block, False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@with_state
|
||||||
|
@bls_switch
|
||||||
|
def test_on_block_before_finalized(spec, state):
|
||||||
|
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store = spec.get_genesis_store(state)
|
||||||
|
time = 100
|
||||||
|
spec.on_tick(store, time)
|
||||||
|
|
||||||
|
store.finalized_checkpoint = spec.Checkpoint(epoch=store.finalized_checkpoint.epoch + 2, root=store.finalized_checkpoint.root)
|
||||||
|
|
||||||
|
# Fail receiving block of `GENESIS_SLOT + 1` slot
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
run_on_block(spec, state, store, block, False)
|
|
@ -1,60 +1 @@
|
||||||
from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
|
|
||||||
|
|
||||||
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
|
||||||
|
|
||||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
|
||||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
|
||||||
from eth2spec.test.helpers.state import next_slot
|
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
|
||||||
@with_state
|
|
||||||
@bls_switch
|
|
||||||
def test_basic(spec, state):
|
|
||||||
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
|
||||||
|
|
||||||
# Initialization
|
|
||||||
store = spec.get_genesis_store(state)
|
|
||||||
blocks = []
|
|
||||||
time = 100
|
|
||||||
spec.on_tick(store, time)
|
|
||||||
assert store.time == time
|
|
||||||
|
|
||||||
# On receiving a block of `GENESIS_SLOT + 1` slot
|
|
||||||
block = build_empty_block_for_next_slot(spec, state)
|
|
||||||
blocks.append(block)
|
|
||||||
spec.on_block(store, block)
|
|
||||||
assert store.blocks[signing_root(block)] == block
|
|
||||||
|
|
||||||
# On receiving a block of next epoch
|
|
||||||
store.time = time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
|
||||||
block = build_empty_block_for_next_slot(spec, state)
|
|
||||||
block.slot += spec.SLOTS_PER_EPOCH
|
|
||||||
blocks.append(block)
|
|
||||||
|
|
||||||
spec.on_block(store, block)
|
|
||||||
assert store.blocks[signing_root(block)] == block
|
|
||||||
|
|
||||||
# TODO: add tests for justified_root and finalized_root
|
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
|
||||||
@with_state
|
|
||||||
@bls_switch
|
|
||||||
def test_on_attestation(spec, state):
|
|
||||||
store = spec.get_genesis_store(state)
|
|
||||||
time = 100
|
|
||||||
spec.on_tick(store, time)
|
|
||||||
|
|
||||||
next_slot(spec, state)
|
|
||||||
|
|
||||||
attestation = get_valid_attestation(spec, state, slot=1)
|
|
||||||
indexed_attestation = spec.convert_to_indexed(state, attestation)
|
|
||||||
spec.on_attestation(store, attestation)
|
|
||||||
assert (
|
|
||||||
store.latest_targets[indexed_attestation.custody_bit_0_indices[0]] ==
|
|
||||||
spec.Target(
|
|
||||||
epoch=attestation.data.target_epoch,
|
|
||||||
root=attestation.data.target_root,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue