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",
|
"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") =
|
||||||
|
|
|
@ -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``.
|
||||||
|
|
|
@ -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
|
|
@ -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,12 +72,8 @@ 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
|
||||||
proc default*(T: typedesc): T = discard
|
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.} =
|
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)
|
||||||
|
@ -99,4 +96,31 @@ proc parseStateTests*(jsonPath: string): StateTest =
|
||||||
writeStackTrace()
|
writeStackTrace()
|
||||||
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".}
|
|
@ -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()
|
Loading…
Reference in New Issue