nimbus-eth2/tests/consensus_spec/test_fixture_ssz_generic_ty...

282 lines
9.4 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.used.}
import
# Standard library
std/[
strutils, streams, strformat, strscans,
macros, typetraits],
# Status libraries
faststreams, snappy, stint, ../testutil,
# Third-party
yaml,
# Beacon chain internals
../../beacon_chain/spec/digest,
../../beacon_chain/spec/datatypes/base,
# Test utilities
./fixtures_utils, ./os_ops
# Parsing definitions
# ------------------------------------------------------------------------
const
SSZDir = SszTestsDir/"general"/"phase0"/"ssz_generic"
type
SSZHashTreeRoot = object
# The test files have the values at the "root"
# so we **must** use "root" as a field name
root: string
# Containers have a root (thankfully) and signing_root field
signing_root {.defaultVal: "".}: string
type
# Heterogeneous containers
SingleFieldTestStruct = object
A: byte
SmallTestStruct = object
A, B: uint16
FixedTestStruct = object
A: uint8
B: uint64
C: uint32
VarTestStruct = object
A: uint16
B: List[uint16, 1024]
C: uint8
ComplexTestStruct = object
A: uint16
B: List[uint16, 128]
C: uint8
D: List[byte, 256]
E: VarTestStruct
F: array[4, FixedTestStruct]
G: array[2, VarTestStruct]
HashArrayComplexTestStruct = object
A: uint16
B: List[uint16, 128]
C: uint8
D: List[byte, 256]
E: VarTestStruct
F: HashArray[4, FixedTestStruct]
G: HashArray[2, VarTestStruct]
BitsStruct = object
A: BitList[5]
B: BitArray[2]
C: BitArray[1]
D: BitList[6]
E: BitArray[8]
# Type specific checks
# ------------------------------------------------------------------------
proc checkBasic(T: typedesc,
dir: string,
expectedHash: SSZHashTreeRoot) =
let fileContents = snappy.decode(readFileBytes(dir/"serialized.ssz_snappy"), MaxObjectSize)
let deserialized = newClone(sszDecodeEntireInput(fileContents, T))
let expectedHash = expectedHash.root
let actualHash = "0x" & toLowerAscii($hash_tree_root(deserialized[]))
check expectedHash == actualHash
check sszSize(deserialized[]) == fileContents.len
# TODO check the value
macro testVector(typeIdent: string, size: int): untyped =
# find the compile-time type to test
# against the runtime combination (cartesian product) of
#
# types: bool, uint8, uint16, uint32, uint64, uint128, uint256
# sizes: 1, 2, 3, 4, 5, 8, 16, 31, 512, 513
#
# We allocate in a ref array to not run out of stack space
let types = ["bool", "uint8", "uint16", "uint32", "uint64", "uint128", "uint256"]
let sizes = [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]
let dispatcher = nnkIfStmt.newTree()
for t in types:
# if typeIdent == t // elif typeIdent == t
let sizeDispatch = nnkIfStmt.newTree()
for s in sizes:
# if size == s // elif size == s
let T = nnkBracketExpr.newTree(
ident"array", newLit(s),
case t
of "uint128": ident("UInt128")
of "uint256": ident("UInt256")
else: ident(t)
)
let testStmt = quote do:
checkBasic(`T`, dir, expectedHash)
sizeDispatch.add nnkElifBranch.newTree(
newCall(ident"==", size, newLit(s)),
testStmt
)
sizeDispatch.add nnkElse.newTree quote do:
raise newException(TestSizeError,
"Unsupported **size** in type/size combination: array[" &
$size & "," & typeIdent & ']')
dispatcher.add nnkElifBranch.newTree(
newCall(ident"==", typeIdent, newLit(t)),
sizeDispatch
)
dispatcher.add nnkElse.newTree quote do:
raise newException(ValueError,
"Unsupported **type** in type/size combination: array[" &
$`size` & ", " & `typeIdent` & ']')
result = dispatcher
# echo result.toStrLit() # view the generated code
proc checkVector(sszSubType, dir: string, expectedHash: SSZHashTreeRoot) =
var typeIdent: string
var size: int
let wasMatched = scanf(sszSubType, "vec_$+_$i", typeIdent, size)
doAssert wasMatched
testVector(typeIdent, size)
proc checkBitVector(sszSubType, dir: string, expectedHash: SSZHashTreeRoot) =
var size: int
let wasMatched = scanf(sszSubType, "bitvec_$i", size)
doAssert wasMatched
case size
of 1: checkBasic(BitArray[1], dir, expectedHash)
of 2: checkBasic(BitArray[2], dir, expectedHash)
of 3: checkBasic(BitArray[3], dir, expectedHash)
of 4: checkBasic(BitArray[4], dir, expectedHash)
of 5: checkBasic(BitArray[5], dir, expectedHash)
of 8: checkBasic(BitArray[8], dir, expectedHash)
of 9: checkBasic(BitArray[9], dir, expectedHash)
of 16: checkBasic(BitArray[16], dir, expectedHash)
of 31: checkBasic(BitArray[31], dir, expectedHash)
of 32: checkBasic(BitArray[32], dir, expectedHash)
of 512: checkBasic(BitArray[512], dir, expectedHash)
of 513: checkBasic(BitArray[513], dir, expectedHash)
else:
raise newException(TestSizeError, "Unsupported BitVector of size " & $size)
proc checkBitList(sszSubType, dir: string, expectedHash: SSZHashTreeRoot) =
var maxLen: int
discard scanf(sszSubType, "bitlist_$i", maxLen)
case maxLen
of 0: checkBasic(BitList[0], dir, expectedHash)
of 1: checkBasic(BitList[1], dir, expectedHash)
of 2: checkBasic(BitList[2], dir, expectedHash)
of 3: checkBasic(BitList[3], dir, expectedHash)
of 4: checkBasic(BitList[4], dir, expectedHash)
of 5: checkBasic(BitList[5], dir, expectedHash)
of 8: checkBasic(BitList[8], dir, expectedHash)
of 16: checkBasic(BitList[16], dir, expectedHash)
of 31: checkBasic(BitList[31], dir, expectedHash)
of 32: checkBasic(BitList[32], dir, expectedHash)
of 512: checkBasic(BitList[512], dir, expectedHash)
of 513: checkBasic(BitList[513], dir, expectedHash)
else:
raise newException(ValueError, "Unsupported Bitlist of max length " & $maxLen)
# Test dispatch for valid inputs
# ------------------------------------------------------------------------
proc sszCheck(baseDir, sszType, sszSubType: string) =
let dir = baseDir/sszSubType
# Hash tree root
var expectedHash: SSZHashTreeRoot
if fileExists(dir/"meta.yaml"):
let s = openFileStream(dir/"meta.yaml")
defer: close(s)
yaml.load(s, expectedHash)
# Deserialization and checks
case sszType
of "boolean": checkBasic(bool, dir, expectedHash)
of "uints":
var bitsize: int
let wasMatched = scanf(sszSubType, "uint_$i", bitsize)
doAssert wasMatched
case bitsize
of 8: checkBasic(uint8, dir, expectedHash)
of 16: checkBasic(uint16, dir, expectedHash)
of 32: checkBasic(uint32, dir, expectedHash)
of 64: checkBasic(uint64, dir, expectedHash)
of 128: checkBasic(UInt128, dir, expectedHash)
of 256: checkBasic(UInt256, dir, expectedHash)
else:
raise newException(ValueError, "unknown uint in test: " & sszSubType)
of "basic_vector": checkVector(sszSubType, dir, expectedHash)
of "bitvector": checkBitVector(sszSubType, dir, expectedHash)
of "bitlist": checkBitList(sszSubType, dir, expectedHash)
of "containers":
var name: string
let wasMatched = scanf(sszSubType, "$+_", name)
doAssert wasMatched
case name
of "SingleFieldTestStruct": checkBasic(SingleFieldTestStruct, dir, expectedHash)
of "SmallTestStruct": checkBasic(SmallTestStruct, dir, expectedHash)
of "FixedTestStruct": checkBasic(FixedTestStruct, dir, expectedHash)
of "VarTestStruct": checkBasic(VarTestStruct, dir, expectedHash)
of "ComplexTestStruct":
checkBasic(ComplexTestStruct, dir, expectedHash)
checkBasic(HashArrayComplexTestStruct, dir, expectedHash)
of "BitsStruct": checkBasic(BitsStruct, dir, expectedHash)
else:
raise newException(ValueError, "unknown container in test: " & sszSubType)
else:
raise newException(ValueError, "unknown ssz type in test: " & sszType)
# Test dispatch for invalid inputs
# ------------------------------------------------------------------------
# TODO
# Test runner
# ------------------------------------------------------------------------
suite "EF - SSZ generic types":
doAssert dirExists(SSZDir), "You need to run the \"download_test_vectors.sh\" script to retrieve the consensus spec test vectors."
for pathKind, sszType in walkDir(SSZDir, relative = true, checkDir = true):
doAssert pathKind == pcDir
var skipped: string
case sszType
of "containers":
skipped = " - skipping BitsStruct"
test &"Testing {sszType:12} inputs - valid" & skipped:
let path = SSZDir/sszType/"valid"
for pathKind, sszSubType in walkDir(
path, relative = true, checkDir = true):
if pathKind != pcDir: continue
sszCheck(path, sszType, sszSubType)
test &"Testing {sszType:12} inputs - invalid" & skipped:
let path = SSZDir/sszType/"invalid"
for pathKind, sszSubType in walkDir(
path, relative = true, checkDir = true):
if pathKind != pcDir: continue
try:
sszCheck(path, sszType, sszSubType)
except SszError, UnconsumedInput:
discard
except TestSizeError as err:
echo err.msg
skip()
except:
checkpoint getStackTrace(getCurrentException())
checkpoint getCurrentExceptionMsg()
check false