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:
parent
c7e6d39279
commit
5784b2d7f7
|
@ -30,7 +30,7 @@ requires "nim >= 0.19.0",
|
|||
"chronos",
|
||||
"yaml",
|
||||
"libp2p",
|
||||
"byteutils" # test only
|
||||
"byteutils" # test only (BitField and bytes datatypes deserialization)
|
||||
|
||||
### Helper functions
|
||||
proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import byteutils, json_serialization
|
||||
|
||||
type
|
||||
BitField* = object
|
||||
## A simple bit field type that follows the semantics of the spec, with
|
||||
## regards to bit endian operations
|
||||
# TODO nim-ranges contains utilities for with bitsets - could try to
|
||||
# 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]
|
||||
|
||||
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 =
|
||||
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
|
||||
func get_bitfield_bit*(bitfield: BitField, i: int): bool =
|
||||
# Extract the bit in ``bitfield`` at position ``i``.
|
||||
|
|
|
@ -484,26 +484,33 @@ proc processBlock(
|
|||
return false
|
||||
|
||||
if not processRandao(state, blck, flags):
|
||||
debug "[Block processing] Randao failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
processEth1Data(state, blck)
|
||||
|
||||
if not processProposerSlashings(state, blck, flags):
|
||||
debug "[Block processing] Proposer slashing failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
if not processAttesterSlashings(state, blck):
|
||||
debug "[Block processing] Attester slashing failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
if not processAttestations(state, blck, flags):
|
||||
debug "[Block processing] Attestation processing failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
if not processDeposits(state, blck):
|
||||
debug "[Block processing] Deposit processing failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
if not processExits(state, blck, flags):
|
||||
debug "[Block processing] Exit processing failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
if not processTransfers(state, blck, flags):
|
||||
debug "[Block processing] Transfer processing failure", slot = humaneSlotNum(state.slot)
|
||||
return false
|
||||
|
||||
true
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 6517fe82f9b1a8a8d7b6f4eefdf1a3fb00d1ffab
|
||||
Subproject commit da49e682bf93c43ffc207e4b1b948962c5068196
|
|
@ -4,7 +4,8 @@ import
|
|||
eth/common, serialization, json_serialization,
|
||||
# Beacon chain internals
|
||||
# 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
|
||||
|
||||
|
@ -71,12 +72,8 @@ type
|
|||
verify_signatures*: bool
|
||||
initial_state*: BeaconState
|
||||
blocks*: seq[BeaconBlock]
|
||||
expected_state*: ExpectedState
|
||||
expected_state*: BeaconState
|
||||
|
||||
ExpectedState* = object
|
||||
## TODO what is this?
|
||||
slot*: Slot
|
||||
|
||||
# #######################
|
||||
# Default init
|
||||
proc default*(T: typedesc): T = discard
|
||||
|
@ -87,7 +84,7 @@ proc default*(T: typedesc): T = discard
|
|||
proc readValue*[N: static int](r: var JsonReader, a: var array[N, byte]) {.inline.} =
|
||||
# Needed for;
|
||||
# - BLS_WITHDRAWAL_PREFIX_BYTE
|
||||
# - FOrk datatypes
|
||||
# - Fork datatypes
|
||||
# TODO: are all bytes and bytearray serialized as hex?
|
||||
# if so export that to nim-eth
|
||||
hexToByteArray(r.readValue(string), a)
|
||||
|
@ -99,4 +96,31 @@ proc parseStateTests*(jsonPath: string): StateTest =
|
|||
writeStackTrace()
|
||||
stderr.write "Json load issue for file \"", 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".}
|
|
@ -6,24 +6,85 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
# Standard lib
|
||||
ospaths, strutils, json, unittest,
|
||||
# Standard libs
|
||||
ospaths, strutils, json, unittest, strformat,
|
||||
# Third parties
|
||||
byteutils,
|
||||
# Beacon chain internals
|
||||
../../beacon_chain/spec/[datatypes, crypto, digest, beaconstate],
|
||||
../../beacon_chain/ssz,
|
||||
../../beacon_chain/[ssz, state_transition],
|
||||
# Test utilities
|
||||
./state_test_utils
|
||||
|
||||
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
|
||||
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":
|
||||
stateTests = parseStateTests(TestFolder / TestsPath)
|
||||
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
|
||||
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 index = 0'u64
|
||||
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_eth1_data = Eth1Data()
|
||||
)
|
||||
test "[For information] Comparing state hashes":
|
||||
test "Comparing state hashes":
|
||||
# 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 " Deserialized state hash: 0x" & $stateTests.test_cases[0].initial_state.hash_tree_root()
|
||||
echo " From-scratch state hash: 0x" & $initialState.hash_tree_root()
|
Loading…
Reference in New Issue