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:
parent
6544adf360
commit
26a8759c34
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 not com.forkGTE(Cancun):
|
||||||
|
if header.dataGasUsed.isSome:
|
||||||
|
return err("unexpected EIP-4844 dataGasUsed in block header")
|
||||||
|
|
||||||
if header.excessDataGas.isSome:
|
if header.excessDataGas.isSome:
|
||||||
return err("EIP-4844 not yet implemented")
|
return err("unexpected EIP-4844 excessDataGas in block header")
|
||||||
|
|
||||||
return ok()
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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 = ##\
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -115,6 +115,20 @@ 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:
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -104,8 +104,9 @@ type
|
||||||
ChainIdOp = 0x46, ## Get current chain’s EIP-155 unique identifier.
|
ChainIdOp = 0x46, ## Get current chain’s EIP-155 unique identifier.
|
||||||
SelfBalance = 0x47, ## Get current contract's balance.
|
SelfBalance = 0x47, ## Get current contract's balance.
|
||||||
BaseFee = 0x48, ## Get block’s base fee. EIP-3198
|
BaseFee = 0x48, ## Get block’s 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
|
||||||
|
|
|
@ -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))]
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,10 +46,36 @@ 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)
|
||||||
|
for c in PrecompileAddresses.low..maxPrecompileAddr:
|
||||||
|
if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
|
||||||
res[^1] = c.byte
|
res[^1] = c.byte
|
||||||
yield res
|
yield res
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() =
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0xac",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
[]
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6dacb2ca5ce4e5528924f56c87620f38e1d0c0d4
|
Subproject commit 6b8a7b009eb94bfdac1410f243865984bdd7c4f2
|
Loading…
Reference in New Issue