2018-04-06 14:52:10 +00:00
|
|
|
# Nimbus
|
2024-02-15 02:57:05 +00:00
|
|
|
# Copyright (c) 2018-2024 Status Research & Development GmbH
|
2018-04-06 14:52:10 +00:00
|
|
|
# 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.
|
|
|
|
|
2018-01-17 12:57:50 +00:00
|
|
|
import
|
2022-12-02 04:39:12 +00:00
|
|
|
./constants, ./errors, eth/[common, keys], ./utils/utils,
|
2023-01-15 07:37:19 +00:00
|
|
|
common/evmforks, ./vm_gas_costs
|
2018-01-17 12:57:50 +00:00
|
|
|
|
2019-08-13 08:13:22 +00:00
|
|
|
import eth/common/transaction as common_transaction
|
2022-05-22 20:44:15 +00:00
|
|
|
export common_transaction, errors
|
2018-11-20 17:35:11 +00:00
|
|
|
|
2023-01-15 07:37:19 +00:00
|
|
|
proc toWordSize(size: GasInt): GasInt =
|
|
|
|
# Round input to the nearest bigger multiple of 32
|
|
|
|
# tx validation will ensure the value is not too big
|
|
|
|
if size > GasInt.high-31:
|
|
|
|
return (GasInt.high shr 5) + 1
|
|
|
|
|
|
|
|
(size + 31) shr 5
|
|
|
|
|
2022-12-02 04:39:12 +00:00
|
|
|
func intrinsicGas*(data: openArray[byte], fork: EVMFork): GasInt =
|
2019-11-11 05:20:46 +00:00
|
|
|
result = gasFees[fork][GasTransaction]
|
2018-12-03 19:47:20 +00:00
|
|
|
for i in data:
|
|
|
|
if i == 0:
|
2019-11-11 05:20:46 +00:00
|
|
|
result += gasFees[fork][GasTXDataZero]
|
2018-12-03 19:47:20 +00:00
|
|
|
else:
|
2019-11-11 05:20:46 +00:00
|
|
|
result += gasFees[fork][GasTXDataNonZero]
|
2018-12-03 19:47:20 +00:00
|
|
|
|
2022-12-02 04:39:12 +00:00
|
|
|
proc intrinsicGas*(tx: Transaction, fork: EVMFork): GasInt =
|
2021-05-15 13:54:34 +00:00
|
|
|
# Compute the baseline gas cost for this transaction. This is the amount
|
|
|
|
# of gas needed to send this transaction (but that is not actually used
|
|
|
|
# for computation)
|
|
|
|
result = tx.payload.intrinsicGas(fork)
|
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
if tx.contractCreation:
|
2021-05-15 13:54:34 +00:00
|
|
|
result = result + gasFees[fork][GasTXCreate]
|
2023-01-04 13:11:33 +00:00
|
|
|
if fork >= FkShanghai:
|
2023-01-15 07:37:19 +00:00
|
|
|
# cannot use wordCount here, it will raise unlisted exception
|
2024-06-14 07:31:08 +00:00
|
|
|
let numWords = toWordSize(GasInt tx.payload.len)
|
2023-01-15 07:37:19 +00:00
|
|
|
result = result + (gasFees[fork][GasInitcodeWord] * numWords)
|
2021-05-15 13:54:34 +00:00
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
if tx.txType > TxLegacy:
|
2024-06-14 07:31:08 +00:00
|
|
|
result = result + GasInt(tx.accessList.len) * ACCESS_LIST_ADDRESS_COST
|
2021-06-27 04:19:43 +00:00
|
|
|
var numKeys = 0
|
|
|
|
for n in tx.accessList:
|
|
|
|
inc(numKeys, n.storageKeys.len)
|
2024-06-14 07:31:08 +00:00
|
|
|
result = result + GasInt(numKeys) * ACCESS_LIST_STORAGE_KEY_COST
|
2021-05-15 06:37:40 +00:00
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
proc getSignature*(tx: Transaction, output: var Signature): bool =
|
2018-08-24 15:46:48 +00:00
|
|
|
var bytes: array[65, byte]
|
2023-09-13 02:32:38 +00:00
|
|
|
bytes[0..31] = tx.R.toBytesBE()
|
|
|
|
bytes[32..63] = tx.S.toBytesBE()
|
2019-04-18 08:33:17 +00:00
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
if tx.txType == TxLegacy:
|
|
|
|
var v = tx.V
|
|
|
|
if v >= EIP155_CHAIN_ID_OFFSET:
|
|
|
|
v = 28 - (v and 0x01)
|
|
|
|
elif v == 27 or v == 28:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
return false
|
|
|
|
bytes[64] = byte(v - 27)
|
2018-09-10 08:44:07 +00:00
|
|
|
else:
|
2021-06-27 04:19:43 +00:00
|
|
|
bytes[64] = tx.V.byte
|
2018-09-10 08:44:07 +00:00
|
|
|
|
2020-04-05 13:12:48 +00:00
|
|
|
let sig = Signature.fromRaw(bytes)
|
|
|
|
if sig.isOk:
|
|
|
|
output = sig[]
|
|
|
|
return true
|
|
|
|
return false
|
2018-09-10 08:44:07 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc toSignature*(tx: Transaction): Signature =
|
|
|
|
if not getSignature(tx, result):
|
2019-08-29 12:57:01 +00:00
|
|
|
raise newException(Exception, "Invalid signature")
|
2018-08-24 15:46:48 +00:00
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
proc getSender*(tx: Transaction, output: var EthAddress): bool =
|
2018-08-24 15:46:48 +00:00
|
|
|
## Find the address the transaction was sent from.
|
2018-09-10 08:44:07 +00:00
|
|
|
var sig: Signature
|
2021-05-15 06:37:40 +00:00
|
|
|
if tx.getSignature(sig):
|
|
|
|
var txHash = tx.txHashNoSignature
|
2020-07-20 06:50:05 +00:00
|
|
|
let pubkey = recover(sig, SkMessage(txHash.data))
|
2020-04-05 13:12:48 +00:00
|
|
|
if pubkey.isOk:
|
|
|
|
output = pubkey[].toCanonicalAddress()
|
2018-09-10 08:44:07 +00:00
|
|
|
result = true
|
2018-08-24 15:46:48 +00:00
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
proc getSender*(tx: Transaction): EthAddress =
|
2018-08-29 15:52:12 +00:00
|
|
|
## Raises error on failure to recover public key
|
2021-05-15 06:37:40 +00:00
|
|
|
if not tx.getSender(result):
|
2018-08-29 15:52:12 +00:00
|
|
|
raise newException(ValidationError, "Could not derive sender address from transaction")
|
2018-09-10 08:44:07 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress =
|
2021-06-27 04:19:43 +00:00
|
|
|
if tx.contractCreation:
|
|
|
|
result = generateAddress(sender, tx.nonce)
|
2021-05-15 07:18:21 +00:00
|
|
|
else:
|
2021-06-27 04:19:43 +00:00
|
|
|
result = tx.to.get()
|
2019-08-29 12:57:01 +00:00
|
|
|
|
2022-12-02 04:39:12 +00:00
|
|
|
proc validateTxLegacy(tx: Transaction, fork: EVMFork) =
|
2019-08-29 13:44:54 +00:00
|
|
|
var
|
2024-06-14 07:31:08 +00:00
|
|
|
vMin = 27'u64
|
|
|
|
vMax = 28'u64
|
2019-08-29 13:44:54 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
if tx.V >= EIP155_CHAIN_ID_OFFSET:
|
|
|
|
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
|
2019-08-29 13:44:54 +00:00
|
|
|
vMin = 35 + (2 * chainId)
|
|
|
|
vMax = vMin + 1
|
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
var isValid = tx.R >= UInt256.one
|
|
|
|
isValid = isValid and tx.S >= UInt256.one
|
2021-05-15 06:37:40 +00:00
|
|
|
isValid = isValid and tx.V >= vMin
|
|
|
|
isValid = isValid and tx.V <= vMax
|
2019-08-29 13:44:54 +00:00
|
|
|
isValid = isValid and tx.S < SECPK1_N
|
|
|
|
isValid = isValid and tx.R < SECPK1_N
|
|
|
|
|
|
|
|
if fork >= FkHomestead:
|
|
|
|
isValid = isValid and tx.S < SECPK1_N div 2
|
|
|
|
|
|
|
|
if not isValid:
|
2023-06-24 13:56:44 +00:00
|
|
|
raise newException(ValidationError, "Invalid legacy transaction")
|
2021-05-15 07:30:39 +00:00
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
proc validateTxEip2930(tx: Transaction) =
|
2024-06-14 07:31:08 +00:00
|
|
|
var isValid = tx.V == 0'u64 or tx.V == 1'u64
|
2022-04-08 04:54:11 +00:00
|
|
|
isValid = isValid and tx.S >= UInt256.one
|
2021-05-15 07:30:39 +00:00
|
|
|
isValid = isValid and tx.S < SECPK1_N
|
|
|
|
isValid = isValid and tx.R < SECPK1_N
|
|
|
|
|
|
|
|
if not isValid:
|
2023-06-24 13:56:44 +00:00
|
|
|
raise newException(ValidationError, "Invalid typed transaction")
|
|
|
|
|
|
|
|
proc validateTxEip4844(tx: Transaction) =
|
|
|
|
validateTxEip2930(tx)
|
|
|
|
|
|
|
|
var isValid = tx.payload.len <= MAX_CALLDATA_SIZE
|
|
|
|
isValid = isValid and tx.accessList.len <= MAX_ACCESS_LIST_SIZE
|
|
|
|
|
|
|
|
for acl in tx.accessList:
|
|
|
|
isValid = isValid and
|
|
|
|
(acl.storageKeys.len <= MAX_ACCESS_LIST_STORAGE_KEYS)
|
|
|
|
|
|
|
|
isValid = isValid and
|
2023-10-20 14:02:22 +00:00
|
|
|
tx.versionedHashes.len <= MAX_BLOBS_PER_BLOCK
|
2023-06-24 13:56:44 +00:00
|
|
|
|
|
|
|
for bv in tx.versionedHashes:
|
|
|
|
isValid = isValid and
|
2023-08-24 05:11:19 +00:00
|
|
|
bv.data[0] == VERSIONED_HASH_VERSION_KZG
|
2023-06-24 13:56:44 +00:00
|
|
|
|
|
|
|
if not isValid:
|
|
|
|
raise newException(ValidationError, "Invalid EIP-4844 transaction")
|
2021-05-15 07:30:39 +00:00
|
|
|
|
2022-12-02 04:39:12 +00:00
|
|
|
proc validate*(tx: Transaction, fork: EVMFork) =
|
2021-06-27 04:19:43 +00:00
|
|
|
# parameters pass validation rules
|
|
|
|
if tx.intrinsicGas(fork) > tx.gasLimit:
|
|
|
|
raise newException(ValidationError, "Insufficient gas")
|
|
|
|
|
2023-01-10 04:25:23 +00:00
|
|
|
if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
|
2023-01-04 13:11:33 +00:00
|
|
|
raise newException(ValidationError, "Initcode size exceeds max")
|
|
|
|
|
2021-06-27 04:19:43 +00:00
|
|
|
# check signature validity
|
|
|
|
var sender: EthAddress
|
|
|
|
if not tx.getSender(sender):
|
|
|
|
raise newException(ValidationError, "Invalid signature or failed message verification")
|
|
|
|
|
|
|
|
case tx.txType
|
|
|
|
of TxLegacy:
|
|
|
|
validateTxLegacy(tx, fork)
|
2023-06-24 13:56:44 +00:00
|
|
|
of TxEip4844:
|
|
|
|
validateTxEip4844(tx)
|
|
|
|
of TxEip2930, TxEip1559:
|
2021-06-27 04:19:43 +00:00
|
|
|
validateTxEip2930(tx)
|
|
|
|
|
|
|
|
proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction =
|
|
|
|
result = tx
|
|
|
|
if eip155:
|
|
|
|
# trigger rlpEncodeEIP155 in nim-eth
|
2024-06-14 07:31:08 +00:00
|
|
|
result.V = chainId.uint64 * 2'u64 + 35'u64
|
2021-06-27 04:19:43 +00:00
|
|
|
|
|
|
|
let
|
|
|
|
rlpTx = rlpEncode(result)
|
|
|
|
sig = sign(privateKey, rlpTx).toRaw
|
|
|
|
|
|
|
|
case tx.txType
|
|
|
|
of TxLegacy:
|
|
|
|
if eip155:
|
2024-06-14 07:31:08 +00:00
|
|
|
result.V = sig[64].uint64 + result.V
|
2021-06-27 04:19:43 +00:00
|
|
|
else:
|
2024-06-14 07:31:08 +00:00
|
|
|
result.V = sig[64].uint64 + 27'u64
|
2021-06-27 04:19:43 +00:00
|
|
|
else:
|
2024-06-14 07:31:08 +00:00
|
|
|
result.V = sig[64].uint64
|
2021-06-27 04:19:43 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
result.R = UInt256.fromBytesBE(sig[0..31])
|
|
|
|
result.S = UInt256.fromBytesBE(sig[32..63])
|
2022-04-05 10:22:46 +00:00
|
|
|
|
2023-10-31 08:12:41 +00:00
|
|
|
# deriveChainId derives the chain id from the given v parameter
|
2024-06-14 07:31:08 +00:00
|
|
|
func deriveChainId*(v: uint64, chainId: ChainId): ChainId =
|
2023-10-31 08:12:41 +00:00
|
|
|
if v == 27 or v == 28:
|
|
|
|
chainId
|
|
|
|
else:
|
|
|
|
((v - 35) div 2).ChainId
|
|
|
|
|
|
|
|
func validateChainId*(tx: Transaction, chainId: ChainId): bool =
|
|
|
|
if tx.txType == TxLegacy:
|
|
|
|
chainId.uint64 == deriveChainId(tx.V, chainId).uint64
|
|
|
|
else:
|
|
|
|
chainId.uint64 == tx.chainId.uint64
|
|
|
|
|
2022-04-05 10:22:46 +00:00
|
|
|
func eip1559TxNormalization*(tx: Transaction;
|
2024-06-14 07:31:08 +00:00
|
|
|
baseFeePerGas: GasInt): Transaction =
|
2022-04-05 10:22:46 +00:00
|
|
|
## This function adjusts a legacy transaction to EIP-1559 standard. This
|
|
|
|
## is needed particularly when using the `validateTransaction()` utility
|
|
|
|
## with legacy transactions.
|
|
|
|
result = tx
|
|
|
|
if tx.txType < TxEip1559:
|
2024-06-14 07:31:08 +00:00
|
|
|
result.maxPriorityFeePerGas = tx.gasPrice
|
|
|
|
result.maxFeePerGas = tx.gasPrice
|
2023-08-22 06:05:10 +00:00
|
|
|
else:
|
2024-06-14 07:31:08 +00:00
|
|
|
result.gasPrice = baseFeePerGas +
|
|
|
|
min(result.maxPriorityFeePerGas, result.maxFeePerGas - baseFeePerGas)
|
2022-04-11 10:00:39 +00:00
|
|
|
|
2024-06-14 07:31:08 +00:00
|
|
|
func effectiveGasTip*(tx: Transaction; baseFeePerGas: Opt[UInt256]): GasInt =
|
2022-04-11 10:00:39 +00:00
|
|
|
var
|
2024-06-14 07:31:08 +00:00
|
|
|
maxPriorityFeePerGas = tx.maxPriorityFeePerGas
|
|
|
|
maxFeePerGas = tx.maxFeePerGas
|
|
|
|
baseFeePerGas = baseFeePerGas.get(0.u256).truncate(GasInt)
|
2022-04-11 10:00:39 +00:00
|
|
|
|
|
|
|
if tx.txType < TxEip1559:
|
2024-06-14 07:31:08 +00:00
|
|
|
maxPriorityFeePerGas = tx.gasPrice
|
|
|
|
maxFeePerGas = tx.gasPrice
|
2022-04-11 10:00:39 +00:00
|
|
|
|
2024-06-14 07:31:08 +00:00
|
|
|
min(maxPriorityFeePerGas, maxFeePerGas - baseFeePerGas)
|
2022-12-15 06:30:18 +00:00
|
|
|
|
|
|
|
proc decodeTx*(bytes: openArray[byte]): Transaction =
|
|
|
|
var rlp = rlpFromBytes(bytes)
|
|
|
|
result = rlp.read(Transaction)
|
|
|
|
if rlp.hasData:
|
|
|
|
raise newException(RlpError, "rlp: input contains more than one value")
|
2024-05-15 03:07:59 +00:00
|
|
|
|
|
|
|
proc decodePooledTx*(bytes: openArray[byte]): PooledTransaction =
|
|
|
|
var rlp = rlpFromBytes(bytes)
|
|
|
|
result = rlp.read(PooledTransaction)
|
|
|
|
if rlp.hasData:
|
|
|
|
raise newException(RlpError, "rlp: input contains more than one value")
|