nimbus-eth2/beacon_chain/ssz/navigator.nim
Jacek Sieka a7a65bce42
disentangle eth2 types from the ssz library (#2785)
* reorganize ssz dependencies

This PR continues the work in
https://github.com/status-im/nimbus-eth2/pull/2646,
https://github.com/status-im/nimbus-eth2/pull/2779 as well as past
issues with serialization and type, to disentangle SSZ from eth2 and at
the same time simplify imports and exports with a structured approach.

The principal idea here is that when a library wants to introduce SSZ
support, they do so via 3 files:

* `ssz_codecs` which imports and reexports `codecs` - this covers the
basic byte conversions and ensures no overloads get lost
* `xxx_merkleization` imports and exports `merkleization` to specialize
and get access to `hash_tree_root` and friends
* `xxx_ssz_serialization` imports and exports `ssz_serialization` to
specialize ssz for a specific library

Those that need to interact with SSZ always import the `xxx_` versions
of the modules and never `ssz` itself so as to keep imports simple and
safe.

This is similar to how the REST / JSON-RPC serializers are structured in
that someone wanting to serialize spec types to REST-JSON will import
`eth2_rest_serialization` and nothing else.

* split up ssz into a core library that is independendent of eth2 types
* rename `bytes_reader` to `codec` to highlight that it contains coding
and decoding of bytes and native ssz types
* remove tricky List init overload that causes compile issues
* get rid of top-level ssz import
* reenable merkleization tests
* move some "standard" json serializers to spec
* remove `ValidatorIndex` serialization for now
* remove test_ssz_merkleization
* add tests for over/underlong byte sequences
* fix broken seq[byte] test - seq[byte] is not an SSZ type

There are a few things this PR doesn't solve:

* like #2646 this PR is weak on how to handle root and other
dontSerialize fields that "sometimes" should be computed - the same
problem appears in REST / JSON-RPC etc

* Fix a build problem on macOS

* Another way to fix the macOS builds

Co-authored-by: Zahary Karadjov <zahary@gmail.com>
2021-08-18 20:57:58 +02:00

144 lines
5.0 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2020 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.
{.push raises: [Defect].}
{.pragma: raisesssz, raises: [Defect, MalformedSszError, SszSizeMismatchError].}
import
stew/[ptrops, objects], stew/ranges/ptr_arith,
./codec, ./types
export codec, types
type
MemRange* = object
startAddr*: ptr byte
length*: int
SszNavigator*[T] = object
m: MemRange
func sszMount*(data: openArray[byte], T: type): SszNavigator[T] =
let startAddr = unsafeAddr data[0]
SszNavigator[T](m: MemRange(startAddr: startAddr, length: data.len))
func sszMount*(data: openArray[char], T: type): SszNavigator[T] =
let startAddr = cast[ptr byte](unsafeAddr data[0])
SszNavigator[T](m: MemRange(startAddr: startAddr, length: data.len))
template sszMount*(data: MemRange, T: type): SszNavigator[T] =
SszNavigator[T](m: data)
template getMemRange*(n: SszNavigator): MemRange =
# Please note that this accessor was created intentionally.
# We don't want to expose the `m` field, because the navigated
# type may have a field by that name. We wan't any dot field
# access to be redirected to the navigated type.
# For this reason, this template should always be used with
# the function call syntax `getMemRange(n)`.
n.m
template checkBounds(m: MemRange, offset: int) =
if offset > m.length:
raise newException(MalformedSszError, "Malformed SSZ")
template toOpenArray(m: MemRange): auto =
makeOpenArray(m.startAddr, m.length)
func navigateToField*[T](n: SszNavigator[T],
fieldName: static string,
FieldType: type): SszNavigator[FieldType] {.raisesssz.} =
mixin toSszType
type SszFieldType = type toSszType(declval FieldType)
const boundingOffsets = getFieldBoundingOffsets(T, fieldName)
checkBounds(n.m, boundingOffsets[1])
when isFixedSize(SszFieldType):
SszNavigator[FieldType](m: MemRange(
startAddr: offset(n.m.startAddr, boundingOffsets[0]),
length: boundingOffsets[1] - boundingOffsets[0]))
else:
template readOffset(off): int =
int fromSszBytes(uint32, makeOpenArray(offset(n.m.startAddr, off),
sizeof(uint32)))
let
startOffset = readOffset boundingOffsets[0]
endOffset = when boundingOffsets[1] == -1: n.m.length
else: readOffset boundingOffsets[1]
if endOffset < startOffset or endOffset > n.m.length:
raise newException(MalformedSszError, "Incorrect offset values")
SszNavigator[FieldType](m: MemRange(
startAddr: offset(n.m.startAddr, startOffset),
length: endOffset - startOffset))
template `.`*[T](n: SszNavigator[T], field: untyped): auto =
type RecType = T
type FieldType = type(default(RecType).field)
navigateToField(n, astToStr(field), FieldType)
func indexVarSizeList(m: MemRange, idx: int): MemRange {.raisesssz.} =
template readOffset(pos): int =
int fromSszBytes(uint32, makeOpenArray(offset(m.startAddr, pos), offsetSize))
let offsetPos = offsetSize * idx
checkBounds(m, offsetPos + offsetSize)
let firstOffset = readOffset 0
let listLen = firstOffset div offsetSize
if idx >= listLen:
# TODO: Use a RangeError here?
# This would require the user to check the `len` upfront
raise newException(MalformedSszError, "Indexing past the end")
let elemPos = readOffset offsetPos
checkBounds(m, elemPos)
let endPos = if idx < listLen - 1:
let nextOffsetPos = offsetPos + offsetSize
# TODO. Is there a way to remove this bounds check?
checkBounds(m, nextOffsetPos + offsetSize)
readOffset(offsetPos + nextOffsetPos)
else:
m.length
MemRange(startAddr: m.startAddr.offset(elemPos), length: endPos - elemPos)
template indexList(n, idx, T: untyped): untyped =
type R = T
mixin toSszType
type ElemType = type toSszType(declval R)
when isFixedSize(ElemType):
const elemSize = fixedPortionSize(ElemType)
let elemPos = idx * elemSize
checkBounds(n.m, elemPos + elemSize)
SszNavigator[R](m: MemRange(startAddr: offset(n.m.startAddr, elemPos),
length: elemSize))
else:
SszNavigator[R](m: indexVarSizeList(n.m, idx))
template `[]`*[T](n: SszNavigator[seq[T]], idx: int): SszNavigator[T] =
indexList n, idx, T
template `[]`*[R, T](n: SszNavigator[array[R, T]], idx: int): SszNavigator[T] =
indexList(n, idx, T)
func `[]`*[T](n: SszNavigator[T]): T {.raisesssz.} =
mixin toSszType, fromSszBytes
type SszRepr = type toSszType(declval T)
when type(SszRepr) is type(T) or T is List:
readSszValue(toOpenArray(n.m), result)
else:
fromSszBytes(T, toOpenArray(n.m))
converter derefNavigator*[T](n: SszNavigator[T]): T {.raisesssz.} =
n[]