implement EIP-1559 Transaction and BlockHeader

This commit is contained in:
jangko 2021-06-26 15:18:42 +07:00
parent 601fa7ff66
commit a10d301085
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
3 changed files with 322 additions and 298 deletions

View File

@ -4,7 +4,7 @@ import
../rlp, ../trie/[trie_defs, db]
export
stint, read, append, KeccakHash, rlp
stint, read, append, KeccakHash, rlp, options
type
Hash256* = MDigest[256]
@ -39,21 +39,10 @@ type
ChainId* = distinct uint64
Account* = object
nonce*: AccountNonce
balance*: UInt256
storageRoot*: Hash256
codeHash*: Hash256
LegacyTx* = object
nonce* : AccountNonce
gasPrice*: GasInt
gasLimit*: GasInt
to* {.rlpCustomSerialization.}: EthAddress
value* : UInt256
payload* : Blob
V* : int64
R*, S* : UInt256
isContractCreation* {.rlpIgnore.}: bool
nonce*: AccountNonce
balance*: UInt256
storageRoot*: Hash256
codeHash*: Hash256
AccessPair* = object
address* : EthAddress
@ -61,29 +50,25 @@ type
AccessList* = seq[AccessPair]
AccessListTx* = object
chainId* {.rlpCustomSerialization.}: ChainId
nonce* : AccountNonce
gasPrice* : GasInt
gasLimit* : GasInt
to* {.rlpCustomSerialization.}: EthAddress
value* : UInt256
payload* : Blob
accessList*: AccessList
V* : int64
R*, S* : UInt256
isContractCreation* {.rlpIgnore.}: bool
TxType* = enum
LegacyTxType
AccessListTxType
TxLegacy
TxEip2930
TxEip1559
Transaction* = object
case txType*: TxType
of LegacyTxType:
legacyTx*: LegacyTx
of AccessListTxType:
accessListTx*: AccessListTx
txType* : TxType # EIP-2718
chainId* : ChainId # EIP-2930
nonce* : AccountNonce
gasPrice* : GasInt
maxPriorityFee*: GasInt # EIP-1559
maxFee* : GasInt # EIP-1559
gasLimit* : GasInt
to* : Option[EthAddress]
value* : UInt256
payload* : Blob
accessList* : AccessList # EIP-2930
V* : int64
R*, S* : UInt256
TransactionStatus* = enum
Unknown,
@ -112,6 +97,8 @@ type
extraData*: Blob
mixDigest*: Hash256
nonce*: BlockNonce
# `baseFee` is the get/set of `fee`
fee*: Option[UInt256] # EIP-1559
BlockBody* = object
transactions*{.rlpCustomSerialization.}: seq[Transaction]
@ -122,35 +109,21 @@ type
topics*: seq[Topic]
data*: Blob
HashOrStatus* = object
case isHash*: bool
of true:
hash*: Hash256
else:
status*: bool
LegacyReceipt* = object
stateRootOrStatus*: HashOrStatus
cumulativeGasUsed*: GasInt
bloom*: BloomFilter
logs* : seq[Log]
AccessListReceipt* = object
status*: bool
cumulativeGasUsed*: GasInt
bloom* : BloomFilter
logs* : seq[Log]
ReceiptType* = enum
LegacyReceiptType
AccessListReceiptType
# easily convertible between
# ReceiptType and TxType
ReceiptType* = TxType
# LegacyReceipt = TxLegacy
# Eip2930Receipt = TxEip2930
# Eip1559Receipt = TxEip1559
Receipt* = object
case receiptType*: ReceiptType
of LegacyReceiptType:
legacyReceipt*: LegacyReceipt
of AccessListReceiptType:
accessListReceipt*: AccessListReceipt
receiptType* : ReceiptType
isHash* : bool # hash or status
status* : bool # EIP-658
hash* : Hash256
cumulativeGasUsed*: GasInt
bloom* : BloomFilter
logs* : seq[Log]
EthBlock* = object
header*: BlockHeader
@ -222,6 +195,11 @@ type
OK
Error
const
LegacyReceipt* = TxLegacy
Eip2930Receipt* = TxEip2930
Eip1559Receipt* = TxEip1559
when BlockNumber is int64:
## The goal of these templates is to make it easier to switch
## the block number type to a different representation
@ -256,6 +234,16 @@ else:
template u256*(n: BlockNumber): UInt256 =
n
# EIP-1559 conveniences
func baseFee*(h: BlockHeader | BlockHeaderRef): UInt256 =
if h.fee.isSome:
h.fee.get()
else:
0.u256
template `baseFee=`*(h: BlockHeader | BlockHeaderRef, data: UInt256) =
h.fee = some(data)
func toBlockNonce*(n: uint64): BlockNonce =
n.toBytesBE()
@ -268,25 +256,24 @@ proc newAccount*(nonce: AccountNonce = 0, balance: UInt256 = 0.u256): Account =
result.storageRoot = emptyRlpHash
result.codeHash = blankStringHash
proc hashOrStatus*(hash: Hash256): HashOrStatus =
HashOrStatus(isHash: true, hash: hash)
proc hasStatus*(rec: Receipt): bool {.inline.} =
rec.isHash == false
proc hashOrStatus*(status: bool): HashOrStatus =
HashOrStatus(isHash: false, status: status)
proc hasStateRoot*(rec: Receipt): bool {.inline.} =
rec.isHash == true
proc hasStatus*(rec: LegacyReceipt): bool {.inline.} =
rec.stateRootOrStatus.isHash == false
proc hasStateRoot*(rec: LegacyReceipt): bool {.inline.} =
rec.stateRootOrStatus.isHash == true
proc stateRoot*(rec: LegacyReceipt): Hash256 {.inline.} =
proc stateRoot*(rec: Receipt): Hash256 {.inline.} =
doAssert(rec.hasStateRoot)
rec.stateRootOrStatus.hash
rec.hash
proc status*(rec: LegacyReceipt): int {.inline.} =
doAssert(rec.hasStatus)
if rec.stateRootOrStatus.status: 1 else: 0
template contractCreation*(tx: Transaction): bool =
tx.to.isNone
func destination*(tx: Transaction): EthAddress =
# use getRecipient if you also want to get
# the contract address
if tx.to.isSome:
return tx.to.get
#
# Rlp serialization:
@ -329,52 +316,132 @@ proc append*(rlpWriter: var RlpWriter, value: Stint) =
{.fatal: "RLP serialization of signed integers is not allowed".}
discard
type
TxTypes* = LegacyTx | AccessListTx
proc append*[T](w: var RlpWriter, val: Option[T]) =
if val.isSome:
w.append(val.get())
else:
w.append("")
proc read*(rlp: var Rlp, t: var TxTypes, _: type EthAddress): EthAddress {.inline.} =
proc appendTxLegacy(w: var RlpWriter, tx: Transaction) =
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip2930(w: var RlpWriter, tx: Transaction) =
w.append(1)
w.startList(11)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip1559(w: var RlpWriter, tx: Transaction) =
w.append(2)
w.startList(12)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFee)
w.append(tx.maxFee)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc append*(w: var RlpWriter, tx: Transaction) =
case tx.txType
of TxLegacy:
w.appendTxLegacy(tx)
of TxEip2930:
w.appendTxEip2930(tx)
of TxEip1559:
w.appendTxEip1559(tx)
template read[T](rlp: var Rlp, val: var T)=
val = rlp.read(type val)
proc read[T](rlp: var Rlp, val: var Option[T])=
if rlp.blobLen != 0:
result = rlp.read(EthAddress)
val = some(rlp.read(T))
else:
t.isContractCreation = true
rlp.skipElem()
rlp.skipElem
proc append*(rlpWriter: var RlpWriter, t: TxTypes, a: EthAddress) {.inline.} =
if t.isContractCreation:
rlpWriter.append("")
else:
rlpWriter.append(a)
proc readTxLegacy(rlp: var Rlp, tx: var Transaction)=
tx.txType = TxLegacy
rlp.tryEnterList()
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc read*(rlp: var Rlp, t: var AccessListTx, _: type ChainId): ChainId {.inline.} =
rlp.read(uint64).ChainId
proc readTxEip2930(rlp: var Rlp, tx: var Transaction)=
tx.txType = TxEip2930
rlp.tryEnterList()
tx.chainId = rlp.read(uint64).ChainId
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.accessList)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc append*(rlpWriter: var RlpWriter, t: AccessListTx, a: ChainId) {.inline.} =
rlpWriter.append(a.uint64)
proc append*(rlpWriter: var RlpWriter, tx: Transaction) =
if tx.txType == LegacyTxType:
rlpWriter.append(tx.legacyTx)
else:
# EIP 2718/2930
rlpWriter.append(1)
rlpWriter.append(tx.accessListTx)
proc readTxEip1559(rlp: var Rlp, tx: var Transaction)=
tx.txType = TxEip1559
rlp.tryEnterList()
tx.chainId = rlp.read(uint64).ChainId
rlp.read(tx.nonce)
rlp.read(tx.maxPriorityFee)
rlp.read(tx.maxFee)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.accessList)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc read*(rlp: var Rlp, T: type Transaction): T =
if rlp.isList:
return Transaction(
txType: LegacyTxType,
legacyTx: rlp.read(LegacyTx)
)
rlp.readTxLegacy(result)
return
# EIP 2718/2930
# EIP 2718
let txType = rlp.read(int)
if txType != 1:
if txType notin {1, 2}:
raise newException(UnsupportedRlpError,
"TxType expect 1 got " & $txType)
return Transaction(
txType: AccessListTxType,
accessListTx: rlp.read(AccessListTx)
)
"TxType expect 1 or 2 got " & $txType)
if TxType(txType) == TxEip2930:
rlp.readTxEip2930(result)
else:
rlp.readTxEip1559(result)
proc read*(rlp: var Rlp, t: var (EthBlock | BlockBody), _: type seq[Transaction]): seq[Transaction] {.inline.} =
# EIP 2718/2930: we have to override this field
@ -395,21 +462,11 @@ proc append*(rlpWriter: var RlpWriter, blk: EthBlock | BlockBody, txs: seq[Trans
# not rlp(txType, txPayload) -> two list elem, wrong!
rlpWriter.startList(txs.len)
for tx in txs:
if tx.txType == LegacyTxType:
if tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))
proc read*(rlp: var Rlp, T: type HashOrStatus): T {.inline.} =
if rlp.isBlob() and (rlp.blobLen() == 32 or rlp.blobLen() == 1):
if rlp.blobLen == 1:
result = hashOrStatus(rlp.read(uint8) == 1)
else:
result = hashOrStatus(rlp.read(Hash256))
else:
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")
func init*(T: type BlockHashOrNumber, str: string): T
{.raises: [ValueError, Defect].} =
if str.startsWith "0x":
@ -428,36 +485,45 @@ func `$`*(x: BlockHashOrNumber): string =
else:
$x.number
proc append*(rlpWriter: var RlpWriter, value: HashOrStatus) {.inline.} =
if value.isHash:
rlpWriter.append(value.hash)
else:
rlpWriter.append(if value.status: 1'u8 else: 0'u8)
proc append*(w: var RlpWriter, rec: Receipt) =
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt}:
w.append(rec.receiptType.int)
proc append*(rlpWriter: var RlpWriter, rec: Receipt) =
if rec.receiptType == LegacyReceiptType:
rlpWriter.append(rec.legacyReceipt)
w.startList(4)
if rec.isHash:
w.append(rec.hash)
else:
# EIP 2718/2930
rlpWriter.append(1)
rlpWriter.append(rec.accessListReceipt)
w.append(rec.status.uint8)
w.append(rec.cumulativeGasUsed)
w.append(rec.bloom)
w.append(rec.logs)
proc read*(rlp: var Rlp, T: type Receipt): T =
if rlp.isList:
return Receipt(
receiptType: LegacyReceiptType,
legacyReceipt: rlp.read(LegacyReceipt)
)
result.receiptType = LegacyReceipt
else:
# EIP 2718
let recType = rlp.read(int)
if recType notin {1, 2}:
raise newException(UnsupportedRlpError,
"TxType expect 1 or 2 got " & $recType)
result.receiptType = ReceiptType(recType)
# EIP 2718/2930
let recType = rlp.read(int)
if recType != 1:
raise newException(UnsupportedRlpError,
"TxType expect 1 got " & $recType)
return Receipt(
receiptType: AccessListReceiptType,
accessListReceipt: rlp.read(AccessListReceipt)
)
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
result.isHash = false
result.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
result.isHash = true
result.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)
proc read*(rlp: var Rlp, T: type Time): T {.inline.} =
result = fromUnix(rlp.read(int64))
@ -478,6 +544,30 @@ proc read*(rlp: var Rlp, T: type HashOrNum): T =
proc append*(rlpWriter: var RlpWriter, t: Time) {.inline.} =
rlpWriter.append(t.toUnix())
proc append*(w: var RlpWriter, h: BlockHeader) =
w.startList(if h.fee.isSome: 16 else: 15)
for k, v in fieldPairs(h):
when k != "fee":
w.append(v)
if h.fee.isSome:
w.append(h.fee.get())
proc read*(rlp: var Rlp, T: type BlockHeader): T =
let len = rlp.listLen
if len notin {15, 16}:
raise newException(UnsupportedRlpError,
"BlockHeader elems should be 15 or 16 got " & $len)
rlp.tryEnterList()
for k, v in fieldPairs(result):
when k != "fee":
v = rlp.read(type v)
if len == 16:
# EIP-1559
result.baseFee = rlp.read(UInt256)
proc rlpHash*[T](v: T): Hash256 =
keccak256.digest(rlp.encode(v))

View File

@ -2,118 +2,76 @@ import
nimcrypto/keccak,
".."/[common, rlp, keys]
proc initLegacyTx*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAddress,
value: UInt256, payload: Blob, V: int64, R, S: UInt256, isContractCreation = false): LegacyTx =
result.nonce = nonce
result.gasPrice = gasPrice
result.gasLimit = gasLimit
result.to = to
result.value = value
result.payload = payload
result.V = V
result.R = R
result.S = S
result.isContractCreation = isContractCreation
type
LegacyUnsignedTx* = object
nonce*: AccountNonce
gasPrice*: GasInt
gasLimit*: GasInt
to* {.rlpCustomSerialization.}: EthAddress
value*: UInt256
payload*: Blob
isContractCreation* {.rlpIgnore.}: bool
AccessListUnsignedTx* = object
chainId* {.rlpCustomSerialization.}: ChainId
nonce* : AccountNonce
gasPrice* : GasInt
gasLimit* : GasInt
to* {.rlpCustomSerialization.}: EthAddress
value* : UInt256
payload* : Blob
accessList*: AccessList
isContractCreation* {.rlpIgnore.}: bool
UnsignedTxTypes* = LegacyUnsignedTx | AccessListUnsignedTx
proc read*(rlp: var Rlp, t: var UnsignedTxTypes, _: type EthAddress): EthAddress {.inline.} =
if rlp.blobLen != 0:
result = rlp.read(EthAddress)
else:
t.isContractCreation = true
proc append*(rlpWriter: var RlpWriter, t: UnsignedTxTypes, a: EthAddress) {.inline.} =
if t.isContractCreation:
rlpWriter.append("")
else:
rlpWriter.append(a)
proc read*(rlp: var Rlp, t: var AccessListUnsignedTx, _: type ChainId): ChainId {.inline.} =
rlp.read(uint64).ChainId
proc append*(rlpWriter: var RlpWriter, t: AccessListUnsignedTx, a: ChainId) {.inline.} =
rlpWriter.append(a.uint64)
const
EIP155_CHAIN_ID_OFFSET* = 35'i64
func rlpEncode*(tx: LegacyTx): auto =
# Encode transaction without signature
return rlp.encode(LegacyUnsignedTx(
nonce: tx.nonce,
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
to: tx.to,
value: tx.value,
payload: tx.payload,
isContractCreation: tx.isContractCreation
))
func rlpEncodeLegacy(tx: Transaction): auto =
var w = initRlpWriter()
w.startList(6)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.finish()
func rlpEncodeEIP155*(tx: LegacyTx): auto =
let V = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
# Encode transaction without signature
return rlp.encode(LegacyTx(
nonce: tx.nonce,
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
to: tx.to,
value: tx.value,
payload: tx.payload,
isContractCreation: tx.isContractCreation,
V: V,
R: 0.u256,
S: 0.u256
))
func rlpEncodeEip155(tx: Transaction): auto =
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
var w = initRlpWriter()
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(chainId)
w.append(0)
w.append(0)
w.finish()
func rlpEncode*(tx: AccessListTx): auto =
# EIP 2718/2930
let unsignedTx = AccessListUnsignedTx(
chainId: tx.chainId,
nonce: tx.nonce,
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
to: tx.to,
value: tx.value,
payload: tx.payload,
accessList: tx.accessList,
isContractCreation: tx.isContractCreation
)
var rw = initRlpWriter()
rw.append(1)
rw.append(unsignedTx)
rw.finish()
func rlpEncodeEip2930(tx: Transaction): auto =
var w = initRlpWriter()
w.append(1)
w.startList(8)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
func txHashNoSignature*(tx: LegacyTx): Hash256 =
# Hash transaction without signature
keccak256.digest(if tx.V >= EIP155_CHAIN_ID_OFFSET: tx.rlpEncodeEIP155 else: tx.rlpEncode)
func rlpEncodeEip1559(tx: Transaction): auto =
var w = initRlpWriter()
w.append(2)
w.startList(9)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFee)
w.append(tx.maxFee)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
func txHashNoSignature*(tx: AccessListTx): Hash256 =
keccak256.digest(tx.rlpEncode)
func rlpEncode*(tx: Transaction): auto =
case tx.txType
of TxLegacy:
if tx.V >= EIP155_CHAIN_ID_OFFSET:
tx.rlpEncodeEIP155
else:
tx.rlpEncodeLegacy
of TxEip2930:
tx.rlpEncodeEip2930
of TxEip1559:
tx.rlpEncodeEip1559
func txHashNoSignature*(tx: Transaction): Hash256 =
if tx.txType == LegacyTxType:
txHashNoSignature(tx.legacyTx)
else:
txHashNoSignature(tx.accessListTx)
# Hash transaction without signature
keccak256.digest(rlpEncode(tx))

View File

@ -9,33 +9,9 @@ type
EthHeader = object
header: BlockHeader
proc `==`(a, b: HashOrStatus): bool =
result = a.isHash == b.isHash
if not result: return
if a.isHash:
result = result and a.hash == b.hash
else:
result = result and a.status == b.status
func `==`(a, b: ChainId): bool =
a.uint64 == b.uint64
func `==`(a, b: Transaction): bool =
if a.txType != b.txType:
return false
if a.txType == LegacyTxType:
return a.legacyTx == b.legacyTx
else:
return a.accessListTx == b.accessListTx
func `==`(a, b: Receipt): bool =
if a.receiptType != b.receiptType:
return false
if a.receiptType == LegacyReceiptType:
return a.legacyReceipt == b.legacyReceipt
else:
return a.accessListReceipt == b.accessListReceipt
proc loadFile(x: int) =
let fileName = "tests" / "rlp" / "eip2718" / "acl_block_" & $x & ".json"
test fileName:
@ -62,20 +38,18 @@ proc suite1() =
suite "rlp encoding":
test "receipt roundtrip":
let a = Receipt(
receiptType: LegacyReceiptType,
legacyReceipt: LegacyReceipt(
stateRootOrStatus: hashOrStatus(true),
cumulativeGasUsed: 51000
)
receiptType: LegacyReceipt,
isHash: false,
status: false,
cumulativeGasUsed: 51000
)
let hash = rlpHash(a)
let b = Receipt(
receiptType: LegacyReceiptType,
legacyReceipt: LegacyReceipt(
stateRootOrStatus: hashOrStatus(hash),
cumulativeGasUsed: 21000
)
receiptType: LegacyReceipt,
isHash: true,
hash: hash,
cumulativeGasUsed: 21000
)
let abytes = rlp.encode(a)
@ -85,20 +59,16 @@ proc suite1() =
check aa == a
check bb == b
test "access list receipt":
test "EIP 2930 receipt":
let a = Receipt(
receiptType: AccessListReceiptType,
accessListReceipt: AccessListReceipt(
status: true
)
receiptType: Eip2930Receipt,
status: true
)
let b = Receipt(
receiptType: AccessListReceiptType,
accessListReceipt: AccessListReceipt(
status: false,
cumulativeGasUsed: 21000
)
receiptType: Eip2930Receipt,
status: false,
cumulativeGasUsed: 21000
)
let abytes = rlp.encode(a)
@ -113,5 +83,11 @@ proc suite2() =
for i in 0..<10:
loadFile(i)
test "rlp roundtrip EIP1559":
var h: BlockHeader
let xy = rlp.encode(h)
let hh = rlp.decode(xy, BlockHeader)
check h == hh
suite1()
suite2()
suite2()