implementation of EIP-4844: Shard Blob Transactions (#1440)

* EIP-4844: add pointEvaluation precompiled contract

* EIP-4844: validate transaction and block header

* EIP-4844: implement DataHash Op Code

* EIP-4844: txPool support excessDataGas calculation

* EIP-4844: make sure tx produce correct txHash

* EIP-4844: node should not automatically broadcast blob tx to it's peers

* EIP-4844: add test cases

* EIP-4844: add EIP-4844 support to t8n tool

* EIP-4844: update nim-eth to branch eip-4844

* fix t8n transaction decoding

* add t8n test data

* EIP-4844: fix blobHash opcode

* disable blobHash test when evmc_enable
This commit is contained in:
andri lim 2023-06-24 20:56:44 +07:00 committed by GitHub
parent 6544adf360
commit 26a8759c34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 886 additions and 197 deletions

View File

@ -349,6 +349,9 @@ proc isBlockAfterTtd*(com: CommonRef, header: BlockHeader): bool
func isShanghaiOrLater*(com: CommonRef, t: EthTime): bool = func isShanghaiOrLater*(com: CommonRef, t: EthTime): bool =
com.config.shanghaiTime.isSome and t >= com.config.shanghaiTime.get com.config.shanghaiTime.isSome and t >= com.config.shanghaiTime.get
func isCancunOrLater*(com: CommonRef, t: EthTime): bool =
com.config.cancunTime.isSome and t >= com.config.cancunTime.get
proc consensus*(com: CommonRef, header: BlockHeader): ConsensusType proc consensus*(com: CommonRef, header: BlockHeader): ConsensusType
{.gcsafe, raises: [CatchableError].} = {.gcsafe, raises: [CatchableError].} =
if com.isBlockAfterTtd(header): if com.isBlockAfterTtd(header):

View File

@ -71,4 +71,19 @@ const
DEFAULT_RPC_GAS_CAP* = 50_000_000.GasInt DEFAULT_RPC_GAS_CAP* = 50_000_000.GasInt
# EIP-4844 constants
MAX_CALLDATA_SIZE* = 1 shl 24 # 2^24
MAX_ACCESS_LIST_SIZE* = 1 shl 24 # 2^24
MAX_ACCESS_LIST_STORAGE_KEYS* = 1 shl 24 # 2^24
MAX_VERSIONED_HASHES_LIST_SIZE* = 1 shl 24 # 2^24
MAX_TX_WRAP_COMMITMENTS* = 1 shl 12 # 2^12
LIMIT_BLOBS_PER_TX* = 1 shl 12 # 2^12
BLOB_COMMITMENT_VERSION_KZG* = 0x01.byte
FIELD_ELEMENTS_PER_BLOB* = 4096
DATA_GAS_PER_BLOB* = (1 shl 17).uint64 # 2^17
TARGET_DATA_GAS_PER_BLOCK* = (1 shl 18).uint64 # 2^18
MIN_DATA_GASPRICE* = 1'u64
DATA_GASPRICE_UPDATE_FRACTION* = 2225652'u64
MAX_DATA_GAS_PER_BLOCK* = (1 shl 19).uint64 # 2^19
# End # End

View File

@ -1,5 +1,5 @@
# Nimbus # Nimbus
# Copyright (c) 2022 Status Research & Development GmbH # Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0) # http://www.apache.org/licenses/LICENSE-2.0)
@ -9,15 +9,216 @@
# according to those terms. # according to those terms.
import import
std/[os, strutils],
kzg4844/kzg_ex as kzg,
stew/results, stew/results,
stint,
../constants,
../common/common ../common/common
{.push raises: [].} {.push raises: [].}
type
Bytes32 = array[32, byte]
Bytes64 = array[64, byte]
Bytes48 = array[48, byte]
const
BLS_MODULUS_STR = "52435875175126190479447740508185965837690552500527637822603658699938581184513"
BLS_MODULUS = parse(BLS_MODULUS_STR, UInt256, 10)
PrecompileInputLength = 192
proc pointEvaluationResult(): Bytes64 {.compileTime.} =
result[0..<32] = FIELD_ELEMENTS_PER_BLOB.u256.toBytesBE[0..^1]
result[32..^1] = BLS_MODULUS.toBytesBE[0..^1]
const
PointEvaluationResult* = pointEvaluationResult()
POINT_EVALUATION_PRECOMPILE_GAS* = 50000.GasInt
# kzgToVersionedHash implements kzg_to_versioned_hash from EIP-4844
proc kzgToVersionedHash(kzg: kzg.KZGCommitment): VersionedHash =
result = keccakHash(kzg)
result.data[0] = BLOB_COMMITMENT_VERSION_KZG
# pointEvaluation implements point_evaluation_precompile from EIP-4844
# return value and gas consumption is handled by pointEvaluation in
# precompiles.nim
proc pointEvaluation*(input: openArray[byte]): Result[void, string] =
# Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof.
# Also verify that the provided commitment matches the provided versioned_hash.
# The data is encoded as follows: versioned_hash | z | y | commitment | proof |
if input.len < PrecompileInputLength:
return err("invalid input length")
var
versionedHash: Bytes32
z: Bytes32
y: Bytes32
commitment: Bytes48
kzgProof: Bytes48
versionedHash[0..<32] = input[0..<32]
z[0..<32] = input[32..<64]
y[0..<32] = input[64..<96]
commitment[0..<48] = input[96..<144]
kzgProof[0..<48] = input[144..<192]
# Verify KZG proof
let res = kzg.verifyKzgProof(commitment, z, y, kzgProof)
if res.isErr:
return err(res.error)
# The actual verify result
if not res.get():
return err("Failed to verify KZG proof")
ok()
# calcExcessDataGas implements calc_excess_data_gas from EIP-4844
proc calcExcessDataGas*(parent: BlockHeader): uint64 =
let
excessDataGas = parent.excessDataGas.get(0'u64)
dataGasUsed = parent.dataGasUsed.get(0'u64)
if excessDataGas + dataGasUsed < TARGET_DATA_GAS_PER_BLOCK:
0'u64
else:
excessDataGas + dataGasUsed - TARGET_DATA_GAS_PER_BLOCK
# fakeExponential approximates factor * e ** (num / denom) using a taylor expansion
# as described in the EIP-4844 spec.
func fakeExponential*(factor, numerator, denominator: uint64): uint64 =
var
i = 1'u64
output = 0'u64
numeratorAccum = factor * denominator
while numeratorAccum > 0'u64:
output += numeratorAccum
numeratorAccum = (numeratorAccum * numerator) div (denominator * i)
i = i + 1'u64
output div denominator
proc getTotalDataGas*(tx: Transaction): uint64 =
DATA_GAS_PER_BLOB * tx.versionedHashes.len.uint64
proc getTotalDataGas*(versionedHashesLen: int): uint64 =
DATA_GAS_PER_BLOB * versionedHasheslen.uint64
# getDataGasPrice implements get_data_gas_price from EIP-4844
func getDataGasprice*(parentExcessDataGas: uint64): uint64 =
fakeExponential(
MIN_DATA_GASPRICE,
parentExcessDataGas,
DATA_GASPRICE_UPDATE_FRACTION
)
proc calcDataFee*(tx: Transaction,
parentExcessDataGas: Option[uint64]): uint64 =
tx.getTotalDataGas *
getDataGasprice(parentExcessDataGas.get(0'u64))
proc calcDataFee*(versionedHashesLen: int,
parentExcessDataGas: Option[uint64]): uint64 =
getTotalDataGas(versionedHashesLen) *
getDataGasprice(parentExcessDataGas.get(0'u64))
func dataGasUsed(txs: openArray[Transaction]): uint64 =
for tx in txs:
result += tx.getTotalDataGas
# https://eips.ethereum.org/EIPS/eip-4844 # https://eips.ethereum.org/EIPS/eip-4844
func validateEip4844Header*( func validateEip4844Header*(
com: CommonRef, header: BlockHeader com: CommonRef, header, parentHeader: BlockHeader,
): Result[void, string] = txs: openArray[Transaction]): Result[void, string] {.raises: [].} =
if header.excessDataGas.isSome:
return err("EIP-4844 not yet implemented") if not com.forkGTE(Cancun):
if header.dataGasUsed.isSome:
return err("unexpected EIP-4844 dataGasUsed in block header")
if header.excessDataGas.isSome:
return err("unexpected EIP-4844 excessDataGas in block header")
return ok()
if header.dataGasUsed.isNone:
return err("expect EIP-4844 dataGasUsed in block header")
if header.excessDataGas.isNone:
return err("expect EIP-4844 excessDataGas in block header")
let
headerDataGasUsed = header.dataGasUsed.get()
dataGasUsed = dataGasUsed(txs)
headerExcessDataGas = header.excessDataGas.get
excessDataGas = calcExcessDataGas(parentHeader)
if dataGasUsed <= MAX_DATA_GAS_PER_BLOCK:
return err("dataGasUsed should greater than MAX_DATA_GAS_PER_BLOCK: " & $dataGasUsed)
if headerDataGasUsed != dataGasUsed:
return err("calculated dataGas not equal header.dataGasUsed")
if headerExcessDataGas != excessDataGas:
return err("calculated excessDataGas not equal header.excessDataGas")
return ok() return ok()
proc validateBlobTransactionWrapper*(tx: Transaction):
Result[void, string] {.raises: [].} =
if not tx.networkPayload.isNil:
return err("tx wrapper is none")
# note: assert blobs are not malformatted
let goodFormatted = tx.versionedHashes.len ==
tx.networkPayload.commitments.len and
tx.versionedHashes.len ==
tx.networkPayload.blobs.len and
tx.versionedHashes.len ==
tx.networkPayload.proofs.len
if not goodFormatted:
return err("tx wrapper is ill formatted")
# Verify that commitments match the blobs by checking the KZG proof
let res = kzg.verifyBlobKzgProofBatch(tx.networkPayload.blobs,
tx.networkPayload.commitments, tx.networkPayload.proofs)
if res.isErr:
return err(res.error)
# Actual verification result
if not res.get():
return err("Failed to verify network payload of a transaction")
# Now that all commitments have been verified, check that versionedHashes matches the commitments
for i in 0 ..< tx.versionedHashes.len:
# this additional check also done in tx validation
if tx.versionedHashes[i].data[0] != BLOB_COMMITMENT_VERSION_KZG:
return err("wrong kzg version in versioned hash at index " & $i)
if tx.versionedHashes[i] != kzgToVersionedHash(tx.networkPayload.commitments[i]):
return err("tx versioned hash not match commitments at index " & $i)
ok()
proc loadKzgTrustedSetup*(): Result[void, string] =
const
vendorDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor"
trustedSetupDir = vendorDir & "/nim-kzg4844/kzg4844/csources/src"
const const_preset = "mainnet"
const trustedSetup =
when const_preset == "mainnet":
staticRead trustedSetupDir & "/trusted_setup.txt"
elif const_preset == "minimal":
staticRead trustedSetupDir & "/trusted_setup_4.txt"
else:
""
if const_preset == "mainnet" or const_preset == "minimal":
Kzg.loadTrustedSetupFromString(trustedSetup)
else:
ok()

View File

@ -78,6 +78,7 @@ proc asyncProcessTransactionImpl(
baseFee = baseFee256.truncate(GasInt) baseFee = baseFee256.truncate(GasInt)
tx = eip1559TxNormalization(tx, baseFee, fork) tx = eip1559TxNormalization(tx, baseFee, fork)
priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee) priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee)
excessDataGas = vmState.parent.excessDataGas.get(0'u64)
# Return failure unless explicitely set `ok()` # Return failure unless explicitely set `ok()`
var res: Result[GasInt, string] = err("") var res: Result[GasInt, string] = err("")
@ -99,7 +100,7 @@ proc asyncProcessTransactionImpl(
# before leaving is crucial for some unit tests that us a direct/deep call # before leaving is crucial for some unit tests that us a direct/deep call
# of the `processTransaction()` function. So there is no `return err()` # of the `processTransaction()` function. So there is no `return err()`
# statement, here. # statement, here.
let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork) let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, excessDataGas, fork)
if txRes.isOk: if txRes.isOk:
# EIP-1153 # EIP-1153

View File

@ -144,7 +144,7 @@ template unsafeQuantityToInt64(q: web3types.Quantity): int64 =
int64 q int64 q
proc toTypedTransaction(tx: Transaction): TypedTransaction = proc toTypedTransaction(tx: Transaction): TypedTransaction =
web3types.TypedTransaction(rlp.encode(tx)) web3types.TypedTransaction(rlp.encode(tx.removeNetworkPayload))
func toWithdrawal(x: WithdrawalV1): Withdrawal = func toWithdrawal(x: WithdrawalV1): Withdrawal =
result.index = x.index.uint64 result.index = x.index.uint64

View File

@ -52,6 +52,10 @@ type
profit: UInt256 ## Net reward (w/o PoW specific block rewards) profit: UInt256 ## Net reward (w/o PoW specific block rewards)
txRoot: Hash256 ## `rootHash` after packing txRoot: Hash256 ## `rootHash` after packing
stateRoot: Hash256 ## `stateRoot` after packing stateRoot: Hash256 ## `stateRoot` after packing
dataGasUsed:
Option[uint64] ## EIP-4844 block dataGasUsed
excessDataGas:
Option[uint64] ## EIP-4844 block excessDataGas
TxChainRef* = ref object ##\ TxChainRef* = ref object ##\
## State cache of the transaction environment for creating a new\ ## State cache of the transaction environment for creating a new\
@ -139,6 +143,8 @@ proc resetTxEnv(dh: TxChainRef; parent: BlockHeader; fee: Option[UInt256])
dh.txEnv.txRoot = EMPTY_ROOT_HASH dh.txEnv.txRoot = EMPTY_ROOT_HASH
dh.txEnv.stateRoot = dh.txEnv.vmState.parent.stateRoot dh.txEnv.stateRoot = dh.txEnv.vmState.parent.stateRoot
dh.txEnv.dataGasUsed = none(uint64)
dh.txEnv.excessDataGas = none(uint64)
proc update(dh: TxChainRef; parent: BlockHeader) proc update(dh: TxChainRef; parent: BlockHeader)
{.gcsafe,raises: [CatchableError].} = {.gcsafe,raises: [CatchableError].} =
@ -216,7 +222,9 @@ proc getHeader*(dh: TxChainRef): BlockHeader
# extraData: Blob # signing data # extraData: Blob # signing data
# mixDigest: Hash256 # mining hash for given difficulty # mixDigest: Hash256 # mining hash for given difficulty
# nonce: BlockNonce # mining free vaiable # nonce: BlockNonce # mining free vaiable
fee: dh.txEnv.vmState.fee) fee: dh.txEnv.vmState.fee,
dataGasUsed: dh.txEnv.dataGasUsed,
excessDataGas: dh.txEnv.excessDataGas)
if dh.com.forkGTE(Shanghai): if dh.com.forkGTE(Shanghai):
result.withdrawalsRoot = some(calcWithdrawalsRoot(dh.withdrawals)) result.withdrawalsRoot = some(calcWithdrawalsRoot(dh.withdrawals))
@ -369,6 +377,14 @@ proc `txRoot=`*(dh: TxChainRef; val: Hash256) =
proc `withdrawals=`*(dh: TxChainRef, val: sink seq[Withdrawal]) = proc `withdrawals=`*(dh: TxChainRef, val: sink seq[Withdrawal]) =
dh.withdrawals = system.move(val) dh.withdrawals = system.move(val)
proc `excessDataGas=`*(dh: TxChainRef; val: Option[uint64]) =
## Setter
dh.txEnv.excessDataGas = val
proc `dataGasUsed=`*(dh: TxChainRef; val: Option[uint64]) =
## Setter
dh.txEnv.dataGasUsed = val
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -101,6 +101,10 @@ type
## Running basic validator failed on current transaction ## Running basic validator failed on current transaction
"Tx rejected by basic validator" "Tx rejected by basic validator"
txInfoErrInvalidBlob = ##\
## Invalid EIP-4844 kzg validation on blob wrapper
"Invalid EIP-4844 blob validation"
# ------ Signature problems ------------------------------------------------ # ------ Signature problems ------------------------------------------------
txInfoErrInvalidSender = ##\ txInfoErrInvalidSender = ##\

View File

@ -23,7 +23,8 @@ import
./tx_recover, ./tx_recover,
chronicles, chronicles,
eth/[common, keys], eth/[common, keys],
stew/[keyed_queue, sorted_set] stew/[keyed_queue, sorted_set],
../../eip4844
{.push raises: [].} {.push raises: [].}
@ -180,6 +181,14 @@ proc addTxs*(xp: TxPoolRef;
for tx in txs.items: for tx in txs.items:
var reason: TxInfo var reason: TxInfo
if tx.txType == TxEip4844:
let res = tx.validateBlobTransactionWrapper()
if res.isErr:
# move item to waste basket
reason = txInfoErrInvalidBlob
xp.txDB.reject(tx, reason, txItemPending, res.error)
invalidTxMeter(1)
# Create tx item wrapper, preferably recovered from waste basket # Create tx item wrapper, preferably recovered from waste basket
let rcTx = xp.recoverItem(tx, txItemPending, info) let rcTx = xp.recoverItem(tx, txItemPending, info)
if rcTx.isErr: if rcTx.isErr:

View File

@ -233,8 +233,9 @@ proc classifyValidatePacked*(xp: TxPoolRef;
else: else:
xp.chain.limits.trgLimit xp.chain.limits.trgLimit
tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt, fork) tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt, fork)
excessDataGas = vmState.parent.excessDataGas.get(0'u64)
roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, fork).isOk roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, excessDataGas, fork).isOk
proc classifyPacked*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool = proc classifyPacked*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool =
## Classifier for *packing* (i.e. adding up `gasUsed` values after executing ## Classifier for *packing* (i.e. adding up `gasUsed` values after executing

View File

@ -18,7 +18,7 @@ import
std/[sets, tables], std/[sets, tables],
../../../db/accounts_cache, ../../../db/accounts_cache,
../../../common/common, ../../../common/common,
"../.."/[dao, executor, validate], "../.."/[dao, executor, validate, eip4844],
../../../transaction/call_evm, ../../../transaction/call_evm,
../../../transaction, ../../../transaction,
../../../vm_state, ../../../vm_state,
@ -43,6 +43,7 @@ type
tr: HexaryTrie tr: HexaryTrie
cleanState: bool cleanState: bool
balance: UInt256 balance: UInt256
dataGasUsed: uint64
const const
receiptsExtensionSize = ##\ receiptsExtensionSize = ##\
@ -137,8 +138,12 @@ proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt)
vmState.cumulativeGasUsed += gasBurned vmState.cumulativeGasUsed += gasBurned
vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType) vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType)
# EIP-4844, count dataGasUsed
if item.tx.txType >= TxEip4844:
pst.dataGasUsed += item.tx.getTotalDataGas
# Update txRoot # Update txRoot
pst.tr.put(rlp.encode(inx), rlp.encode(item.tx)) pst.tr.put(rlp.encode(inx), rlp.encode(item.tx.removeNetworkPayload))
# Add the item to the `packed` bucket. This implicitely increases the # Add the item to the `packed` bucket. This implicitely increases the
# receipts index `inx` at the next visit of this function. # receipts index `inx` at the next visit of this function.
@ -244,6 +249,11 @@ proc vmExecCommit(pst: TxPackerStateRef)
xp.chain.txRoot = pst.tr.rootHash xp.chain.txRoot = pst.tr.rootHash
xp.chain.stateRoot = vmState.stateDB.rootHash xp.chain.stateRoot = vmState.stateDB.rootHash
if vmState.com.forkGTE(Cancun):
# EIP-4844
let excessDataGas = calcExcessDataGas(vmState.parent)
xp.chain.excessDataGas = some(excessDataGas)
proc balanceDelta: UInt256 = proc balanceDelta: UInt256 =
let postBalance = vmState.readOnlyStateDB.getBalance(xp.chain.feeRecipient) let postBalance = vmState.readOnlyStateDB.getBalance(xp.chain.feeRecipient)
if pst.balance < postBalance: if pst.balance < postBalance:

View File

@ -12,7 +12,8 @@ import
std/[sequtils, sets, times, strutils], std/[sequtils, sets, times, strutils],
../common/common, ../common/common,
../db/accounts_cache, ../db/accounts_cache,
".."/[errors, transaction, vm_state, vm_types], ".."/[transaction, common/common],
".."/[errors],
"."/[dao, eip4844, gaslimit, withdrawals], "."/[dao, eip4844, gaslimit, withdrawals],
./pow/[difficulty, header], ./pow/[difficulty, header],
./pow, ./pow,
@ -72,7 +73,7 @@ proc validateSeal(pow: PowRef; header: BlockHeader): Result[void,string] =
ok() ok()
proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader; proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader;
numTransactions: int; checkSealOK: bool; txs: openArray[Transaction]; checkSealOK: bool;
pow: PowRef): Result[void,string] = pow: PowRef): Result[void,string] =
template inDAOExtraRange(blockNumber: BlockNumber): bool = template inDAOExtraRange(blockNumber: BlockNumber): bool =
@ -87,8 +88,8 @@ proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader;
if header.extraData.len > 32: if header.extraData.len > 32:
return err("BlockHeader.extraData larger than 32 bytes") return err("BlockHeader.extraData larger than 32 bytes")
if header.gasUsed == 0 and 0 < numTransactions: if header.gasUsed == 0 and 0 < txs.len:
return err("zero gasUsed but tranactions present"); return err("zero gasUsed but transactions present");
if header.gasUsed < 0 or header.gasUsed > header.gasLimit: if header.gasUsed < 0 or header.gasUsed > header.gasLimit:
return err("gasUsed should be non negative and smaller or equal gasLimit") return err("gasUsed should be non negative and smaller or equal gasLimit")
@ -125,7 +126,7 @@ proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader;
return pow.validateSeal(header) return pow.validateSeal(header)
? com.validateWithdrawals(header) ? com.validateWithdrawals(header)
? com.validateEip4844Header(header) ? com.validateEip4844Header(header, parentHeader, txs)
ok() ok()
@ -147,7 +148,7 @@ func validateUncle(currBlock, uncle, uncleParent: BlockHeader):
proc validateUncles(com: CommonRef; header: BlockHeader; proc validateUncles(com: CommonRef; header: BlockHeader;
uncles: seq[BlockHeader]; checkSealOK: bool; uncles: openArray[BlockHeader]; checkSealOK: bool;
pow: PowRef): Result[void,string] = pow: PowRef): Result[void,string] =
let hasUncles = uncles.len > 0 let hasUncles = uncles.len > 0
let shouldHaveUncles = header.ommersHash != EMPTY_UNCLE_HASH let shouldHaveUncles = header.ommersHash != EMPTY_UNCLE_HASH
@ -235,13 +236,23 @@ proc validateUncles(com: CommonRef; header: BlockHeader;
# Public function, extracted from executor # Public function, extracted from executor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
func gasCost(tx: Transaction): UInt256 =
if tx.txType >= TxEip4844:
tx.gasLimit.u256 * tx.maxFee.u256 + tx.getTotalDataGas.u256 * tx.maxFeePerDataGas.u256
elif tx.txType >= TxEip1559:
tx.gasLimit.u256 * tx.maxFee.u256
else:
tx.gasLimit.u256 * tx.gasPrice.u256
proc validateTransaction*( proc validateTransaction*(
roDB: ReadOnlyStateDB; ## Parent accounts environment for transaction roDB: ReadOnlyStateDB; ## Parent accounts environment for transaction
tx: Transaction; ## tx to validate tx: Transaction; ## tx to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover sender: EthAddress; ## tx.getSender or tx.ecRecover
maxLimit: GasInt; ## gasLimit from block header maxLimit: GasInt; ## gasLimit from block header
baseFee: UInt256; ## baseFee from block header baseFee: UInt256; ## baseFee from block header
excessDataGas: uint64; ## excessDataGas from parent block header
fork: EVMFork): Result[void, string] = fork: EVMFork): Result[void, string] =
let let
balance = roDB.getBalance(sender) balance = roDB.getBalance(sender)
nonce = roDB.getNonce(sender) nonce = roDB.getNonce(sender)
@ -252,6 +263,9 @@ proc validateTransaction*(
if tx.txType == TxEip1559 and fork < FkLondon: if tx.txType == TxEip1559 and fork < FkLondon:
return err("invalid tx: Eip1559 Tx type detected before London") return err("invalid tx: Eip1559 Tx type detected before London")
if tx.txType == TxEip4844 and fork < FkCancun:
return err("invalid tx: Eip4844 Tx type detected before Cancun")
if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE: if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
return err("invalid tx: initcode size exceeds maximum") return err("invalid tx: initcode size exceeds maximum")
@ -319,24 +333,49 @@ proc validateTransaction*(
if codeHash != EMPTY_SHA3: if codeHash != EMPTY_SHA3:
return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [ return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [
sender.toHex, codeHash.data.toHex]) sender.toHex, codeHash.data.toHex])
if fork >= FkCancun:
if tx.payload.len > MAX_CALLDATA_SIZE:
return err("invalid tx: payload len exceeds MAX_CALLDATA_SIZE. len=" &
$tx.payload.len)
if tx.accessList.len > MAX_ACCESS_LIST_SIZE:
return err("invalid tx: access list len exceeds MAX_ACCESS_LIST_SIZE. len=" &
$tx.accessList.len)
for i, acl in tx.accessList:
if acl.storageKeys.len > MAX_ACCESS_LIST_STORAGE_KEYS:
return err("invalid tx: access list storage keys len exceeds MAX_ACCESS_LIST_STORAGE_KEYS. " &
"index=$1, len=$2" % [$i, $acl.storageKeys.len])
if tx.txType == TxEip4844:
if tx.to.isNone:
return err("invalid tx: destination must be not empty")
if tx.versionedHashes.len == 0:
return err("invalid tx: there must be at least one blob")
if tx.versionedHashes.len > MAX_VERSIONED_HASHES_LIST_SIZE:
return err("invalid tx: access list len exceeds MAX_VERSIONED_HASHES_LIST_SIZE. len=" &
$tx.versionedHashes.len)
for i, bv in tx.versionedHashes:
if bv.data[0] != BLOB_COMMITMENT_VERSION_KZG:
return err("invalid tx: one of blobVersionedHash has invalid version. " &
"get=$1, expect=$2" % [$bv.data[0].int, $BLOB_COMMITMENT_VERSION_KZG.int])
# ensure that the user was willing to at least pay the current data gasprice
let dataGasPrice = getDataGasPrice(excessDataGas)
if tx.maxFeePerDataGas.uint64 < dataGasPrice:
return err("invalid tx: maxFeePerDataGas smaller than dataGasPrice. " &
"maxFeePerDataGas=$1, dataGasPrice=$2" % [$tx.maxFeePerDataGas, $dataGasPrice])
except CatchableError as ex: except CatchableError as ex:
return err(ex.msg) return err(ex.msg)
ok() ok()
proc validateTransaction*(
vmState: BaseVMState; ## Parent accounts environment for transaction
tx: Transaction; ## tx to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork): Result[void, string] =
## Variant of `validateTransaction()`
let
roDB = vmState.readOnlyStateDB
gasLimit = header.gasLimit
baseFee = header.baseFee
roDB.validateTransaction(tx, sender, gasLimit, baseFee, fork)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public functions, extracted from test_blockchain_json # Public functions, extracted from test_blockchain_json
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -344,8 +383,8 @@ proc validateTransaction*(
proc validateHeaderAndKinship*( proc validateHeaderAndKinship*(
com: CommonRef; com: CommonRef;
header: BlockHeader; header: BlockHeader;
uncles: seq[BlockHeader]; uncles: openArray[BlockHeader];
numTransactions: int; txs: openArray[Transaction];
checkSealOK: bool; checkSealOK: bool;
pow: PowRef): Result[void, string] = pow: PowRef): Result[void, string] =
if header.isGenesis: if header.isGenesis:
@ -360,7 +399,7 @@ proc validateHeaderAndKinship*(
return err("Failed to load block header from DB") return err("Failed to load block header from DB")
result = com.validateHeader( result = com.validateHeader(
header, parent, numTransactions, checkSealOK, pow) header, parent, txs, checkSealOK, pow)
if result.isErr: if result.isErr:
return return
@ -384,7 +423,7 @@ proc validateHeaderAndKinship*(
pow: PowRef): Result[void, string] = pow: PowRef): Result[void, string] =
com.validateHeaderAndKinship( com.validateHeaderAndKinship(
header, body.uncles, body.transactions.len, checkSealOK, pow) header, body.uncles, body.transactions, checkSealOK, pow)
proc validateHeaderAndKinship*( proc validateHeaderAndKinship*(
com: CommonRef; com: CommonRef;
@ -392,7 +431,7 @@ proc validateHeaderAndKinship*(
checkSealOK: bool; checkSealOK: bool;
pow: PowRef): Result[void,string] = pow: PowRef): Result[void,string] =
com.validateHeaderAndKinship( com.validateHeaderAndKinship(
ethBlock.header, ethBlock.uncles, ethBlock.txs.len, ethBlock.header, ethBlock.uncles, ethBlock.txs,
checkSealOK, pow) checkSealOK, pow)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -186,8 +186,8 @@ proc persistTransactions*(db: ChainDBRef, blockNumber:
var trie = initHexaryTrie(db.db) var trie = initHexaryTrie(db.db)
for idx, tx in transactions: for idx, tx in transactions:
let let
encodedTx = rlp.encode(tx) encodedTx = rlp.encode(tx.removeNetworkPayload)
txHash = keccakHash(encodedTx) txHash = rlpHash(tx) # beware EIP-4844
txKey: TransactionKey = (blockNumber, idx) txKey: TransactionKey = (blockNumber, idx)
trie.put(rlp.encode(idx), encodedTx) trie.put(rlp.encode(idx), encodedTx)
db.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey)) db.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey))
@ -219,7 +219,8 @@ iterator getBlockTransactionHashes*(db: ChainDBRef, blockHeader: BlockHeader): H
## Returns an iterable of the transaction hashes from th block specified ## Returns an iterable of the transaction hashes from th block specified
## by the given block header. ## by the given block header.
for encodedTx in db.getBlockTransactionData(blockHeader.txRoot): for encodedTx in db.getBlockTransactionData(blockHeader.txRoot):
yield keccakHash(encodedTx) let tx = rlp.decode(encodedTx, Transaction)
yield rlpHash(tx) # beware EIP-4844
proc getTransactionCount*(chain: ChainDBRef, txRoot: Hash256): int = proc getTransactionCount*(chain: ChainDBRef, txRoot: Hash256): int =
var trie = initHexaryTrie(chain.db, txRoot) var trie = initHexaryTrie(chain.db, txRoot)

View File

@ -115,8 +115,22 @@ template getGasPrice*(c: Computation): GasInt =
else: else:
c.vmState.txGasPrice c.vmState.txGasPrice
template getVersionedHash*(c: Computation, index: int): VersionedHash =
when evmc_enabled:
# TODO: implement
Hash256()
else:
c.vmState.txVersionedHashes[index]
template getVersionedHashesLen*(c: Computation): int =
when evmc_enabled:
# TODO: implement
0
else:
c.vmState.txVersionedHashes.len
proc getBlockHash*(c: Computation, number: UInt256): Hash256 proc getBlockHash*(c: Computation, number: UInt256): Hash256
{.gcsafe, raises: [CatchableError].} = {.gcsafe, raises: [CatchableError].} =
when evmc_enabled: when evmc_enabled:
let let
blockNumber = c.host.getTxContext().block_number.u256 blockNumber = c.host.getTxContext().block_number.u256

View File

@ -611,6 +611,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
ChainIdOp: fixed GasBase, ChainIdOp: fixed GasBase,
SelfBalance: fixed GasLow, SelfBalance: fixed GasLow,
BaseFee: fixed GasBase, BaseFee: fixed GasBase,
BlobHash: fixed GasVeryLow,
# 50s: Stack, Memory, Storage and Flow Operations # 50s: Stack, Memory, Storage and Flow Operations
Pop: fixed GasBase, Pop: fixed GasBase,

View File

@ -104,8 +104,9 @@ type
ChainIdOp = 0x46, ## Get current chains EIP-155 unique identifier. ChainIdOp = 0x46, ## Get current chains EIP-155 unique identifier.
SelfBalance = 0x47, ## Get current contract's balance. SelfBalance = 0x47, ## Get current contract's balance.
BaseFee = 0x48, ## Get blocks base fee. EIP-3198 BaseFee = 0x48, ## Get blocks base fee. EIP-3198
BlobHash = 0x49, ## Get transaction's versionedHash. EIP-4844
Nop0x49, Nop0x4A, Nop0x4B, Nop0x4C, Nop0x4D, Nop0x4A, Nop0x4B, Nop0x4C, Nop0x4D,
Nop0x4E, Nop0x4F, ## .. Nop0x4E, Nop0x4F, ## ..
# 50s: Stack, Memory, Storage and Flow Operations # 50s: Stack, Memory, Storage and Flow Operations

View File

@ -17,6 +17,7 @@ import
../../computation, ../../computation,
../../stack, ../../stack,
../../async/operations, ../../async/operations,
../utils/utils_numeric,
../op_codes, ../op_codes,
./oph_defs ./oph_defs
@ -80,6 +81,18 @@ const
k.cpt.stack.push: k.cpt.stack.push:
k.cpt.getBaseFee k.cpt.getBaseFee
blobHashOp: Vm2OpFn = proc (k: var Vm2Ctx) =
## 0x49, Get current transaction's EIP-4844 versioned hash.
let index = k.cpt.stack.popInt().safeInt
let len = k.cpt.getVersionedHashesLen
if index < len:
k.cpt.stack.push:
k.cpt.getVersionedHash(index)
else:
k.cpt.stack.push:
0
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public, op exec table entries # Public, op exec table entries
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -157,6 +170,14 @@ const
info: "Get current block's EIP-1559 base fee", info: "Get current block's EIP-1559 base fee",
exec: (prep: vm2OpIgnore, exec: (prep: vm2OpIgnore,
run: baseFeeOp, run: baseFeeOp,
post: vm2OpIgnore)),
(opCode: BlobHash, ## 0x49, EIP-4844 Transaction versioned hash
forks: Vm2OpCancunAndLater,
name: "blobHash",
info: "Get current transaction's EIP-4844 versioned hash",
exec: (prep: vm2OpIgnore,
run: blobHashOp,
post: vm2OpIgnore))] post: vm2OpIgnore))]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -86,8 +86,7 @@ const
Vm2OpShanghaiAndLater* = Vm2OpShanghaiAndLater* =
Vm2OpParisAndLater - {FkParis} Vm2OpParisAndLater - {FkParis}
# TODO: fix this after EIP-1153 accepted in a fork Vm2OpCancunAndLater* =
Vm2Op1153AndLater* =
Vm2OpShanghaiAndLater - {FkShanghai} Vm2OpShanghaiAndLater - {FkShanghai}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -512,7 +512,7 @@ const
post: vm2OpIgnore)), post: vm2OpIgnore)),
(opCode: Tload, ## 0xb3, Load word from transient storage. (opCode: Tload, ## 0xb3, Load word from transient storage.
forks: Vm2Op1153AndLater, forks: Vm2OpCancunAndLater,
name: "tLoad", name: "tLoad",
info: "Load word from transient storage", info: "Load word from transient storage",
exec: (prep: vm2OpIgnore, exec: (prep: vm2OpIgnore,
@ -520,7 +520,7 @@ const
post: vm2OpIgnore)), post: vm2OpIgnore)),
(opCode: Tstore, ## 0xb4, Save word to transient storage. (opCode: Tstore, ## 0xb4, Save word to transient storage.
forks: Vm2Op1153AndLater, forks: Vm2OpCancunAndLater,
name: "tStore", name: "tStore",
info: "Save word to transient storage", info: "Save word to transient storage",
exec: (prep: vm2OpIgnore, exec: (prep: vm2OpIgnore,

View File

@ -9,28 +9,31 @@
# according to those terms. # according to those terms.
import import
std/[macros], std/[math, macros],
stew/results,
"."/[types, blake2b_f, blscurve], "."/[types, blake2b_f, blscurve],
./interpreter/[gas_meter, gas_costs, utils/utils_numeric], ./interpreter/[gas_meter, gas_costs, utils/utils_numeric],
../errors, eth/[common, keys], chronicles, ../errors, eth/[common, keys], chronicles,
nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups], nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups],
../common/evmforks, ../common/evmforks,
../core/eip4844,
./modexp ./modexp
type type
PrecompileAddresses* = enum PrecompileAddresses* = enum
# Frontier to Spurious Dragron # Frontier to Spurious Dragron
paEcRecover = 1 paEcRecover = 0x01,
paSha256 paSha256 = 0x02,
paRipeMd160 paRipeMd160 = 0x03,
paIdentity paIdentity = 0x04,
# Byzantium and Constantinople # Byzantium and Constantinople
paModExp paModExp = 0x05,
paEcAdd paEcAdd = 0x06,
paEcMul paEcMul = 0x07,
paPairing paPairing = 0x08,
# Istanbul # Istanbul
paBlake2bf paBlake2bf = 0x09,
# Berlin # Berlin
# EIP-2537: disabled # EIP-2537: disabled
# reason: not included in berlin # reason: not included in berlin
@ -43,12 +46,38 @@ type
# paBlsPairing # paBlsPairing
# paBlsMapG1 # paBlsMapG1
# paBlsMapG2 # paBlsMapG2
# Cancun
paNoop0x0A, paNoop0x0B, paNoop0x0C,
paNoop0x0D, paNoop0x0E, paNoop0x0F,
paNoop0x10, paNoop0x11, paNoop0x12,
paNoop0x13,
iterator activePrecompiles*(): EthAddress = paPointEvaluation = 0x14
proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
if fork < FkByzantium: paIdentity
elif fork < FkIstanbul: paPairing
# EIP 2537: disabled
# reason: not included in berlin
# elif fork < FkBerlin: paBlake2bf
elif fork < FkCancun: paBlake2bf
else: PrecompileAddresses.high
proc validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool =
(addrByte in PrecompileAddresses.low.byte .. maxPrecompileAddr) and
(addrByte notin paNoop0x0A.byte .. paNoop0x13.byte)
proc validPrecompileAddr(addrByte: byte, fork: EVMFork): bool =
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
validPrecompileAddr(addrByte, maxPrecompileAddr.byte)
iterator activePrecompiles*(fork: EVMFork): EthAddress =
var res: EthAddress var res: EthAddress
for c in PrecompileAddresses.low..PrecompileAddresses.high: let maxPrecompileAddr = getMaxPrecompileAddr(fork)
res[^1] = c.byte for c in PrecompileAddresses.low..maxPrecompileAddr:
yield res if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
res[^1] = c.byte
yield res
proc getSignature(computation: Computation): (array[32, byte], Signature) = proc getSignature(computation: Computation): (array[32, byte], Signature) =
# input is Hash, V, R, S # input is Hash, V, R, S
@ -643,21 +672,30 @@ proc blsMapG2*(c: Computation) =
if not encodePoint(p, c.output): if not encodePoint(p, c.output):
raise newException(ValidationError, "blsMapG2 encodePoint error") raise newException(ValidationError, "blsMapG2 encodePoint error")
proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses = proc pointEvaluation*(c: Computation) =
if fork < FkByzantium: paIdentity # Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof.
elif fork < FkIstanbul: paPairing # Also verify that the provided commitment matches the provided versioned_hash.
# EIP 2537: disabled # The data is encoded as follows: versioned_hash | z | y | commitment | proof |
# reason: not included in berlin
# elif fork < FkBerlin: paBlake2bf template input: untyped =
else: PrecompileAddresses.high c.msg.data
c.gasMeter.consumeGas(POINT_EVALUATION_PRECOMPILE_GAS,
reason = "EIP-4844 Point Evaluation Precompile")
let res = pointEvaluation(input)
if res.isErr:
raise newException(ValidationError, res.error)
# return a constant
c.output = @PointEvaluationResult
proc execPrecompiles*(computation: Computation, fork: EVMFork): bool {.inline.} = proc execPrecompiles*(computation: Computation, fork: EVMFork): bool {.inline.} =
for i in 0..18: for i in 0..18:
if computation.msg.codeAddress[i] != 0: return if computation.msg.codeAddress[i] != 0: return
let lb = computation.msg.codeAddress[19] let lb = computation.msg.codeAddress[19]
let maxPrecompileAddr = getMaxPrecompileAddr(fork) if validPrecompileAddr(lb, fork):
if lb in PrecompileAddresses.low.byte .. maxPrecompileAddr.byte:
result = true result = true
let precompile = PrecompileAddresses(lb) let precompile = PrecompileAddresses(lb)
#trace "Call precompile", precompile = precompile, codeAddr = computation.msg.codeAddress #trace "Call precompile", precompile = precompile, codeAddr = computation.msg.codeAddress
@ -672,6 +710,8 @@ proc execPrecompiles*(computation: Computation, fork: EVMFork): bool {.inline.}
of paEcMul: bn256ecMul(computation, fork) of paEcMul: bn256ecMul(computation, fork)
of paPairing: bn256ecPairing(computation, fork) of paPairing: bn256ecPairing(computation, fork)
of paBlake2bf: blake2bf(computation) of paBlake2bf: blake2bf(computation)
of paPointEvaluation: pointEvaluation(computation)
else: discard
# EIP 2537: disabled # EIP 2537: disabled
# reason: not included in berlin # reason: not included in berlin
# of paBlsG1Add: blsG1Add(computation) # of paBlsG1Add: blsG1Add(computation)

View File

@ -23,7 +23,11 @@ import
{.push raises: [].} {.push raises: [].}
proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt, forkOverride=none(EVMFork)) = proc setupTxContext*(vmState: BaseVMState,
origin: EthAddress,
gasPrice: GasInt,
versionedHashes: openArray[VersionedHash],
forkOverride=none(EVMFork)) =
## this proc will be called each time a new transaction ## this proc will be called each time a new transaction
## is going to be executed ## is going to be executed
vmState.txOrigin = origin vmState.txOrigin = origin
@ -34,6 +38,7 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
else: else:
vmState.determineFork vmState.determineFork
vmState.gasCosts = vmState.fork.forkToSchedule vmState.gasCosts = vmState.fork.forkToSchedule
vmState.txVersionedHashes = @versionedHashes
# FIXME-awkwardFactoring: the factoring out of the pre and # FIXME-awkwardFactoring: the factoring out of the pre and
# post parts feels awkward to me, but for now I'd really like # post parts feels awkward to me, but for now I'd really like

View File

@ -52,6 +52,7 @@ type
cumulativeGasUsed*: GasInt cumulativeGasUsed*: GasInt
txOrigin* : EthAddress txOrigin* : EthAddress
txGasPrice* : GasInt txGasPrice* : GasInt
txVersionedHashes*: VersionedHashes
gasCosts* : GasCosts gasCosts* : GasCosts
fork* : EVMFork fork* : EVMFork
minerAddress* : EthAddress minerAddress* : EthAddress

View File

@ -569,8 +569,7 @@ const logProcs = {
proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let let
tx = TxNode(parent) tx = TxNode(parent)
encodedTx = rlp.encode(tx.tx) txHash = rlpHash(tx.tx) # beware EIP-4844
txHash = keccakHash(encodedTx)
resp(txHash) resp(txHash)
proc txNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = proc txNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
@ -1249,8 +1248,8 @@ proc sendRawTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.a
let ctx = GraphqlContextRef(ud) let ctx = GraphqlContextRef(ud)
try: try:
let data = hexToSeqByte(params[0].val.stringVal) let data = hexToSeqByte(params[0].val.stringVal)
let _ = decodeTx(data) # we want to know if it is a valid tx blob let tx = decodeTx(data) # we want to know if it is a valid tx blob
let txHash = keccakHash(data) let txHash = rlpHash(tx) # beware EIP-4844
resp(txHash) resp(txHash)
except CatchableError as em: except CatchableError as em:
return err("failed to process raw transaction: " & em.msg) return err("failed to process raw transaction: " & em.msg)

View File

@ -258,10 +258,9 @@ proc setupEthRpc*(
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1) tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1)
eip155 = com.isEIP155(com.syncCurrent) eip155 = com.isEIP155(com.syncCurrent)
signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155) signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155)
rlpTx = rlp.encode(signedTx)
txPool.add(signedTx) txPool.add(signedTx)
result = keccakHash(rlpTx).ethHashStr result = rlpHash(signedTx).ethHashStr
server.rpc("eth_sendRawTransaction") do(data: HexDataStr) -> EthHashStr: server.rpc("eth_sendRawTransaction") do(data: HexDataStr) -> EthHashStr:
## Creates new message call transaction or a contract creation for signed transactions. ## Creates new message call transaction or a contract creation for signed transactions.
@ -274,7 +273,7 @@ proc setupEthRpc*(
signedTx = decodeTx(txBytes) signedTx = decodeTx(txBytes)
txPool.add(signedTx) txPool.add(signedTx)
result = keccakHash(txBytes).ethHashStr result = rlpHash(signedTx).ethHashStr
server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr: server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr:
## Executes a new message call immediately without creating a transaction on the block chain. ## Executes a new message call immediately without creating a transaction on the block chain.

View File

@ -483,7 +483,9 @@ method handleAnnouncedTxs*(ctx: EthWireRef, peer: Peer, txs: openArray[Transacti
var newTxHashes = newSeqOfCap[Hash256](txHashes.len) var newTxHashes = newSeqOfCap[Hash256](txHashes.len)
var validTxs = newSeqOfCap[Transaction](txHashes.len) var validTxs = newSeqOfCap[Transaction](txHashes.len)
for i, txHash in txHashes: for i, txHash in txHashes:
if ctx.inPoolAndOk(txHash): # Nodes must not automatically broadcast blob transactions to
# their peers. per EIP-4844 spec
if ctx.inPoolAndOk(txHash) and txs[i].txType != TxEip4844:
newTxHashes.add txHash newTxHashes.add txHash
validTxs.add txs[i] validTxs.add txs[i]

View File

@ -180,6 +180,7 @@ proc validateDifficulty(ctx: LegacySyncRef,
return false return false
proc validateHeader(ctx: LegacySyncRef, header: BlockHeader, proc validateHeader(ctx: LegacySyncRef, header: BlockHeader,
txs: openArray[Transaction],
height = none(BlockNumber)): bool height = none(BlockNumber)): bool
{.raises: [CatchableError].} = {.raises: [CatchableError].} =
if header.parentHash == GENESIS_PARENT_HASH: if header.parentHash == GENESIS_PARENT_HASH:
@ -242,7 +243,7 @@ proc validateHeader(ctx: LegacySyncRef, header: BlockHeader,
msg=res.error msg=res.error
return false return false
res = com.validateEip4844Header(header) res = com.validateEip4844Header(header, parentHeader, txs)
if res.isErr: if res.isErr:
trace "validate eip4844 error", trace "validate eip4844 error",
msg=res.error msg=res.error
@ -1052,7 +1053,7 @@ proc handleNewBlock(ctx: LegacySyncRef,
number=blk.header.blockNumber number=blk.header.blockNumber
return return
if not ctx.validateHeader(blk.header): if not ctx.validateHeader(blk.header, blk.txs):
error "invalid header from peer", error "invalid header from peer",
peer, hash=short(blk.header.blockHash) peer, hash=short(blk.header.blockHash)
return return

View File

@ -458,10 +458,7 @@ p2pProtocol les(version = lesVersion,
var results: seq[TransactionStatusMsg] var results: seq[TransactionStatusMsg]
for t in transactions: for t in transactions:
let hash = t.rlpHash # TODO: this is not optimal, we can compute let hash = t.rlpHash
# the hash from the request bytes.
# The RLP module can offer a helper Hashed[T]
# to make this easy.
var s = ctx.getTransactionStatus(hash) var s = ctx.getTransactionStatus(hash)
if s.status == TransactionStatus.Unknown: if s.status == TransactionStatus.Unknown:
ctx.addTransactions([t]) ctx.addTransactions([t])

View File

@ -117,7 +117,7 @@ proc validateTxLegacy(tx: Transaction, fork: EVMFork) =
isValid = isValid and tx.S < SECPK1_N div 2 isValid = isValid and tx.S < SECPK1_N div 2
if not isValid: if not isValid:
raise newException(ValidationError, "Invalid transaction") raise newException(ValidationError, "Invalid legacy transaction")
proc validateTxEip2930(tx: Transaction) = proc validateTxEip2930(tx: Transaction) =
var isValid = tx.V in {0'i64, 1'i64} var isValid = tx.V in {0'i64, 1'i64}
@ -126,7 +126,27 @@ proc validateTxEip2930(tx: Transaction) =
isValid = isValid and tx.R < SECPK1_N isValid = isValid and tx.R < SECPK1_N
if not isValid: if not isValid:
raise newException(ValidationError, "Invalid transaction") 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
tx.versionedHashes.len <= MAX_VERSIONED_HASHES_LIST_SIZE
for bv in tx.versionedHashes:
isValid = isValid and
bv.data[0] == BLOB_COMMITMENT_VERSION_KZG
if not isValid:
raise newException(ValidationError, "Invalid EIP-4844 transaction")
proc validate*(tx: Transaction, fork: EVMFork) = proc validate*(tx: Transaction, fork: EVMFork) =
# parameters pass validation rules # parameters pass validation rules
@ -144,7 +164,9 @@ proc validate*(tx: Transaction, fork: EVMFork) =
case tx.txType case tx.txType
of TxLegacy: of TxLegacy:
validateTxLegacy(tx, fork) validateTxLegacy(tx, fork)
else: of TxEip4844:
validateTxEip4844(tx)
of TxEip2930, TxEip1559:
validateTxEip2930(tx) validateTxEip2930(tx)
proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction = proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction =

View File

@ -16,6 +16,7 @@ import
".."/[db/accounts_cache], ".."/[db/accounts_cache],
../evm/async/operations, ../evm/async/operations,
../common/evmforks, ../common/evmforks,
../core/eip4844,
./host_types ./host_types
when defined(evmc_enabled): when defined(evmc_enabled):
@ -36,6 +37,7 @@ type
value*: HostValue # Value sent from sender to recipient. value*: HostValue # Value sent from sender to recipient.
input*: seq[byte] # Input data. input*: seq[byte] # Input data.
accessList*: AccessList # EIP-2930 (Berlin) tx access list. accessList*: AccessList # EIP-2930 (Berlin) tx access list.
versionedHashes*: VersionedHashes # EIP-4844 (Cancun) blob versioned hashes
noIntrinsic*: bool # Don't charge intrinsic gas. noIntrinsic*: bool # Don't charge intrinsic gas.
noAccessList*: bool # Don't initialise EIP-2929 access list. noAccessList*: bool # Don't initialise EIP-2929 access list.
noGasCharge*: bool # Don't charge sender account for gas. noGasCharge*: bool # Don't charge sender account for gas.
@ -67,10 +69,11 @@ proc hostToComputationMessage*(msg: EvmcMessage): Message =
flags: if msg.isStatic: emvcStatic else: emvcNoFlags flags: if msg.isStatic: emvcStatic else: emvcNoFlags
) )
func intrinsicGas*(call: CallParams, fork: EVMFork): GasInt {.inline.} = func intrinsicGas*(call: CallParams, vmState: BaseVMState): GasInt {.inline.} =
# Compute the baseline gas cost for this transaction. This is the amount # 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 # of gas needed to send this transaction (but that is not actually used
# for computation). # for computation).
let fork = vmState.fork
var gas = gasFees[fork][GasTransaction] var gas = gasFees[fork][GasTransaction]
# EIP-2 (Homestead) extra intrinsic gas for contract creations. # EIP-2 (Homestead) extra intrinsic gas for contract creations.
@ -90,6 +93,12 @@ func intrinsicGas*(call: CallParams, fork: EVMFork): GasInt {.inline.} =
for account in call.accessList: for account in call.accessList:
gas += ACCESS_LIST_ADDRESS_COST gas += ACCESS_LIST_ADDRESS_COST
gas += account.storageKeys.len * ACCESS_LIST_STORAGE_KEY_COST gas += account.storageKeys.len * ACCESS_LIST_STORAGE_KEY_COST
# EIP-4844
if fork >= FkCancun:
gas += calcDataFee(call.versionedHashes.len,
vmState.parent.excessDataGas).GasInt
return gas return gas
proc initialAccessListEIP2929(call: CallParams) = proc initialAccessListEIP2929(call: CallParams) =
@ -109,8 +118,8 @@ proc initialAccessListEIP2929(call: CallParams) =
if vmState.fork >= FkShanghai: if vmState.fork >= FkShanghai:
db.accessList(vmState.coinbase) db.accessList(vmState.coinbase)
# TODO: Check this only adds the correct subset of precompiles. # Adds the correct subset of precompiles.
for c in activePrecompiles(): for c in activePrecompiles(vmState.fork):
db.accessList(c) db.accessList(c)
# EIP2930 optional access list. # EIP2930 optional access list.
@ -124,12 +133,13 @@ proc setupHost(call: CallParams): TransactionHost =
vmState.setupTxContext( vmState.setupTxContext(
origin = call.origin.get(call.sender), origin = call.origin.get(call.sender),
gasPrice = call.gasPrice, gasPrice = call.gasPrice,
versionedHashes = call.versionedHashes,
forkOverride = call.forkOverride forkOverride = call.forkOverride
) )
var intrinsicGas: GasInt = 0 var intrinsicGas: GasInt = 0
if not call.noIntrinsic: if not call.noIntrinsic:
intrinsicGas = intrinsicGas(call, vmState.fork) intrinsicGas = intrinsicGas(call, vmState)
let host = TransactionHost( let host = TransactionHost(
vmState: vmState, vmState: vmState,

View File

@ -29,6 +29,7 @@ type
value* : Option[UInt256] value* : Option[UInt256]
data* : seq[byte] data* : seq[byte]
accessList* : AccessList accessList* : AccessList
versionedHashes*: VersionedHashes
proc toCallParams(vmState: BaseVMState, cd: RpcCallData, proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
globalGasCap: GasInt, baseFee: Option[UInt256], globalGasCap: GasInt, baseFee: Option[UInt256],
@ -73,7 +74,8 @@ proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
gasPrice: gasPrice, gasPrice: gasPrice,
value: cd.value.get(0.u256), value: cd.value.get(0.u256),
input: cd.data, input: cd.data,
accessList: cd.accessList accessList: cd.accessList,
versionedHashes:cd.versionedHashes
) )
proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, com: CommonRef): CallResult proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, com: CommonRef): CallResult
@ -152,7 +154,7 @@ proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, com: CommonRef, gasCa
hi = gasCap hi = gasCap
cap = hi cap = hi
let intrinsicGas = intrinsicGas(params, fork) let intrinsicGas = intrinsicGas(params, vmState)
# Create a helper to check if a gas allowance results in an executable transaction # Create a helper to check if a gas allowance results in an executable transaction
proc executable(gasLimit: GasInt): bool proc executable(gasLimit: GasInt): bool
@ -201,6 +203,9 @@ proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState,
if tx.txType > TxLegacy: if tx.txType > TxLegacy:
shallowCopy(result.accessList, tx.accessList) shallowCopy(result.accessList, tx.accessList)
if tx.txType >= TxEip4844:
result.versionedHashes = tx.versionedHashes
proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams = proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams =
result = CallParams( result = CallParams(
vmState: vmState, vmState: vmState,
@ -219,6 +224,9 @@ proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState
if tx.txType > TxLegacy: if tx.txType > TxLegacy:
shallowCopy(result.accessList, tx.accessList) shallowCopy(result.accessList, tx.accessList)
if tx.txType >= TxEip4844:
result.versionedHashes = tx.versionedHashes
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt
{.gcsafe, raises: [CatchableError].} = {.gcsafe, raises: [CatchableError].} =
let call = callParamsForTx(tx, sender, vmState, fork) let call = callParamsForTx(tx, sender, vmState, fork)

View File

@ -119,7 +119,10 @@ proc ecRecover*(tx: var Transaction): EcAddrResult =
let txSig = tx.vrsSerialised let txSig = tx.vrsSerialised
if txSig.isErr: if txSig.isErr:
return err(txSig.error) return err(txSig.error)
txSig.value.recoverImpl(tx.txHashNoSignature) try:
result = txSig.value.recoverImpl(tx.txHashNoSignature)
except ValueError as ex:
return err((errTxEncError, ex.msg))
proc ecRecover*(tx: Transaction): EcAddrResult = proc ecRecover*(tx: Transaction): EcAddrResult =
## Variant of `ecRecover()` for call-by-value header. ## Variant of `ecRecover()` for call-by-value header.

View File

@ -42,6 +42,10 @@ type
## database lookup failed ## database lookup failed
"not found" "not found"
errTxEncError = ##\
## TRansaction encoding error
"tx enc error"
UtilsError* = ##\ UtilsError* = ##\
## Error message, tinned component + explanatory text (if any) ## Error message, tinned component + explanatory text (if any)
(UtilsErrorType,string) (UtilsErrorType,string)

View File

@ -48,4 +48,5 @@ cliBuilder:
./test_configuration, ./test_configuration,
./test_keyed_queue_rlp, ./test_keyed_queue_rlp,
./test_txpool, ./test_txpool,
./test_merge ./test_merge,
./test_eip4844

View File

@ -377,13 +377,14 @@ proc verifyAsmResult(vmState: BaseVMState, boa: Assembler, asmResult: CallResult
proc createSignedTx(payload: Blob, chainId: ChainId): Transaction = proc createSignedTx(payload: Blob, chainId: ChainId): Transaction =
let privateKey = PrivateKey.fromHex("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d")[] let privateKey = PrivateKey.fromHex("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d")[]
let unsignedTx = Transaction( let unsignedTx = Transaction(
txType: TxLegacy, txType: TxEIP4844,
nonce: 0, nonce: 0,
gasPrice: 1.GasInt, gasPrice: 1.GasInt,
gasLimit: 500_000_000.GasInt, gasLimit: 500_000_000.GasInt,
to: codeAddress.some, to: codeAddress.some,
value: 500.u256, value: 500.u256,
payload: payload payload: payload,
versionedHashes: @[EMPTY_UNCLE_HASH, EMPTY_SHA3]
) )
signTransaction(unsignedTx, privateKey, chainId, false) signTransaction(unsignedTx, privateKey, chainId, false)

View File

@ -412,6 +412,7 @@ proc blockchainJsonMain*(debugMode = false) =
testFixture(n, testStatusIMPL, debugMode = true, config.trace) testFixture(n, testStatusIMPL, debugMode = true, config.trace)
when isMainModule: when isMainModule:
import std/times
var message: string var message: string
let start = getTime() let start = getTime()

164
tests/test_eip4844.nim Normal file
View File

@ -0,0 +1,164 @@
# Nimbus
# Copyright (c) 2023 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.
import
stew/byteutils,
unittest2,
eth/[common, keys],
../nimbus/transaction
const
recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87")
zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
source = hexToByteArray[20]("0x0000000000000000000000000000000000000001")
storageKey= default(StorageKey)
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
blob = default(NetworkBlob)
abcdef = hexToSeqByte("abcdef")
hexKey = "af1a9be9f1a54421cac82943820a0fe0f601bb5f4f6d0bccc81c613f0ce6ae22"
senderTop = hexToByteArray[20]("73cf19657412508833f618a15e8251306b3e6ee5")
proc tx0(i: int): Transaction =
Transaction(
txType: TxLegacy,
nonce: i.AccountNonce,
to: recipient.some,
gasLimit: 1.GasInt,
gasPrice: 2.GasInt,
payload: abcdef)
proc tx1(i: int): Transaction =
Transaction(
# Legacy tx contract creation.
txType: TxLegacy,
nonce: i.AccountNonce,
gasLimit: 1.GasInt,
gasPrice: 2.GasInt,
payload: abcdef)
proc tx2(i: int): Transaction =
Transaction(
# Tx with non-zero access list.
txType: TxEip2930,
chainId: 1.ChainId,
nonce: i.AccountNonce,
to: recipient.some,
gasLimit: 123457.GasInt,
gasPrice: 10.GasInt,
accessList: accesses,
payload: abcdef)
proc tx3(i: int): Transaction =
Transaction(
# Tx with empty access list.
txType: TxEip2930,
chainId: 1.ChainId,
nonce: i.AccountNonce,
to: recipient.some,
gasLimit: 123457.GasInt,
gasPrice: 10.GasInt,
payload: abcdef)
proc tx4(i: int): Transaction =
Transaction(
# Contract creation with access list.
txType: TxEip2930,
chainId: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
gasPrice: 10.GasInt,
accessList: accesses)
proc tx5(i: int): Transaction =
Transaction(
txType: TxEip1559,
chainId: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
maxPriorityFee: 42.GasInt,
maxFee: 10.GasInt,
accessList: accesses)
proc tx6(i: int): Transaction =
const
digest = "010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014".toDigest
Transaction(
txType: TxEip4844,
chainId: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
maxPriorityFee: 42.GasInt,
maxFee: 10.GasInt,
accessList: accesses,
versionedHashes: @[digest],
networkPayload: NetworkPayload(
commitments: @[zeroG1],
blobs: @[blob],
proofs: @[zeroG1],
)
)
proc tx7(i: int): Transaction =
const
digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest
Transaction(
txType: TxEip4844,
chainID: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
maxPriorityFee: 42.GasInt,
maxFee: 10.GasInt,
accessList: accesses,
versionedHashes: @[digest],
maxFeePerDataGas: 10000000.GasInt,
)
proc tx8(i: int): Transaction =
const
digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest
Transaction(
txType: TxEip4844,
chainID: 1.ChainId,
nonce: i.AccountNonce,
to: some(recipient),
gasLimit: 123457.GasInt,
maxPriorityFee: 42.GasInt,
maxFee: 10.GasInt,
accessList: accesses,
versionedHashes: @[digest],
maxFeePerDataGas: 10000000.GasInt,
)
proc privKey(keyHex: string): PrivateKey =
let kRes = PrivateKey.fromHex(keyHex)
if kRes.isErr:
echo kRes.error
quit(QuitFailure)
kRes.get()
proc eip4844Main*() =
var signerKey = privKey(hexKey)
suite "EIP4844 sign transaction":
let txs = @[tx0(3), tx1(3), tx2(3), tx3(3), tx4(3),
tx5(3), tx6(3), tx7(3), tx8(3)]
test "sign transaction":
for tx in txs:
let signedTx = signTransaction(tx, signerKey, 1.ChainId, true)
let sender = signedTx.getSender()
check sender == senderTop
when isMainModule:
eip4844Main()

View File

@ -190,6 +190,7 @@ proc generalStateJsonMain*(debugMode = false) =
testFixture(n, testStatusIMPL, config.trace, true) testFixture(n, testStatusIMPL, config.trace, true)
when isMainModule: when isMainModule:
import std/times
var message: string var message: string
let start = getTime() let start = getTime()

View File

@ -374,5 +374,79 @@ proc opEnvMain*() =
"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
fork: Merge fork: Merge
# from here onward, versionedHashes is taken from
# tx.versionedHashes = @[EMPTY_UNCLE_HASH, EMPTY_SHA3]
# preset in macro_assembler: createSignedTx
when not defined(evmc_enabled):
assembler:
title: "EIP-4844: BlobHash 1"
code:
PUSH1 "0x01"
BlobHash
STOP
stack:
"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
fork: Cancun
assembler:
title: "EIP-4844: BlobHash 0"
code:
PUSH1 "0x00"
BlobHash
STOP
stack:
"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
fork: Cancun
assembler:
title: "EIP-4844: BlobHash 2"
code:
PUSH1 "0x02"
BlobHash
STOP
stack:
"0x0000000000000000000000000000000000000000000000000000000000000000"
fork: Cancun
assembler:
title: "EIP-4844: BlobHash 32 Bit high"
code:
PUSH4 "0xffffffff"
BlobHash
STOP
stack:
"0x0000000000000000000000000000000000000000000000000000000000000000"
fork: Cancun
assembler:
title: "EIP-4844: BlobHash 64 Bit high"
code:
PUSH8 "0xffffffffffffffff"
BlobHash
STOP
stack:
"0x0000000000000000000000000000000000000000000000000000000000000000"
fork: Cancun
assembler:
title: "EIP-4844: BlobHash 128 Bit high"
code:
PUSH16 "0xffffffffffffffffffffffffffffffff"
BlobHash
STOP
stack:
"0x0000000000000000000000000000000000000000000000000000000000000000"
fork: Cancun
assembler:
title: "EIP-4844: BlobHash 256 Bit high"
code:
PUSH32 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
BlobHash
STOP
stack:
"0x0000000000000000000000000000000000000000000000000000000000000000"
fork: Cancun
when isMainModule: when isMainModule:
opEnvMain() opEnvMain()

View File

@ -61,13 +61,16 @@ template fromJson(T: type GasInt, n: JsonNode, field: string): GasInt =
template fromJson(T: type ChainId, n: JsonNode, field: string): ChainId = template fromJson(T: type ChainId, n: JsonNode, field: string): ChainId =
parseHexOrInt[uint64](n[field].getStr()).ChainId parseHexOrInt[uint64](n[field].getStr()).ChainId
proc fromJson(T: type Hash256, n: JsonNode, field: string): Hash256 = proc fromJson(T: type Hash256, n: JsonNode): Hash256 =
var num = n[field].getStr() var num = n.getStr()
num.removePrefix("0x") num.removePrefix("0x")
if num.len < 64: if num.len < 64:
num = repeat('0', 64 - num.len) & num num = repeat('0', 64 - num.len) & num
Hash256(data: hexToByteArray(num, 32)) Hash256(data: hexToByteArray(num, 32))
proc fromJson(T: type Hash256, n: JsonNode, field: string): Hash256 =
fromJson(T, n[field])
template fromJson(T: type EthTime, n: JsonNode, field: string): EthTime = template fromJson(T: type EthTime, n: JsonNode, field: string): EthTime =
fromUnix(parseHexOrInt[int64](n[field].getStr())) fromUnix(parseHexOrInt[int64](n[field].getStr()))
@ -99,6 +102,11 @@ proc fromJson(T: type Withdrawal, n: JsonNode): Withdrawal =
amount: fromJson(uint64, n, "amount") amount: fromJson(uint64, n, "amount")
) )
proc fromJson(T: type VersionedHashes, n: JsonNode, field: string): VersionedHashes =
let list = n[field]
for x in list:
result.add Hash256.fromJson(x)
template `gas=`(tx: var Transaction, x: GasInt) = template `gas=`(tx: var Transaction, x: GasInt) =
tx.gasLimit = x tx.gasLimit = x
@ -120,6 +128,9 @@ template `maxPriorityFeePerGas=`(tx: var Transaction, x: GasInt) =
template `maxFeePerGas=`(tx: var Transaction, x: GasInt) = template `maxFeePerGas=`(tx: var Transaction, x: GasInt) =
tx.maxFee = x tx.maxFee = x
template `blobVersionedHashes=`(tx: var Transaction, x: VersionedHashes) =
tx.versionedHashes = x
template required(o: untyped, T: type, oField: untyped) = template required(o: untyped, T: type, oField: untyped) =
const fName = astToStr(oField) const fName = astToStr(oField)
if not n.hasKey(fName): if not n.hasKey(fName):
@ -168,6 +179,8 @@ proc parseEnv*(ctx: var TransContext, n: JsonNode) =
optional(ctx.env, UInt256, parentBaseFee) optional(ctx.env, UInt256, parentBaseFee)
optional(ctx.env, GasInt, parentGasUsed) optional(ctx.env, GasInt, parentGasUsed)
optional(ctx.env, GasInt, parentGasLimit) optional(ctx.env, GasInt, parentGasLimit)
optional(ctx.env, uint64, parentDataGasUsed)
optional(ctx.env, uint64, parentExcessDataGas)
if n.hasKey("blockHashes"): if n.hasKey("blockHashes"):
let w = n["blockHashes"] let w = n["blockHashes"]
@ -216,6 +229,13 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
required(tx, GasInt, maxPriorityFeePerGas) required(tx, GasInt, maxPriorityFeePerGas)
required(tx, GasInt, maxFeePerGas) required(tx, GasInt, maxFeePerGas)
omitZero(tx, AccessList, accessList) omitZero(tx, AccessList, accessList)
of TxEip4844:
required(tx, ChainId, chainId)
required(tx, GasInt, maxPriorityFeePerGas)
required(tx, GasInt, maxFeePerGas)
omitZero(tx, AccessList, accessList)
required(tx, GasInt, maxFeePerDataGas)
required(tx, VersionedHashes, blobVersionedHashes)
var eip155 = true var eip155 = true
if n.hasKey("protected"): if n.hasKey("protected"):
@ -231,7 +251,7 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
proc parseTxLegacy(item: var Rlp): Result[Transaction, string] = proc parseTxLegacy(item: var Rlp): Result[Transaction, string] =
try: try:
var tx: Transaction var tx: Transaction
item.readTxLegacy(tx) item.decodeTxLegacy(tx)
return ok(tx) return ok(tx)
except RlpError as x: except RlpError as x:
return err(x.msg) return err(x.msg)
@ -240,7 +260,7 @@ proc parseTxTyped(item: var Rlp): Result[Transaction, string] =
try: try:
var tx: Transaction var tx: Transaction
var rr = rlpFromBytes(item.read(Blob)) var rr = rlpFromBytes(item.read(Blob))
rr.readTxTyped(tx) rr.decodeTxTyped(tx)
return ok(tx) return ok(tx)
except RlpError as x: except RlpError as x:
return err(x.msg) return err(x.msg)
@ -420,3 +440,7 @@ proc `@@`*(x: ExecutionResult): JsonNode =
result["currentBaseFee"] = @@(x.currentBaseFee) result["currentBaseFee"] = @@(x.currentBaseFee)
if x.withdrawalsRoot.isSome: if x.withdrawalsRoot.isSome:
result["withdrawalsRoot"] = @@(x.withdrawalsRoot) result["withdrawalsRoot"] = @@(x.withdrawalsRoot)
if x.dataGasUsed.isSome:
result["dataGasUsed"] = @@(x.dataGasUsed)
if x.excessDataGas.isSome:
result["excessDataGas"] = @@(x.excessDataGas)

View File

@ -466,6 +466,15 @@ const
output: T8nOutput(alloc: true, result: true), output: T8nOutput(alloc: true, result: true),
expOut: "exp.json", expOut: "exp.json",
), ),
TestSpec(
name : "Cancun optional fields",
base : "testdata/00-517",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Cancun", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
] ]
proc main() = proc main() =

8
tools/t8n/testdata/00-517/alloc.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0x0",
"code": "0x",
"nonce": "0xac",
"storage": {}
}
}

19
tools/t8n/testdata/00-517/env.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"currentDifficulty": null,
"currentRandom": "0xdeadc0de",
"currentGasLimit": "0x750a163df65e8a",
"currentBaseFee": "0x500",
"currentNumber": "1",
"currentTimestamp": "1000",
"withdrawals": [
{
"index": "0x42",
"validatorIndex": "0x42",
"address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"amount": "0x2a"
}
],
"parentDataGasUsed": "0x100",
"parentExcessDataGas": "0x100"
}

22
tools/t8n/testdata/00-517/exp.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"alloc": {
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0x9c7652400",
"nonce": "0xac"
}
},
"result": {
"stateRoot": "0x6e061c2f6513af27d267a0e3b07cb9a10f1ba3a0f65ab648d3a17c36e15021d2",
"txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"receipts": [],
"currentDifficulty": null,
"gasUsed": "0x0",
"currentBaseFee": "0x500",
"withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5",
"dataGasUsed": "0x0",
"excessDataGas": "0x0"
}
}

1
tools/t8n/testdata/00-517/txs.json vendored Normal file
View File

@ -0,0 +1 @@
[]

View File

@ -20,7 +20,8 @@ import
../../nimbus/utils/utils, ../../nimbus/utils/utils,
../../nimbus/core/pow/difficulty, ../../nimbus/core/pow/difficulty,
../../nimbus/core/dao, ../../nimbus/core/dao,
../../nimbus/core/executor/[process_transaction, executor_helpers] ../../nimbus/core/executor/[process_transaction, executor_helpers],
../../nimbus/core/eip4844
import stew/byteutils import stew/byteutils
const const
@ -203,6 +204,7 @@ proc exec(ctx: var TransContext,
vmState.receipts = newSeqOfCap[Receipt](txList.len) vmState.receipts = newSeqOfCap[Receipt](txList.len)
vmState.cumulativeGasUsed = 0 vmState.cumulativeGasUsed = 0
var dataGasUsed = 0'u64
for txIndex, txRes in txList: for txIndex, txRes in txList:
if txRes.isErr: if txRes.isErr:
rejected.add RejectedTx( rejected.add RejectedTx(
@ -239,6 +241,7 @@ proc exec(ctx: var TransContext,
rec, tx, sender, txIndex, gasUsed rec, tx, sender, txIndex, gasUsed
) )
includedTx.add tx includedTx.add tx
dataGasUsed += tx.getTotalDataGas
# Add mining reward? (-1 means rewards are disabled) # Add mining reward? (-1 means rewards are disabled)
if stateReward.isSome and stateReward.get >= 0: if stateReward.isSome and stateReward.get >= 0:
@ -286,6 +289,10 @@ proc exec(ctx: var TransContext,
withdrawalsRoot: header.withdrawalsRoot withdrawalsRoot: header.withdrawalsRoot
) )
if fork >= FkCancun:
result.result.dataGasUsed = some dataGasUsed
result.result.excessDataGas = some calcExcessDataGas(vmState.parent)
template wrapException(body: untyped) = template wrapException(body: untyped) =
when wrapExceptionEnabled: when wrapExceptionEnabled:
try: try:
@ -403,7 +410,9 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
timestamp: ctx.env.parentTimestamp, timestamp: ctx.env.parentTimestamp,
difficulty: ctx.env.parentDifficulty.get(0.u256), difficulty: ctx.env.parentDifficulty.get(0.u256),
ommersHash: uncleHash, ommersHash: uncleHash,
blockNumber: ctx.env.currentNumber - 1.toBlockNumber blockNumber: ctx.env.currentNumber - 1.toBlockNumber,
dataGasUsed: ctx.env.parentDataGasUsed,
excessDataGas: ctx.env.parentExcessDataGas
) )
# Sanity check, to not `panic` in state_transition # Sanity check, to not `panic` in state_transition
@ -419,6 +428,17 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
if com.isShanghaiOrLater(ctx.env.currentTimestamp) and ctx.env.withdrawals.isNone: if com.isShanghaiOrLater(ctx.env.currentTimestamp) and ctx.env.withdrawals.isNone:
raise newError(ErrorConfig, "Shanghai config but missing 'withdrawals' in env section") raise newError(ErrorConfig, "Shanghai config but missing 'withdrawals' in env section")
if com.isCancunOrLater(ctx.env.currentTimestamp):
if ctx.env.parentDataGasUsed.isNone:
raise newError(ErrorConfig, "Cancun config but missing 'parentDataGasUsed' in env section")
if ctx.env.parentExcessDataGas.isNone:
raise newError(ErrorConfig, "Cancun config but missing 'parentExcessDataGas' in env section")
let res = loadKzgTrustedSetup()
if res.isErr:
raise newError(ErrorConfig, res.error)
if com.forkGTE(MergeFork): if com.forkGTE(MergeFork):
if ctx.env.currentRandom.isNone: if ctx.env.currentRandom.isNone:
raise newError(ErrorConfig, "post-merge requires currentRandom to be defined in env") raise newError(ErrorConfig, "post-merge requires currentRandom to be defined in env")

View File

@ -8,99 +8,12 @@
# at your option. This file may not be copied, modified, or distributed except # at your option. This file may not be copied, modified, or distributed except
# according to those terms. # according to those terms.
import include eth/common/eth_types_rlp
eth/common
from stew/objects # reexport private procs
import checkedEnumAssign
# these procs are duplicates of nim-eth/eth_types_rlp.nim template decodeTxLegacy*(rlp: var Rlp, tx: var Transaction) =
# both `readTxLegacy` and `readTxTyped` are exported here readTxLegacy(rlp, tx)
template read[T](rlp: var Rlp, val: var T)= template decodeTxTyped*(rlp: var Rlp, tx: var Transaction) =
val = rlp.read(type val) readTxTyped(rlp, tx)
proc read[T](rlp: var Rlp, val: var Option[T])=
if rlp.blobLen != 0:
val = some(rlp.read(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 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 readTxTyped*(rlp: var Rlp, tx: var Transaction) {.inline.} =
# 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")
raise newException(MalformedRlpError,
"TypedTransaction type byte is out of range, must be 0x00 to 0x7f")
let txType = rlp.getByteValue
rlp.position += 1
var txVal: TxType
if checkedEnumAssign(txVal, txType):
case txVal:
of TxEip2930:
rlp.readTxEip2930(tx)
return
of TxEip1559:
rlp.readTxEip1559(tx)
return
else:
discard
raise newException(UnsupportedRlpError,
"TypedTransaction type must be 1 or 2 in this version, got " & $txType)

View File

@ -44,6 +44,8 @@ type
parentGasUsed*: Option[GasInt] parentGasUsed*: Option[GasInt]
parentGasLimit*: Option[GasInt] parentGasLimit*: Option[GasInt]
withdrawals*: Option[seq[Withdrawal]] withdrawals*: Option[seq[Withdrawal]]
parentDataGasUsed*: Option[uint64]
parentExcessDataGas*: Option[uint64]
TxsType* = enum TxsType* = enum
TxsNone TxsNone
@ -92,6 +94,8 @@ type
gasUsed*: GasInt gasUsed*: GasInt
currentBaseFee*: Option[UInt256] currentBaseFee*: Option[UInt256]
withdrawalsRoot*: Option[Hash256] withdrawalsRoot*: Option[Hash256]
dataGasUsed*: Option[uint64]
excessDataGas*: Option[uint64]
const const
ErrorEVM* = 2.T8NExitCode ErrorEVM* = 2.T8NExitCode

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit 6dacb2ca5ce4e5528924f56c87620f38e1d0c0d4 Subproject commit 6b8a7b009eb94bfdac1410f243865984bdd7c4f2