mirror of
https://github.com/status-im/nim-eth.git
synced 2025-02-02 17:44:05 +00:00
X
This commit is contained in:
parent
34adff98a6
commit
24ed2afba7
@ -8,8 +8,10 @@ skipDirs = @["tests"]
|
||||
|
||||
requires "nim >= 1.6.0",
|
||||
"nimcrypto",
|
||||
"results",
|
||||
"stint",
|
||||
"secp256k1",
|
||||
"ssz_serialization",
|
||||
"chronos#head",
|
||||
"chronicles",
|
||||
"stew",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) =
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user