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
|
2024-05-13 08:00:13 +00:00
|
|
|
std/[sequtils, typetraits],
|
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)
|
2024-05-13 08:00:13 +00:00
|
|
|
result = distinctBase(tx.payload.input).intrinsicGas(fork)
|
2021-05-15 13:54:34 +00:00
|
|
|
|
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-05-13 08:00:13 +00:00
|
|
|
let numWords = toWordSize(tx.payload.input.len)
|
2023-01-15 07:37:19 +00:00
|
|
|
result = result + (gasFees[fork][GasInitcodeWord] * numWords)
|
2021-05-15 13:54:34 +00:00
|
|
|
|
2024-05-13 08:00:13 +00:00
|
|
|
if tx.payload.access_list.isSome:
|
|
|
|
template access_list: untyped = tx.payload.access_list.unsafeGet
|
|
|
|
result = result + access_list.len * ACCESS_LIST_ADDRESS_COST
|
2021-06-27 04:19:43 +00:00
|
|
|
var numKeys = 0
|
2024-05-13 08:00:13 +00:00
|
|
|
for n in access_list:
|
|
|
|
inc(numKeys, n.storage_keys.len)
|
2021-06-28 13:12:14 +00:00
|
|
|
result = result + 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 =
|
2024-05-13 08:00:13 +00:00
|
|
|
let sig = Signature.fromRaw(tx.signature.ecdsa_signature)
|
2020-04-05 13:12:48 +00:00
|
|
|
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.
|
2024-05-13 08:00:13 +00:00
|
|
|
output = tx.signature.from_address
|
|
|
|
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:
|
2024-05-13 08:00:13 +00:00
|
|
|
result = generateAddress(sender, tx.payload.nonce)
|
2021-05-15 07:18:21 +00:00
|
|
|
else:
|
2024-05-13 08:00:13 +00:00
|
|
|
result = tx.payload.to.get()
|
2023-06-24 13:56:44 +00:00
|
|
|
|
2024-05-13 08:00:13 +00:00
|
|
|
proc validate*(tx: Transaction, fork: EVMFork, chainId: ChainId) =
|
2021-06-27 04:19:43 +00:00
|
|
|
# parameters pass validation rules
|
2024-05-13 08:00:13 +00:00
|
|
|
if tx.intrinsicGas(fork).uint64 > tx.payload.gas:
|
2021-06-27 04:19:43 +00:00
|
|
|
raise newException(ValidationError, "Insufficient gas")
|
|
|
|
|
2024-05-13 08:00:13 +00:00
|
|
|
if fork >= FkShanghai and tx.contractCreation and
|
|
|
|
tx.payload.input.len > EIP3860_MAX_INITCODE_SIZE:
|
2023-01-04 13:11:33 +00:00
|
|
|
raise newException(ValidationError, "Initcode size exceeds max")
|
|
|
|
|
2024-05-13 08:00:13 +00:00
|
|
|
if tx.payload.blob_versioned_hashes.isSome:
|
|
|
|
template vhs: untyped = tx.payload.blob_versioned_hashes.unsafeGet
|
|
|
|
if vhs.len > MAX_BLOBS_PER_BLOCK:
|
|
|
|
raise newException(ValidationError, "Too many blob versioned hashes")
|
|
|
|
if not vhs.allIt(it.data[0] == VERSIONED_HASH_VERSION_KZG):
|
|
|
|
raise newException(ValidationError, "Invalid blob versioned hash")
|
2023-10-31 08:12:41 +00:00
|
|
|
|
2024-05-13 08:00:13 +00:00
|
|
|
# check signature validity
|
|
|
|
let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr:
|
|
|
|
raise newException(ValidationError, "Invalid combination of fields")
|
|
|
|
withTxVariant(anyTx):
|
|
|
|
if txVariant.validate_transaction(chainId).isErr:
|
|
|
|
raise newException(ValidationError,
|
|
|
|
"Invalid signature or failed message verification")
|