More complete integration of the List type; Detect more invalid inputs; Enable more tests
This commit is contained in:
parent
accd5fe954
commit
a8003e7e38
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue