Full EF state tests parsing (#242)

* Load full state test + Add json-serialization for Bitfield

* Implement empty_block_transition test (but test is missing the end state)

* Use the provided empty block instead of mocking one

* Add failing block signing test

* Tests that can't be passed now are now "for information" + indent the "information"/hash given
This commit is contained in:
Mamy Ratsimbazafy 2019-04-29 18:10:01 +02:00 committed by GitHub
parent c7e6d39279
commit 5784b2d7f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 21 deletions

View File

@ -30,7 +30,7 @@ requires "nim >= 0.19.0",
"chronos", "chronos",
"yaml", "yaml",
"libp2p", "libp2p",
"byteutils" # test only "byteutils" # test only (BitField and bytes datatypes deserialization)
### Helper functions ### Helper functions
proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") = proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =

View File

@ -1,11 +1,11 @@
import byteutils, json_serialization
type type
BitField* = object BitField* = object
## A simple bit field type that follows the semantics of the spec, with ## A simple bit field type that follows the semantics of the spec, with
## regards to bit endian operations ## regards to bit endian operations
# TODO nim-ranges contains utilities for with bitsets - could try to # TODO nim-ranges contains utilities for with bitsets - could try to
# recycle that, but there are open questions about bit endianess there. # recycle that, but there are open questions about bit endianess there.
# TODO define a json serialization.. together with spec tests?
# https://github.com/ethereum/eth2.0-tests/tree/master/state
bits*: seq[byte] bits*: seq[byte]
func ceil_div8(v: int): int = (v + 7) div 8 func ceil_div8(v: int): int = (v + 7) div 8
@ -13,6 +13,9 @@ func ceil_div8(v: int): int = (v + 7) div 8
func init*(T: type BitField, bits: int): BitField = func init*(T: type BitField, bits: int): BitField =
BitField(bits: newSeq[byte](ceil_div8(bits))) BitField(bits: newSeq[byte](ceil_div8(bits)))
proc readValue*(r: var JsonReader, a: var BitField) {.inline.} =
a.bits = r.readValue(string).hexToSeqByte()
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#get_bitfield_bit # https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#get_bitfield_bit
func get_bitfield_bit*(bitfield: BitField, i: int): bool = func get_bitfield_bit*(bitfield: BitField, i: int): bool =
# Extract the bit in ``bitfield`` at position ``i``. # Extract the bit in ``bitfield`` at position ``i``.

View File

@ -484,26 +484,33 @@ proc processBlock(
return false return false
if not processRandao(state, blck, flags): if not processRandao(state, blck, flags):
debug "[Block processing] Randao failure", slot = humaneSlotNum(state.slot)
return false return false
processEth1Data(state, blck) processEth1Data(state, blck)
if not processProposerSlashings(state, blck, flags): if not processProposerSlashings(state, blck, flags):
debug "[Block processing] Proposer slashing failure", slot = humaneSlotNum(state.slot)
return false return false
if not processAttesterSlashings(state, blck): if not processAttesterSlashings(state, blck):
debug "[Block processing] Attester slashing failure", slot = humaneSlotNum(state.slot)
return false return false
if not processAttestations(state, blck, flags): if not processAttestations(state, blck, flags):
debug "[Block processing] Attestation processing failure", slot = humaneSlotNum(state.slot)
return false return false
if not processDeposits(state, blck): if not processDeposits(state, blck):
debug "[Block processing] Deposit processing failure", slot = humaneSlotNum(state.slot)
return false return false
if not processExits(state, blck, flags): if not processExits(state, blck, flags):
debug "[Block processing] Exit processing failure", slot = humaneSlotNum(state.slot)
return false return false
if not processTransfers(state, blck, flags): if not processTransfers(state, blck, flags):
debug "[Block processing] Transfer processing failure", slot = humaneSlotNum(state.slot)
return false return false
true true

@ -1 +1 @@
Subproject commit 6517fe82f9b1a8a8d7b6f4eefdf1a3fb00d1ffab Subproject commit da49e682bf93c43ffc207e4b1b948962c5068196

View File

@ -4,7 +4,8 @@ import
eth/common, serialization, json_serialization, eth/common, serialization, json_serialization,
# Beacon chain internals # Beacon chain internals
# submodule in nim-beacon-chain/tests/official/fixtures/ # submodule in nim-beacon-chain/tests/official/fixtures/
../../beacon_chain/spec/[datatypes, crypto, digest] ../../beacon_chain/spec/[datatypes, crypto, digest],
../../beacon_chain/ssz
export nimcrypto.toHex export nimcrypto.toHex
@ -71,11 +72,7 @@ type
verify_signatures*: bool verify_signatures*: bool
initial_state*: BeaconState initial_state*: BeaconState
blocks*: seq[BeaconBlock] blocks*: seq[BeaconBlock]
expected_state*: ExpectedState expected_state*: BeaconState
ExpectedState* = object
## TODO what is this?
slot*: Slot
# ####################### # #######################
# Default init # Default init
@ -87,7 +84,7 @@ proc default*(T: typedesc): T = discard
proc readValue*[N: static int](r: var JsonReader, a: var array[N, byte]) {.inline.} = proc readValue*[N: static int](r: var JsonReader, a: var array[N, byte]) {.inline.} =
# Needed for; # Needed for;
# - BLS_WITHDRAWAL_PREFIX_BYTE # - BLS_WITHDRAWAL_PREFIX_BYTE
# - FOrk datatypes # - Fork datatypes
# TODO: are all bytes and bytearray serialized as hex? # TODO: are all bytes and bytearray serialized as hex?
# if so export that to nim-eth # if so export that to nim-eth
hexToByteArray(r.readValue(string), a) hexToByteArray(r.readValue(string), a)
@ -100,3 +97,30 @@ proc parseStateTests*(jsonPath: string): StateTest =
stderr.write "Json load issue for file \"", jsonPath, "\"\n" stderr.write "Json load issue for file \"", jsonPath, "\"\n"
stderr.write err.formatMsg(jsonPath), "\n" stderr.write err.formatMsg(jsonPath), "\n"
quit 1 quit 1
# #######################
# Mocking helpers
# https://github.com/ethereum/eth2.0-specs/blob/75f0af45bb0613bb406fc72d10266cee4cfb402a/tests/phase0/helpers.py#L107
proc build_empty_block_for_next_slot*(state: BeaconState): BeaconBlock =
## TODO: why can the official spec get away with a simple proc
# result.slot = state.slot + 1
# var previous_block_header = state.latest_block_header
# if previous_block_header.state_root == ZERO_HASH:
# previous_block_header.state_root = state.hash_tree_root()
# result.previous_block_root = signed_root(previous_block_header)
## TODO: `makeBlock` from testutil.nim
## doesn't work either due to use of fake private keys
# let prev_root = block:
# if state.latest_block_header.state_root == ZERO_HASH:
# state.hash_tree_root()
# else: state.latest_block_header.state_root
# result = makeBlock(
# state,
# prev_root,
# BeaconBlockBody()
# )
{.error: "Not implemented".}

View File

@ -6,24 +6,85 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
# Standard lib # Standard libs
ospaths, strutils, json, unittest, ospaths, strutils, json, unittest, strformat,
# Third parties
byteutils,
# Beacon chain internals # Beacon chain internals
../../beacon_chain/spec/[datatypes, crypto, digest, beaconstate], ../../beacon_chain/spec/[datatypes, crypto, digest, beaconstate],
../../beacon_chain/ssz, ../../beacon_chain/[ssz, state_transition],
# Test utilities # Test utilities
./state_test_utils ./state_test_utils
const TestFolder = currentSourcePath.rsplit(DirSep, 1)[0] const TestFolder = currentSourcePath.rsplit(DirSep, 1)[0]
const TestsPath = "fixtures" / "json_tests" / "state" / "sanity-check_default-config_100-vals-first_test.json" const TestsPath = "fixtures" / "json_tests" / "state" / "sanity-check_default-config_100-vals.json"
var stateTests: StateTest
suite "Official - State tests": # Initializing a beacon state from the deposits suite "Official - State tests": # Initializing a beacon state from the deposits
var stateTests: StateTest # 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": test "Parsing the official state tests into Nimbus beacon types":
stateTests = parseStateTests(TestFolder / TestsPath) stateTests = parseStateTests(TestFolder / TestsPath)
doAssert $stateTests.test_cases[0].name == "test_empty_block_transition" 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
# - Block headers are using that signature
# - Block processing checks that block.previous_block_root == signed_root(state.latest_block_header)
# -> Changing EF provided previous_block_root would render the block transition tests meaningless
# -> Changing the signature to a valid "0xc000..." makes all hashes/signed_root wrong ...
#
# 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"
let header = BeaconBlockHeader(
slot: Slot(4294967296),
previous_block_root: ZERO_HASH,
state_root: ZERO_HASH,
block_body_root: Eth2Digest(data:
hexToByteArray[32]("0x13f2001ff0ee4a528b3c43f63d70a997aefca990ed8eada2223ee6ec3807f7cc")
),
signature: ValidatorSig()
)
let previous_block_root = Eth2Digest(data:
hexToByteArray[32]("0x1179346f489d8be1731377cb199af5cc61faa38353e2d67e096bed182677062a")
)
echo " Expected previous block root: 0x", previous_block_root
echo " Computed header signed root: 0x", signed_root(header)
test "[For information] Print list of official tests to implement":
for i, test in stateTests.test_cases:
echo &" Test #{i:03}: {test.name}"
# test "Empty block transition":
# # 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]
# deepCopy(state, tcase.initial_state)
# # Use the provided empty block
# # 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(tcase.expected_state, state.slot) == blck.previous_block_root
suite "[For information - non-blocking] Extra state tests":
var initialState: BeaconState var initialState: BeaconState
test "Initializing from scratch a new beacon chain with the same constants and deposit configuration as official state": test "Initializing from scratch a new beacon chain with the same constants and deposit configuration as official state test 0":
var deposits: seq[Deposit] var deposits: seq[Deposit]
var index = 0'u64 var index = 0'u64
for v in stateTests.test_cases[0].initial_state.validator_registry: for v in stateTests.test_cases[0].initial_state.validator_registry:
@ -46,8 +107,8 @@ suite "Official - State tests": # Initializing a beacon state from the deposits
genesis_time = 0, genesis_time = 0,
genesis_eth1_data = Eth1Data() genesis_eth1_data = Eth1Data()
) )
test "[For information] Comparing state hashes": test "Comparing state hashes":
# TODO - Add official hashes when available # TODO - Add official hashes when available
# TODO - Make that a blocking test requirement # TODO - Make that a blocking test requirement
echo "Deserialized state hash: 0x" & $stateTests.test_cases[0].initial_state.hash_tree_root() 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()