This commit is contained in:
Etan Kissling 2024-05-13 10:57:08 +03:00
parent 34adff98a6
commit 24ed2afba7
No known key found for this signature in database
GPG Key ID: B21DA824C5A3D03D
4 changed files with 1111 additions and 391 deletions

View File

@ -8,8 +8,10 @@ skipDirs = @["tests"]
requires "nim >= 1.6.0",
"nimcrypto",
"results",
"stint",
"secp256k1",
"ssz_serialization",
"chronos#head",
"chronicles",
"stew",

View File

@ -11,11 +11,22 @@
import
std/[options, strutils],
stew/[byteutils, endians2], stint,
results, stew/[byteutils, endians2], stint,
ssz_serialization,
./eth_hash, ./eth_times
export
options, stint, eth_hash, eth_times
options, results, stint, ssz_serialization, eth_hash, eth_times
const
MAX_CALLDATA_SIZE* = 1 shl 24 # 2^24
MAX_ACCESS_LIST_STORAGE_KEYS* = 1 shl 24 # 2^24
MAX_ACCESS_LIST_SIZE* = 1 shl 24 # 2^24
MAX_BLOB_COMMITMENTS_PER_BLOCK* = 4_096
ECDSA_SIGNATURE_SIZE* = 65
MAX_TRANSACTION_PAYLOAD_FIELDS* = 32
MAX_TRANSACTION_SIGNATURE_FIELDS* = 16
MAX_POOLED_TRANSACTION_FIELDS* = 8
type
Hash256* = MDigest[256]
@ -54,12 +65,6 @@ type
storageRoot*: Hash256
codeHash*: Hash256
AccessPair* = object
address* : EthAddress
storageKeys*: seq[StorageKey]
AccessList* = seq[AccessPair]
VersionedHash* = Hash256
VersionedHashes* = seq[VersionedHash]
KzgCommitment* = array[48, byte]
@ -69,37 +74,265 @@ type
# 4096 -> FIELD_ELEMENTS_PER_BLOB
NetworkBlob* = array[32*4096, byte]
TxType* = enum
TxType* = enum # EIP-2718
TxLegacy # 0
TxEip2930 # 1
TxEip1559 # 2
TxEip4844 # 3
StorageKeys* = List[StorageKey, Limit MAX_ACCESS_LIST_STORAGE_KEYS]
AccessPair* = object
address*: EthAddress
storage_keys*: StorageKeys
AccessList* = List[AccessPair, Limit MAX_ACCESS_LIST_SIZE]
TransactionPayload* {.
sszStableContainer: MAX_TRANSACTION_PAYLOAD_FIELDS.} = object
nonce*: uint64
max_fee_per_gas*: UInt256
gas*: uint64
to*: Opt[EthAddress]
value*: UInt256
input*: List[byte, Limit MAX_CALLDATA_SIZE]
# EIP-2718
tx_type* {.serializedFieldName: "type".}: Opt[TxType]
# EIP-2930
access_list*: Opt[AccessList]
# EIP-1559
max_priority_fee_per_gas*: Opt[UInt256]
# EIP-4844
max_fee_per_blob_gas*: Opt[UInt256]
blob_versioned_hashes*:
Opt[List[VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]]
TransactionSignature* {.
sszStableContainer: MAX_TRANSACTION_SIGNATURE_FIELDS.} = object
from_address* {.serializedFieldName: "from".}: EthAddress
ecdsa_signature*: array[ECDSA_SIGNATURE_SIZE, byte]
Transaction* = object # EIP-6493
payload*: TransactionPayload
signature*: TransactionSignature
ReplayableTransactionPayload* {.sszVariant: TransactionPayload.} = object
nonce*: uint64
max_fee_per_gas*: UInt256
gas*: uint64
to*: Opt[EthAddress]
value*: UInt256
input*: List[byte, Limit MAX_CALLDATA_SIZE]
ReplayableTransaction* {.sszVariant: Transaction.} = object # EIP-6493
payload*: ReplayableTransactionPayload
signature*: TransactionSignature
LegacyTransactionPayload* {.sszVariant: TransactionPayload.} = object
nonce*: uint64
max_fee_per_gas*: UInt256
gas*: uint64
to*: Opt[EthAddress]
value*: UInt256
input*: List[byte, Limit MAX_CALLDATA_SIZE]
tx_type* {.serializedFieldName: "type".}: TxType
LegacyTransaction* {.sszVariant: Transaction.} = object # EIP-6493
payload*: LegacyTransactionPayload
signature*: TransactionSignature
Eip2930TransactionPayload* {.sszVariant: TransactionPayload.} = object
nonce*: uint64
max_fee_per_gas*: UInt256
gas*: uint64
to*: Opt[EthAddress]
value*: UInt256
input*: List[byte, Limit MAX_CALLDATA_SIZE]
tx_type* {.serializedFieldName: "type".}: TxType
access_list*: AccessList
Eip2930Transaction* {.sszVariant: Transaction.} = object # EIP-6493
payload*: Eip2930TransactionPayload
signature*: TransactionSignature
Eip1559TransactionPayload* {.sszVariant: TransactionPayload.} = object
nonce*: uint64
max_fee_per_gas*: UInt256
gas*: uint64
to*: Opt[EthAddress]
value*: UInt256
input*: List[byte, Limit MAX_CALLDATA_SIZE]
tx_type* {.serializedFieldName: "type".}: TxType
access_list*: AccessList
max_priority_fee_per_gas*: UInt256
Eip1559Transaction* {.sszVariant: Transaction.} = object # EIP-6493
payload*: Eip1559TransactionPayload
signature*: TransactionSignature
Eip4844TransactionPayload* {.sszVariant: TransactionPayload.} = object
nonce*: uint64
max_fee_per_gas*: UInt256
gas*: uint64
to*: EthAddress
value*: UInt256
input*: List[byte, Limit MAX_CALLDATA_SIZE]
tx_type* {.serializedFieldName: "type".}: TxType
access_list*: AccessList
max_priority_fee_per_gas*: UInt256
max_fee_per_blob_gas*: UInt256
blob_versioned_hashes*:
List[VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
Eip4844Transaction* {.sszVariant: Transaction.} = object # EIP-6493
payload*: Eip4844TransactionPayload
signature*: TransactionSignature
TransactionKind* {.pure.} = enum
Replayable
Legacy
Eip2930
Eip1559
Eip4844
AnyTransactionPayloadVariant* =
ReplayableTransactionPayload |
LegacyTransactionPayload |
Eip2930TransactionPayload |
Eip1559TransactionPayload |
Eip4844TransactionPayload
AnyTransactionPayload* {.sszOneOf: TransactionPayload.} = object
case kind*: TransactionKind
of TransactionKind.Eip4844:
eip4844Data*: Eip4844TransactionPayload
of TransactionKind.Eip1559:
eip1559Data*: Eip1559TransactionPayload
of TransactionKind.Eip2930:
eip2930Data*: Eip2930TransactionPayload
of TransactionKind.Legacy:
legacyData*: LegacyTransactionPayload
of TransactionKind.Replayable:
replayableData*: ReplayableTransactionPayload
AnyTransactionVariant* =
ReplayableTransaction |
LegacyTransaction |
Eip2930Transaction |
Eip1559Transaction |
Eip4844Transaction
AnyTransaction* {.sszOneOf: Transaction.} = object
case kind*: TransactionKind
of TransactionKind.Eip4844:
eip4844Data*: Eip4844Transaction
of TransactionKind.Eip1559:
eip1559Data*: Eip1559Transaction
of TransactionKind.Eip2930:
eip2930Data*: Eip2930Transaction
of TransactionKind.Legacy:
legacyData*: LegacyTransaction
of TransactionKind.Replayable:
replayableData*: ReplayableTransaction
template withTxPayloadVariant*(
x: AnyTransactionPayload, body: untyped): untyped =
case x.kind
of TransactionKind.Eip4844:
const txKind {.inject, used.} = TransactionKind.Eip4844
template txPayloadVariant: untyped {.inject, used.} = x.eip4844Data
body
of TransactionKind.Eip1559:
const txKind {.inject, used.} = TransactionKind.Eip1559
template txPayloadVariant: untyped {.inject, used.} = x.eip1559Data
body
of TransactionKind.Eip2930:
const txKind {.inject, used.} = TransactionKind.Eip2930
template txPayloadVariant: untyped {.inject, used.} = x.eip2930Data
body
of TransactionKind.Legacy:
const txKind {.inject, used.} = TransactionKind.Legacy
template txPayloadVariant: untyped {.inject, used.} = x.legacyData
body
of TransactionKind.Replayable:
const txKind {.inject, used.} = TransactionKind.Replayable
template txPayloadVariant: untyped {.inject, used.} = x.replayableData
body
func init*(T: typedesc[AnyTransaction], tx: Eip4844Transaction): T =
T(kind: TransactionKind.Eip4844, eip4844Data: tx)
func init*(T: typedesc[AnyTransaction], tx: Eip1559Transaction): T =
T(kind: TransactionKind.Eip1559, eip1559Data: tx)
func init*(T: typedesc[AnyTransaction], tx: Eip2930Transaction): T =
T(kind: TransactionKind.Eip2930, eip2930Data: tx)
func init*(T: typedesc[AnyTransaction], tx: LegacyTransaction): T =
T(kind: TransactionKind.Legacy, legacyData: tx)
func init*(T: typedesc[AnyTransaction], tx: ReplayableTransaction): T =
T(kind: TransactionKind.Replayable, replayableData: tx)
template withTxVariant*(x: AnyTransaction, body: untyped): untyped =
case x.kind
of TransactionKind.Eip4844:
const txKind {.inject, used.} = TransactionKind.Eip4844
template txVariant: untyped {.inject, used.} = x.eip4844Data
body
of TransactionKind.Eip1559:
const txKind {.inject, used.} = TransactionKind.Eip1559
template txVariant: untyped {.inject, used.} = x.eip1559Data
body
of TransactionKind.Eip2930:
const txKind {.inject, used.} = TransactionKind.Eip2930
template txVariant: untyped {.inject, used.} = x.eip2930Data
body
of TransactionKind.Legacy:
const txKind {.inject, used.} = TransactionKind.Legacy
template txVariant: untyped {.inject, used.} = x.legacyData
body
of TransactionKind.Replayable:
const txKind {.inject, used.} = TransactionKind.Replayable
template txVariant: untyped {.inject, used.} = x.replayableData
body
# https://eips.ethereum.org/EIPS/eip-6493#ssz-signedtransaction-container
func selectVariant*(value: TransactionPayload): Opt[TransactionKind] =
if value.tx_type == Opt.some TxEip4844:
return Opt.some TransactionKind.Eip4844
if value.tx_type == Opt.some TxEip1559:
return Opt.some TransactionKind.Eip1559
if value.tx_type == Opt.some TxEip2930:
return Opt.some TransactionKind.Eip2930
if value.tx_type == Opt.some TxLegacy:
return Opt.some TransactionKind.Legacy
if value.tx_type.isNone:
return Opt.some TransactionKind.Replayable
Opt.none TransactionKind
func selectVariant*(value: Transaction): Opt[TransactionKind] =
selectVariant(value.payload)
type
NetworkPayload* = ref object
blobs* : seq[NetworkBlob]
commitments* : seq[KzgCommitment]
proofs* : seq[KzgProof]
blobs* : List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
commitments* : List[KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
proofs* : List[KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
Transaction* = object
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
maxFeePerBlobGas*: UInt256 # EIP-4844
versionedHashes*: VersionedHashes # EIP-4844
V* : int64
R*, S* : UInt256
PooledTransaction* = object
PooledTransaction* {.
sszStableContainer: MAX_POOLED_TRANSACTION_FIELDS.} = object
tx*: Transaction
networkPayload*: NetworkPayload # EIP-4844
blob_data*: Opt[NetworkPayload] # EIP-4844
TransactionStatus* = enum
Unknown,
@ -290,14 +523,17 @@ func stateRoot*(rec: Receipt): Hash256 {.inline.} =
rec.hash
template contractCreation*(tx: Transaction): bool =
tx.to.isNone
tx.payload.to.isNone
func destination*(tx: Transaction): EthAddress =
func destination*(tx: TransactionPayload): EthAddress =
# use getRecipient if you also want to get
# the contract address
if tx.to.isSome:
return tx.to.get
func destination*(tx: Transaction): EthAddress =
tx.payload.destination
func init*(T: type BlockHashOrNumber, str: string): T
{.raises: [ValueError].} =
if str.startsWith "0x":
@ -323,6 +559,8 @@ template deref*(b: Blob): auto = b
template deref*(o: Option): auto = o.get
template deref*(r: EthResourceRefs): auto = r[]
func `==`*(a, b: ChainId): bool {.borrow.}
func `==`*(a, b: NetworkId): bool =
a.uint == b.uint

View File

@ -5,8 +5,10 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/[sequtils, typetraits],
ssz_serialization,
"."/[eth_types, eth_hash_rlp],
../rlp
".."/[keys, rlp]
from stew/objects
import checkedEnumAssign
@ -18,6 +20,14 @@ export
# Rlp serialization:
#
type RawRlp* = distinct seq[byte]
proc append*(w: var RlpWriter, value: RawRlp) =
w.appendRawBytes(distinctBase(value))
func read*(rlp: var Rlp, T: type RawRlp): T =
rlp.toBytes().T
proc read*(rlp: var Rlp, T: type StUint): T {.inline.} =
if rlp.isBlob:
let bytes = rlp.toBytes
@ -64,102 +74,20 @@ proc append*(rlpWriter: var RlpWriter, value: StInt) =
{.fatal: "RLP serialization of signed integers is not allowed".}
discard
proc append*[T](w: var RlpWriter, val: Option[T]) =
func append*(w: var RlpWriter, val: ChainId) =
w.append(distinctBase(val))
func read*(rlp: var Rlp, T: typedesc[ChainId]): T =
rlp.read(distinctBase(T)).T
proc append*[T](w: var RlpWriter, val: Option[T] | Opt[T]) =
if val.isSome:
w.append(val.get())
else:
w.append("")
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.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.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 appendTxEip4844(w: var RlpWriter, tx: Transaction) =
w.startList(14)
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.maxFeePerBlobGas)
w.append(tx.versionedHashes)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
case tx.txType
of TxLegacy:
w.appendTxLegacy(tx)
of TxEip2930:
w.appendTxEip2930(tx)
of TxEip1559:
w.appendTxEip1559(tx)
of TxEip4844:
w.appendTxEip4844(tx)
proc append*(w: var RlpWriter, tx: Transaction) =
if tx.txType != TxLegacy:
w.append(tx.txType)
w.appendTxPayload(tx)
proc append(w: var RlpWriter, networkPayload: NetworkPayload) =
w.append(networkPayload.blobs)
w.append(networkPayload.commitments)
w.append(networkPayload.proofs)
proc append*(w: var RlpWriter, tx: PooledTransaction) =
if tx.tx.txType != TxLegacy:
w.append(tx.tx.txType)
if tx.networkPayload != nil:
w.startList(4) # spec: rlp([tx_payload, blobs, commitments, proofs])
w.appendTxPayload(tx.tx)
if tx.networkPayload != nil:
w.append(tx.networkPayload)
template read[T](rlp: var Rlp, val: var T) =
val = rlp.read(type val)
proc append*(w: var RlpWriter, val: List) =
w.append(distinctBase(val))
proc read[T](rlp: var Rlp, val: var Option[T]) =
if rlp.blobLen != 0:
@ -167,214 +95,21 @@ proc read[T](rlp: var Rlp, val: var Option[T]) =
else:
rlp.skipElem
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[T](rlp: var Rlp, val: var Opt[T]) =
if rlp.blobLen != 0:
val.ok rlp.read(T)
else:
rlp.skipElem
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 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 readTxEip4844(rlp: var Rlp, tx: var Transaction) =
tx.txType = TxEip4844
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.maxFeePerBlobGas)
rlp.read(tx.versionedHashes)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxType(rlp: var Rlp): TxType =
if rlp.isList:
raise newException(RlpTypeMismatch,
"Transaction type expected, but source RLP is a list")
# EIP-2718: We MUST decode the first byte as a byte, not `rlp.read(int)`.
# If decoded with `rlp.read(int)`, bad transaction data (from the network)
# or even just incorrectly framed data for other reasons fails with
# any of these misleading error messages:
# - "Message too large to fit in memory"
# - "Number encoded with a leading zero"
# - "Read past the end of the RLP stream"
# - "Small number encoded in a non-canonical way"
# - "Attempt to read an Int value past the RLP end"
# - "The RLP contains a larger than expected Int value"
if not rlp.isSingleByte:
if not rlp.hasData:
raise newException(MalformedRlpError,
"Transaction expected but source RLP is empty")
proc read*[E, N](rlp: var Rlp, T: typedesc[List[E, N]]): T =
let v = rlp.read(seq[E])
if v.len > N:
raise newException(MalformedRlpError,
"TypedTransaction type byte is out of range, must be 0x00 to 0x7f")
let txType = rlp.getByteValue
rlp.position += 1
"List[" & $E & ", Limit " & $N & "] cannot fit " & $v.len & " items")
List[E, N].init(v)
var txVal: TxType
if checkedEnumAssign(txVal, txType):
return txVal
raise newException(UnsupportedRlpError,
"TypedTransaction type must be 1, 2, or 3 in this version, got " & $txType)
proc readTxPayload(rlp: var Rlp, tx: var Transaction, txType: TxType) =
case txType
of TxLegacy:
raise newException(RlpTypeMismatch,
"LegacyTransaction should not be wrapped in a list")
of TxEip2930:
rlp.readTxEip2930(tx)
of TxEip1559:
rlp.readTxEip1559(tx)
of TxEip4844:
rlp.readTxEip4844(tx)
proc readTxTyped(rlp: var Rlp, tx: var Transaction) =
let txType = rlp.readTxType()
rlp.readTxPayload(tx, txType)
proc read*(rlp: var Rlp, T: type Transaction): T =
# Individual transactions are encoded and stored as either `RLP([fields..])`
# for legacy transactions, 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.
if rlp.isList:
rlp.readTxLegacy(result)
else:
rlp.readTxTyped(result)
proc read(rlp: var Rlp, T: type NetworkPayload): T =
result = NetworkPayload()
rlp.read(result.blobs)
rlp.read(result.commitments)
rlp.read(result.proofs)
proc readTxTyped(rlp: var Rlp, tx: var PooledTransaction) =
let
txType = rlp.readTxType()
hasNetworkPayload =
if txType == TxEip4844:
rlp.listLen == 4
else:
false
if hasNetworkPayload:
rlp.tryEnterList() # spec: rlp([tx_payload, blobs, commitments, proofs])
rlp.readTxPayload(tx.tx, txType)
if hasNetworkPayload:
rlp.read(tx.networkPayload)
proc read*(rlp: var Rlp, T: type PooledTransaction): T =
if rlp.isList:
rlp.readTxLegacy(result.tx)
else:
rlp.readTxTyped(result)
proc read*(
rlp: var Rlp,
T: (type seq[Transaction]) | (type openArray[Transaction])
): seq[Transaction] =
# In arrays (sequences), transactions are encoded as either `RLP([fields..])`
# for legacy transactions, or `RLP(Type || RLP([fields..]))` for all typed
# transactions 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.
#
# In practice the extra `RLP(..)` applies to all arrays/sequences of
# transactions. In principle, all aggregates (objects etc.), but
# arrays/sequences are enough. In `eth/65` protocol this is essential for
# the correct encoding/decoding of `Transactions`, `NewBlock`, and
# `PooledTransactions` network calls. We need a type match on both
# `openArray[Transaction]` and `seq[Transaction]` to catch all cases.
if not rlp.isList:
raise newException(RlpTypeMismatch,
"Transaction list expected, but source RLP is not a list")
for item in rlp:
var tx: Transaction
if item.isList:
item.readTxLegacy(tx)
else:
var rr = rlpFromBytes(rlp.read(Blob))
rr.readTxTyped(tx)
result.add tx
proc read*(
rlp: var Rlp,
T: (type seq[PooledTransaction]) | (type openArray[PooledTransaction])
): seq[PooledTransaction] =
if not rlp.isList:
raise newException(RlpTypeMismatch,
"PooledTransaction list expected, but source RLP is not a list")
for item in rlp:
var tx: PooledTransaction
if item.isList:
item.readTxLegacy(tx.tx)
else:
var rr = rlpFromBytes(rlp.read(Blob))
rr.readTxTyped(tx)
result.add tx
proc append*(rlpWriter: var RlpWriter,
txs: seq[Transaction] | openArray[Transaction]) {.inline.} =
# See above about encoding arrays/sequences of transactions.
rlpWriter.startList(txs.len)
for tx in txs:
if tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))
proc append*(
rlpWriter: var RlpWriter,
txs: seq[PooledTransaction] | openArray[PooledTransaction]) {.inline.} =
rlpWriter.startList(txs.len)
for tx in txs:
if tx.tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))
template read*[T](rlp: var Rlp, val: var T) =
val = rlp.read(type val)
proc append*(w: var RlpWriter, rec: Receipt) =
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt, Eip4844Receipt}:
@ -518,9 +253,6 @@ proc append*(rlpWriter: var RlpWriter, t: EthTime) {.inline.} =
proc rlpHash*[T](v: T): Hash256 =
keccakHash(rlp.encode(v))
proc rlpHash*(tx: PooledTransaction): Hash256 =
keccakHash(rlp.encode(tx.tx))
func blockHash*(h: BlockHeader): KeccakHash {.inline.} = rlpHash(h)
proc append*(rlpWriter: var RlpWriter, id: NetworkId) =

View File

@ -1,5 +1,16 @@
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [].}
import
./eth_types_rlp
std/typetraits,
ssz_serialization,
./eth_types_rlp,
".."/[keys, rlp]
export eth_types_rlp
@ -20,102 +31,839 @@ type
proc effectiveGasTip*(tx: Transaction; baseFee: GasPrice): GasPriceEx =
## The effective miner gas tip for the globally argument `baseFee`. The
## result (which is a price per gas) might well be negative.
if tx.txType != TxEip1559:
(tx.gasPrice - baseFee.int64).GasPriceEx
else:
# London, EIP1559
min(tx.maxPriorityFee, tx.maxFee - baseFee.int64).GasPriceEx
let
maxFee = tx.payload.max_fee_per_gas
maxPriorityFee = tx.payload.max_priority_fee_per_gas.get(maxFee)
min(
maxPriorityFee.truncate(int64),
maxFee.truncate(int64) - baseFee.int64).GasPriceEx
proc effectiveGasTip*(tx: Transaction; baseFee: UInt256): GasPriceEx =
## Variant of `effectiveGasTip()`
tx.effectiveGasTip(baseFee.truncate(uint64).GasPrice)
func rlpEncodeLegacy(tx: Transaction): auto =
# https://eips.ethereum.org/EIPS/eip-6493#transaction-validation
func ecdsa_pack_signature*(
y_parity: bool, r: UInt256, s: UInt256): array[ECDSA_SIGNATURE_SIZE, byte] =
var res: array[ECDSA_SIGNATURE_SIZE, byte]
res[0 ..< 32] = r.toBytesBE()
res[32 ..< 64] = s.toBytesBE()
res[64] = if y_parity: 0x01 else: 0x00
res
func ecdsa_unpack_signature*(
signature: array[ECDSA_SIGNATURE_SIZE, byte]
): tuple[y_parity: bool, r: UInt256, s: UInt256] =
(
y_parity: signature[64] != 0,
r: UInt256.fromBytesBE(signature[0 ..< 32]),
s: UInt256.fromBytesBE(signature[32 ..< 64]))
const SECP256K1N* =
0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141.u256
func ecdsa_validate_signature(
signature: array[ECDSA_SIGNATURE_SIZE, byte]): Opt[void] =
static: doAssert signature.len == 65
if signature[64] > 1: return err()
let (_, r, s) = ecdsa_unpack_signature(signature)
if r >= SECP256K1N: return err()
if s < UInt256.one or s >= SECP256K1N: return err()
ok()
func ecdsa_recover_from_address*(
signature: array[ECDSA_SIGNATURE_SIZE, byte],
sig_hash: Hash256): Opt[EthAddress] =
let
recover_sig = Signature.fromRaw(signature).valueOr:
return Opt.none EthAddress
public_key = recover_sig.recover(sig_hash.data.SkMessage).valueOr:
return Opt.none EthAddress
Opt.some public_key.toCanonicalAddress()
# https://github.com/ethereum/EIPs/blob/master/assets/eip-6493/tx_hashes.py
func compute_sig_hash*(
tx: ReplayableTransactionPayload, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.startList(6)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.max_fee_per_gas)
w.append(tx.gas)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.finish()
w.append(tx.input)
keccakHash(w.finish())
func rlpEncodeEip155(tx: Transaction): auto =
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
func append_tx(w: var RlpWriter, tx: ReplayableTransaction, chain_id: ChainId) =
let
(y_parity, r, s) = ecdsa_unpack_signature(tx.signature.ecdsa_signature)
v = (if y_parity: 28.u256 else: 27.u256)
w.startList(9)
w.append(tx.payload.nonce)
w.append(tx.payload.max_fee_per_gas)
w.append(tx.payload.gas)
w.append(tx.payload.to)
w.append(tx.payload.value)
w.append(tx.payload.input)
w.append(v)
w.append(r)
w.append(s)
func compute_tx_hash*(tx: ReplayableTransaction, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append_tx(tx, chain_id)
keccakHash(w.finish())
func compute_sig_hash*(
tx: LegacyTransactionPayload, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.max_fee_per_gas)
w.append(tx.gas)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(chainId)
w.append(tx.input)
w.append(chain_id)
w.append(0)
w.append(0)
w.finish()
keccakHash(w.finish())
func rlpEncodeEip2930(tx: Transaction): auto =
func append_tx(w: var RlpWriter, tx: LegacyTransaction, chain_id: ChainId) =
let
(y_parity, r, s) = ecdsa_unpack_signature(tx.signature.ecdsa_signature)
v = distinctBase(chain_id).u256 * 2 + (if y_parity: 36.u256 else: 35.u256)
w.startList(9)
w.append(tx.payload.nonce)
w.append(tx.payload.max_fee_per_gas)
w.append(tx.payload.gas)
w.append(tx.payload.to)
w.append(tx.payload.value)
w.append(tx.payload.input)
w.append(v)
w.append(r)
w.append(s)
func compute_tx_hash*(tx: LegacyTransaction, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append_tx(tx, chain_id)
keccakHash(w.finish())
func compute_sig_hash*(
tx: Eip2930TransactionPayload, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append(TxEip2930)
w.startList(8)
w.append(tx.chainId.uint64)
w.append(chain_id)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.max_fee_per_gas)
w.append(tx.gas)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
w.append(tx.input)
w.append(tx.access_list)
keccakHash(w.finish())
func rlpEncodeEip1559(tx: Transaction): auto =
func append_tx(w: var RlpWriter, tx: Eip2930Transaction, chain_id: ChainId) =
let (y_parity, r, s) = ecdsa_unpack_signature(tx.signature.ecdsa_signature)
w.startList(11)
w.append(chain_id)
w.append(tx.payload.nonce)
w.append(tx.payload.max_fee_per_gas)
w.append(tx.payload.gas)
w.append(tx.payload.to)
w.append(tx.payload.value)
w.append(tx.payload.input)
w.append(tx.payload.access_list)
w.append(y_parity)
w.append(r)
w.append(s)
func compute_tx_hash*(tx: Eip2930Transaction, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append(TxEip2930)
w.append_tx(tx, chain_id)
keccakHash(w.finish())
func compute_sig_hash*(
tx: Eip1559TransactionPayload, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append(TxEip1559)
w.startList(9)
w.append(tx.chainId.uint64)
w.append(chain_id)
w.append(tx.nonce)
w.append(tx.maxPriorityFee)
w.append(tx.maxFee)
w.append(tx.gasLimit)
w.append(tx.max_priority_fee_per_gas)
w.append(tx.max_fee_per_gas)
w.append(tx.gas)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
w.append(tx.input)
w.append(tx.access_list)
keccakHash(w.finish())
func rlpEncodeEip4844(tx: Transaction): auto =
func append_tx(w: var RlpWriter, tx: Eip1559Transaction, chain_id: ChainId) =
let (y_parity, r, s) = ecdsa_unpack_signature(tx.signature.ecdsa_signature)
w.startList(12)
w.append(chain_id)
w.append(tx.payload.nonce)
w.append(tx.payload.max_priority_fee_per_gas)
w.append(tx.payload.max_fee_per_gas)
w.append(tx.payload.gas)
w.append(tx.payload.to)
w.append(tx.payload.value)
w.append(tx.payload.input)
w.append(tx.payload.access_list)
w.append(y_parity)
w.append(r)
w.append(s)
func compute_tx_hash*(tx: Eip1559Transaction, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append(TxEip1559)
w.append_tx(tx, chain_id)
keccakHash(w.finish())
func compute_sig_hash*(
tx: Eip4844TransactionPayload, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append(TxEip4844)
w.startList(11)
w.append(tx.chainId.uint64)
w.append(chain_id)
w.append(tx.nonce)
w.append(tx.maxPriorityFee)
w.append(tx.maxFee)
w.append(tx.gasLimit)
w.append(tx.max_priority_fee_per_gas)
w.append(tx.max_fee_per_gas)
w.append(tx.gas)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.maxFeePerBlobGas)
w.append(tx.versionedHashes)
w.finish()
w.append(tx.input)
w.append(tx.access_list)
w.append(tx.max_fee_per_blob_gas)
w.append(tx.blob_versioned_hashes)
keccakHash(w.finish())
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 append_tx(w: var RlpWriter, tx: Eip4844Transaction, chain_id: ChainId) =
let (y_parity, r, s) = ecdsa_unpack_signature(tx.signature.ecdsa_signature)
w.startList(14)
w.append(tx.payload.nonce)
w.append(tx.payload.max_priority_fee_per_gas)
w.append(tx.payload.max_fee_per_gas)
w.append(tx.payload.gas)
w.append(tx.payload.to)
w.append(tx.payload.value)
w.append(tx.payload.input)
w.append(tx.payload.access_list)
w.append(tx.payload.max_fee_per_blob_gas)
w.append(tx.payload.blob_versioned_hashes)
w.append(y_parity)
w.append(r)
w.append(s)
func compute_tx_hash*(tx: Eip4844Transaction, chain_id: ChainId): Hash256 =
var w = initRlpWriter()
w.append(TxEip4844)
w.append_tx(tx, chain_id)
keccakHash(w.finish())
func compute_sig_hash*(tx: AnyTransactionPayload, chain_id: ChainId): Hash256 =
withTxPayloadVariant(tx):
txPayloadVariant.compute_sig_hash(chain_id)
func compute_sig_hash*(tx: TransactionPayload, chain_id: ChainId): Hash256 =
let anyTx = AnyTransactionPayload.fromOneOfBase(tx).valueOr:
raiseAssert "Cannot get sig hash for invalid `TransactionPayload`: " & $tx
anyTx.compute_sig_hash(chain_id)
func compute_tx_hash*(tx: AnyTransaction, chain_id: ChainId): Hash256 =
withTxVariant(tx):
txVariant.compute_tx_hash(chain_id)
func compute_tx_hash*(tx: Transaction, chain_id: ChainId): Hash256 =
let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr:
raiseAssert "Cannot get tx hash for invalid `Transaction`: " & $tx
anyTx.compute_tx_hash(chain_id)
func validate_transaction*(
tx: AnyTransactionVariant, chain_id: ChainId): Opt[void] =
? ecdsa_validate_signature(tx.signature.ecdsa_signature)
let from_address = ? ecdsa_recover_from_address(
tx.signature.ecdsa_signature,
tx.payload.compute_sig_hash(chain_id))
if tx.signature.from_address != from_address:
return err()
ok()
func toBytes(tx: AnyTransaction, chain_id: ChainId): seq[byte] =
withTxVariant(tx):
case txKind
of TransactionKind.Eip4844:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
@[TxEip4844.ord.uint8] & w.finish()
of TransactionKind.Eip1559:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
@[TxEip1559.ord.uint8] & w.finish()
of TransactionKind.Eip2930:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
@[TxEip2930.ord.uint8] & w.finish()
of TransactionKind.Legacy:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
w.finish()
of TransactionKind.Replayable:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
w.finish()
func toBytes*(tx: Transaction, chain_id: ChainId): seq[byte] =
let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr:
raiseAssert "Cannot serialize invalid `Transaction`: " & $tx
anyTx.toBytes(chain_id)
func toBytes*(txs: openArray[Transaction], chain_id: ChainId): seq[byte] =
var writer = initRlpWriter()
for tx in txs:
let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr:
raiseAssert "Cannot serialize an invalid `Transaction`: " & $tx
withTxVariant(anyTx):
case txKind
of TransactionKind.Eip4844:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
writer.append @[TxEip4844.ord.uint8] & w.finish()
of TransactionKind.Eip1559:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
writer.append @[TxEip1559.ord.uint8] & w.finish()
of TransactionKind.Eip2930:
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
writer.append @[TxEip2930.ord.uint8] & w.finish()
of TransactionKind.Legacy:
writer.append_tx(txVariant, chain_id)
of TransactionKind.Replayable:
writer.append_tx(txVariant, chain_id)
writer.finish()
func append_blob_data(w: var RlpWriter, blob_data: NetworkPayload) =
w.append(distinctBase(blob_data.blobs))
w.append(distinctBase(blob_data.commitments))
w.append(distinctBase(blob_data.proofs))
func toBytes*(tx: PooledTransaction, chain_id: ChainId): seq[byte] =
let anyTx = AnyTransaction.fromOneOfBase(tx.tx).valueOr:
raiseAssert "Cannot serialize an invalid `PooledTransaction`: " & $tx
withTxVariant(anyTx):
case txKind
of TransactionKind.Eip4844:
doAssert tx.blob_data.isSome, "EIP-4844 requires `blob_data`"
var w = initRlpWriter()
w.startList(4) # spec: rlp([tx_payload, blobs, commitments, proofs])
w.append_tx(txVariant, chain_id)
w.append_blob_data(tx.blob_data.unsafeGet)
@[TxEip4844.ord.uint8] & w.finish()
of TransactionKind.Eip1559:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
@[TxEip1559.ord.uint8] & w.finish()
of TransactionKind.Eip2930:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
@[TxEip2930.ord.uint8] & w.finish()
of TransactionKind.Legacy:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
w.finish()
of TransactionKind.Replayable:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
w.finish()
func toBytes*(txs: openArray[PooledTransaction], chain_id: ChainId): seq[byte] =
var writer = initRlpWriter()
for tx in txs:
let anyTx = AnyTransaction.fromOneOfBase(tx.tx).valueOr:
raiseAssert "Cannot serialize an invalid `PooledTransaction`: " & $tx
withTxVariant(anyTx):
case txKind
of TransactionKind.Eip4844:
doAssert tx.blob_data.isSome, "EIP-4844 requires `blob_data`"
var w = initRlpWriter()
w.startList(4) # spec: rlp([tx_payload, blobs, commitments, proofs])
w.append_tx(txVariant, chain_id)
w.append_blob_data(tx.blob_data.unsafeGet)
writer.append @[TxEip4844.ord.uint8] & w.finish()
of TransactionKind.Eip1559:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
writer.append @[TxEip1559.ord.uint8] & w.finish()
of TransactionKind.Eip2930:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
var w = initRlpWriter()
w.append_tx(txVariant, chain_id)
writer.append @[TxEip2930.ord.uint8] & w.finish()
of TransactionKind.Legacy:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
writer.append_tx(txVariant, chain_id)
of TransactionKind.Replayable:
doAssert tx.blob_data.isNone, "`blob_data` requires EIP-4844"
writer.append_tx(txVariant, chain_id)
writer.finish()
# https://github.com/ethereum/EIPs/blob/master/assets/eip-6493/convert.py
func read_tx[T: LegacyTransactionPayload](
rlp: var Rlp, t: typedesc[T], chain_id: ChainId
): Opt[tuple[tx: T, y_parity: bool, r: UInt256, s: UInt256, has_eip155: bool]] =
try:
var
tx = LegacyTransactionPayload(tx_type: TxLegacy)
v: UInt256
r: UInt256
s: UInt256
rlp.tryEnterList()
rlp.read(tx.nonce)
rlp.read(tx.max_fee_per_gas)
rlp.read(tx.gas)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.input)
rlp.read(v)
rlp.read(r)
rlp.read(s)
let
y_parity = v.isEven
has_eip155 = (v > 28.u256 or v < 27.u256)
if has_eip155:
let expected_v =
distinctBase(chain_id).u256 * 2 + (if y_parity: 36.u256 else: 35.u256)
if v != expected_v: return err()
if r >= SECP256K1N: return err()
if s < UInt256.one or s >= SECP256K1N: return err()
Opt.some (tx: tx, y_parity: y_parity, r: r, s: s, has_eip155: has_eip155)
except RlpError:
err()
func read_tx[T: Eip2930TransactionPayload](
rlp: var Rlp, t: typedesc[T], chain_id: ChainId
): Opt[tuple[tx: T, y_parity: bool, r: UInt256, s: UInt256]] =
try:
var
tx = Eip2930TransactionPayload(tx_type: TxEip2930)
y_parity: uint8
r: UInt256
s: UInt256
rlp.tryEnterList()
if rlp.read(ChainId) != chain_id: return err()
rlp.read(tx.nonce)
rlp.read(tx.max_fee_per_gas)
rlp.read(tx.gas)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.input)
rlp.read(tx.access_list)
rlp.read(y_parity)
rlp.read(r)
rlp.read(s)
if y_parity > 1: return err()
if r >= SECP256K1N: return err()
if s < UInt256.one or s >= SECP256K1N: return err()
Opt.some (tx: tx, y_parity: y_parity != 0, r: r, s: s)
except RlpError:
err()
func read_tx[T: Eip1559TransactionPayload](
rlp: var Rlp, t: typedesc[T], chain_id: ChainId
): Opt[tuple[tx: T, y_parity: bool, r: UInt256, s: UInt256]] =
try:
var
tx = Eip1559TransactionPayload(tx_type: TxEip1559)
y_parity: uint8
r: UInt256
s: UInt256
rlp.tryEnterList()
if rlp.read(ChainId) != chain_id: return err()
rlp.read(tx.nonce)
rlp.read(tx.max_priority_fee_per_gas)
rlp.read(tx.max_fee_per_gas)
rlp.read(tx.gas)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.input)
rlp.read(tx.access_list)
rlp.read(y_parity)
rlp.read(r)
rlp.read(s)
if y_parity > 1: return err()
if r >= SECP256K1N: return err()
if s < UInt256.one or s >= SECP256K1N: return err()
Opt.some (tx: tx, y_parity: y_parity != 0, r: r, s: s)
except RlpError:
err()
func read_tx[T: Eip4844TransactionPayload](
rlp: var Rlp, t: typedesc[T], chain_id: ChainId
): Opt[tuple[tx: T, y_parity: bool, r: UInt256, s: UInt256]] =
try:
var
tx = Eip4844TransactionPayload(tx_type: TxEip4844)
y_parity: uint8
r: UInt256
s: UInt256
rlp.tryEnterList()
if rlp.read(ChainId) != chain_id: return err()
rlp.read(tx.nonce)
rlp.read(tx.max_priority_fee_per_gas)
rlp.read(tx.max_fee_per_gas)
rlp.read(tx.gas)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.input)
rlp.read(tx.access_list)
rlp.read(tx.max_fee_per_blob_gas)
rlp.read(tx.blob_versioned_hashes)
rlp.read(y_parity)
rlp.read(r)
rlp.read(s)
if y_parity > 1: return err()
if r >= SECP256K1N: return err()
if s < UInt256.one or s >= SECP256K1N: return err()
Opt.some (tx: tx, y_parity: y_parity != 0, r: r, s: s)
except RlpError:
err()
func read_tx(
rlp: var Rlp,
chain_id: ChainId,
tx_type: TxType): Opt[AnyTransaction] =
case tx_type
of TxEip4844:
tx.rlpEncodeEip4844
let (tx, y_parity, r, s) =
? rlp.read_tx(Eip4844TransactionPayload, chain_id)
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ? ecdsa_recover_from_address(
signature.ecdsa_signature, tx.compute_sig_hash(chain_id))
Opt.some AnyTransaction.init(
Eip4844Transaction(payload: tx, signature: signature))
of TxEip1559:
let (tx, y_parity, r, s) =
? rlp.read_tx(Eip1559TransactionPayload, chain_id)
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ? ecdsa_recover_from_address(
signature.ecdsa_signature, tx.compute_sig_hash(chain_id))
Opt.some AnyTransaction.init(
Eip1559Transaction(payload: tx, signature: signature))
of TxEip2930:
let (tx, y_parity, r, s) =
? rlp.read_tx(Eip2930TransactionPayload, chain_id)
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ? ecdsa_recover_from_address(
signature.ecdsa_signature, tx.compute_sig_hash(chain_id))
Opt.some AnyTransaction.init(
Eip2930Transaction(payload: tx, signature: signature))
of TxLegacy:
let (tx, y_parity, r, s, has_eip155) =
? rlp.read_tx(LegacyTransactionPayload, chain_id)
if has_eip155:
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ? ecdsa_recover_from_address(
signature.ecdsa_signature, tx.compute_sig_hash(chain_id))
Opt.some AnyTransaction.init(
LegacyTransaction(payload: tx, signature: signature))
else:
let tx = ReplayableTransactionPayload(
nonce: tx.nonce,
max_fee_per_gas: tx.max_fee_per_gas,
gas: tx.gas,
to: tx.to,
value: tx.value,
input: tx.input)
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ? ecdsa_recover_from_address(
signature.ecdsa_signature, tx.compute_sig_hash(chain_id))
Opt.some AnyTransaction.init(
ReplayableTransaction(payload: tx, signature: signature))
func txHashNoSignature*(tx: Transaction): Hash256 =
# Hash transaction without signature
keccakHash(rlpEncode(tx))
func fromBytes(
T: typedesc[AnyTransaction],
data: openArray[byte],
chain_id: ChainId): Opt[AnyTransaction] =
if data.len < 1:
return Opt.none AnyTransaction
case data[0]
of TxEip4844.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = rlp.read_tx(chain_id, TxEip4844)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
of TxEip1559.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = rlp.read_tx(chain_id, TxEip1559)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
of TxEip2930.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = rlp.read_tx(chain_id, TxEip2930)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
of 0xc0 .. 0xfe:
var rlp = rlpFromBytes(data)
let tx = rlp.read_tx(chain_id, TxLegacy)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
else:
Opt.none AnyTransaction
func fromBytes*(
T: typedesc[Transaction],
data: openArray[byte],
chain_id: ChainId): Opt[Transaction] =
let tx = ? AnyTransaction.fromBytes(data, chain_id)
Opt.some tx.toOneOfBase()
func fromBytesWrapped(
T: typedesc[AnyTransaction],
data: openArray[byte],
chain_id: ChainId): Opt[AnyTransaction] =
# In arrays (sequences), transactions are encoded as either `RLP([fields..])`
# for legacy transactions, or `RLP(Type || RLP([fields..]))` for all typed
# transactions 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.
#
# In practice the extra `RLP(..)` applies to all arrays/sequences of
# transactions. In principle, all aggregates (objects etc.), but
# arrays/sequences are enough. In `eth/65` protocol this is essential for
# the correct encoding/decoding of `Transactions`, `NewBlock`, and
# `PooledTransactions` network calls. We need a type match on both
# `openArray[Transaction]` and `seq[Transaction]` to catch all cases.
var rlp = rlpFromBytes(data)
if rlp.isList:
let tx = rlp.read_tx(chain_id, TxLegacy)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
else:
try:
var data = rlp.read(Blob)
if rlp.hasData:
return Opt.none AnyTransaction
if data.len < 1:
return Opt.none AnyTransaction
case data[0]
of TxEip4844.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = rlp.read_tx(chain_id, TxEip4844)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
of TxEip1559.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = rlp.read_tx(chain_id, TxEip1559)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
of TxEip2930.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = rlp.read_tx(chain_id, TxEip2930)
if tx.isSome and rlp.hasData:
return Opt.none AnyTransaction
tx
else:
Opt.none AnyTransaction
except RlpError:
Opt.none AnyTransaction
func fromBytesWrapped(
T: typedesc[Transaction],
data: openArray[byte],
chain_id: ChainId): Opt[Transaction] =
let tx = ? AnyTransaction.fromBytesWrapped(data, chain_id)
Opt.some tx.toOneOfBase()
func fromBytes*(
T: typedesc[seq[Transaction]],
data: openArray[byte],
chain_id: ChainId): Opt[seq[Transaction]] =
var rlp = rlpFromBytes(data)
if not rlp.isList:
return Opt.none seq[Transaction]
try:
var res: seq[Transaction]
for item in rlp:
res.add(? Transaction.fromBytesWrapped(item.toBytes(), chain_id))
if rlp.hasData:
return Opt.none seq[Transaction]
Opt.some res
except RlpError:
Opt.none seq[Transaction]
func read_blob_data(rlp: var Rlp): Opt[NetworkPayload] =
try:
var
blobs: seq[NetworkBlob]
commitments: seq[KzgCommitment]
proofs: seq[KzgProof]
rlp.read(blobs)
rlp.read(commitments)
rlp.read(proofs)
Opt.some NetworkPayload(
blobs: List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init blobs,
commitments: List[KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init commitments,
proofs: List[KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init proofs)
except RlpError:
Opt.none NetworkPayload
func fromBytes*(
T: typedesc[PooledTransaction],
data: openArray[byte],
chain_id: ChainId): Opt[PooledTransaction] =
if data.len < 1:
return Opt.none PooledTransaction
try:
case data[0]
of TxEip4844.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
rlp.tryEnterList() # spec: rlp([tx_payload, blobs, commitments, proofs])
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxEip4844)).toOneOfBase(),
blob_data: Opt.some(? rlp.read_blob_data()))
if rlp.hasData:
return Opt.none PooledTransaction
tx
of TxEip1559.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxEip1559)).toOneOfBase())
if rlp.hasData:
return Opt.none PooledTransaction
tx
of TxEip2930.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxEip2930)).toOneOfBase())
if rlp.hasData:
return Opt.none PooledTransaction
tx
of 0xc0 .. 0xfe:
var rlp = rlpFromBytes(data)
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxLegacy)).toOneOfBase())
if rlp.hasData:
return Opt.none PooledTransaction
tx
else:
Opt.none PooledTransaction
except RlpError:
Opt.none PooledTransaction
func fromBytesWrapped(
T: typedesc[PooledTransaction],
data: openArray[byte],
chain_id: ChainId): Opt[PooledTransaction] =
# In arrays (sequences), transactions are encoded as either `RLP([fields..])`
# for legacy transactions, or `RLP(Type || RLP([fields..]))` for all typed
# transactions 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.
#
# In practice the extra `RLP(..)` applies to all arrays/sequences of
# transactions. In principle, all aggregates (objects etc.), but
# arrays/sequences are enough. In `eth/65` protocol this is essential for
# the correct encoding/decoding of `Transactions`, `NewBlock`, and
# `PooledTransactions` network calls. We need a type match on both
# `openArray[Transaction]` and `seq[Transaction]` to catch all cases.
var rlp = rlpFromBytes(data)
if rlp.isList:
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxLegacy)).toOneOfBase())
if tx.isSome and rlp.hasData:
return Opt.none PooledTransaction
tx
else:
try:
var data = rlp.read(Blob)
if rlp.hasData:
return Opt.none PooledTransaction
if data.len < 1:
return Opt.none PooledTransaction
case data[0]
of TxEip4844.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
rlp.tryEnterList()
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxEip4844)).toOneOfBase(),
blob_data: Opt.some(? rlp.read_blob_data()))
if rlp.hasData:
return Opt.none PooledTransaction
tx
of TxEip1559.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxEip1559)).toOneOfBase())
if rlp.hasData:
return Opt.none PooledTransaction
tx
of TxEip2930.ord:
var rlp = rlpFromBytes(data[1 ..< data.len])
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxEip2930)).toOneOfBase())
if rlp.hasData:
return Opt.none PooledTransaction
tx
of 0xc0 .. 0xfe:
var rlp = rlpFromBytes(data)
let tx = Opt.some PooledTransaction(
tx: (? rlp.read_tx(chain_id, TxLegacy)).toOneOfBase())
if rlp.hasData:
return Opt.none PooledTransaction
tx
else:
Opt.none PooledTransaction
except RlpError:
Opt.none PooledTransaction
func fromBytes*(
T: typedesc[seq[PooledTransaction]],
data: openArray[byte],
chain_id: ChainId): Opt[seq[PooledTransaction]] =
var rlp = rlpFromBytes(data)
if not rlp.isList:
return Opt.none seq[PooledTransaction]
try:
var res: seq[PooledTransaction]
for item in rlp:
res.add(? PooledTransaction.fromBytesWrapped(item.toBytes(), chain_id))
if rlp.hasData:
return Opt.none seq[PooledTransaction]
Opt.some res
except RlpError:
Opt.none seq[PooledTransaction]
proc signTransaction*(
tx: TransactionPayload,
privateKey: PrivateKey,
chainId: ChainId): Transaction =
var signature: TransactionSignature
signature.ecdsa_signature = privateKey.sign(
tx.compute_sig_hash(chainId).data.SkMessage).toRaw()
signature.from_address = ecdsa_recover_from_address(
signature.ecdsa_signature, tx.compute_sig_hash(chainId)).expect("Sig OK")
Transaction(payload: tx, signature: signature)