More complete integration of the List type; Detect more invalid inputs; Enable more tests

This commit is contained in:
Zahary Karadjov 2020-05-22 22:55:00 +03:00 committed by zah
parent accd5fe954
commit a8003e7e38
7 changed files with 201 additions and 159 deletions

View File

@ -459,7 +459,7 @@ func get_indexed_attestation*(state: BeaconState, attestation: Attestation,
IndexedAttestation(
attesting_indices:
List[uint64, MAX_VALIDATORS_PER_COMMITTEE](
List[uint64, MAX_VALIDATORS_PER_COMMITTEE].init(
sorted(mapIt(attesting_indices.toSeq, it.uint64), system.cmp)),
data: attestation.data,
signature: attestation.signature

View File

@ -384,7 +384,7 @@ func process_final_updates*(state: var BeaconState) {.nbench.}=
# Reset eth1 data votes
if next_epoch mod EPOCHS_PER_ETH1_VOTING_PERIOD == 0:
state.eth1_data_votes = typeof(state.eth1_data_votes) @[]
state.eth1_data_votes = type(state.eth1_data_votes) @[]
# Update effective balances with hysteresis
for index, validator in state.validators:

View File

@ -58,13 +58,14 @@ type
FixedSizedWriterCtx = object
ByteList = seq[byte]
serializationFormat SSZ,
Reader = SszReader,
Writer = SszWriter,
PreferedOutput = seq[byte]
template bytes(x: BitSeq): untyped =
seq[byte](x)
template sizePrefixed*[TT](x: TT): untyped =
type T = TT
SizePrefixed[T](x)
@ -89,12 +90,13 @@ method formatMsg*(err: ref SszSizeMismatchError, filename: string): string {.gcs
template toSszType*(x: auto): auto =
mixin toSszType
# Please note that BitArray doesn't need any special treatment here
# because it can be considered a regular fixed-size object type.
when x is Slot|Epoch|ValidatorIndex|enum: uint64(x)
elif x is Eth2Digest: x.data
elif x is BlsCurveType: toRaw(x)
elif x is BitSeq|BitList: ByteList(x)
elif x is ForkDigest|Version: array[4, byte](x)
elif x is List: asSeq(x)
else: x
proc writeFixedSized(s: var (OutputStream|WriteCursor), x: auto) {.raises: [Defect, IOError].} =
@ -102,14 +104,14 @@ proc writeFixedSized(s: var (OutputStream|WriteCursor), x: auto) {.raises: [Defe
when x is byte:
s.write x
elif x is bool|char:
elif x is bool:
s.write byte(ord(x))
elif x is SomeUnsignedInt:
elif x is UintN:
when cpuEndian == bigEndian:
s.write toBytesLE(x)
else:
s.writeMemCopy x
elif x is array|seq|openarray:
elif x is array:
when x[0] is byte:
trs "APPENDING FIXED SIZE BYTES", x
s.write x
@ -135,7 +137,7 @@ func init*(T: type SszWriter, stream: OutputStream): T {.raises: [Defect].} =
result.stream = stream
template enumerateSubFields(holder, fieldVar, body: untyped) =
when holder is array|seq|openarray:
when holder is array:
for fieldVar in holder: body
else:
enumInstanceSerializedFields(holder, _, fieldVar): body
@ -169,8 +171,8 @@ template writeField*(w: var SszWriter,
writeOffset(ctx.fixedParts, ctx.offset)
let initPos = w.stream.pos
trs "WRITING VAR SIZE VALUE OF TYPE ", name(FieldType)
when FieldType is BitSeq:
trs "BIT SEQ ", ByteList(field)
when FieldType is BitList:
trs "BIT SEQ ", bytes(field)
writeVarSizeType(w, toSszType(field))
ctx.offset += w.stream.pos - initPos
@ -178,37 +180,42 @@ template endRecord*(w: var SszWriter, ctx: var auto) =
when ctx is VarSizedWriterCtx:
finalize ctx.fixedParts
proc writeSeq[T](w: var SszWriter, value: seq[T])
{.raises: [Defect, IOError].} =
when isFixedSize(T):
trs "WRITING LIST WITH FIXED SIZE ELEMENTS"
for elem in value:
w.stream.writeFixedSized toSszType(elem)
trs "DONE"
else:
trs "WRITING LIST WITH VAR SIZE ELEMENTS"
var offset = value.len * offsetSize
var cursor = w.stream.delayFixedSizeWrite offset
for elem in value:
cursor.writeFixedSized uint32(offset)
let initPos = w.stream.pos
w.writeVarSizeType toSszType(elem)
offset += w.stream.pos - initPos
finalize cursor
trs "DONE"
proc writeVarSizeType(w: var SszWriter, value: auto) {.raises: [Defect, IOError].} =
trs "STARTING VAR SIZE TYPE"
mixin toSszType
type T = type toSszType(value)
when T is seq|openarray:
type E = ElemType(T)
const isFixed = isFixedSize(E)
when isFixed:
trs "WRITING LIST WITH FIXED SIZE ELEMENTS"
for elem in value:
w.stream.writeFixedSized toSszType(elem)
trs "DONE"
else:
trs "WRITING LIST WITH VAR SIZE ELEMENTS"
var offset = value.len * offsetSize
var cursor = w.stream.delayFixedSizeWrite offset
for elem in value:
cursor.writeFixedSized uint32(offset)
let initPos = w.stream.pos
w.writeVarSizeType toSszType(elem)
offset += w.stream.pos - initPos
finalize cursor
trs "DONE"
when T is List:
writeSeq(w, asSeq value)
elif T is BitList:
writeSeq(w, bytes value)
elif T is object|tuple|array:
trs "WRITING OBJECT OR ARRAY"
var ctx = beginRecord(w, T)
enumerateSubFields(value, field):
writeField w, ctx, astToStr(field), field
endRecord w, ctx
else:
unsupported type(value)
proc writeValue*(w: var SszWriter, x: auto) {.gcsafe, raises: [Defect, IOError].} =
mixin toSszType
@ -216,26 +223,34 @@ proc writeValue*(w: var SszWriter, x: auto) {.gcsafe, raises: [Defect, IOError].
when isFixedSize(T):
w.stream.writeFixedSized toSszType(x)
elif T is array|seq|openarray|object|tuple:
w.writeVarSizeType toSszType(x)
else:
unsupported type(x)
w.writeVarSizeType toSszType(x)
func sszSize*(value: auto): int =
func sszSize*(value: auto): int {.gcsafe, raises: [Defect].}
func sszSizeForVarSizeList[T](value: openarray[T]): int =
result = len(value) * offsetSize
for elem in value:
result += sszSize(toSszType elem)
func sszSize*(value: auto): int {.gcsafe, raises: [Defect].} =
mixin toSszType
type T = type toSszType(value)
when isFixedSize(T):
anonConst fixedPortionSize(T)
elif T is seq|array|openarray:
elif T is array|List:
type E = ElemType(T)
when isFixedSize(E):
len(value) * anonConst(fixedPortionSize(E))
elif T is array:
sszSizeForVarSizeList(value)
else:
result = len(value) * offsetSize
for elem in value:
result += sszSize(toSszType elem)
sszSizeForVarSizeList(asSeq value)
elif T is BitList:
return len(bytes(value))
elif T is object|tuple:
result = anonConst fixedPortionSize(T)
@ -332,7 +347,7 @@ func addChunk(merkleizer: var SszChunksMerkleizer, data: openarray[byte]) =
let chunkStartAddr = addr merkleizer.combinedChunks[0].data[0]
copyMem(chunkStartAddr, unsafeAddr data[0], data.len)
zeroMem(chunkStartAddr.offset(data.len), bytesPerChunk - data.len)
trs "WROTE BASE CHUNK ", merkleizer.combinedChunks[0]
trs "WROTE BASE CHUNK ", merkleizer.combinedChunks[0], " ", data.len
else:
var hash = mergeBranches(merkleizer.combinedChunks[0], data)
@ -441,7 +456,7 @@ func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
if remainingBytes > 0:
merkleizer.addChunk(makeOpenArray(pos, remainingBytes))
elif T is char or cpuEndian == littleEndian:
elif T is bool or cpuEndian == littleEndian:
let
baseAddr = cast[ptr byte](unsafeAddr arr[0])
len = arr.len * sizeof(T)
@ -449,7 +464,7 @@ func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
else:
static:
assert T is SomeUnsignedInt
assert T is UintN
assert bytesPerChunk mod sizeof(Т) == 0
const valuesPerChunk = bytesPerChunk div sizeof(Т)
@ -472,14 +487,14 @@ func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
getFinalHash(merkleizer)
func bitlistHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Digest =
func bitListHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Digest =
# TODO: Switch to a simpler BitList representation and
# replace this with `chunkedHashTreeRoot`
trs "CHUNKIFYING BIT SEQ WITH TOP INDEX ", merkleizer.topIndex
var
totalBytes = ByteList(x).len
lastCorrectedByte = ByteList(x)[^1]
totalBytes = bytes(x).len
lastCorrectedByte = bytes(x)[^1]
if lastCorrectedByte == byte(1):
if totalBytes == 1:
@ -489,7 +504,7 @@ func bitlistHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Di
getZeroHashWithoutSideEffect(0)) # this is the mixed length
totalBytes -= 1
lastCorrectedByte = ByteList(x)[^2]
lastCorrectedByte = bytes(x)[^2]
else:
let markerPos = log2trunc(lastCorrectedByte)
lastCorrectedByte.clearBit(markerPos)
@ -507,14 +522,14 @@ func bitlistHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Di
chunkStartPos = i * bytesPerChunk
chunkEndPos = chunkStartPos + bytesPerChunk - 1
merkleizer.addChunk ByteList(x).toOpenArray(chunkEndPos, chunkEndPos)
merkleizer.addChunk bytes(x).toOpenArray(chunkStartPos, chunkEndPos)
var
lastChunk: array[bytesPerChunk, byte]
chunkStartPos = fullChunks * bytesPerChunk
for i in 0 .. bytesInLastChunk - 2:
lastChunk[i] = ByteList(x)[chunkStartPos + i]
lastChunk[i] = bytes(x)[chunkStartPos + i]
lastChunk[bytesInLastChunk - 1] = lastCorrectedByte
@ -523,9 +538,9 @@ func bitlistHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Di
mixInLength contentsHash, x.len
func maxChunksCount(T: type, maxLen: int64): int64 =
when T is BitSeq|BitList:
when T is BitList|BitArray:
(maxLen + bitsPerChunk - 1) div bitsPerChunk
elif T is array|List|seq|openarray:
elif T is array|List:
type E = ElemType(T)
when E is BasicType:
(maxLen * sizeof(E) + bytesPerChunk - 1) div bytesPerChunk
@ -547,7 +562,7 @@ func hashTreeRootAux[T](x: T): Eth2Digest =
elif (when T is array: ElemType(T) is BasicType else: false):
type E = ElemType(T)
when sizeof(T) <= sizeof(result.data):
when E is byte|char|bool or cpuEndian == littleEndian:
when E is byte|bool or cpuEndian == littleEndian:
copyMem(addr result.data[0], unsafeAddr x, sizeof x)
else:
var pos = 0
@ -558,6 +573,8 @@ func hashTreeRootAux[T](x: T): Eth2Digest =
trs "FIXED TYPE; USE CHUNK STREAM"
var markleizer = createMerkleizer(maxChunksCount(T, x.len))
chunkedHashTreeRootForBasicTypes(markleizer, x)
elif T is BitArray:
hashTreeRootAux(x.bytes)
elif T is array|object|tuple:
trs "MERKLEIZING FIELDS"
const totalFields = when T is array: len(x)
@ -580,7 +597,7 @@ func hash_tree_root*(x: auto): Eth2Digest {.raises: [Defect], nbench.} =
var merkleizer = createMerkleizer(limit)
when x is BitList:
result = merkleizer.bitlistHashTreeRoot(BitSeq x)
result = merkleizer.bitListHashTreeRoot(BitSeq x)
else:
type E = ElemType(T)
let contentsHash = when E is BasicType:

View File

@ -9,40 +9,40 @@ import
const
maxListAllocation = 1 * 1024 * 1024 * 1024 # 1 GiB
template raiseIncorrectSize(T: type) =
const typeName = name(T)
raise newException(MalformedSszError,
"SSZ " & typeName & " input of incorrect size")
template setOutputSize[R, T](a: var array[R, T], length: int) =
if length != a.len:
raise newException(MalformedSszError, "SSZ input of insufficient size")
raiseIncorrectSize a.type
proc setOutputSize[T](s: var seq[T], length: int) {.inline, raisesssz.} =
if sizeof(T) * length > maxListAllocation:
raise newException(MalformedSszError, "SSZ list size is too large to fit in memory")
s.setLen length
proc setOutputSize(s: var string, length: int) {.inline, raisesssz.} =
if length > maxListAllocation:
raise newException(MalformedSszError, "SSZ string is too large to fit in memory")
s.setLen length
proc setOutputSize(list: var List, length: int) {.inline, raisesssz.} =
if int64(length) > list.maxLen:
raise newException(MalformedSszError, "SSZ list maximum size exceeded")
list.setLen length
# fromSszBytes copies the wire representation to a Nim variable,
# assuming there's enough data in the buffer
func fromSszBytes*(T: type SomeInteger, data: openarray[byte]): T {.raisesssz.} =
func fromSszBytes*(T: type UintN, data: openarray[byte]): T {.raisesssz.} =
## Convert directly to bytes the size of the int. (e.g. ``uint16 = 2 bytes``)
## All integers are serialized as **little endian**.
if data.len < sizeof(result):
raise newException(MalformedSszError, "SSZ input of insufficient size")
if data.len != sizeof(result):
raiseIncorrectSize T
T.fromBytesLE(data)
func fromSszBytes*(T: type bool, data: openarray[byte]): T {.raisesssz.} =
# TODO: spec doesn't say what to do if the value is >1 - we'll use the C
# definition for now, but maybe this should be a parse error instead?
if data.len == 0 or byte(data[0]) > byte(1):
if data.len != 1 or byte(data[0]) > byte(1):
raise newException(MalformedSszError, "invalid boolean value")
data[0] == 1
func fromSszBytes*(T: type Eth2Digest, data: openarray[byte]): T {.raisesssz.} =
if data.len < sizeof(result.data):
raise newException(MalformedSszError, "SSZ input of insufficient size")
if data.len != sizeof(result.data):
raiseIncorrectSize T
copyMem(result.data.addr, unsafeAddr data[0], sizeof(result.data))
template fromSszBytes*(T: type Slot, bytes: openarray[byte]): Slot =
@ -52,13 +52,13 @@ template fromSszBytes*(T: type Epoch, bytes: openarray[byte]): Epoch =
Epoch fromSszBytes(uint64, bytes)
func fromSszBytes*(T: type ForkDigest, bytes: openarray[byte]): T {.raisesssz.} =
if bytes.len < sizeof(result):
raise newException(MalformedSszError, "SSZ input of insufficient size")
if bytes.len != sizeof(result):
raiseIncorrectSize T
copyMem(result.addr, unsafeAddr bytes[0], sizeof(result))
func fromSszBytes*(T: type Version, bytes: openarray[byte]): T {.raisesssz.} =
if bytes.len < sizeof(result):
raise newException(MalformedSszError, "SSZ input of insufficient size")
if bytes.len != sizeof(result):
raiseIncorrectSize T
copyMem(result.addr, unsafeAddr bytes[0], sizeof(result))
template fromSszBytes*(T: type enum, bytes: openarray[byte]): auto =
@ -67,43 +67,67 @@ template fromSszBytes*(T: type enum, bytes: openarray[byte]): auto =
template fromSszBytes*(T: type BitSeq, bytes: openarray[byte]): auto =
BitSeq @bytes
func fromSszBytes*(T: type BitList, bytes: openarray[byte]): auto {.raisesssz.} =
if bytes.len == 0:
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/ssz/simple-serialize.md#bitlistn
# "An additional 1 bit is added to the end, at index e where e is the
# length of the bitlist (not the limit), so that the length in bits will
# also be known."
# It's not possible to have a literally 0-byte (raw) Bitlist.
# https://github.com/status-im/nim-beacon-chain/issues/931
raise newException(MalformedSszError, "SSZ input Bitlist too small")
T @bytes
func fromSszBytes*[N](T: type BitArray[N], bytes: openarray[byte]): T {.raisesssz.} =
# A bit vector doesn't have a marker bit, but we'll use the helper from
# nim-stew to determine the position of the leading (marker) bit.
# If it's outside the BitArray size, we have an overflow:
if bitsLen(bytes) > N - 1:
raise newException(MalformedSszError, "SSZ bit array overflow")
copyMem(addr result.bytes[0], unsafeAddr bytes[0], bytes.len)
proc `[]`[T, U, V](s: openArray[T], x: HSlice[U, V]) {.error:
"Please don't use openarray's [] as it allocates a result sequence".}
# func readOpenArray[T](result: var openarray[T], input: openarray[byte]) =
template checkForForbiddenBits(ResulType: type,
input: openarray[byte],
expectedBits: static int) =
## This checks if the input contains any bits set above the maximum
## sized allowed. We only need to check the last byte to verify this:
const bitsInLastByte = (expectedBits mod 8)
when bitsInLastByte != 0:
# As an example, if there are 3 bits expected in the last byte,
# we calculate a bitmask equal to 11111000. If the input has any
# raised bits in range of the bitmask, this would be a violation
# of the size of the BitArray:
const forbiddenBitsMask = byte(byte(0xff) shl bitsInLastByte)
if (input[^1] and forbiddenBitsMask) != 0:
raiseIncorrectSize ResulType
func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
mixin fromSszBytes, toSszType
type T {.used.} = type(result)
template readOffset(n: int): int {.used.}=
template readOffsetUnchecked(n: int): int {.used.}=
int fromSszBytes(uint32, input.toOpenArray(n, n + offsetSize - 1))
when result is List:
type ElemType = type result[0]
result = T readSszValue(input, seq[ElemType])
template readOffset(n: int): int {.used.} =
let offset = readOffsetUnchecked(n)
if offset > input.len:
raise newException(MalformedSszError, "SSZ list element offset points past the end of the input")
offset
elif result is seq|openarray|array:
#when result is List:
# result.setOutputSize input.len
# readOpenArray(toSeq result, input)
#elif result is array:
# result.checkOutputSize input.len
# readOpenArray(result, input)
when result is BitList:
if input.len == 0:
raise newException(MalformedSszError, "Invalid empty SSZ BitList value")
const maxExpectedSize = (result.maxLen div 8) + 1
result = T readSszValue(input, List[byte, maxExpectedSize])
let resultBytesCount = len bytes(result)
if bytes(result)[resultBytesCount - 1] == 0:
raise newException(MalformedSszError, "SSZ BitList is not properly terminated")
if resultBytesCount == maxExpectedSize:
checkForForbiddenBits(T, input, result.maxLen + 1)
elif result is List|array:
type ElemType = type result[0]
when ElemType is byte|char:
when ElemType is byte:
result.setOutputSize input.len
if input.len > 0:
copyMem(addr result[0], unsafeAddr input[0], input.len)
@ -133,6 +157,7 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
raise newException(MalformedSszError, "SSZ input of insufficient size")
var offset = readOffset 0
trs "GOT OFFSET ", offset
let resultLen = offset div offsetSize
trs "LEN ", resultLen
@ -148,19 +173,24 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
let nextOffset = readOffset(i * offsetSize)
if nextOffset <= offset:
raise newException(MalformedSszError, "SSZ list element offsets are not monotonically increasing")
elif nextOffset > input.len:
raise newException(MalformedSszError, "SSZ list element offset points past the end of the input")
else:
result[i - 1] = readSszValue(input.toOpenArray(offset, nextOffset - 1), ElemType)
offset = nextOffset
result[resultLen - 1] = readSszValue(input.toOpenArray(offset, input.len - 1), ElemType)
elif result is SomeInteger|bool|enum|BitArray:
# TODO: Should be possible to remove BitArray from here
elif result is UintN|bool|enum:
trs "READING BASIC TYPE ", type(result).name, " input=", input.len
result = fromSszBytes(type(result), input)
trs "RESULT WAS ", repr(result)
elif result is BitArray:
if sizeof(result) != input.len:
raiseIncorrectSize T
checkForForbiddenBits(T, input, result.bits)
copyMem(addr result.bytes[0], unsafeAddr input[0], input.len)
elif result is object|tuple:
const minimallyExpectedSize = fixedPortionSize(T)
if input.len < minimallyExpectedSize:
@ -180,9 +210,9 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
trs "FIXED FIELD ", startOffset, "-", endOffset
else:
let
startOffset = readOffset(boundingOffsets[0])
startOffset = readOffsetUnchecked(boundingOffsets[0])
endOffset = if boundingOffsets[1] == -1: input.len
else: readOffset(boundingOffsets[1])
else: readOffsetUnchecked(boundingOffsets[1])
trs "VAR FIELD ", startOffset, "-", endOffset
if startOffset > endOffset:
raise newException(MalformedSszError, "SSZ field offsets are not monotonically increasing")
@ -194,7 +224,7 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
# TODO The extra type escaping here is a work-around for a Nim issue:
when type(FieldType) is type(SszType):
trs "READING NATIVE ", fieldName, ": ", name(SszType)
field = readSszValue(
field = typeof(field) readSszValue(
input.toOpenArray(startOffset, endOffset - 1),
SszType)
trs "READING COMPLETE ", fieldName

View File

@ -72,25 +72,40 @@ type
of Field:
discard
template add*(x: List, val: x.T) = add(distinctBase x, val)
template asSeq*(x: List): auto = distinctBase(x)
template init*[T](L: type List, x: seq[T], N: static Limit): auto =
List[T, N](x)
template init*[T, N](L: type List[T, N], x: seq[T]): auto =
List[T, N](x)
template `$`*(x: List): auto = $(distinctBase x)
template add*(x: List, val: auto) = add(distinctBase x, val)
template len*(x: List): auto = len(distinctBase x)
template setLen*(x: List, val: auto) = setLen(distinctBase x, val)
template low*(x: List): auto = low(distinctBase x)
template high*(x: List): auto = high(distinctBase x)
template `[]`*(x: List, idx: auto): auto = distinctBase(x)[idx]
template `[]=`*[T; N](x: List[T, N], idx: auto, val: T) = seq[T](x)[idx] = val
template `==`*(a, b: List): bool = distinctBase(a) == distinctBase(b)
template asSeq*(x: List): auto = distinctBase x
template `&`*[T; N](a, b: List[T, N]): List[T, N] = List[T, N](seq[T](a) & seq[T](b))
template `$`*(x: List): auto = $(distinctBase x)
template `[]`*(x: List, idx: auto): untyped = distinctBase(x)[idx]
template `[]=`*(x: List, idx: auto, val: auto) = distinctBase(x)[idx] = val
template `==`*(a, b: List): bool = asSeq(a) == distinctBase(b)
template `&`*(a, b: List): auto = (type(a)(distinctBase(a) & distinctBase(b)))
template items* (x: List): untyped = items(distinctBase x)
template pairs* (x: List): untyped = pairs(distinctBase x)
template mitems*(x: List): untyped = mitems(distinctBase x)
template mpairs*(x: List): untyped = mpairs(distinctBase x)
template init*(L: type BitList, x: seq[byte], N: static Limit): auto =
BitList[N](data: x)
template init*[N](L: type BitList[N], x: seq[byte]): auto =
L(data: x)
template init*(T: type BitList, len: int): auto = T init(BitSeq, len)
template len*(x: BitList): auto = len(BitSeq(x))
template bytes*(x: BitList): auto = bytes(BitSeq(x))
template bytes*(x: BitList): auto = seq[byte](x)
template `[]`*(x: BitList, idx: auto): auto = BitSeq(x)[idx]
template `[]=`*(x: var BitList, idx: auto, val: bool) = BitSeq(x)[idx] = val
template `==`*(a, b: BitList): bool = BitSeq(a) == BitSeq(b)

View File

@ -58,7 +58,7 @@ type
A: uint16
B: List[uint16, 128]
C: uint8
D: array[256, byte]
D: List[byte, 256]
E: VarTestStruct
F: array[4, FixedTestStruct]
G: array[2, VarTestStruct]
@ -79,9 +79,8 @@ proc checkBasic(T: typedesc,
var fileContents = readFileBytes(dir/"serialized.ssz")
var deserialized = sszDecodeEntireInput(fileContents, T)
let
expectedHash = expectedHash.root
actualHash = "0x" & toLowerASCII($deserialized.hashTreeRoot())
let expectedHash = expectedHash.root
let actualHash = "0x" & toLowerASCII($deserialized.hashTreeRoot())
check expectedHash == actualHash
check sszSize(deserialized) == fileContents.len
@ -159,24 +158,24 @@ proc checkBitVector(sszSubType, dir: string, expectedHash: SSZHashTreeRoot) =
else:
raise newException(TestSizeError, "Unsupported BitVector of size " & $size)
# TODO: serialization of "type BitList[maxLen] = distinct BitSeq is not supported"
# https://github.com/status-im/nim-beacon-chain/issues/518
# proc checkBitList(sszSubType, dir: string, expectedHash: SSZHashTreeRoot) =
# var maxLen: int
# let wasMatched = scanf(sszSubType, "bitlist_$i", maxLen)
# case maxLen
# 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 512: checkBasic(BitList[512], dir, expectedHash)
# of 513: checkBasic(BitList[513], dir, expectedHash)
# else:
# raise newException(ValueError, "Unsupported Bitlist of max length " & $maxLen)
proc checkBitList(sszSubType, dir: string, expectedHash: SSZHashTreeRoot) =
var maxLen: int
let wasMatched = 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
# ------------------------------------------------------------------------
@ -213,7 +212,7 @@ proc sszCheck(baseDir, sszType, sszSubType: string) =
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 "bitlist": checkBitList(sszSubType, dir, expectedHash)
of "containers":
var name: string
let wasMatched = scanf(sszSubtype, "$+_", name)
@ -222,15 +221,12 @@ proc sszCheck(baseDir, sszType, sszSubType: string) =
of "SingleFieldTestStruct": checkBasic(SingleFieldTestStruct, dir, expectedHash)
of "SmallTestStruct": checkBasic(SmallTestStruct, dir, expectedHash)
of "FixedTestStruct": checkBasic(FixedTestStruct, dir, expectedHash)
of "VarTestStruct":
# Runtime issues
discard # checkBasic(VarTestStruct, dir, expectedHash)
of "ComplexTestStruct":
# Compile-time issues
discard # checkBasic(ComplexTestStruct, dir, expectedHash)
of "VarTestStruct": checkBasic(VarTestStruct, dir, expectedHash)
of "ComplexTestStruct": checkBasic(ComplexTestStruct, dir, expectedHash)
of "BitsStruct":
discard
# Compile-time issues
discard # checkBasic(BitsStruct, dir, expectedHash)
# checkBasic(BitsStruct, dir, expectedHash)
else:
raise newException(ValueError, "unknown container in test: " & sszSubType)
else:
@ -248,12 +244,6 @@ proc runSSZtests() =
doAssert existsDir(SSZDir), "You need to run the \"download_test_vectors.sh\" script to retrieve the official test vectors."
for pathKind, sszType in walkDir(SSZDir, relative = true):
doAssert pathKind == pcDir
if sszType == "bitlist":
timedTest &"**Skipping** {sszType} inputs - valid - skipped altogether":
# TODO: serialization of "type BitList[maxLen] = distinct BitSeq is not supported"
# https://github.com/status-im/nim-beacon-chain/issues/518
discard
continue
var skipped: string
case sszType
@ -262,7 +252,7 @@ proc runSSZtests() =
of "basic_vector":
skipped = " - skipping Vector[uint128, N] and Vector[uint256, N]"
of "containers":
skipped = " - skipping VarTestStruct, ComplexTestStruct, BitsStruct"
skipped = " - skipping BitsStruct"
timedTest &"Testing {sszType:12} inputs - valid" & skipped:
let path = SSZDir/sszType/"valid"
@ -286,12 +276,6 @@ proc runSSZtests() =
checkpoint getCurrentExceptionMsg()
check false
# TODO: nim-serialization forces us to use exceptions as control flow
# as we always have to check user supplied inputs
# Skipped
# test "Testing " & name & " inputs (" & $T & ") - invalid":
# const path = SSZDir/name/"invalid"
suiteReport "Official - SSZ generic types":
runSSZtests()

View File

@ -33,10 +33,6 @@ static:
doAssert fixedPortionSize(array[SomeEnum, uint64]) == 24
doAssert fixedPortionSize(array[3..5, List[byte, 256]]) == 12
doAssert fixedPortionSize(List[byte, 256]) == 4
doAssert fixedPortionSize(List[bool, 128]) == 4
doAssert fixedPortionSize(List[List[byte, 128], 256]) == 4
doAssert isFixedSize(array[20, bool]) == true
doAssert isFixedSize(Simple) == true
doAssert isFixedSize(List[bool, 128]) == false