mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-12 23:34:44 +00:00
[Tests] SSZ unsigned integer + test suite genericity (#284)
* Generics over tests (https://github.com/status-im/nim-serialization/issues/4, https://github.com/status-im/nim-serialization/issues/5, https://github.com/nim-lang/Nim/issues/11225) * Skeleton of SSZ uint tests * Check all primitive uint types * Add deserialization test. "wrong length" skipped due to https://github.com/status-im/nim-beacon-chain/issues/280 * Move test types to their specific test files * Stint also sometimes throws an AssertionError for invalid ranges * Add debug path for Access denied issue in Appveyor 64-bit (https://ci.appveyor.com/project/nimbus/nim-beacon-chain/builds/25278666/job/fs8q0bcluvj2gdor#L866) * indent the Appveyor debug info
This commit is contained in:
parent
e9180c490e
commit
0926ebf8fe
@ -22,4 +22,5 @@ import # Official fixtures
|
||||
# TODO - re-enable
|
||||
#./official/test_fixture_state
|
||||
./official/test_fixture_shuffling,
|
||||
./official/test_fixture_bls
|
||||
./official/test_fixture_bls,
|
||||
./official/test_fixture_ssz_uint
|
||||
|
@ -1,26 +1,20 @@
|
||||
import
|
||||
# Status libs
|
||||
blscurve, nimcrypto, byteutils,
|
||||
byteutils,
|
||||
eth/common, serialization, json_serialization,
|
||||
# Beacon chain internals
|
||||
# submodule in nim-beacon-chain/tests/official/fixtures/
|
||||
../../beacon_chain/spec/[datatypes, crypto, digest],
|
||||
../../beacon_chain/ssz,
|
||||
# Workarounds
|
||||
endians # parseHex into uint64
|
||||
../../beacon_chain/spec/datatypes
|
||||
|
||||
export nimcrypto.toHex
|
||||
export # Workaround:
|
||||
# - https://github.com/status-im/nim-serialization/issues/4
|
||||
# - https://github.com/status-im/nim-serialization/issues/5
|
||||
# - https://github.com/nim-lang/Nim/issues/11225
|
||||
serialization.readValue
|
||||
|
||||
type
|
||||
# TODO: use ref object to avoid allocating
|
||||
# so much on the stack - pending https://github.com/status-im/nim-json-serialization/issues/3
|
||||
StateTests* = object
|
||||
title*: string
|
||||
summary*: string
|
||||
test_suite*: string
|
||||
fork*: string
|
||||
test_cases*: seq[StateTestCase]
|
||||
|
||||
|
||||
TestConstants* = object
|
||||
# TODO - 0.5.1 constants
|
||||
SHARD_COUNT*: int
|
||||
@ -69,14 +63,6 @@ type
|
||||
DOMAIN_VOLUNTARY_EXIT*: SignatureDomain
|
||||
DOMAIN_TRANSFER*: SignatureDomain
|
||||
|
||||
StateTestCase* = object
|
||||
name*: string
|
||||
config*: TestConstants
|
||||
verify_signatures*: bool
|
||||
initial_state*: BeaconState
|
||||
blocks*: seq[BeaconBlock]
|
||||
expected_state*: BeaconState
|
||||
|
||||
Tests*[T] = object
|
||||
title*: string
|
||||
summary*: string
|
||||
@ -87,51 +73,6 @@ type
|
||||
handler*: string
|
||||
test_cases*: seq[T]
|
||||
|
||||
Shuffling* = object
|
||||
seed*: Eth2Digest
|
||||
count*: uint64
|
||||
shuffled*: seq[ValidatorIndex]
|
||||
|
||||
# # TODO - but already tested in nim-blscurve
|
||||
# BLSUncompressedG2 = object
|
||||
# input*: tuple[
|
||||
# message: seq[byte],
|
||||
# domain: array[1, byte]
|
||||
# ]
|
||||
# output*: ECP2_BLS381
|
||||
|
||||
# # TODO - but already tested in nim-blscurve
|
||||
# BLSCompressedG2 = object
|
||||
# input*: tuple[
|
||||
# message: seq[byte],
|
||||
# domain: array[1, byte]
|
||||
# ]
|
||||
# output*: ECP2_BLS381
|
||||
|
||||
Domain = distinct uint64
|
||||
## Domains have custom hex serialization
|
||||
|
||||
BLSPrivToPub* = object
|
||||
input*: ValidatorPrivKey
|
||||
output*: ValidatorPubKey
|
||||
|
||||
BLSSignMsgInput = object
|
||||
privkey*: ValidatorPrivKey
|
||||
message*: seq[byte]
|
||||
domain*: Domain
|
||||
|
||||
BLSSignMsg* = object
|
||||
input*: BLSSignMsgInput
|
||||
output*: Signature
|
||||
|
||||
BLSAggSig* = object
|
||||
input*: seq[Signature]
|
||||
output*: Signature
|
||||
|
||||
BLSAggPubKey* = object
|
||||
input*: seq[ValidatorPubKey]
|
||||
output*: ValidatorPubKey
|
||||
|
||||
# #######################
|
||||
# Default init
|
||||
proc default*(T: typedesc): T = discard
|
||||
@ -150,47 +91,20 @@ proc readValue*[N: static int](r: var JsonReader, a: var array[N, byte]) {.inlin
|
||||
proc readValue*(r: var JsonReader, a: var ValidatorIndex) {.inline.} =
|
||||
a = r.readValue(uint32)
|
||||
|
||||
proc readValue*(r: var JsonReader, a: var Domain) {.inline.} =
|
||||
## Custom deserializer for Domain
|
||||
## They are uint64 stored in hex values
|
||||
# Furthermore Nim parseHex doesn't support uint
|
||||
# until https://github.com/nim-lang/Nim/pull/11067
|
||||
# (0.20)
|
||||
let be_uint = hexToPaddedByteArray[8](r.readValue(string))
|
||||
bigEndian64(a.addr, be_uint.unsafeAddr)
|
||||
|
||||
proc readValue*(r: var JsonReader, a: var seq[byte]) {.inline.} =
|
||||
## Custom deserializer for seq[byte]
|
||||
a = hexToSeqByte(r.readValue(string))
|
||||
|
||||
template parseTestsImpl(T: untyped) {.dirty.} =
|
||||
# TODO: workaround typedesc/generics
|
||||
# being broken with nim-serialization
|
||||
# - https://github.com/status-im/nim-serialization/issues/4
|
||||
# - https://github.com/status-im/nim-serialization/issues/5
|
||||
proc parseTests*(jsonPath: string, T: typedesc): Tests[T] =
|
||||
try:
|
||||
result = Json.loadFile(jsonPath, T)
|
||||
debugEcho " [Debug] Loading file: \"", jsonPath, '\"'
|
||||
result = Json.loadFile(jsonPath, Tests[T])
|
||||
except SerializationError as err:
|
||||
writeStackTrace()
|
||||
stderr.write "Json load issue for file \"", jsonPath, "\"\n"
|
||||
stderr.write err.formatMsg(jsonPath), "\n"
|
||||
quit 1
|
||||
|
||||
proc parseTestsShuffling*(jsonPath: string): Tests[Shuffling] =
|
||||
parseTestsImpl(Tests[Shuffling])
|
||||
|
||||
proc parseTestsBLSPrivToPub*(jsonPath: string): Tests[BLSPrivToPub] =
|
||||
parseTestsImpl(Tests[BLSPrivToPub])
|
||||
|
||||
proc parseTestsBLSSignMsg*(jsonPath: string): Tests[BLSSignMsg] =
|
||||
parseTestsImpl(Tests[BLSSignMsg])
|
||||
|
||||
proc parseTestsBLSAggSig*(jsonPath: string): Tests[BLSAggSig] =
|
||||
parseTestsImpl(Tests[BLSAggSig])
|
||||
|
||||
proc parseTestsBLSAggPubKey*(jsonPath: string): Tests[BLSAggPubKey] =
|
||||
parseTestsImpl(Tests[BLSAggPubKey])
|
||||
|
||||
# #######################
|
||||
# Mocking helpers
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/75f0af45bb0613bb406fc72d10266cee4cfb402a/tests/phase0/helpers.py#L107
|
||||
@ -216,4 +130,4 @@ proc build_empty_block_for_next_slot*(state: BeaconState): BeaconBlock =
|
||||
# prev_root,
|
||||
# BeaconBlockBody()
|
||||
# )
|
||||
{.error: "Not implemented".}
|
||||
{.error: "Not implemented".}
|
||||
|
@ -7,16 +7,67 @@
|
||||
|
||||
import
|
||||
# Standard libs
|
||||
ospaths, strutils, json, unittest,
|
||||
# Third parties
|
||||
|
||||
ospaths, strutils, unittest, endians,
|
||||
# Status libs
|
||||
blscurve, byteutils,
|
||||
# Beacon chain internals
|
||||
../../beacon_chain/spec/crypto,
|
||||
# Test utilities
|
||||
./fixtures_utils
|
||||
|
||||
type
|
||||
# # TODO - but already tested in nim-blscurve
|
||||
# BLSUncompressedG2 = object
|
||||
# input*: tuple[
|
||||
# message: seq[byte],
|
||||
# domain: array[1, byte]
|
||||
# ]
|
||||
# output*: ECP2_BLS381
|
||||
|
||||
# # TODO - but already tested in nim-blscurve
|
||||
# BLSCompressedG2 = object
|
||||
# input*: tuple[
|
||||
# message: seq[byte],
|
||||
# domain: array[1, byte]
|
||||
# ]
|
||||
# output*: ECP2_BLS381
|
||||
|
||||
Domain = distinct uint64
|
||||
## Domains have custom hex serialization
|
||||
|
||||
BLSPrivToPub* = object
|
||||
input*: ValidatorPrivKey
|
||||
output*: ValidatorPubKey
|
||||
|
||||
BLSSignMsgInput = object
|
||||
privkey*: ValidatorPrivKey
|
||||
message*: seq[byte]
|
||||
domain*: Domain
|
||||
|
||||
BLSSignMsg* = object
|
||||
input*: BLSSignMsgInput
|
||||
output*: Signature
|
||||
|
||||
BLSAggSig* = object
|
||||
input*: seq[Signature]
|
||||
output*: Signature
|
||||
|
||||
BLSAggPubKey* = object
|
||||
input*: seq[ValidatorPubKey]
|
||||
output*: ValidatorPubKey
|
||||
|
||||
proc readValue*(r: var JsonReader, a: var Domain) {.inline.} =
|
||||
## Custom deserializer for Domain
|
||||
## They are uint64 stored in hex values
|
||||
# Furthermore Nim parseHex doesn't support uint
|
||||
# until https://github.com/nim-lang/Nim/pull/11067
|
||||
# (0.20)
|
||||
let be_uint = hexToPaddedByteArray[8](r.readValue(string))
|
||||
bigEndian64(a.addr, be_uint.unsafeAddr)
|
||||
|
||||
const TestFolder = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const TestsPath = "fixtures" / "json_tests" / "bls"
|
||||
|
||||
var
|
||||
blsPrivToPubTests: Tests[BLSPrivToPub]
|
||||
blsSignMsgTests: Tests[BLSSignMsg]
|
||||
@ -25,10 +76,10 @@ var
|
||||
|
||||
suite "Official - BLS tests":
|
||||
test "Parsing the official BLS tests":
|
||||
blsPrivToPubTests = parseTestsBLSPrivToPub(TestFolder / TestsPath / "priv_to_pub" / "priv_to_pub.json")
|
||||
blsSignMsgTests = parseTestsBLSSignMsg(TestFolder / TestsPath / "sign_msg" / "sign_msg.json")
|
||||
blsAggSigTests = parseTestsBLSAggSig(TestFolder / TestsPath / "aggregate_sigs" / "aggregate_sigs.json")
|
||||
blsAggPubKeyTests = parseTestsBLSAggPubKey(TestFolder / TestsPath / "aggregate_pubkeys" / "aggregate_pubkeys.json")
|
||||
blsPrivToPubTests = parseTests(TestFolder / TestsPath / "priv_to_pub" / "priv_to_pub.json", BLSPrivToPub)
|
||||
blsSignMsgTests = parseTests(TestFolder / TestsPath / "sign_msg" / "sign_msg.json", BLSSignMsg)
|
||||
blsAggSigTests = parseTests(TestFolder / TestsPath / "aggregate_sigs" / "aggregate_sigs.json", BLSAggSig)
|
||||
blsAggPubKeyTests = parseTests(TestFolder / TestsPath / "aggregate_pubkeys" / "aggregate_pubkeys.json", BLSAggPubKey)
|
||||
|
||||
test "Private to public key conversion":
|
||||
for t in blsPrivToPubTests.test_cases:
|
||||
|
@ -7,15 +7,19 @@
|
||||
|
||||
import
|
||||
# Standard library
|
||||
ospaths, strutils, json, unittest,
|
||||
# Third parties
|
||||
|
||||
ospaths, strutils, unittest,
|
||||
# Beacon chain internals
|
||||
../../beacon_chain/spec/[datatypes, validator],
|
||||
../../beacon_chain/spec/[datatypes, validator, digest],
|
||||
# Test utilities
|
||||
../testutil,
|
||||
./fixtures_utils
|
||||
|
||||
type
|
||||
Shuffling* = object
|
||||
seed*: Eth2Digest
|
||||
count*: uint64
|
||||
shuffled*: seq[ValidatorIndex]
|
||||
|
||||
const TestFolder = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
|
||||
when const_preset == "mainnet":
|
||||
@ -27,7 +31,7 @@ var shufflingTests: Tests[Shuffling]
|
||||
|
||||
suite "Official - Shuffling tests [Preset: " & preset():
|
||||
test "Parsing the official shuffling tests [Preset: " & preset():
|
||||
shufflingTests = parseTestsShuffling(TestFolder / TestsPath)
|
||||
shufflingTests = parseTests(TestFolder / TestsPath, Shuffling)
|
||||
|
||||
test "Shuffling a sequence of N validators" & preset():
|
||||
for t in shufflingTests.test_cases:
|
||||
|
115
tests/official/test_fixture_ssz_uint.nim
Normal file
115
tests/official/test_fixture_ssz_uint.nim
Normal file
@ -0,0 +1,115 @@
|
||||
# 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.
|
||||
|
||||
import
|
||||
# Standard library
|
||||
ospaths, strutils, unittest, sequtils,
|
||||
# Status libs
|
||||
stint, serialization,
|
||||
# Beacon chain internals
|
||||
../../beacon_chain/ssz,
|
||||
../../beacon_chain/spec/[datatypes, validator],
|
||||
# Test utilities
|
||||
../testutil,
|
||||
./fixtures_utils
|
||||
|
||||
type
|
||||
SSZUint* = object
|
||||
`type`*: string
|
||||
value*: string
|
||||
valid*: bool
|
||||
ssz*: seq[byte]
|
||||
tags*: seq[string]
|
||||
|
||||
const TestFolder = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const TestsPath = "fixtures" / "json_tests" / "ssz_generic" / "uint"
|
||||
|
||||
func to(val: string, T: typedesc): T =
|
||||
when T is StUint:
|
||||
val.parse(T)
|
||||
else: # result is unsigned int
|
||||
val.parse(StUint[8 * sizeof(T)]).data
|
||||
|
||||
# TODO strformat for the skipped checks
|
||||
|
||||
template checkSerialization(test: SSZUint, T: typedesc) =
|
||||
if test.valid:
|
||||
let value: T = test.value.to(T)
|
||||
let serialized = SSZ.encode(value)
|
||||
check(serialized == test.ssz)
|
||||
elif test.value != "":
|
||||
# No SSZ encoding -> expected failing serialization test
|
||||
if test.tags.anyIt(it == "uint_underflow"):
|
||||
# TODO: Stint throws RangeError for negative number parsing
|
||||
# https://github.com/status-im/nim-stint/blob/ccf87daac1eef15238ff3d6d2edb138e22180d19/stint/io.nim#L130-L132
|
||||
# TODO: Stint checks with an assert that integer is positive or zero
|
||||
# https://github.com/status-im/nim-stint/blob/ccf87daac1eef15238ff3d6d2edb138e22180d19/stint/io.nim#L35
|
||||
expect RangeError, OverflowError, AssertionError:
|
||||
let value: T = test.value.to(T)
|
||||
else:
|
||||
# TODO tag "uint_overflow" does not throw an exception at the moment
|
||||
echo " [Skipped - Serialization - TODO] tags: ", test.tags
|
||||
else:
|
||||
echo " [Skipped - Serialization - N/A] tags: ", test.tags
|
||||
|
||||
template checkDeserialization(test: SSZUint, T: typedesc) =
|
||||
if test.valid:
|
||||
let deser = SSZ.decode(test.ssz, T)
|
||||
check($deser == test.value)
|
||||
elif test.value == "":
|
||||
# No literal value -> expected failing deserialization test
|
||||
if test.tags.anyIt(it == "wrong_length"):
|
||||
expect IndexError:
|
||||
let deser = SSZ.decode(test.ssz, T)
|
||||
else:
|
||||
echo " [Skipped - Deserialization] tags: ", test.tags
|
||||
else:
|
||||
echo " [Skipped - Deserialization - N/A] tags: ", test.tags
|
||||
|
||||
proc runSSZUintTest(inputTests: Tests[SSZUint]) =
|
||||
# We use Stint string -> uint parser + casting
|
||||
# as it's generic over all unsigned integer size
|
||||
# and not just BiggestUint
|
||||
for test in inputTests.test_cases:
|
||||
if test.`type` == "uint8":
|
||||
test.checkSerialization(uint8)
|
||||
test.checkDeserialization(uint8)
|
||||
elif test.`type` == "uint16":
|
||||
test.checkSerialization(uint16)
|
||||
test.checkDeserialization(uint16)
|
||||
elif test.`type` == "uint32":
|
||||
test.checkSerialization(uint32)
|
||||
test.checkDeserialization(uint32)
|
||||
elif test.`type` == "uint64":
|
||||
test.checkSerialization(uint64)
|
||||
test.checkDeserialization(uint64)
|
||||
# TODO: Stint serialization
|
||||
# elif test.`type` == "uint128":
|
||||
# test.checkSerialization(StUint[128])
|
||||
# elif test.`type` == "uint256":
|
||||
# test.checkSerialization(StUint[256])
|
||||
else:
|
||||
echo " [Skipped] uint size: ", test.`type`
|
||||
|
||||
suite "Official - SSZ unsigned integer tests" & preset():
|
||||
block: # "Integers right at or beyond the bounds of the allowed value range"
|
||||
let uintBounds = parseTests(TestFolder / TestsPath / "uint_bounds.json", SSZUint)
|
||||
test uintBounds.summary & preset():
|
||||
runSSZUintTest(uintBounds)
|
||||
|
||||
block: # "Random integers chosen uniformly over the allowed value range"
|
||||
let uintRandom = parseTests(TestFolder / TestsPath / "uint_random.json", SSZUint)
|
||||
test uintRandom.summary & preset():
|
||||
runSSZUintTest(uintRandom)
|
||||
|
||||
# TODO: pending fix for https://github.com/status-im/nim-beacon-chain/issues/280
|
||||
block: # "Serialized integers that are too short or too long"
|
||||
let uintWrongLength = parseTests(TestFolder / TestsPath / "uint_wrong_length.json", SSZUint)
|
||||
test "[Skipped] " & uintWrongLength.summary & preset():
|
||||
# TODO: pending fix for https://github.com/status-im/nim-beacon-chain/issues/280
|
||||
echo " [Skipped] Pending https://github.com/status-im/nim-beacon-chain/issues/280"
|
||||
# runSSZUintTest(uintWrongLength)
|
@ -8,7 +8,7 @@
|
||||
import
|
||||
# Standard libs
|
||||
ospaths, strutils, json, unittest, strformat,
|
||||
# Third parties
|
||||
# Status libs
|
||||
byteutils,
|
||||
# Beacon chain internals
|
||||
../../beacon_chain/spec/[datatypes, crypto, digest, beaconstate],
|
||||
@ -16,17 +16,26 @@ import
|
||||
# Test utilities
|
||||
./fixtures_utils
|
||||
|
||||
type
|
||||
# TODO: recativate those tests
|
||||
State* = object
|
||||
name*: string
|
||||
config*: TestConstants
|
||||
verify_signatures*: bool
|
||||
initial_state*: BeaconState
|
||||
blocks*: seq[BeaconBlock]
|
||||
expected_state*: BeaconState
|
||||
|
||||
const TestFolder = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const TestsPath = "fixtures" / "json_tests" / "state" / "sanity-check_default-config_100-vals.json"
|
||||
|
||||
|
||||
var stateTests: StateTests
|
||||
var stateTests: Tests[State]
|
||||
suite "Official - State tests": # Initializing a beacon state from the deposits
|
||||
# Source: https://github.com/ethereum/eth2.0-specs/blob/2baa242ac004b0475604c4c4ef4315e14f56c5c7/tests/phase0/test_sanity.py#L55-L460
|
||||
test "Parsing the official state tests into Nimbus beacon types":
|
||||
stateTests = parseTests(TestFolder / TestsPath, StateTests)
|
||||
stateTests = parseTests(TestFolder / TestsPath, State)
|
||||
doAssert $stateTests.test_cases[0].name == "test_empty_block_transition"
|
||||
|
||||
|
||||
test "[For information - Non-blocking] Block root signing":
|
||||
# TODO: Currently we are unable to use the official EF tests:
|
||||
# - The provided zero signature "0x0000..." is an invalid compressed BLS signature
|
||||
@ -37,7 +46,7 @@ suite "Official - State tests": # Initializing a beacon state from the deposits
|
||||
#
|
||||
# So we only test that block header signing in Nimbus matches block header signing from the EF
|
||||
# And we can't deserialize from the raw YAML/JSON to avoid sanity checks on the signature
|
||||
|
||||
|
||||
# TODO: Move that in an actual SSZ test suite
|
||||
|
||||
block: # sanity-check_default-config_100-vals.yaml - test "test_empty_block_transition"
|
||||
@ -64,7 +73,7 @@ suite "Official - State tests": # Initializing a beacon state from the deposits
|
||||
# # TODO - assert that the constants match
|
||||
# var state: BeaconState
|
||||
# doAssert stateTests.test_cases[0].name == "test_empty_block_transition"
|
||||
|
||||
|
||||
# template tcase(): untyped {.dirty.} =
|
||||
# # Alias
|
||||
# stateTests.test_cases[0]
|
||||
@ -75,13 +84,13 @@ suite "Official - State tests": # Initializing a beacon state from the deposits
|
||||
# # Alternatively, generate one with `build_empty_block_for_next_slot`
|
||||
# let blck = tcase.blocks[0]
|
||||
# debugEcho blck.previous_block_root
|
||||
|
||||
|
||||
# let ok = updateState(state, blck, flags = {})
|
||||
# check:
|
||||
# ok
|
||||
# tcase.expected_state.eth1_data_votes.len == state.eth1_data_votes.len + 1
|
||||
# get_block_root_at_slot(tcase.expected_state, state.slot) == blck.previous_block_root
|
||||
|
||||
# get_block_root(tcase.expected_state, state.slot) == blck.previous_block_root
|
||||
|
||||
suite "[For information - non-blocking] Extra state tests":
|
||||
var initialState: BeaconState
|
||||
test "Initializing from scratch a new beacon chain with the same constants and deposit configuration as official state test 0":
|
||||
@ -111,4 +120,4 @@ suite "[For information - non-blocking] Extra state tests":
|
||||
# TODO - Add official hashes when available
|
||||
# TODO - Make that a blocking test requirement
|
||||
echo " Deserialized state hash: 0x" & $stateTests.test_cases[0].initial_state.hash_tree_root()
|
||||
echo " From-scratch state hash: 0x" & $initialState.hash_tree_root()
|
||||
echo " From-scratch state hash: 0x" & $initialState.hash_tree_root()
|
||||
|
Loading…
x
Reference in New Issue
Block a user