diff --git a/tests/all_tests.nim b/tests/all_tests.nim index b39f162b2b..cbb357d05a 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -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 diff --git a/tests/official/fixtures_utils.nim b/tests/official/fixtures_utils.nim index 5f2f242af2..7f8676e8d5 100644 --- a/tests/official/fixtures_utils.nim +++ b/tests/official/fixtures_utils.nim @@ -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".} \ No newline at end of file + {.error: "Not implemented".} diff --git a/tests/official/test_fixture_bls.nim b/tests/official/test_fixture_bls.nim index c726054320..a3c3d827c2 100644 --- a/tests/official/test_fixture_bls.nim +++ b/tests/official/test_fixture_bls.nim @@ -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: diff --git a/tests/official/test_fixture_shuffling.nim b/tests/official/test_fixture_shuffling.nim index 4633714906..b0c25c247f 100644 --- a/tests/official/test_fixture_shuffling.nim +++ b/tests/official/test_fixture_shuffling.nim @@ -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: diff --git a/tests/official/test_fixture_ssz_uint.nim b/tests/official/test_fixture_ssz_uint.nim new file mode 100644 index 0000000000..3ef24bc10e --- /dev/null +++ b/tests/official/test_fixture_ssz_uint.nim @@ -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) diff --git a/tests/official/test_fixture_state.nim b/tests/official/test_fixture_state.nim index 636ad2bf2e..b4a8a98cef 100644 --- a/tests/official/test_fixture_state.nim +++ b/tests/official/test_fixture_state.nim @@ -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() \ No newline at end of file + echo " From-scratch state hash: 0x" & $initialState.hash_tree_root()