V0.8.3 tests part 3 - sanity checks for slots and blocks (#375)
* Add sanity check for slot processing (also impacted by https://github.com/status-im/nim-beacon-chain/issues/373) * use reportDiff also for all state tests vs EF * initial sanity checks for blocks - workaround zero signature in block headers: https://github.com/status-im/nim-beacon-chain/issues/374 * Remove generic object variant compare commented code * Add the one block state transition sanity checks * generalize blocks test to multiple blocks * simplify slots test runner * Add official epoch transitions, sanity blocks, sanity slots to the test suite * Fix index out-of-bounds in initiate_validator_exit - enable proposer slashings unittest
This commit is contained in:
parent
297e9079d4
commit
c11f37e550
|
@ -51,10 +51,12 @@ task test, "Run all tests":
|
|||
buildBinary "test_fixture_ssz_static", "tests/official/", "-r -d:release -d:chronicles_log_level=DEBUG -d:const_preset=minimal"
|
||||
buildBinary "test_fixture_ssz_static", "tests/official/", "-r -d:release -d:chronicles_log_level=DEBUG -d:const_preset=mainnet"
|
||||
|
||||
buildBinary "all_fixtures_require_ssz", "tests/official/", "-r -d:release -d:chronicles_log_level=DEBUG -d:const_preset=minimal"
|
||||
buildBinary "all_fixtures_require_ssz", "tests/official/", "-r -d:release -d:chronicles_log_level=DEBUG -d:const_preset=mainnet"
|
||||
|
||||
# State sim; getting into 3rd epoch useful
|
||||
buildBinary "state_sim", "research/", "-r -d:release", "--validators=128 --slots=24"
|
||||
|
||||
task sync_lfs_tests, "Sync LFS json tests":
|
||||
# Syncs the json test files (but not the EF yaml tests)
|
||||
exec "scripts/process_lfs.sh"
|
||||
|
||||
|
|
|
@ -128,12 +128,11 @@ func initiate_validator_exit*(state: var BeaconState,
|
|||
|
||||
# Compute exit queue epoch
|
||||
# TODO try zero-functional here
|
||||
let exit_epochs = mapIt(
|
||||
var exit_epochs = mapIt(
|
||||
filterIt(state.validators, it.exit_epoch != FAR_FUTURE_EPOCH),
|
||||
it.exit_epoch)
|
||||
var exit_queue_epoch =
|
||||
max(max(exit_epochs),
|
||||
compute_activation_exit_epoch(get_current_epoch(state)))
|
||||
exit_epochs.add compute_activation_exit_epoch(get_current_epoch(state))
|
||||
var exit_queue_epoch = max(exit_epochs)
|
||||
let exit_queue_churn = foldl(
|
||||
state.validators,
|
||||
a + (if b.exit_epoch == exit_queue_epoch: 1'u64 else: 0'u64),
|
||||
|
|
|
@ -53,7 +53,10 @@ import
|
|||
export
|
||||
json_serialization
|
||||
|
||||
export blscurve.init, blscurve.getBytes, blscurve.combine, blscurve.`$`, blscurve.`==`
|
||||
export
|
||||
blscurve.init, blscurve.getBytes, blscurve.combine,
|
||||
blscurve.`$`, blscurve.`==`,
|
||||
blscurve.Signature
|
||||
|
||||
type
|
||||
BlsValueType* = enum
|
||||
|
@ -232,8 +235,13 @@ proc readValue*(reader: var JsonReader, value: var ValidatorPubKey) {.inline.} =
|
|||
|
||||
proc writeValue*(writer: var JsonWriter, value: ValidatorSig) {.inline.} =
|
||||
when value is BlsValue:
|
||||
doAssert value.kind == Real
|
||||
writer.writeValue($value.blsValue)
|
||||
if value.kind == Real:
|
||||
writer.writeValue($value.blsValue)
|
||||
else:
|
||||
# Workaround: https://github.com/status-im/nim-beacon-chain/issues/374
|
||||
let asHex = toHex(value.blob, true)
|
||||
# echo "[Warning] writing raw opaque signature: ", asHex
|
||||
writer.writeValue(asHex)
|
||||
else:
|
||||
writer.writeValue($value)
|
||||
|
||||
|
@ -293,4 +301,3 @@ proc writeValue*(writer: var JsonWriter, value: Signature) {.inline.} =
|
|||
|
||||
proc readValue*(reader: var JsonReader, value: var Signature) {.inline.} =
|
||||
value = Signature.init(reader.readValue(string))
|
||||
|
||||
|
|
|
@ -64,8 +64,11 @@ proc processBlockHeader(
|
|||
# state_root: zeroed, overwritten in the next `process_slot` call
|
||||
body_root: hash_tree_root(blck.body),
|
||||
# signature is always zeroed
|
||||
# TODO - Pure BLSSig cannot be zero: https://github.com/status-im/nim-beacon-chain/issues/374
|
||||
signature: BlsValue[Signature](kind: OpaqueBlob)
|
||||
)
|
||||
|
||||
|
||||
# Verify proposer is not slashed
|
||||
let proposer =
|
||||
state.validators[get_beacon_proposer_index(state, stateCache)]
|
||||
|
|
|
@ -24,7 +24,8 @@ import # Refactor state transition unit tests
|
|||
./spec_block_processing/test_process_attestation,
|
||||
./spec_epoch_processing/test_process_crosslinks
|
||||
|
||||
import # Official fixtures
|
||||
import # Official fixtures that don't require SSZ parsing of invalid BLS signatures
|
||||
# https://github.com/status-im/nim-beacon-chain/issues/374
|
||||
./official/test_fixture_shuffling,
|
||||
./official/test_fixture_bls,
|
||||
./official/test_fixture_ssz_uint
|
||||
|
|
|
@ -7,66 +7,30 @@
|
|||
|
||||
import
|
||||
macros,
|
||||
nimcrypto/utils,
|
||||
../../beacon_chain/spec/[datatypes, crypto, digest]
|
||||
# digest is necessary for them to be printed as hex
|
||||
|
||||
# Define comparison of object variants for BLSValue
|
||||
# https://github.com/nim-lang/Nim/issues/6676
|
||||
# (fully generic available - see also https://github.com/status-im/nim-beacon-chain/commit/993789bad684721bd7c74ea14b35c2d24dbb6e51)
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
proc processNode(arg, a,b, result: NimNode) =
|
||||
case arg.kind
|
||||
of nnkIdentDefs:
|
||||
let field = arg[0]
|
||||
result.add quote do:
|
||||
if `a`.`field` != `b`.`field`:
|
||||
return false
|
||||
of nnkRecCase:
|
||||
let kindField = arg[0][0]
|
||||
processNode(arg[0], a,b, result)
|
||||
let caseStmt = nnkCaseStmt.newTree(newDotExpr(a, kindField))
|
||||
for i in 1 ..< arg.len:
|
||||
let inputBranch = arg[i]
|
||||
let outputBranch = newTree(inputBranch.kind)
|
||||
let body = newStmtList()
|
||||
if inputBranch.kind == nnkOfBranch:
|
||||
outputBranch.add inputBranch[0]
|
||||
processNode(inputBranch[1], a,b, body)
|
||||
else:
|
||||
inputBranch.expectKind nnkElse
|
||||
processNode(inputBranch[0], a,b, body)
|
||||
outputBranch.add body
|
||||
caseStmt.add outputBranch
|
||||
result.add caseStmt
|
||||
of nnkRecList:
|
||||
for child in arg:
|
||||
child.expectKind {nnkIdentDefs, nnkRecCase}
|
||||
processNode(child, a,b, result)
|
||||
proc `==`*[T](a, b: BlsValue[T]): bool =
|
||||
## We sometimes need to compare real BlsValue
|
||||
## from parsed opaque blobs that are not really on the BLS curve
|
||||
## and full of zeros
|
||||
if a.kind == Real:
|
||||
if b.kind == Real:
|
||||
a.blsvalue == b.blsValue
|
||||
else:
|
||||
$a.blsvalue == toHex(b.blob, true)
|
||||
else:
|
||||
arg.expectKind {nnkIdentDefs, nnkRecCase, nnkRecList}
|
||||
if b.kind == Real:
|
||||
toHex(a.blob, true) == $b.blsValue
|
||||
else:
|
||||
a.blob == b.blob
|
||||
|
||||
macro myCompareImpl(a,b: typed): untyped =
|
||||
a.expectKind nnkSym
|
||||
b.expectKind nnkSym
|
||||
assert sameType(a, b)
|
||||
|
||||
let typeImpl = a.getTypeImpl
|
||||
var checks = newSeq[NimNode]()
|
||||
|
||||
# uncomment to debug
|
||||
# echo typeImpl.treeRepr
|
||||
|
||||
result = newStmtList()
|
||||
processNode(typeImpl[2], a, b, result)
|
||||
|
||||
result.add quote do:
|
||||
return true
|
||||
|
||||
# uncomment to debug
|
||||
# echo result.repr
|
||||
|
||||
proc `==`*[T](a,b: BlsValue[T]): bool =
|
||||
myCompareImpl(a,b)
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
# This tool inspects and compare 2 instances of a type recursively
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2018 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.
|
||||
|
||||
# All non-pure SSZ tests that require the -d:ssz_testing
|
||||
# to ignore invalid BLS signature in EF test vectors
|
||||
# https://github.com/status-im/nim-beacon-chain/issues/374
|
||||
|
||||
import
|
||||
./test_fixture_sanity_slots,
|
||||
./test_fixture_sanity_blocks,
|
||||
./test_fixture_state_transition_epoch
|
|
@ -0,0 +1 @@
|
|||
-d:"ssz_testing"
|
|
@ -0,0 +1,111 @@
|
|||
# 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],
|
||||
../../beacon_chain/[ssz, state_transition, extras],
|
||||
# Test utilities
|
||||
../testutil,
|
||||
./fixtures_utils,
|
||||
../helpers/debug_state,
|
||||
../mocking/mock_blocks
|
||||
|
||||
const SanityBlocksDir = SszTestsDir/const_preset/"phase0"/"sanity"/"blocks"/"pyspec_tests"
|
||||
|
||||
template runValidTest(testName: string, identifier: untyped, num_blocks: int): 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 = SanityBlocksDir / astToStr(identifier)
|
||||
|
||||
proc `testImpl _ blck _ identifier`() =
|
||||
test "[Valid] " & testName & " (" & astToStr(identifier) & ")":
|
||||
var stateRef, postRef: ref BeaconState
|
||||
new stateRef
|
||||
new postRef
|
||||
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
|
||||
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState)
|
||||
|
||||
var success = true
|
||||
for i in 0 ..< num_blocks:
|
||||
let blck = parseTest(testDir/"blocks_" & $i & ".ssz", SSZ, BeaconBlock)
|
||||
|
||||
# TODO: The EF is using invalid BLS keys so we can't verify them
|
||||
let success = state_transition(stateRef[], blck, flags = {skipValidation})
|
||||
doAssert success, "Failure when applying block " & $i
|
||||
|
||||
# Checks:
|
||||
# check: stateRef.hash_tree_root() == postRef.hash_tree_root()
|
||||
reportDiff(stateRef, postRef)
|
||||
|
||||
`testImpl _ blck _ identifier`()
|
||||
|
||||
suite "Official - Sanity - Blocks " & preset():
|
||||
test "[Invalid] Previous slot block transition (prev_slot_block_transition)":
|
||||
const testDir = SanityBlocksDir/"prev_slot_block_transition"
|
||||
var stateRef: ref BeaconState
|
||||
new stateRef
|
||||
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
|
||||
|
||||
let blck = parseTest(testDir/"blocks_0.ssz", SSZ, BeaconBlock)
|
||||
|
||||
# Check that a block build for an old slot cannot be used for state transition
|
||||
expect(AssertionError):
|
||||
# assert in process_slots. This should not be triggered
|
||||
# for blocks from block_pool/network
|
||||
let done = state_transition(stateRef[], blck, flags = {skipValidation})
|
||||
|
||||
runValidTest("Same slot block transition", same_slot_block_transition, 1)
|
||||
runValidTest("Empty block transition", empty_block_transition, 1)
|
||||
|
||||
when false: # TODO: we need more granular skipValidation
|
||||
test "[Invalid] Invalid state root":
|
||||
const testDir = SanityBlocksDir/"invalid_state_root"
|
||||
var stateRef: ref BeaconState
|
||||
new stateRef
|
||||
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
|
||||
|
||||
let blck = parseTest(testDir/"blocks_0.ssz", SSZ, BeaconBlock)
|
||||
|
||||
expect(AssertionError):
|
||||
let done = state_transition(stateRef[], blck, flags = {skipValidation})
|
||||
|
||||
runValidTest("Skipped Slots", skipped_slots, 1)
|
||||
when false: # TODO: failing due to state_roots[8]
|
||||
runValidTest("Empty epoch transition", empty_epoch_transition, 1)
|
||||
runValidTest("Empty epoch transition not finalizing", empty_epoch_transition_not_finalizing, 1)
|
||||
runValidTest("Proposer slashing", proposer_slashing, 1)
|
||||
when false: # TODO: Assert spec/crypto.nim(156, 12) `x.kind == Real and other.kind == Real`
|
||||
runValidTest("Attester slashing", attester_slashing, 1)
|
||||
|
||||
# TODO: Expected deposit in block
|
||||
|
||||
when false: # TODO: Assert .spec/crypto.nim(175, 14) `sig.kind == Real and pubkey.kind == Real`
|
||||
runValidTest("Deposit in block", deposit_in_block, 1)
|
||||
runValidTest("Deposit top up", deposit_top_up, 1)
|
||||
|
||||
when false: # TODO: Assert spec/crypto.nim(156, 12) `x.kind == Real and other.kind == Real`
|
||||
runValidTest("Attestation", attestation, 2)
|
||||
when false: # TODO: failing due to state_roots[8]
|
||||
runValidTest("Voluntary exit", voluntary_exit, 2)
|
||||
runValidTest("Balance-driven status transitions", balance_driven_status_transitions, 1)
|
||||
|
||||
when false: # TODO: `stateRef3946003.balances[idx3953625] == postRef3946005.balances[idx3953625]`
|
||||
# stateRef3946003.balances[0] = 31998855136
|
||||
# postRef3946005.balances[0] = 31997418334
|
||||
runValidTest("Historical batch", historical_batch, 1)
|
||||
|
||||
when false: # TODO: `stateRef3870603.block_roots[idx3874628] == postRef3870605.block_roots[idx3874628]`
|
||||
# stateRef3856003.block_roots[16] = 06013007F8A1D4E310344192C5DF6157B1F9F0F5B3A8404103ED822DF47CD85D
|
||||
# postRef3856005.block_roots[16] = 73F47FF01C106CC82BF839C953C4171E019A22590D762076306F4CEE1CB77583
|
||||
runValidTest("ETH1 data votes consensus", eth1_data_votes_consensus, 17)
|
||||
runValidTest("ETH1 data votes no consensus", eth1_data_votes_no_consensus, 16)
|
|
@ -0,0 +1 @@
|
|||
-d:"ssz_testing"
|
|
@ -0,0 +1,58 @@
|
|||
# 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,
|
||||
../../beacon_chain/state_transition,
|
||||
# Test utilities
|
||||
../testutil,
|
||||
./fixtures_utils,
|
||||
../helpers/debug_state
|
||||
|
||||
const SanitySlotsDir = SszTestsDir/const_preset/"phase0"/"sanity"/"slots"/"pyspec_tests"
|
||||
|
||||
template runTest(testName: string, identifier: untyped, num_slots: uint64): 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 = SanitySlotsDir / astToStr(identifier)
|
||||
|
||||
proc `testImpl _ slots _ identifier`() =
|
||||
test "Slots - " & testName & " (" & astToStr(identifier) & ")":
|
||||
var stateRef, postRef: ref BeaconState
|
||||
new stateRef
|
||||
new postRef
|
||||
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
|
||||
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState)
|
||||
|
||||
process_slots(stateRef[], stateRef.slot + num_slots)
|
||||
# check: stateRef.hash_tree_root() == postRef.hash_tree_root()
|
||||
reportDiff(stateRef, postRef)
|
||||
|
||||
`testImpl _ slots _ identifier`()
|
||||
|
||||
# 1 slot
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
suite "Official - Sanity - Slots " & preset():
|
||||
runTest("Advance 1 slot", slots_1, 1)
|
||||
runTest("Advance 2 slots", slots_2, 2)
|
||||
|
||||
when false: # TODO: issue in active_index_roots - https://github.com/status-im/nim-beacon-chain/issues/373
|
||||
runTest("Advance an empty epoch", empty_epoch, SLOTS_PER_EPOCH)
|
||||
|
||||
when false: # TODO: issue in state_roots
|
||||
const DoubleEpoch = SLOTS_PER_EPOCH.uint64*2 # workaround undeclared identifier "double_empty_epoch"
|
||||
runTest("Advance 2 empty epochs", double_empty_epoch, DoubleEpoch)
|
||||
|
||||
# This starts in the middle of an epoch
|
||||
runTest("Advance over an epoch boundary", over_epoch_boundary, SLOTS_PER_EPOCH)
|
|
@ -0,0 +1 @@
|
|||
-d:"ssz_testing"
|
|
@ -12,7 +12,8 @@ import
|
|||
../../beacon_chain/spec/[datatypes, validator, state_transition_epoch],
|
||||
# Test utilities
|
||||
../testutil,
|
||||
./fixtures_utils
|
||||
./fixtures_utils,
|
||||
../helpers/debug_state
|
||||
|
||||
from ../../beacon_chain/spec/beaconstate import process_registry_updates
|
||||
# XXX: move to state_transition_epoch?
|
||||
|
@ -30,7 +31,7 @@ template runSuite(suiteDir, testName: string, transitionProc: untyped{ident}, us
|
|||
# https://github.com/nim-lang/Nim/issues/12084#issue-486866402
|
||||
|
||||
proc `suiteImpl _ transitionProc`() =
|
||||
suite "Official - Epoch Processing - " & testName & " [Preset: " & preset():
|
||||
suite "Official - Epoch Processing - " & testName & preset():
|
||||
for testDir in walkDirRec(suiteDir, yieldFilter = {pcDir}):
|
||||
|
||||
let unitTestName = testDir.rsplit(DirSep, 1)[1]
|
||||
|
@ -47,7 +48,7 @@ template runSuite(suiteDir, testName: string, transitionProc: untyped{ident}, us
|
|||
else:
|
||||
transitionProc(stateRef[])
|
||||
|
||||
check: stateRef.hash_tree_root() == postRef.hash_tree_root()
|
||||
reportDiff(stateRef, postRef)
|
||||
|
||||
`suiteImpl _ transitionProc`()
|
||||
|
||||
|
|
Loading…
Reference in New Issue