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
|
# 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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue