diff --git a/eth2/beacon/epoch_processing_helpers.py b/eth2/beacon/epoch_processing_helpers.py index 8e55f623b1..5be09314b2 100644 --- a/eth2/beacon/epoch_processing_helpers.py +++ b/eth2/beacon/epoch_processing_helpers.py @@ -94,6 +94,8 @@ def _filter_attestations_by_latest_crosslinks_and_shard( shard: Shard) -> Iterable[PendingAttestationRecord]: for attestation in attestations: is_latest_crosslink_matched = attestation.data.previous_crosslink == latest_crosslink + # NOTE: v0.5.1 doesn't check is_shard_matched but it's fixed in v0.6.0 + # We implemented ahead here. is_shard_matched = attestation.data.shard == shard if is_latest_crosslink_matched and is_shard_matched: yield attestation diff --git a/eth2/beacon/state_machines/base.py b/eth2/beacon/state_machines/base.py index bd37350d07..8349222919 100644 --- a/eth2/beacon/state_machines/base.py +++ b/eth2/beacon/state_machines/base.py @@ -45,7 +45,8 @@ class BaseBeaconStateMachine(Configurable, ABC): @abstractmethod def __init__(self, chaindb: BaseBeaconChainDB, - block_root: Hash32) -> None: + block: BaseBeaconBlock, + state: BeaconState=None) -> None: pass @classmethod @@ -87,9 +88,13 @@ def create_block_from_parent(parent_block: BaseBeaconBlock, class BeaconStateMachine(BaseBeaconStateMachine): def __init__(self, chaindb: BaseBeaconChainDB, - block: BaseBeaconBlock) -> None: + block: BaseBeaconBlock, + state: BeaconState=None) -> None: self.chaindb = chaindb - self.block = block + if state is not None: + self._state = state + else: + self.block = block @property def state(self) -> BeaconState: diff --git a/eth2/beacon/state_machines/forks/serenity/block_validation.py b/eth2/beacon/state_machines/forks/serenity/block_validation.py index 7b245a33ae..f86cc3bb21 100644 --- a/eth2/beacon/state_machines/forks/serenity/block_validation.py +++ b/eth2/beacon/state_machines/forks/serenity/block_validation.py @@ -60,7 +60,6 @@ from eth2.beacon.typing import ( Bitfield, Epoch, - Shard, Slot, ValidatorIndex, ) diff --git a/eth2/beacon/state_machines/forks/serenity/state_transitions.py b/eth2/beacon/state_machines/forks/serenity/state_transitions.py index 1bf5a2f2b1..08bffa5194 100644 --- a/eth2/beacon/state_machines/forks/serenity/state_transitions.py +++ b/eth2/beacon/state_machines/forks/serenity/state_transitions.py @@ -57,7 +57,7 @@ def apply_state_transition(self, else: raise Exception( f"Invariant: state.slot ({state.slot}) should be less " - "than block.slot ({block.slot}) so that state transition terminates" + f"than block.slot ({block.slot}) so that state transition terminates" ) return state diff --git a/setup.py b/setup.py index b78f8cbbde..2d0173eccd 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ "pytest-cov==2.5.1", "pytest-watch>=4.1.0,<5", "pytest-xdist==1.18.1", + "pytest-mock==1.10.4", # only needed for p2p "pytest-asyncio-network-simulator==0.1.0a2;python_version>='3.6'", ], diff --git a/tests/eth2/beacon/state-fixtures/test_minimal_state.py b/tests/eth2/beacon/state-fixtures/test_minimal_state.py new file mode 100644 index 0000000000..f47c81be86 --- /dev/null +++ b/tests/eth2/beacon/state-fixtures/test_minimal_state.py @@ -0,0 +1,138 @@ +import os +import yaml +import pytest + +from py_ecc import bls +import ssz + +from eth2.configs import ( + Eth2Config, +) +from eth2.beacon.db.chain import BeaconChainDB +from eth2.beacon.tools.misc.ssz_vector import ( + override_vector_lengths, +) +from eth2.beacon.types.states import BeaconState +from eth2.beacon.state_machines.forks.serenity.blocks import SerenityBeaconBlock +from eth2.beacon.state_machines.forks.serenity import ( + SerenityStateMachine, +) + +# Test files +ROOT_PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + +BASE_FIXTURE_PATH = os.path.join(ROOT_PROJECT_DIR, '../../', 'eth2-fixtures', 'state') + +FILE_NAMES = [ + "sanity-check_small-config_32-vals.yaml", + "sanity-check_default-config_100-vals.yaml", +] + +# +# Mock bls verification for these tests +# +bls = bls + + +def mock_bls_verify(message_hash, pubkey, signature, domain): + return True + + +def mock_bls_verify_multiple(pubkeys, + message_hashes, + signature, + domain): + return True + + +@pytest.fixture(autouse=True) +def mock_bls(mocker, request): + if 'noautofixt' in request.keywords: + return + + mocker.patch('py_ecc.bls.verify', side_effect=mock_bls_verify) + mocker.patch('py_ecc.bls.verify_multiple', side_effect=mock_bls_verify_multiple) + + +def get_test_case(file_names): + all_test_cases = [] + for file_name in file_names: + with open(BASE_FIXTURE_PATH + '/' + file_name, 'U') as f: + # TODO: `proof_of_possession` is used in v0.5.1 spec and will be renamed to `signature` + # Trinity renamed it ahead due to py-ssz signing_root requirements + new_text = f.read().replace('proof_of_possession', 'signature') + try: + data = yaml.load(new_text) + all_test_cases += data['test_cases'] + except yaml.YAMLError as exc: + print(exc) + return all_test_cases + + +test_cases = get_test_case(FILE_NAMES) + + +@pytest.mark.parametrize("test_case", test_cases) +def test_state(base_db, test_case): + test_name = test_case['name'] + if test_name == 'test_transfer': + print('skip') + else: + execute_state_transtion(test_case, base_db) + + +def generate_config_by_dict(dict_config): + dict_config['DEPOSIT_CONTRACT_ADDRESS'] = b'\x00' * 20 + for key in list(dict_config): + if 'DOMAIN_' in key in key: + # DOMAIN is defined in SignatureDomain + dict_config.pop(key, None) + return Eth2Config(**dict_config) + + +def execute_state_transtion(test_case, base_db): + test_name = test_case['name'] + dict_config = test_case['config'] + verify_signatures = test_case['verify_signatures'] + dict_initial_state = test_case['initial_state'] + dict_blocks = test_case['blocks'] + dict_expected_state = test_case['expected_state'] + + # TODO: make it case by case + assert verify_signatures is False + + print(f"[{test_name}]") + + # Set config + config = generate_config_by_dict(dict_config) + + # Set Vector fields + override_vector_lengths(config) + + # Set pre_state + pre_state = ssz.tools.from_formatted_dict(dict_initial_state, BeaconState) + + # Set blocks + blocks = () + for dict_block in dict_blocks: + block = ssz.tools.from_formatted_dict(dict_block, SerenityBeaconBlock) + blocks += (block,) + + sm_class = SerenityStateMachine.configure( + __name__='SerenityStateMachineForTesting', + config=config, + ) + chaindb = BeaconChainDB(base_db) + + post_state = pre_state.copy() + for block in blocks: + sm = sm_class(chaindb, None, post_state) + post_state, _ = sm.import_block(block) + + # Use dict diff, easier to see the diff + dict_post_state = ssz.tools.to_formatted_dict(post_state, BeaconState) + + for key, value in dict_expected_state.items(): + if isinstance(value, list): + value = tuple(value) + assert dict_post_state[key] == value