2018-09-20 15:45:02 +00:00
|
|
|
|
# beacon_chain
|
|
|
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
|
# Licensed and distributed under either of
|
2019-11-25 15:30:02 +00:00
|
|
|
|
# * 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).
|
2018-09-20 15:45:02 +00:00
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
|
|
|
|
|
# SSZ Serialization (simple serialize)
|
2018-12-17 18:03:53 +00:00
|
|
|
|
# See https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md
|
2018-09-20 15:45:02 +00:00
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
# TODO Cannot override push, even though the function is annotated
|
|
|
|
|
# nim-beacon-chain/beacon_chain/ssz.nim(212, 18) Error: can raise an unlisted exception: IOError
|
|
|
|
|
#{.push raises: [Defect].}
|
|
|
|
|
|
2020-05-27 15:04:43 +00:00
|
|
|
|
# TODO Many RVO bugs, careful
|
|
|
|
|
# https://github.com/nim-lang/Nim/issues/14470
|
|
|
|
|
# https://github.com/nim-lang/Nim/issues/14126
|
|
|
|
|
|
2018-11-22 10:17:05 +00:00
|
|
|
|
import
|
2020-05-18 17:49:22 +00:00
|
|
|
|
options, algorithm, options, strformat, typetraits,
|
2020-05-20 17:05:22 +00:00
|
|
|
|
stew/[bitops2, bitseqs, endians2, objects, varints, ptrops],
|
2020-05-21 13:21:29 +00:00
|
|
|
|
stew/ranges/ptr_arith, stew/shims/macros,
|
2020-05-11 19:11:01 +00:00
|
|
|
|
faststreams/[inputs, outputs, buffers],
|
|
|
|
|
serialization, serialization/testing/tracing,
|
2019-07-03 07:35:05 +00:00
|
|
|
|
./spec/[crypto, datatypes, digest],
|
2020-05-14 22:54:10 +00:00
|
|
|
|
./ssz/[types, bytes_reader],
|
|
|
|
|
../nbench/bench_lab
|
2018-11-22 10:17:05 +00:00
|
|
|
|
|
2018-09-20 15:45:02 +00:00
|
|
|
|
# ################### Helper functions ###################################
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2019-03-05 22:54:08 +00:00
|
|
|
|
export
|
2019-07-03 07:35:05 +00:00
|
|
|
|
serialization, types, bytes_reader
|
|
|
|
|
|
|
|
|
|
when defined(serialization_tracing):
|
|
|
|
|
import
|
2020-05-27 11:36:02 +00:00
|
|
|
|
typetraits
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
const
|
|
|
|
|
bytesPerChunk = 32
|
|
|
|
|
bitsPerChunk = bytesPerChunk * 8
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
SszReader* = object
|
2020-04-09 18:36:00 +00:00
|
|
|
|
stream: InputStream
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
|
|
|
|
SszWriter* = object
|
2020-04-09 18:36:00 +00:00
|
|
|
|
stream: OutputStream
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
SszChunksMerkleizer = object
|
2020-05-21 13:21:29 +00:00
|
|
|
|
combinedChunks: ptr UncheckedArray[Eth2Digest]
|
2019-08-22 02:59:35 +00:00
|
|
|
|
totalChunks: uint64
|
2020-05-21 13:21:29 +00:00
|
|
|
|
topIndex: int
|
2020-04-09 18:36:00 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
SizePrefixed*[T] = distinct T
|
|
|
|
|
SszMaxSizeExceeded* = object of SerializationError
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
VarSizedWriterCtx = object
|
|
|
|
|
fixedParts: WriteCursor
|
|
|
|
|
offset: int
|
|
|
|
|
|
|
|
|
|
FixedSizedWriterCtx = object
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
|
|
|
|
serializationFormat SSZ,
|
|
|
|
|
Reader = SszReader,
|
|
|
|
|
Writer = SszWriter,
|
|
|
|
|
PreferedOutput = seq[byte]
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
template bytes(x: BitSeq): untyped =
|
|
|
|
|
seq[byte](x)
|
|
|
|
|
|
2019-09-09 02:39:44 +00:00
|
|
|
|
template sizePrefixed*[TT](x: TT): untyped =
|
|
|
|
|
type T = TT
|
|
|
|
|
SizePrefixed[T](x)
|
|
|
|
|
|
2020-05-28 12:36:37 +00:00
|
|
|
|
proc init*(T: type SszReader, stream: InputStream): T {.raises: [Defect].} =
|
|
|
|
|
T(stream: stream)
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
2020-05-27 11:36:02 +00:00
|
|
|
|
method formatMsg*(
|
|
|
|
|
err: ref SszSizeMismatchError,
|
|
|
|
|
filename: string): string {.gcsafe, raises: [Defect].} =
|
2020-05-11 18:08:52 +00:00
|
|
|
|
try:
|
|
|
|
|
&"SSZ size mismatch, element {err.elementSize}, actual {err.actualSszSize}, type {err.deserializedType}, file {filename}"
|
|
|
|
|
except CatchableError:
|
|
|
|
|
"SSZ size mismatch"
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
template toSszType*(x: auto): auto =
|
|
|
|
|
mixin toSszType
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
# Please note that BitArray doesn't need any special treatment here
|
|
|
|
|
# because it can be considered a regular fixed-size object type.
|
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
when x is Slot|Epoch|ValidatorIndex|enum: uint64(x)
|
|
|
|
|
elif x is Eth2Digest: x.data
|
2020-04-11 08:51:07 +00:00
|
|
|
|
elif x is BlsCurveType: toRaw(x)
|
2020-05-27 11:36:02 +00:00
|
|
|
|
elif x is ForkDigest|Version: distinctBase(x)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else: x
|
|
|
|
|
|
2020-05-06 12:31:05 +00:00
|
|
|
|
proc writeFixedSized(s: var (OutputStream|WriteCursor), x: auto) {.raises: [Defect, IOError].} =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
mixin toSszType
|
|
|
|
|
|
|
|
|
|
when x is byte:
|
2020-05-06 12:31:05 +00:00
|
|
|
|
s.write x
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif x is bool:
|
2020-05-06 12:31:05 +00:00
|
|
|
|
s.write byte(ord(x))
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif x is UintN:
|
2020-05-11 19:11:01 +00:00
|
|
|
|
when cpuEndian == bigEndian:
|
|
|
|
|
s.write toBytesLE(x)
|
|
|
|
|
else:
|
|
|
|
|
s.writeMemCopy x
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif x is array:
|
2019-07-03 07:35:05 +00:00
|
|
|
|
when x[0] is byte:
|
|
|
|
|
trs "APPENDING FIXED SIZE BYTES", x
|
2020-05-06 12:31:05 +00:00
|
|
|
|
s.write x
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
for elem in x:
|
2019-11-26 14:32:45 +00:00
|
|
|
|
trs "WRITING FIXED SIZE ARRAY ELEMENT"
|
2020-05-06 12:31:05 +00:00
|
|
|
|
s.writeFixedSized toSszType(elem)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
elif x is tuple|object:
|
|
|
|
|
enumInstanceSerializedFields(x, fieldName, field):
|
|
|
|
|
trs "WRITING FIXED SIZE FIELD", fieldName
|
2020-05-06 12:31:05 +00:00
|
|
|
|
s.writeFixedSized toSszType(field)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
unsupported x.type
|
2018-12-27 20:14:37 +00:00
|
|
|
|
|
2020-05-06 12:31:05 +00:00
|
|
|
|
template writeOffset(cursor: var WriteCursor, offset: int) =
|
|
|
|
|
write cursor, toBytesLE(uint32 offset)
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
template supports*(_: type SSZ, T: type): bool =
|
|
|
|
|
mixin toSszType
|
2020-04-22 23:35:55 +00:00
|
|
|
|
anonConst compiles(fixedPortionSize toSszType(declval T))
|
2018-12-17 18:03:53 +00:00
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
func init*(T: type SszWriter, stream: OutputStream): T {.raises: [Defect].} =
|
2019-03-05 22:54:08 +00:00
|
|
|
|
result.stream = stream
|
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
template enumerateSubFields(holder, fieldVar, body: untyped) =
|
2020-05-22 19:55:00 +00:00
|
|
|
|
when holder is array:
|
2019-07-03 07:35:05 +00:00
|
|
|
|
for fieldVar in holder: body
|
2018-12-17 18:03:53 +00:00
|
|
|
|
else:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
enumInstanceSerializedFields(holder, _{.used.}, fieldVar): body
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
2020-04-09 18:36:00 +00:00
|
|
|
|
proc writeVarSizeType(w: var SszWriter, value: auto) {.gcsafe.}
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
proc beginRecord*(w: var SszWriter, TT: type): auto {.raises: [Defect].} =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
type T = TT
|
|
|
|
|
when isFixedSize(T):
|
|
|
|
|
FixedSizedWriterCtx()
|
|
|
|
|
else:
|
|
|
|
|
const offset = when T is array: len(T) * offsetSize
|
|
|
|
|
else: fixedPortionSize(T)
|
|
|
|
|
VarSizedWriterCtx(offset: offset,
|
|
|
|
|
fixedParts: w.stream.delayFixedSizeWrite(offset))
|
|
|
|
|
|
|
|
|
|
template writeField*(w: var SszWriter,
|
|
|
|
|
ctx: var auto,
|
|
|
|
|
fieldName: string,
|
|
|
|
|
field: auto) =
|
|
|
|
|
mixin toSszType
|
|
|
|
|
when ctx is FixedSizedWriterCtx:
|
2019-08-05 00:00:49 +00:00
|
|
|
|
writeFixedSized(w.stream, toSszType(field))
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
type FieldType = type toSszType(field)
|
2019-03-05 22:54:08 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
when isFixedSize(FieldType):
|
2020-05-06 12:31:05 +00:00
|
|
|
|
writeFixedSized(ctx.fixedParts, toSszType(field))
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
trs "WRITING OFFSET ", ctx.offset, " FOR ", fieldName
|
2020-05-06 12:31:05 +00:00
|
|
|
|
writeOffset(ctx.fixedParts, ctx.offset)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
let initPos = w.stream.pos
|
|
|
|
|
trs "WRITING VAR SIZE VALUE OF TYPE ", name(FieldType)
|
2020-05-22 19:55:00 +00:00
|
|
|
|
when FieldType is BitList:
|
|
|
|
|
trs "BIT SEQ ", bytes(field)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
writeVarSizeType(w, toSszType(field))
|
|
|
|
|
ctx.offset += w.stream.pos - initPos
|
|
|
|
|
|
|
|
|
|
template endRecord*(w: var SszWriter, ctx: var auto) =
|
|
|
|
|
when ctx is VarSizedWriterCtx:
|
|
|
|
|
finalize ctx.fixedParts
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
proc writeSeq[T](w: var SszWriter, value: seq[T])
|
|
|
|
|
{.raises: [Defect, IOError].} =
|
2020-05-24 08:17:33 +00:00
|
|
|
|
# Please note that `writeSeq` exists in order to reduce the code bloat
|
|
|
|
|
# produced from generic instantiations of the unique `List[N, T]` types.
|
2020-05-22 19:55:00 +00:00
|
|
|
|
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"
|
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
proc writeVarSizeType(w: var SszWriter, value: auto) {.raises: [Defect, IOError].} =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "STARTING VAR SIZE TYPE"
|
|
|
|
|
mixin toSszType
|
|
|
|
|
type T = type toSszType(value)
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
when T is List:
|
2020-05-24 08:17:33 +00:00
|
|
|
|
# We reduce code bloat by forwarding all `List` types to a general `seq[T]` proc.
|
2020-05-22 19:55:00 +00:00
|
|
|
|
writeSeq(w, asSeq value)
|
|
|
|
|
elif T is BitList:
|
2020-05-24 08:17:33 +00:00
|
|
|
|
# ATTENTION! We can reuse `writeSeq` only as long as our BitList type is implemented
|
|
|
|
|
# to internally match the binary representation of SSZ BitLists in memory.
|
2020-05-22 19:55:00 +00:00
|
|
|
|
writeSeq(w, bytes value)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
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
|
2020-05-22 19:55:00 +00:00
|
|
|
|
else:
|
|
|
|
|
unsupported type(value)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
proc writeValue*(w: var SszWriter, x: auto) {.gcsafe, raises: [Defect, IOError].} =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
mixin toSszType
|
|
|
|
|
type T = type toSszType(x)
|
|
|
|
|
|
|
|
|
|
when isFixedSize(T):
|
|
|
|
|
w.stream.writeFixedSized toSszType(x)
|
|
|
|
|
else:
|
2020-05-22 19:55:00 +00:00
|
|
|
|
w.writeVarSizeType toSszType(x)
|
|
|
|
|
|
|
|
|
|
func sszSize*(value: auto): int {.gcsafe, raises: [Defect].}
|
2018-09-20 15:45:02 +00:00
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
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].} =
|
2020-05-11 16:27:44 +00:00
|
|
|
|
mixin toSszType
|
|
|
|
|
type T = type toSszType(value)
|
|
|
|
|
|
|
|
|
|
when isFixedSize(T):
|
|
|
|
|
anonConst fixedPortionSize(T)
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif T is array|List:
|
2020-05-11 16:27:44 +00:00
|
|
|
|
type E = ElemType(T)
|
|
|
|
|
when isFixedSize(E):
|
|
|
|
|
len(value) * anonConst(fixedPortionSize(E))
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif T is array:
|
|
|
|
|
sszSizeForVarSizeList(value)
|
2020-05-11 16:27:44 +00:00
|
|
|
|
else:
|
2020-05-22 19:55:00 +00:00
|
|
|
|
sszSizeForVarSizeList(asSeq value)
|
|
|
|
|
|
|
|
|
|
elif T is BitList:
|
|
|
|
|
return len(bytes(value))
|
2020-05-11 16:27:44 +00:00
|
|
|
|
|
|
|
|
|
elif T is object|tuple:
|
|
|
|
|
result = anonConst fixedPortionSize(T)
|
2020-05-28 08:28:14 +00:00
|
|
|
|
enumInstanceSerializedFields(value, _{.used.}, field):
|
2020-05-11 16:27:44 +00:00
|
|
|
|
type FieldType = type toSszType(field)
|
|
|
|
|
when not isFixedSize(FieldType):
|
|
|
|
|
result += sszSize(toSszType field)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
unsupported T
|
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
proc writeValue*[T](w: var SszWriter, x: SizePrefixed[T]) {.raises: [Defect, IOError].} =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
var cursor = w.stream.delayVarSizeWrite(10)
|
|
|
|
|
let initPos = w.stream.pos
|
|
|
|
|
w.writeValue T(x)
|
2019-09-09 02:39:44 +00:00
|
|
|
|
let length = uint64(w.stream.pos - initPos)
|
|
|
|
|
when false:
|
|
|
|
|
discard
|
|
|
|
|
# TODO varintBytes is sub-optimal at the moment
|
|
|
|
|
# cursor.writeAndFinalize length.varintBytes
|
|
|
|
|
else:
|
|
|
|
|
var buf: VarintBuffer
|
2020-05-06 12:31:05 +00:00
|
|
|
|
buf.writeVarint length
|
|
|
|
|
cursor.finalWrite buf.writtenBytes
|
2018-12-17 18:03:53 +00:00
|
|
|
|
|
2020-05-06 12:31:05 +00:00
|
|
|
|
proc readValue*[T](r: var SszReader, val: var T) {.raises: [Defect, MalformedSszError, SszSizeMismatchError, IOError].} =
|
2020-01-29 00:40:27 +00:00
|
|
|
|
when isFixedSize(T):
|
2020-02-05 14:26:45 +00:00
|
|
|
|
const minimalSize = fixedPortionSize(T)
|
2020-04-09 18:36:00 +00:00
|
|
|
|
if r.stream.readable(minimalSize):
|
2020-05-27 15:04:43 +00:00
|
|
|
|
readSszValue(r.stream.read(minimalSize), val)
|
2020-01-29 00:40:27 +00:00
|
|
|
|
else:
|
|
|
|
|
raise newException(MalformedSszError, "SSZ input of insufficient size")
|
|
|
|
|
else:
|
|
|
|
|
# TODO Read the fixed portion first and precisely measure the size of
|
|
|
|
|
# the dynamic portion to consume the right number of bytes.
|
2020-05-27 15:04:43 +00:00
|
|
|
|
readSszValue(r.stream.read(r.stream.len.get), val)
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2019-03-25 16:46:31 +00:00
|
|
|
|
const
|
2019-07-03 07:35:05 +00:00
|
|
|
|
zeroChunk = default array[32, byte]
|
|
|
|
|
|
|
|
|
|
func hash(a, b: openArray[byte]): Eth2Digest =
|
|
|
|
|
result = withEth2Hash:
|
|
|
|
|
trs "MERGING BRANCHES "
|
2020-05-27 11:36:02 +00:00
|
|
|
|
trs toHex(a)
|
|
|
|
|
trs toHex(b)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
h.update a
|
|
|
|
|
h.update b
|
|
|
|
|
trs "HASH RESULT ", result
|
|
|
|
|
|
|
|
|
|
func mergeBranches(existing: Eth2Digest, newData: openarray[byte]): Eth2Digest =
|
|
|
|
|
result = withEth2Hash:
|
|
|
|
|
trs "MERGING BRANCHES OPEN ARRAY"
|
2020-05-27 11:36:02 +00:00
|
|
|
|
trs toHex(existing.data)
|
|
|
|
|
trs toHex(newData)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
h.update existing.data
|
|
|
|
|
h.update newData
|
|
|
|
|
|
|
|
|
|
let paddingBytes = bytesPerChunk - newData.len
|
|
|
|
|
if paddingBytes > 0:
|
|
|
|
|
trs "USING ", paddingBytes, " PADDING BYTES"
|
2020-05-22 08:31:19 +00:00
|
|
|
|
h.update zeroChunk.toOpenArray(0, paddingBytes - 1)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "HASH RESULT ", result
|
|
|
|
|
|
|
|
|
|
template mergeBranches(a, b: Eth2Digest): Eth2Digest =
|
|
|
|
|
hash(a.data, b.data)
|
|
|
|
|
|
2020-05-28 12:36:37 +00:00
|
|
|
|
func computeZeroHashes: array[sizeof(Limit) * 8, Eth2Digest] =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
result[0] = Eth2Digest(data: zeroChunk)
|
|
|
|
|
for i in 1 .. result.high:
|
|
|
|
|
result[i] = mergeBranches(result[i - 1], result[i - 1])
|
|
|
|
|
|
2020-05-27 11:36:02 +00:00
|
|
|
|
const zeroHashes = computeZeroHashes()
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
func addChunk(merkleizer: var SszChunksMerkleizer, data: openarray[byte]) =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
doAssert data.len > 0 and data.len <= bytesPerChunk
|
|
|
|
|
|
2020-04-09 18:36:00 +00:00
|
|
|
|
if not getBitLE(merkleizer.totalChunks, 0):
|
|
|
|
|
let chunkStartAddr = addr merkleizer.combinedChunks[0].data[0]
|
2019-07-03 07:35:05 +00:00
|
|
|
|
copyMem(chunkStartAddr, unsafeAddr data[0], data.len)
|
2019-10-25 10:59:56 +00:00
|
|
|
|
zeroMem(chunkStartAddr.offset(data.len), bytesPerChunk - data.len)
|
2020-05-22 19:55:00 +00:00
|
|
|
|
trs "WROTE BASE CHUNK ", merkleizer.combinedChunks[0], " ", data.len
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
2020-04-09 18:36:00 +00:00
|
|
|
|
var hash = mergeBranches(merkleizer.combinedChunks[0], data)
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2020-05-21 13:21:29 +00:00
|
|
|
|
for i in 1 .. merkleizer.topIndex:
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "ITERATING"
|
2020-04-09 18:36:00 +00:00
|
|
|
|
if getBitLE(merkleizer.totalChunks, i):
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "CALLING MERGE BRANCHES"
|
2020-04-09 18:36:00 +00:00
|
|
|
|
hash = mergeBranches(merkleizer.combinedChunks[i], hash)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
trs "WRITING FRESH CHUNK AT ", i, " = ", hash
|
2020-04-09 18:36:00 +00:00
|
|
|
|
merkleizer.combinedChunks[i] = hash
|
2019-07-03 07:35:05 +00:00
|
|
|
|
break
|
|
|
|
|
|
2020-04-09 18:36:00 +00:00
|
|
|
|
inc merkleizer.totalChunks
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
2020-05-21 13:21:29 +00:00
|
|
|
|
template createMerkleizer(totalElements: static Limit): SszChunksMerkleizer =
|
2020-05-11 19:11:01 +00:00
|
|
|
|
trs "CREATING A MERKLEIZER FOR ", totalElements
|
2020-05-21 13:21:29 +00:00
|
|
|
|
|
|
|
|
|
const treeHeight = bitWidth nextPow2(uint64 totalElements)
|
|
|
|
|
var combinedChunks {.noInit.}: array[treeHeight, Eth2Digest]
|
2020-05-11 19:11:01 +00:00
|
|
|
|
|
|
|
|
|
SszChunksMerkleizer(
|
2020-05-21 13:21:29 +00:00
|
|
|
|
combinedChunks: cast[ptr UncheckedArray[Eth2Digest]](addr combinedChunks),
|
|
|
|
|
topIndex: treeHeight - 1,
|
2020-05-11 19:11:01 +00:00
|
|
|
|
totalChunks: 0)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
func getFinalHash(merkleizer: var SszChunksMerkleizer): Eth2Digest =
|
2020-04-09 18:36:00 +00:00
|
|
|
|
if merkleizer.totalChunks == 0:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
return zeroHashes[merkleizer.topIndex]
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
let
|
2020-04-09 18:36:00 +00:00
|
|
|
|
bottomHashIdx = firstOne(merkleizer.totalChunks) - 1
|
|
|
|
|
submittedChunksHeight = bitWidth(merkleizer.totalChunks - 1)
|
2020-05-21 13:21:29 +00:00
|
|
|
|
topHashIdx = merkleizer.topIndex
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
trs "BOTTOM HASH ", bottomHashIdx
|
|
|
|
|
trs "SUBMITTED HEIGHT ", submittedChunksHeight
|
2020-05-11 19:11:01 +00:00
|
|
|
|
trs "TOP HASH IDX ", topHashIdx
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
if bottomHashIdx != submittedChunksHeight:
|
|
|
|
|
# Our tree is not finished. We must complete the work in progress
|
|
|
|
|
# branches and then extend the tree to the right height.
|
2020-04-09 18:36:00 +00:00
|
|
|
|
result = mergeBranches(merkleizer.combinedChunks[bottomHashIdx],
|
2020-05-27 11:36:02 +00:00
|
|
|
|
zeroHashes[bottomHashIdx])
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
for i in bottomHashIdx + 1 ..< topHashIdx:
|
2020-04-09 18:36:00 +00:00
|
|
|
|
if getBitLE(merkleizer.totalChunks, i):
|
|
|
|
|
result = mergeBranches(merkleizer.combinedChunks[i], result)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "COMBINED"
|
|
|
|
|
else:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
result = mergeBranches(result, zeroHashes[i])
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "COMBINED WITH ZERO"
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
elif bottomHashIdx == topHashIdx:
|
|
|
|
|
# We have a perfect tree (chunks == 2**n) at just the right height!
|
2020-04-09 18:36:00 +00:00
|
|
|
|
result = merkleizer.combinedChunks[bottomHashIdx]
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
# We have a perfect tree of user chunks, but we have more work to
|
|
|
|
|
# do - we must extend it to reach the desired height
|
2020-04-09 18:36:00 +00:00
|
|
|
|
result = mergeBranches(merkleizer.combinedChunks[bottomHashIdx],
|
2020-05-27 11:36:02 +00:00
|
|
|
|
zeroHashes[bottomHashIdx])
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
for i in bottomHashIdx + 1 ..< topHashIdx:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
result = mergeBranches(result, zeroHashes[i])
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
func mixInLength(root: Eth2Digest, length: int): Eth2Digest =
|
|
|
|
|
var dataLen: array[32, byte]
|
2020-03-05 00:29:27 +00:00
|
|
|
|
dataLen[0..<8] = uint64(length).toBytesLE()
|
2019-07-03 07:35:05 +00:00
|
|
|
|
hash(root.data, dataLen)
|
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
|
func hash_tree_root*(x: auto): Eth2Digest {.gcsafe, raises: [Defect].}
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
2020-05-21 13:21:29 +00:00
|
|
|
|
template merkleizeFields(totalElements: static Limit, body: untyped): Eth2Digest =
|
2020-05-11 19:11:01 +00:00
|
|
|
|
var merkleizer {.inject.} = createMerkleizer(totalElements)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
template addField(field) =
|
2019-08-28 12:07:00 +00:00
|
|
|
|
let hash = hash_tree_root(field)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "MERKLEIZING FIELD ", astToStr(field), " = ", hash
|
2020-04-09 18:36:00 +00:00
|
|
|
|
addChunk(merkleizer, hash.data)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "CHUNK ADDED"
|
|
|
|
|
|
|
|
|
|
body
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
getFinalHash(merkleizer)
|
|
|
|
|
|
2020-05-18 17:49:22 +00:00
|
|
|
|
template writeBytesLE(chunk: var array[bytesPerChunk, byte], atParam: int,
|
2020-05-20 17:05:22 +00:00
|
|
|
|
val: SomeUnsignedInt) =
|
2020-05-18 17:49:22 +00:00
|
|
|
|
let at = atParam
|
|
|
|
|
chunk[at ..< at + sizeof(val)] = toBytesLE(val)
|
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
|
|
|
|
|
arr: openarray[T]): Eth2Digest =
|
|
|
|
|
static:
|
2020-05-24 08:17:33 +00:00
|
|
|
|
doAssert T is BasicType
|
2020-05-11 19:11:01 +00:00
|
|
|
|
|
|
|
|
|
when T is byte:
|
|
|
|
|
var
|
|
|
|
|
remainingBytes = arr.len
|
|
|
|
|
pos = cast[ptr byte](unsafeAddr arr[0])
|
|
|
|
|
|
|
|
|
|
while remainingBytes >= bytesPerChunk:
|
|
|
|
|
merkleizer.addChunk(makeOpenArray(pos, bytesPerChunk))
|
|
|
|
|
pos = offset(pos, bytesPerChunk)
|
|
|
|
|
remainingBytes -= bytesPerChunk
|
|
|
|
|
|
|
|
|
|
if remainingBytes > 0:
|
|
|
|
|
merkleizer.addChunk(makeOpenArray(pos, remainingBytes))
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif T is bool or cpuEndian == littleEndian:
|
2020-05-11 19:11:01 +00:00
|
|
|
|
let
|
|
|
|
|
baseAddr = cast[ptr byte](unsafeAddr arr[0])
|
|
|
|
|
len = arr.len * sizeof(T)
|
|
|
|
|
return chunkedHashTreeRootForBasicTypes(merkleizer, makeOpenArray(baseAddr, len))
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
static:
|
2020-05-24 08:17:33 +00:00
|
|
|
|
doAssert T is UintN
|
|
|
|
|
doAssert bytesPerChunk mod sizeof(Т) == 0
|
2020-05-11 19:11:01 +00:00
|
|
|
|
|
|
|
|
|
const valuesPerChunk = bytesPerChunk div sizeof(Т)
|
|
|
|
|
|
|
|
|
|
var writtenValues = 0
|
|
|
|
|
|
|
|
|
|
var chunk: array[bytesPerChunk, byte]
|
|
|
|
|
while writtenValues < arr.len - valuesPerChunk:
|
|
|
|
|
for i in 0 ..< valuesPerChunk:
|
2020-05-18 17:49:22 +00:00
|
|
|
|
chunk.writeBytesLE(i * sizeof(T), arr[writtenValues + i])
|
2020-05-11 19:11:01 +00:00
|
|
|
|
merkleizer.addChunk chunk
|
|
|
|
|
inc writtenValues, valuesPerChunk
|
|
|
|
|
|
|
|
|
|
let remainingValues = arr.len - writtenValues
|
|
|
|
|
if remainingValues > 0:
|
|
|
|
|
var lastChunk: array[bytesPerChunk, byte]
|
|
|
|
|
for i in 0 ..< remainingValues:
|
2020-05-18 17:49:22 +00:00
|
|
|
|
chunk.writeBytesLE(i * sizeof(T), arr[writtenValues + i])
|
2020-05-11 19:11:01 +00:00
|
|
|
|
merkleizer.addChunk lastChunk
|
2019-03-25 16:46:31 +00:00
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
getFinalHash(merkleizer)
|
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
func bitListHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Digest =
|
2020-05-11 19:11:01 +00:00
|
|
|
|
# TODO: Switch to a simpler BitList representation and
|
|
|
|
|
# replace this with `chunkedHashTreeRoot`
|
2020-05-21 13:21:29 +00:00
|
|
|
|
trs "CHUNKIFYING BIT SEQ WITH TOP INDEX ", merkleizer.topIndex
|
2018-11-14 20:06:04 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
var
|
2020-05-22 19:55:00 +00:00
|
|
|
|
totalBytes = bytes(x).len
|
|
|
|
|
lastCorrectedByte = bytes(x)[^1]
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
if lastCorrectedByte == byte(1):
|
|
|
|
|
if totalBytes == 1:
|
|
|
|
|
# This is an empty bit list.
|
|
|
|
|
# It should be hashed as a tree containing all zeros:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
return mergeBranches(zeroHashes[merkleizer.topIndex],
|
|
|
|
|
zeroHashes[0]) # this is the mixed length
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
totalBytes -= 1
|
2020-05-22 19:55:00 +00:00
|
|
|
|
lastCorrectedByte = bytes(x)[^2]
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
|
|
|
|
let markerPos = log2trunc(lastCorrectedByte)
|
2019-12-20 13:25:33 +00:00
|
|
|
|
lastCorrectedByte.clearBit(markerPos)
|
2019-03-20 20:01:48 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
var
|
|
|
|
|
bytesInLastChunk = totalBytes mod bytesPerChunk
|
|
|
|
|
fullChunks = totalBytes div bytesPerChunk
|
2019-03-25 16:46:31 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
if bytesInLastChunk == 0:
|
|
|
|
|
fullChunks -= 1
|
|
|
|
|
bytesInLastChunk = 32
|
2019-04-03 15:46:22 +00:00
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
|
for i in 0 ..< fullChunks:
|
|
|
|
|
let
|
|
|
|
|
chunkStartPos = i * bytesPerChunk
|
|
|
|
|
chunkEndPos = chunkStartPos + bytesPerChunk - 1
|
2019-04-03 15:46:22 +00:00
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
|
merkleizer.addChunk bytes(x).toOpenArray(chunkStartPos, chunkEndPos)
|
2019-04-03 15:46:22 +00:00
|
|
|
|
|
|
|
|
|
var
|
2019-07-03 07:35:05 +00:00
|
|
|
|
lastChunk: array[bytesPerChunk, byte]
|
|
|
|
|
chunkStartPos = fullChunks * bytesPerChunk
|
|
|
|
|
|
|
|
|
|
for i in 0 .. bytesInLastChunk - 2:
|
2020-05-22 19:55:00 +00:00
|
|
|
|
lastChunk[i] = bytes(x)[chunkStartPos + i]
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
lastChunk[bytesInLastChunk - 1] = lastCorrectedByte
|
|
|
|
|
|
2020-04-09 18:36:00 +00:00
|
|
|
|
merkleizer.addChunk lastChunk.toOpenArray(0, bytesInLastChunk - 1)
|
|
|
|
|
let contentsHash = merkleizer.getFinalHash
|
2019-07-03 07:35:05 +00:00
|
|
|
|
mixInLength contentsHash, x.len
|
|
|
|
|
|
2020-05-11 19:11:01 +00:00
|
|
|
|
func maxChunksCount(T: type, maxLen: int64): int64 =
|
2020-05-22 19:55:00 +00:00
|
|
|
|
when T is BitList|BitArray:
|
2020-05-11 19:11:01 +00:00
|
|
|
|
(maxLen + bitsPerChunk - 1) div bitsPerChunk
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif T is array|List:
|
2020-05-11 19:11:01 +00:00
|
|
|
|
type E = ElemType(T)
|
|
|
|
|
when E is BasicType:
|
|
|
|
|
(maxLen * sizeof(E) + bytesPerChunk - 1) div bytesPerChunk
|
|
|
|
|
else:
|
|
|
|
|
maxLen
|
|
|
|
|
else:
|
|
|
|
|
unsupported T # This should never happen
|
|
|
|
|
|
2020-05-18 17:49:22 +00:00
|
|
|
|
func hashTreeRootAux[T](x: T): Eth2Digest =
|
2020-04-29 20:12:07 +00:00
|
|
|
|
when T is SignedBeaconBlock:
|
|
|
|
|
unsupported T # Blocks are identified by htr(BeaconBlock) so we avoid these
|
2020-05-11 19:11:01 +00:00
|
|
|
|
elif T is bool|char:
|
|
|
|
|
result.data[0] = byte(x)
|
2020-05-20 17:05:22 +00:00
|
|
|
|
elif T is SomeUnsignedInt:
|
2020-05-11 19:11:01 +00:00
|
|
|
|
when cpuEndian == bigEndian:
|
|
|
|
|
result.data[0..<sizeof(x)] = toBytesLE(x)
|
|
|
|
|
else:
|
|
|
|
|
copyMem(addr result.data[0], unsafeAddr x, sizeof x)
|
|
|
|
|
elif (when T is array: ElemType(T) is BasicType else: false):
|
2020-05-18 17:49:22 +00:00
|
|
|
|
type E = ElemType(T)
|
|
|
|
|
when sizeof(T) <= sizeof(result.data):
|
2020-05-22 19:55:00 +00:00
|
|
|
|
when E is byte|bool or cpuEndian == littleEndian:
|
2020-05-18 17:49:22 +00:00
|
|
|
|
copyMem(addr result.data[0], unsafeAddr x, sizeof x)
|
|
|
|
|
else:
|
|
|
|
|
var pos = 0
|
|
|
|
|
for e in x:
|
|
|
|
|
writeBytesLE(result.data, pos, e)
|
|
|
|
|
pos += sizeof(E)
|
|
|
|
|
else:
|
|
|
|
|
trs "FIXED TYPE; USE CHUNK STREAM"
|
|
|
|
|
var markleizer = createMerkleizer(maxChunksCount(T, x.len))
|
|
|
|
|
chunkedHashTreeRootForBasicTypes(markleizer, x)
|
2020-05-22 19:55:00 +00:00
|
|
|
|
elif T is BitArray:
|
|
|
|
|
hashTreeRootAux(x.bytes)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
elif T is array|object|tuple:
|
2020-04-15 11:51:30 +00:00
|
|
|
|
trs "MERKLEIZING FIELDS"
|
2020-05-11 19:11:01 +00:00
|
|
|
|
const totalFields = when T is array: len(x)
|
|
|
|
|
else: totalSerializedFields(T)
|
|
|
|
|
merkleizeFields(totalFields):
|
2019-07-03 07:35:05 +00:00
|
|
|
|
x.enumerateSubFields(f):
|
2020-05-18 17:49:22 +00:00
|
|
|
|
addField f
|
2019-07-03 07:35:05 +00:00
|
|
|
|
#elif isCaseObject(T):
|
|
|
|
|
# # TODO implement this
|
|
|
|
|
else:
|
|
|
|
|
unsupported T
|
|
|
|
|
|
2020-05-14 22:54:10 +00:00
|
|
|
|
func hash_tree_root*(x: auto): Eth2Digest {.raises: [Defect], nbench.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
|
trs "STARTING HASH TREE ROOT FOR TYPE ", name(type(x))
|
|
|
|
|
mixin toSszType
|
2020-05-27 11:36:02 +00:00
|
|
|
|
result = when x is List|BitList:
|
2020-05-18 17:49:22 +00:00
|
|
|
|
const maxLen = static(x.maxLen)
|
|
|
|
|
type T = type(x)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
const limit = maxChunksCount(T, maxLen)
|
2020-05-11 19:11:01 +00:00
|
|
|
|
var merkleizer = createMerkleizer(limit)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
2020-05-18 17:49:22 +00:00
|
|
|
|
when x is BitList:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
merkleizer.bitListHashTreeRoot(BitSeq x)
|
2020-05-18 17:49:22 +00:00
|
|
|
|
else:
|
2019-07-03 07:35:05 +00:00
|
|
|
|
type E = ElemType(T)
|
|
|
|
|
let contentsHash = when E is BasicType:
|
2020-05-18 17:49:22 +00:00
|
|
|
|
chunkedHashTreeRootForBasicTypes(merkleizer, asSeq x)
|
2019-03-25 16:46:31 +00:00
|
|
|
|
else:
|
2020-05-18 17:49:22 +00:00
|
|
|
|
for elem in x:
|
2019-08-28 12:07:00 +00:00
|
|
|
|
let elemHash = hash_tree_root(elem)
|
2020-04-09 18:36:00 +00:00
|
|
|
|
merkleizer.addChunk(elemHash.data)
|
|
|
|
|
merkleizer.getFinalHash()
|
2020-05-27 11:36:02 +00:00
|
|
|
|
mixInLength(contentsHash, x.len)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
else:
|
2020-05-27 11:36:02 +00:00
|
|
|
|
hashTreeRootAux toSszType(x)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
|
|
trs "HASH TREE ROOT FOR ", name(type x), " = ", "0x", $result
|
|
|
|
|
|
2020-05-21 13:21:29 +00:00
|
|
|
|
iterator hash_tree_roots_prefix*[T](lst: openarray[T], limit: static Limit): Eth2Digest =
|
2019-11-25 11:46:47 +00:00
|
|
|
|
# This is a particular type's instantiation of a general fold, reduce,
|
|
|
|
|
# accumulation, prefix sums, etc family of operations. As long as that
|
|
|
|
|
# Eth1 deposit case is the only notable example -- the usual uses of a
|
|
|
|
|
# list involve, at some point, tree-hashing it -- finalized hashes are
|
|
|
|
|
# the only abstraction that escapes from this module this way.
|
2020-05-11 19:11:01 +00:00
|
|
|
|
var merkleizer = createMerkleizer(limit)
|
2019-11-25 11:46:47 +00:00
|
|
|
|
for i, elem in lst:
|
2020-04-09 18:36:00 +00:00
|
|
|
|
merkleizer.addChunk(hash_tree_root(elem).data)
|
|
|
|
|
yield mixInLength(merkleizer.getFinalHash(), i + 1)
|