Fix two bugs in Receipts RLP encoding/decoding (#672)

1. Fix Assertion error when receipt is not a List nor has a single
byte value. Receiving such garbage data would cause a crash.
2. Fix decoding of Receipt list by adding the missing Blob
encapsulation

Also added tests for these scenarios.
This commit is contained in:
Kim De Mey 2024-02-05 23:22:15 +01:00 committed by GitHub
parent 4433efe484
commit 2ad07a6010
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 201 additions and 40 deletions

View File

@ -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 # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * 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). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -349,33 +349,111 @@ proc append*(w: var RlpWriter, rec: Receipt) =
w.append(rec.bloom) w.append(rec.bloom)
w.append(rec.logs) w.append(rec.logs)
proc read*(rlp: var Rlp, T: type Receipt): T = proc readReceiptLegacy(rlp: var Rlp, receipt: var Receipt) =
if rlp.isList: receipt.receiptType = LegacyReceipt
result.receiptType = LegacyReceipt
else:
# EIP 2718
let recType = rlp.getByteValue
rlp.position += 1
if recType notin {1, 2, 3}:
raise newException(UnsupportedRlpError,
"TxType expect 1, 2, or 3 got " & $recType)
result.receiptType = ReceiptType(recType)
rlp.tryEnterList() rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}: if rlp.isBlob and rlp.blobLen in {0, 1}:
result.isHash = false receipt.isHash = false
result.status = rlp.read(uint8) == 1 receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32: elif rlp.isBlob and rlp.blobLen == 32:
result.isHash = true receipt.isHash = true
result.hash = rlp.read(Hash256) receipt.hash = rlp.read(Hash256)
else: else:
raise newException(RlpTypeMismatch, raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.") "HashOrStatus expected, but the source RLP is not a blob of right size.")
rlp.read(result.cumulativeGasUsed) rlp.read(receipt.cumulativeGasUsed)
rlp.read(result.bloom) rlp.read(receipt.bloom)
rlp.read(result.logs) rlp.read(receipt.logs)
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}:
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:
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)
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.} = proc read*(rlp: var Rlp, T: type EthTime): T {.inline.} =
result = EthTime rlp.read(uint64) result = EthTime rlp.read(uint64)

View File

@ -1,6 +1,6 @@
{ {
"listsoflists2": { "listsoflists2": {
"in": "VALID", "in": "VALID",
"out": "c7c0c1c0c3c0c1c0" "out": "c7c0c1c0c3c0c1c0"
} }
} }

View File

@ -1,31 +1,31 @@
{ {
"int32Overflow": { "int32Overflow": {
"in": "INVALID", "in": "INVALID",
"out": "bf0f000000000000021111" "out": "bf0f000000000000021111"
}, },
"int32Overflow2": { "int32Overflow2": {
"in": "INVALID", "in": "INVALID",
"out": "ff0f000000000000021111" "out": "ff0f000000000000021111"
}, },
"wrongSizeList": { "wrongSizeList": {
"in": "INVALID", "in": "INVALID",
"out": "f80180" "out": "f80180"
}, },
"wrongSizeList2": { "wrongSizeList2": {
"in": "INVALID", "in": "INVALID",
"out": "f80100" "out": "f80100"
}, },
"incorrectLengthInArray": { "incorrectLengthInArray": {
"in": "INVALID", "in": "INVALID",
"out": "b9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df0" "out": "b9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df0"
}, },
"randomRLP": { "randomRLP": {
"in": "INVALID", "in": "INVALID",
"out": "f861f83eb9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df027b90015002d5ef8325ae4d034df55d4b58d0dfba64d61ddd17be00000b9001a00dae30907045a2f66fa36f2bb8aa9029cbb0b8a7b3b5c435ab331" "out": "f861f83eb9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df027b90015002d5ef8325ae4d034df55d4b58d0dfba64d61ddd17be00000b9001a00dae30907045a2f66fa36f2bb8aa9029cbb0b8a7b3b5c435ab331"
}, },

View File

@ -1,31 +1,31 @@
{ {
"T1": { "T1": {
"in": "INVALID", "in": "INVALID",
"out": "" "out": ""
}, },
"T2": { "T2": {
"in": "INVALID", "in": "INVALID",
"out": "00ab" "out": "00ab"
}, },
"T3": { "T3": {
"in": "INVALID", "in": "INVALID",
"out": "0000ff" "out": "0000ff"
}, },
"T4": { "T4": {
"in": "VALID", "in": "VALID",
"out": "83646F67636174" "out": "83646F67636174"
}, },
"T5": { "T5": {
"in": "INVALID", "in": "INVALID",
"out": "83646F" "out": "83646F"
}, },
"T6": { "T6": {
"in": "INVALID", "in": "INVALID",
"out": "c7c0c1c0c3c0c1c0ff" "out": "c7c0c1c0c3c0c1c0ff"
}, },

View File

@ -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.} {.used.}
import import
@ -33,8 +39,8 @@ proc loadFile(x: int) =
check bytes1 == bytes3 check bytes1 == bytes3
proc suite1() = proc suite1() =
suite "rlp encoding": suite "RLP encoding":
test "receipt roundtrip": test "Receipt roundtrip":
let a = Receipt( let a = Receipt(
receiptType: LegacyReceipt, receiptType: LegacyReceipt,
isHash: false, isHash: false,
@ -57,7 +63,7 @@ proc suite1() =
check aa == a check aa == a
check bb == b check bb == b
test "EIP 2930 receipt": test "EIP-2930 receipt":
let a = Receipt( let a = Receipt(
receiptType: Eip2930Receipt, receiptType: Eip2930Receipt,
status: true status: true
@ -76,7 +82,7 @@ proc suite1() =
check aa == a check aa == a
check bb == b check bb == b
test "EIP 4895 roundtrip": test "EIP-4895 roundtrip":
let a = Withdrawal( let a = Withdrawal(
index: 1, index: 1,
validatorIndex: 2, validatorIndex: 2,
@ -90,11 +96,11 @@ proc suite1() =
check aa == a check aa == a
proc suite2() = proc suite2() =
suite "eip 2718 transaction": suite "EIP-2718 transaction / receipt":
for i in 0..<10: for i in 0..<10:
loadFile(i) loadFile(i)
test "rlp roundtrip EIP1559 / EIP4895 / EIP4844": test "BlockHeader: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844":
proc doTest(h: BlockHeader) = proc doTest(h: BlockHeader) =
let xy = rlp.encode(h) let xy = rlp.encode(h)
let hh = rlp.decode(xy, BlockHeader) let hh = rlp.decode(xy, BlockHeader)
@ -117,5 +123,82 @@ proc suite2() =
h.excessBlobGas = some 1234'u64 h.excessBlobGas = some 1234'u64
doTest h 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() suite1()
suite2() suite2()