Voluntary exit - split single + multiple exits proc & tests (#421)
* add test suite for voluntary exit * update API to process_voluntary_exit * Add range check of validator_index for voluntary exits * Revert to dual single + multiple voluntary exits API + enable in test suite * no cache or mocking needed
This commit is contained in:
parent
061b6e0ddf
commit
81b47f35d1
|
@ -329,53 +329,68 @@ proc processDeposits(state: var BeaconState, blck: BeaconBlock): bool =
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#voluntary-exits
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.3/specs/core/0_beacon-chain.md#voluntary-exits
|
||||||
proc processVoluntaryExits(
|
proc process_voluntary_exit*(
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
state: var BeaconState,
|
||||||
# Process ``VoluntaryExit`` transaction.
|
exit: VoluntaryExit,
|
||||||
if len(blck.body.voluntary_exits) > MAX_VOLUNTARY_EXITS:
|
flags: UpdateFlags): bool =
|
||||||
notice "Exit: too many!"
|
|
||||||
|
# Not in spec. Check that validator_index is in range
|
||||||
|
if exit.validator_index.int >= state.validators.len:
|
||||||
|
notice "Exit: invalid validator index",
|
||||||
|
index = exit.validator_index,
|
||||||
|
num_validators = state.validators.len
|
||||||
return false
|
return false
|
||||||
|
|
||||||
for exit in blck.body.voluntary_exits:
|
let validator = state.validators[exit.validator_index.int]
|
||||||
let validator = state.validators[exit.validator_index.int]
|
|
||||||
|
|
||||||
# Verify the validator is active
|
# Verify the validator is active
|
||||||
if not is_active_validator(validator, get_current_epoch(state)):
|
if not is_active_validator(validator, get_current_epoch(state)):
|
||||||
notice "Exit: validator not active"
|
notice "Exit: validator not active"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify the validator has not yet exited
|
||||||
|
if validator.exit_epoch != FAR_FUTURE_EPOCH:
|
||||||
|
notice "Exit: validator has exited"
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Exits must specify an epoch when they become valid; they are not valid
|
||||||
|
## before then
|
||||||
|
if not (get_current_epoch(state) >= exit.epoch):
|
||||||
|
notice "Exit: exit epoch not passed"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify the validator has been active long enough
|
||||||
|
if not (get_current_epoch(state) >= validator.activation_epoch +
|
||||||
|
PERSISTENT_COMMITTEE_PERIOD):
|
||||||
|
notice "Exit: not in validator set long enough"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify signature
|
||||||
|
if skipValidation notin flags:
|
||||||
|
let domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch)
|
||||||
|
if not bls_verify(
|
||||||
|
validator.pubkey,
|
||||||
|
signing_root(exit).data,
|
||||||
|
exit.signature,
|
||||||
|
domain):
|
||||||
|
notice "Exit: invalid signature"
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Verify the validator has not yet exited
|
# Initiate exit
|
||||||
if not (validator.exit_epoch == FAR_FUTURE_EPOCH):
|
initiate_validator_exit(state, exit.validator_index.ValidatorIndex)
|
||||||
notice "Exit: validator has exited"
|
|
||||||
return false
|
|
||||||
|
|
||||||
## Exits must specify an epoch when they become valid; they are not valid
|
|
||||||
## before then
|
|
||||||
if not (get_current_epoch(state) >= exit.epoch):
|
|
||||||
notice "Exit: exit epoch not passed"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify the validator has been active long enough
|
|
||||||
# TODO detect underflow
|
|
||||||
if not (get_current_epoch(state) - validator.activation_epoch >=
|
|
||||||
PERSISTENT_COMMITTEE_PERIOD):
|
|
||||||
notice "Exit: not in validator set long enough"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify signature
|
|
||||||
if skipValidation notin flags:
|
|
||||||
if not bls_verify(
|
|
||||||
validator.pubkey, signing_root(exit).data, exit.signature,
|
|
||||||
get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch)):
|
|
||||||
notice "Exit: invalid signature"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Initiate exit
|
|
||||||
initiate_validator_exit(state, exit.validator_index.ValidatorIndex)
|
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
|
proc processVoluntaryExits(state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||||
|
if len(blck.body.voluntary_exits) > MAX_VOLUNTARY_EXITS:
|
||||||
|
notice "[Block processing - Voluntary Exit]: too many exits!"
|
||||||
|
return false
|
||||||
|
for exit in blck.body.voluntary_exits:
|
||||||
|
if not process_voluntary_exit(state, exit, flags):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#transfers
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#transfers
|
||||||
proc processTransfers(state: var BeaconState, blck: BeaconBlock,
|
proc processTransfers(state: var BeaconState, blck: BeaconBlock,
|
||||||
flags: UpdateFlags, stateCache: var StateCache): bool =
|
flags: UpdateFlags, stateCache: var StateCache): bool =
|
||||||
|
@ -469,6 +484,9 @@ proc processBlock*(
|
||||||
|
|
||||||
processEth1Data(state, blck.body)
|
processEth1Data(state, blck.body)
|
||||||
|
|
||||||
|
# TODO, everything below is now in process_operations
|
||||||
|
# and implementation is per element instead of the whole seq
|
||||||
|
|
||||||
if not processProposerSlashings(state, blck, flags, stateCache):
|
if not processProposerSlashings(state, blck, flags, stateCache):
|
||||||
debug "[Block processing] Proposer slashing failure", slot = shortLog(state.slot)
|
debug "[Block processing] Proposer slashing failure", slot = shortLog(state.slot)
|
||||||
return false
|
return false
|
||||||
|
@ -486,7 +504,7 @@ proc processBlock*(
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not processVoluntaryExits(state, blck, flags):
|
if not processVoluntaryExits(state, blck, flags):
|
||||||
debug "[Block processing] Exit processing failure", slot = shortLog(state.slot)
|
debug "[Block processing - Voluntary Exit] Exit processing failure", slot = shortLog(state.slot)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not processTransfers(state, blck, flags, stateCache):
|
if not processTransfers(state, blck, flags, stateCache):
|
||||||
|
|
|
@ -14,4 +14,5 @@ import
|
||||||
./test_fixture_sanity_blocks,
|
./test_fixture_sanity_blocks,
|
||||||
./test_fixture_state_transition_epoch,
|
./test_fixture_state_transition_epoch,
|
||||||
./test_fixture_operations_attestations,
|
./test_fixture_operations_attestations,
|
||||||
./test_fixture_operations_block_header
|
./test_fixture_operations_block_header,
|
||||||
|
./test_fixture_operations_voluntary_exit
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
# 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],
|
||||||
|
../../beacon_chain/[ssz, extras],
|
||||||
|
# Test utilities
|
||||||
|
../testutil,
|
||||||
|
./fixtures_utils,
|
||||||
|
../helpers/debug_state
|
||||||
|
|
||||||
|
const OpVoluntaryExitDir = SszTestsDir/const_preset/"phase0"/"operations"/"voluntary_exit"/"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 = OpVoluntaryExitDir / astToStr(identifier)
|
||||||
|
|
||||||
|
proc `testImpl _ voluntary_exit _ 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 voluntaryExit: ref VoluntaryExit
|
||||||
|
new voluntaryExit
|
||||||
|
new stateRef
|
||||||
|
|
||||||
|
voluntaryExit[] = parseTest(testDir/"voluntary_exit.ssz", SSZ, VoluntaryExit)
|
||||||
|
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
|
||||||
|
|
||||||
|
if existsFile(testDir/"post.ssz"):
|
||||||
|
new postRef
|
||||||
|
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState)
|
||||||
|
|
||||||
|
if postRef.isNil:
|
||||||
|
let done = process_voluntary_exit(stateRef[], voluntaryExit[], flags)
|
||||||
|
doAssert done == false, "We didn't expect this invalid voluntary exit to be processed."
|
||||||
|
else:
|
||||||
|
let done = process_voluntary_exit(stateRef[], voluntaryExit[], flags)
|
||||||
|
doAssert done, "Valid voluntary exit not processed"
|
||||||
|
check: stateRef.hash_tree_root() == postRef.hash_tree_root()
|
||||||
|
reportDiff(stateRef, postRef)
|
||||||
|
|
||||||
|
`testImpl _ voluntary_exit _ identifier`()
|
||||||
|
|
||||||
|
suite "Official - Operations - Voluntary exit " & preset():
|
||||||
|
runTest(success)
|
||||||
|
runTest(invalid_signature)
|
||||||
|
runTest(success_exit_queue)
|
||||||
|
runTest(validator_exit_in_future)
|
||||||
|
runTest(validator_invalid_validator_index)
|
||||||
|
runTest(validator_not_active)
|
||||||
|
runTest(validator_already_exited)
|
||||||
|
runTest(validator_not_active_long_enough)
|
||||||
|
|
Loading…
Reference in New Issue