Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clean up redundant code in eth/rlp/writer.nim #755

Merged
merged 6 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions eth/common/base_rlp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,17 @@

{.push raises: [].}

import std/typetraits, ./base, ../rlp
import
std/typetraits, ./base, ../rlp,
../rlp/results as rlp_results

export base, rlp
export base, rlp, rlp_results

# TODO why is rlp serialization of `Opt` here and not in rlp?
proc append*[T](w: var RlpWriter, val: Opt[T]) =
mixin append

if val.isSome:
w.append(val.get())
else:
w.append("")

template read*[T](rlp: var Rlp, val: var T) =
mixin read
val = rlp.read(type val)

proc read*[T](rlp: var Rlp, val: var Opt[T]) {.raises: [RlpError].} =
mixin read
if rlp.blobLen != 0:
val = Opt.some(rlp.read(T))
else:
rlp.skipElem

proc read*(rlp: var Rlp, T: type StUint): T {.raises: [RlpError].} =
if rlp.isBlob:
let bytes = rlp.toBytes
Expand Down
13 changes: 0 additions & 13 deletions eth/rlp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -448,26 +448,13 @@ func readImpl(
else:
rlp.bytes.len()

template getUnderlyingType[T](_: Option[T]): untyped =
T

template getUnderlyingType[T](_: Opt[T]): untyped =
T

template op(RecordType, fieldName, field) {.used.} =
type FieldType {.used.} = type field
when hasCustomPragmaFixed(RecordType, fieldName, rlpCustomSerialization):
field = rlp.read(result, FieldType)
elif field is Option:
# this works for optional fields at the end of an object/tuple
# if the optional field is followed by a mandatory field,
# custom serialization for a field or for the parent object
# will be better
type UT = getUnderlyingType(field)
if rlp.position < payloadEnd:
field = some(rlp.read(UT))
else:
field = none(UT)
elif field is Opt:
# this works for optional fields at the end of an object/tuple
# if the optional field is followed by a mandatory field,
Expand Down
17 changes: 0 additions & 17 deletions eth/rlp/options.nim

This file was deleted.

22 changes: 22 additions & 0 deletions eth/rlp/results.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ../rlp
import writer
import pkg/results

export
rlp, results

proc append*[T](w: var RlpWriter, val: Opt[T]) =
mixin append

if val.isSome:
w.append(val.get())
else:
w.append("")

proc read*[T](rlp: var Rlp, val: var Opt[T]) {.raises: [RlpError].} =
mixin read
if rlp.blobLen != 0:
val = Opt.some(rlp.read(T))
else:
rlp.skipElem

168 changes: 60 additions & 108 deletions eth/rlp/writer.nim
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import
std/options,
results,
pkg/results,
stew/[arraybuf, assign2, bitops2, shims/macros],
./priv/defs

export arraybuf

type
RlpWriter* = object
pendingLists: seq[tuple[remainingItems, outBytes: int]]
pendingLists: seq[tuple[remainingItems, startPos: int]]
output: seq[byte]

RlpIntBuf* = ArrayBuf[9, byte]
Expand Down Expand Up @@ -41,7 +41,7 @@ func writeCount(bytes: var auto, count: int, baseMarker: byte) =
origLen = bytes.len
lenPrefixBytes = uint64(count).bytesNeeded

bytes.setLen(origLen + int(lenPrefixBytes) + 1)
bytes.setLen(origLen + lenPrefixBytes + 1)
bytes[origLen] = baseMarker + (THRESHOLD_LIST_LEN - 1) + byte(lenPrefixBytes)
bytes.writeBigEndian(uint64(count), bytes.len - 1, lenPrefixBytes)

Expand All @@ -60,17 +60,16 @@ proc initRlpWriter*: RlpWriter =
# expected to be short-lived, it doesn't hurt to allocate this buffer
result.output = newSeqOfCap[byte](2000)

proc decRet(n: var int, delta: int): int =
n -= delta
n

proc maybeClosePendingLists(self: var RlpWriter) =
while self.pendingLists.len > 0:
let lastListIdx = self.pendingLists.len - 1
doAssert self.pendingLists[lastListIdx].remainingItems >= 1
if decRet(self.pendingLists[lastListIdx].remainingItems, 1) == 0:
doAssert self.pendingLists[lastListIdx].remainingItems > 0

self.pendingLists[lastListIdx].remainingItems -= 1
# if one last item is remaining in the list
if self.pendingLists[lastListIdx].remainingItems == 0:
# A list have been just finished. It was started in `startList`.
let listStartPos = self.pendingLists[lastListIdx].outBytes
let listStartPos = self.pendingLists[lastListIdx].startPos
self.pendingLists.setLen lastListIdx

# How many bytes were written since the start?
Expand Down Expand Up @@ -104,98 +103,69 @@ proc appendRawBytes*(self: var RlpWriter, bytes: openArray[byte]) =
self.output.len - bytes.len, self.output.len - 1), bytes)
self.maybeClosePendingLists()

proc appendRawList(self: var RlpWriter, bytes: openArray[byte]) =
self.output.writeCount(bytes.len, LIST_START_MARKER)
self.appendRawBytes(bytes)

proc startList*(self: var RlpWriter, listSize: int) =
if listSize == 0:
self.appendRawList([])
self.output.writeCount(0, LIST_START_MARKER)
self.appendRawBytes([])
else:
self.pendingLists.add((listSize, self.output.len))

proc appendBlob(self: var RlpWriter, data: openArray[byte], startMarker: byte) =
proc appendBlob(self: var RlpWriter, data: openArray[byte]) =
if data.len == 1 and byte(data[0]) < BLOB_START_MARKER:
self.output.add byte(data[0])
self.maybeClosePendingLists()
else:
self.output.writeCount(data.len, startMarker)
self.output.writeCount(data.len, BLOB_START_MARKER)
self.appendRawBytes(data)

proc appendImpl(self: var RlpWriter, data: string) =
appendBlob(self, data.toOpenArrayByte(0, data.high), BLOB_START_MARKER)

proc appendBlob(self: var RlpWriter, data: openArray[byte]) =
appendBlob(self, data, BLOB_START_MARKER)

proc appendBlob(self: var RlpWriter, data: openArray[char]) =
appendBlob(self, data.toOpenArrayByte(0, data.high), BLOB_START_MARKER)

proc appendInt(self: var RlpWriter, i: SomeUnsignedInt) =
# this is created as a separate proc as an extra precaution against
# any overloading resolution problems when matching the IntLike concept.
self.output.writeInt(i)

self.maybeClosePendingLists()

template appendImpl(self: var RlpWriter, i: SomeUnsignedInt) =
appendInt(self, i)

template appendImpl(self: var RlpWriter, e: enum) =
appendImpl(self, int(e))
template appendImpl(self: var RlpWriter, data: openArray[byte]) =
self.appendBlob(data)

template appendImpl(self: var RlpWriter, b: bool) =
appendImpl(self, int(b))
template appendImpl(self: var RlpWriter, data: openArray[char]) =
self.appendBlob(data.toOpenArrayByte(0, data.high))

proc appendImpl[T](self: var RlpWriter, listOrBlob: openArray[T]) =
mixin append
template appendImpl(self: var RlpWriter, data: string) =
self.appendBlob(data.toOpenArrayByte(0, data.high))

# TODO: This append proc should be overloaded by `openArray[byte]` after
# nim bug #7416 is fixed.
when T is (byte or char):
self.appendBlob(listOrBlob)
else:
self.startList listOrBlob.len
for i in 0 ..< listOrBlob.len:
self.append listOrBlob[i]
template appendImpl(self: var RlpWriter, i: SomeUnsignedInt) =
self.appendInt(i)

proc hasOptionalFields(T: type): bool =
mixin enumerateRlpFields
template appendImpl(self: var RlpWriter, e: enum) =
# TODO: check for negative enums
self.appendInt(uint64(e))

proc helper: bool =
var dummy: T
result = false
template detectOptionalField(RT, n, x) {.used.} =
when x is Option or x is Opt:
return true
enumerateRlpFields(dummy, detectOptionalField)
template appendImpl(self: var RlpWriter, b: bool) =
self.appendInt(uint64(b))

const res = helper()
return res
proc appendImpl[T](self: var RlpWriter, list: openArray[T]) =
mixin append

proc optionalFieldsNum(x: openArray[bool]): int =
# count optional fields backward
for i in countdown(x.len-1, 0):
if x[i]: inc result
else: break
self.startList list.len
for i in 0 ..< list.len:
self.append list[i]

proc checkedOptionalFields(T: type, FC: static[int]): int =
proc countOptionalFields(T: type): int {.compileTime.} =
mixin enumerateRlpFields

var
i = 0
dummy: T
res: array[FC, bool]
var dummy: T

# closure signature matches the one in object_serialization.nim
template op(RT, fN, f) =
res[i] = f is Option or f is Opt
inc i
when f is Option or f is Opt:
inc result
else: # this will count only optional fields at the end
result = 0

enumerateRlpFields(dummy, op)

# ignoring first optional fields
optionalFieldsNum(res) - 1

proc genPrevFields(obj: NimNode, fd: openArray[FieldDescription], hi, lo: int): NimNode =
result = newStmtList()
for i in countdown(hi, lo):
Expand Down Expand Up @@ -230,58 +200,44 @@ macro genOptionalFieldsValidation(obj: untyped, T: type, num: static[int]): unty
doAssert obj.blobGasUsed.isSome == obj.excessBlobGas.isSome,
"blobGasUsed and excessBlobGas must both be present or absent"

macro countFieldsRuntimeImpl(obj: untyped, T: type, num: static[int]): untyped =
let
Tresolved = getType(T)[1]
fd = recordFields(Tresolved.getImpl)
res = ident("result")
mlen = fd.len - num

result = newStmtList()
result.add quote do:
`res` = `mlen`

for i in countdown(fd.high, fd.len-num):
let fieldName = fd[i].name
result.add quote do:
`res` += `obj`.`fieldName`.isSome.ord

proc countFieldsRuntime(obj: object|tuple): int =
# count mandatory fields and non empty optional fields
type ObjType = type obj
mixin enumerateRlpFields

const
fieldsCount = ObjType.rlpFieldsCount
# include first optional fields
cof = checkedOptionalFields(ObjType, fieldsCount) + 1
var numOptionals: int = 0

countFieldsRuntimeImpl(obj, ObjType, cof)
template op(RT, fN, f) {.used.} =
when f is Option or f is Opt:
if f.isSome: # if optional and non empty
inc numOptionals
else: # if mandatory field
inc result
numOptionals = 0 # count only optionals at the end (after mandatory)

enumerateRlpFields(obj, op)
result += numOptionals

proc appendRecordType*(self: var RlpWriter, obj: object|tuple, wrapInList = wrapObjsInList) =
mixin enumerateRlpFields, append

type ObjType = type obj

const
hasOptional = hasOptionalFields(ObjType)
fieldsCount = ObjType.rlpFieldsCount
cof = countOptionalFields(ObjType)

when hasOptional:
const
cof = checkedOptionalFields(ObjType, fieldsCount)
when cof > 0:
genOptionalFieldsValidation(obj, ObjType, cof)
when cof > 0:
# ignoring first optional fields
genOptionalFieldsValidation(obj, ObjType, cof - 1)

if wrapInList:
when hasOptional:
when cof > 0:
self.startList(obj.countFieldsRuntime)
else:
self.startList(fieldsCount)
self.startList(ObjType.rlpFieldsCount)

template op(RecordType, fieldName, field) {.used.} =
when hasCustomPragmaFixed(RecordType, fieldName, rlpCustomSerialization):
append(self, obj, field)
elif (field is Option or field is Opt) and hasOptional:
elif (field is Option or field is Opt) and cof > 0:
# this works for optional fields at the end of an object/tuple
# if the optional field is followed by a mandatory field,
# custom serialization for a field or for the parent object
Expand All @@ -293,20 +249,16 @@ proc appendRecordType*(self: var RlpWriter, obj: object|tuple, wrapInList = wrap

enumerateRlpFields(obj, op)

proc appendImpl(self: var RlpWriter, data: object) {.inline.} =
template appendImpl(self: var RlpWriter, data: object) =
self.appendRecordType(data)

proc appendImpl(self: var RlpWriter, data: tuple) {.inline.} =
template appendImpl(self: var RlpWriter, data: tuple) =
self.appendRecordType(data)

# We define a single `append` template with a pretty low specificity
# score in order to facilitate easier overloading with user types:
template append*[T](w: var RlpWriter; data: T) =
when data is (enum|bool):
# TODO detect negative enum values at compile time?
appendImpl(w, uint64(data))
else:
appendImpl(w, data)
appendImpl(w, data)

template append*(w: var RlpWriter; data: SomeSignedInt) =
{.error: "Signed integer encoding is not defined for rlp".}
Expand Down
3 changes: 2 additions & 1 deletion tests/rlp/all_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import
./test_api_usage,
./test_json_suite,
./test_empty_string,
./test_object_serialization
./test_object_serialization,
./test_optional_fields
Loading