Skip to content
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
114 changes: 96 additions & 18 deletions eth/common/eth_types_rlp.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2022 Status Research & Development GmbH
# Copyright (c) 2022-2024 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).
Expand Down Expand Up @@ -349,33 +349,111 @@ proc append*(w: var RlpWriter, rec: Receipt) =
w.append(rec.bloom)
w.append(rec.logs)

proc read*(rlp: var Rlp, T: type Receipt): T =
if rlp.isList:
result.receiptType = LegacyReceipt
proc readReceiptLegacy(rlp: var Rlp, receipt: var Receipt) =
receipt.receiptType = LegacyReceipt
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
receipt.isHash = false
receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
receipt.isHash = true
receipt.hash = rlp.read(Hash256)
else:
# EIP 2718
let recType = rlp.getByteValue
rlp.position += 1
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")

rlp.read(receipt.cumulativeGasUsed)
rlp.read(receipt.bloom)
rlp.read(receipt.logs)

if recType notin {1, 2, 3}:
raise newException(UnsupportedRlpError,
"TxType expect 1, 2, or 3 got " & $recType)
result.receiptType = ReceiptType(recType)
proc readReceiptTyped(rlp: var Rlp, receipt: var Receipt) =
if not rlp.hasData:
raise newException(MalformedRlpError,
"Receipt expected but source RLP is empty")
if not rlp.isSingleByte:
raise newException(MalformedRlpError,
"ReceiptType byte is out of range, must be 0x00 to 0x7f")
let recType = rlp.getByteValue
rlp.position += 1

var txVal: ReceiptType
if checkedEnumAssign(txVal, recType):
case txVal:
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt:
receipt.receiptType = txVal
of LegacyReceipt:
# The legacy type should not be used here.
raise newException(MalformedRlpError,
"Invalid ReceiptType: " & $recType)
else:
raise newException(UnsupportedRlpError,
"Unsupported ReceiptType: " & $recType)

# Note: This currently remains the same as the legacy receipt.
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
result.isHash = false
result.status = rlp.read(uint8) == 1
receipt.isHash = false
receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
result.isHash = true
result.hash = rlp.read(Hash256)
receipt.isHash = true
receipt.hash = rlp.read(Hash256)
else:
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")

rlp.read(result.cumulativeGasUsed)
rlp.read(result.bloom)
rlp.read(result.logs)
rlp.read(receipt.cumulativeGasUsed)
rlp.read(receipt.bloom)
rlp.read(receipt.logs)

proc read*(rlp: var Rlp, T: type Receipt): T =
# Individual receipts are encoded and stored as either `RLP([fields..])`
# for legacy receipts, or `Type || RLP([fields..])`. Both of these
# encodings are byte sequences. The part after `Type` doesn't have to be
# RLP in theory, but all types so far use RLP. EIP-2718 covers this.
var receipt: Receipt
if rlp.isList:
rlp.readReceiptLegacy(receipt)
else:
rlp.readReceiptTyped(receipt)
receipt

proc read*(
rlp: var Rlp,
T: (type seq[Receipt]) | (type openArray[Receipt])
): seq[Receipt] =
# In arrays (sequences), receipts are encoded as either `RLP([fields..])`
# for legacy receipts, or `RLP(Type || RLP([fields..]))` for all typed
# receipts to date. Spot the extra `RLP(..)` blob encoding, to make it
# valid RLP inside a larger RLP. EIP-2976 covers this, "Typed Transactions
# over Gossip", although it's not very clear about the blob encoding.
#
# See also note about transactions above.
if not rlp.isList:
raise newException(RlpTypeMismatch,
"Receipts list expected, but source RLP is not a list")

var receipts: seq[Receipt]
for item in rlp:
var receipt: Receipt
if item.isList:
item.readReceiptLegacy(receipt)
else:
var rr = rlpFromBytes(rlp.read(Blob))
rr.readReceiptTyped(receipt)
receipts.add receipt

receipts

proc append*(
rlpWriter: var RlpWriter, receipts: seq[Receipt] | openArray[Receipt]
) =
# See above about encoding arrays/sequences of receipts.
rlpWriter.startList(receipts.len)
for receipt in receipts:
if receipt.receiptType == LegacyReceipt:
rlpWriter.append(receipt)
else:
rlpWriter.append(rlp.encode(receipt))

proc read*(rlp: var Rlp, T: type EthTime): T {.inline.} =
result = EthTime rlp.read(uint64)
Expand Down
2 changes: 1 addition & 1 deletion tests/rlp/cases/RandomRLPTests/example.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"listsoflists2": {
"in": "VALID",
"in": "VALID",
"out": "c7c0c1c0c3c0c1c0"
}
}
12 changes: 6 additions & 6 deletions tests/rlp/cases/invalidRLPTest.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
{
"int32Overflow": {
"in": "INVALID",
"in": "INVALID",
"out": "bf0f000000000000021111"
},

"int32Overflow2": {
"in": "INVALID",
"in": "INVALID",
"out": "ff0f000000000000021111"
},

"wrongSizeList": {
"in": "INVALID",
"in": "INVALID",
"out": "f80180"
},

"wrongSizeList2": {
"in": "INVALID",
"in": "INVALID",
"out": "f80100"
},

"incorrectLengthInArray": {
"in": "INVALID",
"in": "INVALID",
"out": "b9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df0"
},

"randomRLP": {
"in": "INVALID",
"in": "INVALID",
"out": "f861f83eb9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df027b90015002d5ef8325ae4d034df55d4b58d0dfba64d61ddd17be00000b9001a00dae30907045a2f66fa36f2bb8aa9029cbb0b8a7b3b5c435ab331"
},

Expand Down
12 changes: 6 additions & 6 deletions tests/rlp/cases/pyRlpInvalidCases.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
{
"T1": {
"in": "INVALID",
"in": "INVALID",
"out": ""
},

"T2": {
"in": "INVALID",
"in": "INVALID",
"out": "00ab"
},

"T3": {
"in": "INVALID",
"in": "INVALID",
"out": "0000ff"
},

"T4": {
"in": "VALID",
"in": "VALID",
"out": "83646F67636174"
},

"T5": {
"in": "INVALID",
"in": "INVALID",
"out": "83646F"
},

"T6": {
"in": "INVALID",
"in": "INVALID",
"out": "c7c0c1c0c3c0c1c0ff"
},

Expand Down
95 changes: 89 additions & 6 deletions tests/rlp/test_common.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Copyright (c) 2019-2024 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.

{.used.}

import
Expand Down Expand Up @@ -33,8 +39,8 @@ proc loadFile(x: int) =
check bytes1 == bytes3

proc suite1() =
suite "rlp encoding":
test "receipt roundtrip":
suite "RLP encoding":
test "Receipt roundtrip":
let a = Receipt(
receiptType: LegacyReceipt,
isHash: false,
Expand All @@ -57,7 +63,7 @@ proc suite1() =
check aa == a
check bb == b

test "EIP 2930 receipt":
test "EIP-2930 receipt":
let a = Receipt(
receiptType: Eip2930Receipt,
status: true
Expand All @@ -76,7 +82,7 @@ proc suite1() =
check aa == a
check bb == b

test "EIP 4895 roundtrip":
test "EIP-4895 roundtrip":
let a = Withdrawal(
index: 1,
validatorIndex: 2,
Expand All @@ -90,11 +96,11 @@ proc suite1() =
check aa == a

proc suite2() =
suite "eip 2718 transaction":
suite "EIP-2718 transaction / receipt":
for i in 0..<10:
loadFile(i)

test "rlp roundtrip EIP1559 / EIP4895 / EIP4844":
test "BlockHeader: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844":
proc doTest(h: BlockHeader) =
let xy = rlp.encode(h)
let hh = rlp.decode(xy, BlockHeader)
Expand All @@ -117,5 +123,82 @@ proc suite2() =
h.excessBlobGas = some 1234'u64
doTest h

test "Receipts EIP-2718 + EIP-2976 encoding":
const
# Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L370
payload = "f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"
receiptsBytes = hexToSeqByte(payload)
let receipts = rlp.decode(receiptsBytes, seq[Receipt])

check receipts.len() == 4
for receipt in receipts:
check receipt.receiptType == TxEip2930

let encoded = rlp.encode(receipts)

check receiptsBytes == encoded

test "Receipts EIP-2718 encoding - invalid - empty":
let receiptBytes: seq[byte] = @[]
expect MalformedRlpError:
let _ = rlp.decode(receiptBytes, Receipt)

test "Receipts EIP-2718 encoding - invalid - unsupported tx type":
let receiptBytes: seq[byte] = @[0x04]
expect UnsupportedRlpError:
let _ = rlp.decode(receiptBytes, Receipt)

test "Receipts EIP-2718 encoding - invalid - legacy tx type":
let receiptBytes: seq[byte] = @[0x00]
expect MalformedRlpError:
let _ = rlp.decode(receiptBytes, Receipt)

test "Receipts EIP-2718 encoding - invalid - out of bounds tx type":
let receiptBytes: seq[byte] = @[0x81, 0x80]
expect MalformedRlpError:
let _ = rlp.decode(receiptBytes, Receipt)

test "Receipts EIP-2718 encoding - invalid - empty receipt payload":
let receiptBytes: seq[byte] = @[0x02]
expect RlpTypeMismatch:
let _ = rlp.decode(receiptBytes, Receipt)

test "Receipt legacy":
const
# Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L417
payload = "f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
receiptsBytes = hexToSeqByte(payload)

let receipt = rlp.decode(receiptsBytes, Receipt)
check receipt.receiptType == LegacyReceipt
let encoded = rlp.encode(receipt)
check receiptsBytes == encoded

test "Receipt EIP-2930":
const
# Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L435
payload = "01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
receiptsBytes = hexToSeqByte(payload)

let receipt = rlp.decode(receiptsBytes, Receipt)
check receipt.receiptType == Eip2930Receipt
let encoded = rlp.encode(receipt)
check receiptsBytes == encoded

test "Receipt EIP-1559":
const
# Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L453
payload = "02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
receiptsBytes = hexToSeqByte(payload)

let receipt = rlp.decode(receiptsBytes, Receipt)
check receipt.receiptType == Eip1559Receipt
let encoded = rlp.encode(receipt)
check receiptsBytes == encoded

suite1()
suite2()