mirror of https://github.com/status-im/nim-eth.git
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:
parent
4433efe484
commit
2ad07a6010
|
@ -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).
|
||||
|
@ -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
|
||||
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)
|
||||
|
||||
proc readReceiptLegacy(rlp: var Rlp, receipt: var Receipt) =
|
||||
receipt.receiptType = LegacyReceipt
|
||||
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 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.} =
|
||||
result = EthTime rlp.read(uint64)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"listsoflists2": {
|
||||
"in": "VALID",
|
||||
"in": "VALID",
|
||||
"out": "c7c0c1c0c3c0c1c0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue