diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim index b2b15b84f..9e144a93e 100644 --- a/beacon_chain/spec/state_transition_block.nim +++ b/beacon_chain/spec/state_transition_block.nim @@ -140,37 +140,41 @@ func is_slashable_validator(validator: Validator, epoch: Epoch): bool = (epoch < validator.withdrawable_epoch) # https://github.com/ethereum/eth2.0-specs/blob/v0.8.3/specs/core/0_beacon-chain.md#proposer-slashings -proc process_proposer_slashing( +proc process_proposer_slashing*( state: var BeaconState, proposer_slashing: ProposerSlashing, flags: UpdateFlags, stateCache: var StateCache): bool = + if proposer_slashing.proposer_index.int >= state.validators.len: + notice "Proposer slashing: invalid proposer index" + return false + let proposer = state.validators[proposer_slashing.proposer_index.int] # Verify that the epoch is the same if not (compute_epoch_of_slot(proposer_slashing.header_1.slot) == compute_epoch_of_slot(proposer_slashing.header_2.slot)): - notice "PropSlash: epoch mismatch" + notice "Proposer slashing: epoch mismatch" return false # But the headers are different if not (proposer_slashing.header_1 != proposer_slashing.header_2): - notice "PropSlash: headers not different" + notice "Proposer slashing: headers not different" return false # Check proposer is slashable if not is_slashable_validator(proposer, get_current_epoch(state)): - notice "PropSlash: slashed proposer" + notice "Proposer slashing: slashed proposer" return false # Signatures are valid if skipValidation notin flags: - for i, header in @[proposer_slashing.header_1, proposer_slashing.header_2]: + for i, header in [proposer_slashing.header_1, proposer_slashing.header_2]: if not bls_verify( proposer.pubkey, signing_root(header).data, header.signature, get_domain( state, DOMAIN_BEACON_PROPOSER, compute_epoch_of_slot(header.slot))): - notice "PropSlash: invalid signature", + notice "Proposer slashing: invalid signature", signature_index = i return false diff --git a/tests/official/all_fixtures_require_ssz.nim b/tests/official/all_fixtures_require_ssz.nim index fa6a29a7a..3ff23d060 100644 --- a/tests/official/all_fixtures_require_ssz.nim +++ b/tests/official/all_fixtures_require_ssz.nim @@ -14,6 +14,7 @@ import ./test_fixture_sanity_blocks, ./test_fixture_state_transition_epoch, ./test_fixture_operations_attestations, + ./test_fixture_operations_attester_slashings, ./test_fixture_operations_block_header, - ./test_fixture_operations_voluntary_exit, - ./test_fixture_operations_attester_slashings + ./test_fixture_operations_proposer_slashings, + ./test_fixture_operations_voluntary_exit \ No newline at end of file diff --git a/tests/official/test_fixture_operations_proposer_slashings.nim b/tests/official/test_fixture_operations_proposer_slashings.nim new file mode 100644 index 000000000..a77a59007 --- /dev/null +++ b/tests/official/test_fixture_operations_proposer_slashings.nim @@ -0,0 +1,76 @@ +# beacon_chain +# Copyright (c) 2018-Present Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + # Standard library + os, unittest, strutils, + # Beacon chain internals + ../../beacon_chain/spec/[datatypes, state_transition_block, validator], + ../../beacon_chain/[ssz, extras], + # Test utilities + ../testutil, + ./fixtures_utils, + ../helpers/debug_state + +const OpProposerSlashingDir = SszTestsDir/const_preset/"phase0"/"operations"/"proposer_slashing"/"pyspec_tests" + +template runTest(identifier: untyped) = + # We wrap the tests in a proc to avoid running out of globals + # in the future: Nim supports up to 3500 globals + # but unittest with the macro/templates put everything as globals + # https://github.com/nim-lang/Nim/issues/12084#issue-486866402 + + const testDir = OpProposerSlashingDir / astToStr(identifier) + + proc `testImpl_proposer_slashing _ identifier`() = + + var flags: UpdateFlags + var prefix: string + if not existsFile(testDir/"meta.yaml"): + flags.incl skipValidation + if existsFile(testDir/"post.ssz"): + prefix = "[Valid] " + else: + prefix = "[Invalid] " + + test prefix & astToStr(identifier): + var stateRef, postRef: ref BeaconState + var proposerSlashing: ref ProposerSlashing + new proposerSlashing + new stateRef + + proposerSlashing[] = parseTest(testDir/"proposer_slashing.ssz", SSZ, ProposerSlashing) + stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState) + + if existsFile(testDir/"post.ssz"): + new postRef + postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState) + + var cache = get_empty_per_epoch_cache() + + if postRef.isNil: + let done = process_proposer_slashing(stateRef[], proposerSlashing[], flags, cache) + doAssert done == false, "We didn't expect this invalid proposer slashing to be processed." + else: + let done = process_proposer_slashing(stateRef[], proposerSlashing[], flags, cache) + doAssert done, "Valid proposer slashing not processed" + check: stateRef.hash_tree_root() == postRef.hash_tree_root() + reportDiff(stateRef, postRef) + + `testImpl_proposer_slashing _ identifier`() + +suite "Official - Operations - Proposer slashing " & preset(): + runTest(success) + runTest(invalid_sig_1) + runTest(invalid_sig_2) + runTest(invalid_sig_1_and_2) + runTest(invalid_proposer_index) + runTest(epochs_are_different) + runTest(headers_are_same) + runTest(proposer_is_not_activated) + runTest(proposer_is_slashed) + runTest(proposer_is_withdrawn)