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 =
|
||||
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
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
if com.isBlockAfterTtd(header):
|
||||
|
|
|
@ -71,4 +71,19 @@ const
|
|||
|
||||
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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2022 Status Research & Development GmbH
|
||||
# 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)
|
||||
|
@ -9,15 +9,216 @@
|
|||
# according to those terms.
|
||||
|
||||
import
|
||||
std/[os, strutils],
|
||||
kzg4844/kzg_ex as kzg,
|
||||
stew/results,
|
||||
stint,
|
||||
../constants,
|
||||
../common/common
|
||||
|
||||
{.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
|
||||
func validateEip4844Header*(
|
||||
com: CommonRef, header: BlockHeader
|
||||
): Result[void, string] =
|
||||
com: CommonRef, header, parentHeader: BlockHeader,
|
||||
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:
|
||||
return err("EIP-4844 not yet implemented")
|
||||
return err("unexpected EIP-4844 excessDataGas in block header")
|
||||
|
||||
return ok()
|
||||
|
||||
if header.dataGasUsed.isNone:
|
||||
return err("expect EIP-4844 dataGasUsed in block header")
|
||||
|
||||
if header.excessDataGas.isNone:
|
||||
return err("expect EIP-4844 excessDataGas in block header")
|
||||
|
||||
let
|
||||
headerDataGasUsed = header.dataGasUsed.get()
|
||||
dataGasUsed = dataGasUsed(txs)
|
||||
headerExcessDataGas = header.excessDataGas.get
|
||||
excessDataGas = calcExcessDataGas(parentHeader)
|
||||
|
||||
if dataGasUsed <= MAX_DATA_GAS_PER_BLOCK:
|
||||
return err("dataGasUsed should greater than MAX_DATA_GAS_PER_BLOCK: " & $dataGasUsed)
|
||||
|
||||
if headerDataGasUsed != dataGasUsed:
|
||||
return err("calculated dataGas not equal header.dataGasUsed")
|
||||
|
||||
if headerExcessDataGas != excessDataGas:
|
||||
return err("calculated excessDataGas not equal header.excessDataGas")
|
||||
|
||||
return ok()
|
||||
|
||||
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)
|
||||
tx = eip1559TxNormalization(tx, baseFee, fork)
|
||||
priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee)
|
||||
excessDataGas = vmState.parent.excessDataGas.get(0'u64)
|
||||
|
||||
# Return failure unless explicitely set `ok()`
|
||||
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
|
||||
# of the `processTransaction()` function. So there is no `return err()`
|
||||
# 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:
|
||||
|
||||
# EIP-1153
|
||||
|
|
|
@ -144,7 +144,7 @@ template unsafeQuantityToInt64(q: web3types.Quantity): int64 =
|
|||
int64 q
|
||||
|
||||
proc toTypedTransaction(tx: Transaction): TypedTransaction =
|
||||
web3types.TypedTransaction(rlp.encode(tx))
|
||||
web3types.TypedTransaction(rlp.encode(tx.removeNetworkPayload))
|
||||
|
||||
func toWithdrawal(x: WithdrawalV1): Withdrawal =
|
||||
result.index = x.index.uint64
|
||||
|
|
|
@ -52,6 +52,10 @@ type
|
|||
profit: UInt256 ## Net reward (w/o PoW specific block rewards)
|
||||
txRoot: Hash256 ## `rootHash` after packing
|
||||
stateRoot: Hash256 ## `stateRoot` after packing
|
||||
dataGasUsed:
|
||||
Option[uint64] ## EIP-4844 block dataGasUsed
|
||||
excessDataGas:
|
||||
Option[uint64] ## EIP-4844 block excessDataGas
|
||||
|
||||
TxChainRef* = ref object ##\
|
||||
## 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.stateRoot = dh.txEnv.vmState.parent.stateRoot
|
||||
dh.txEnv.dataGasUsed = none(uint64)
|
||||
dh.txEnv.excessDataGas = none(uint64)
|
||||
|
||||
proc update(dh: TxChainRef; parent: BlockHeader)
|
||||
{.gcsafe,raises: [CatchableError].} =
|
||||
|
@ -216,7 +222,9 @@ proc getHeader*(dh: TxChainRef): BlockHeader
|
|||
# extraData: Blob # signing data
|
||||
# mixDigest: Hash256 # mining hash for given difficulty
|
||||
# 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):
|
||||
result.withdrawalsRoot = some(calcWithdrawalsRoot(dh.withdrawals))
|
||||
|
@ -369,6 +377,14 @@ proc `txRoot=`*(dh: TxChainRef; val: Hash256) =
|
|||
proc `withdrawals=`*(dh: TxChainRef, val: sink seq[Withdrawal]) =
|
||||
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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -101,6 +101,10 @@ type
|
|||
## Running basic validator failed on current transaction
|
||||
"Tx rejected by basic validator"
|
||||
|
||||
txInfoErrInvalidBlob = ##\
|
||||
## Invalid EIP-4844 kzg validation on blob wrapper
|
||||
"Invalid EIP-4844 blob validation"
|
||||
|
||||
# ------ Signature problems ------------------------------------------------
|
||||
|
||||
txInfoErrInvalidSender = ##\
|
||||
|
|
|
@ -23,7 +23,8 @@ import
|
|||
./tx_recover,
|
||||
chronicles,
|
||||
eth/[common, keys],
|
||||
stew/[keyed_queue, sorted_set]
|
||||
stew/[keyed_queue, sorted_set],
|
||||
../../eip4844
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
|
@ -180,6 +181,14 @@ proc addTxs*(xp: TxPoolRef;
|
|||
for tx in txs.items:
|
||||
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
|
||||
let rcTx = xp.recoverItem(tx, txItemPending, info)
|
||||
if rcTx.isErr:
|
||||
|
|
|
@ -233,8 +233,9 @@ proc classifyValidatePacked*(xp: TxPoolRef;
|
|||
else:
|
||||
xp.chain.limits.trgLimit
|
||||
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 =
|
||||
## Classifier for *packing* (i.e. adding up `gasUsed` values after executing
|
||||
|
|
|
@ -18,7 +18,7 @@ import
|
|||
std/[sets, tables],
|
||||
../../../db/accounts_cache,
|
||||
../../../common/common,
|
||||
"../.."/[dao, executor, validate],
|
||||
"../.."/[dao, executor, validate, eip4844],
|
||||
../../../transaction/call_evm,
|
||||
../../../transaction,
|
||||
../../../vm_state,
|
||||
|
@ -43,6 +43,7 @@ type
|
|||
tr: HexaryTrie
|
||||
cleanState: bool
|
||||
balance: UInt256
|
||||
dataGasUsed: uint64
|
||||
|
||||
const
|
||||
receiptsExtensionSize = ##\
|
||||
|
@ -137,8 +138,12 @@ proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt)
|
|||
vmState.cumulativeGasUsed += gasBurned
|
||||
vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType)
|
||||
|
||||
# EIP-4844, count dataGasUsed
|
||||
if item.tx.txType >= TxEip4844:
|
||||
pst.dataGasUsed += item.tx.getTotalDataGas
|
||||
|
||||
# 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
|
||||
# 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.stateRoot = vmState.stateDB.rootHash
|
||||
|
||||
if vmState.com.forkGTE(Cancun):
|
||||
# EIP-4844
|
||||
let excessDataGas = calcExcessDataGas(vmState.parent)
|
||||
xp.chain.excessDataGas = some(excessDataGas)
|
||||
|
||||
proc balanceDelta: UInt256 =
|
||||
let postBalance = vmState.readOnlyStateDB.getBalance(xp.chain.feeRecipient)
|
||||
if pst.balance < postBalance:
|
||||
|
|
|
@ -12,7 +12,8 @@ import
|
|||
std/[sequtils, sets, times, strutils],
|
||||
../common/common,
|
||||
../db/accounts_cache,
|
||||
".."/[errors, transaction, vm_state, vm_types],
|
||||
".."/[transaction, common/common],
|
||||
".."/[errors],
|
||||
"."/[dao, eip4844, gaslimit, withdrawals],
|
||||
./pow/[difficulty, header],
|
||||
./pow,
|
||||
|
@ -72,7 +73,7 @@ proc validateSeal(pow: PowRef; header: BlockHeader): Result[void,string] =
|
|||
ok()
|
||||
|
||||
proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader;
|
||||
numTransactions: int; checkSealOK: bool;
|
||||
txs: openArray[Transaction]; checkSealOK: bool;
|
||||
pow: PowRef): Result[void,string] =
|
||||
|
||||
template inDAOExtraRange(blockNumber: BlockNumber): bool =
|
||||
|
@ -87,8 +88,8 @@ proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader;
|
|||
if header.extraData.len > 32:
|
||||
return err("BlockHeader.extraData larger than 32 bytes")
|
||||
|
||||
if header.gasUsed == 0 and 0 < numTransactions:
|
||||
return err("zero gasUsed but tranactions present");
|
||||
if header.gasUsed == 0 and 0 < txs.len:
|
||||
return err("zero gasUsed but transactions present");
|
||||
|
||||
if header.gasUsed < 0 or header.gasUsed > header.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)
|
||||
|
||||
? com.validateWithdrawals(header)
|
||||
? com.validateEip4844Header(header)
|
||||
? com.validateEip4844Header(header, parentHeader, txs)
|
||||
|
||||
ok()
|
||||
|
||||
|
@ -147,7 +148,7 @@ func validateUncle(currBlock, uncle, uncleParent: BlockHeader):
|
|||
|
||||
|
||||
proc validateUncles(com: CommonRef; header: BlockHeader;
|
||||
uncles: seq[BlockHeader]; checkSealOK: bool;
|
||||
uncles: openArray[BlockHeader]; checkSealOK: bool;
|
||||
pow: PowRef): Result[void,string] =
|
||||
let hasUncles = uncles.len > 0
|
||||
let shouldHaveUncles = header.ommersHash != EMPTY_UNCLE_HASH
|
||||
|
@ -235,13 +236,23 @@ proc validateUncles(com: CommonRef; header: BlockHeader;
|
|||
# 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*(
|
||||
roDB: ReadOnlyStateDB; ## Parent accounts environment for transaction
|
||||
tx: Transaction; ## tx to validate
|
||||
sender: EthAddress; ## tx.getSender or tx.ecRecover
|
||||
maxLimit: GasInt; ## gasLimit from block header
|
||||
baseFee: UInt256; ## baseFee from block header
|
||||
excessDataGas: uint64; ## excessDataGas from parent block header
|
||||
fork: EVMFork): Result[void, string] =
|
||||
|
||||
let
|
||||
balance = roDB.getBalance(sender)
|
||||
nonce = roDB.getNonce(sender)
|
||||
|
@ -252,6 +263,9 @@ proc validateTransaction*(
|
|||
if tx.txType == TxEip1559 and fork < FkLondon:
|
||||
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:
|
||||
return err("invalid tx: initcode size exceeds maximum")
|
||||
|
||||
|
@ -319,24 +333,49 @@ proc validateTransaction*(
|
|||
if codeHash != EMPTY_SHA3:
|
||||
return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [
|
||||
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:
|
||||
return err(ex.msg)
|
||||
|
||||
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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -344,8 +383,8 @@ proc validateTransaction*(
|
|||
proc validateHeaderAndKinship*(
|
||||
com: CommonRef;
|
||||
header: BlockHeader;
|
||||
uncles: seq[BlockHeader];
|
||||
numTransactions: int;
|
||||
uncles: openArray[BlockHeader];
|
||||
txs: openArray[Transaction];
|
||||
checkSealOK: bool;
|
||||
pow: PowRef): Result[void, string] =
|
||||
if header.isGenesis:
|
||||
|
@ -360,7 +399,7 @@ proc validateHeaderAndKinship*(
|
|||
return err("Failed to load block header from DB")
|
||||
|
||||
result = com.validateHeader(
|
||||
header, parent, numTransactions, checkSealOK, pow)
|
||||
header, parent, txs, checkSealOK, pow)
|
||||
if result.isErr:
|
||||
return
|
||||
|
||||
|
@ -384,7 +423,7 @@ proc validateHeaderAndKinship*(
|
|||
pow: PowRef): Result[void, string] =
|
||||
|
||||
com.validateHeaderAndKinship(
|
||||
header, body.uncles, body.transactions.len, checkSealOK, pow)
|
||||
header, body.uncles, body.transactions, checkSealOK, pow)
|
||||
|
||||
proc validateHeaderAndKinship*(
|
||||
com: CommonRef;
|
||||
|
@ -392,7 +431,7 @@ proc validateHeaderAndKinship*(
|
|||
checkSealOK: bool;
|
||||
pow: PowRef): Result[void,string] =
|
||||
com.validateHeaderAndKinship(
|
||||
ethBlock.header, ethBlock.uncles, ethBlock.txs.len,
|
||||
ethBlock.header, ethBlock.uncles, ethBlock.txs,
|
||||
checkSealOK, pow)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -186,8 +186,8 @@ proc persistTransactions*(db: ChainDBRef, blockNumber:
|
|||
var trie = initHexaryTrie(db.db)
|
||||
for idx, tx in transactions:
|
||||
let
|
||||
encodedTx = rlp.encode(tx)
|
||||
txHash = keccakHash(encodedTx)
|
||||
encodedTx = rlp.encode(tx.removeNetworkPayload)
|
||||
txHash = rlpHash(tx) # beware EIP-4844
|
||||
txKey: TransactionKey = (blockNumber, idx)
|
||||
trie.put(rlp.encode(idx), encodedTx)
|
||||
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
|
||||
## by the given block header.
|
||||
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 =
|
||||
var trie = initHexaryTrie(chain.db, txRoot)
|
||||
|
|
|
@ -115,6 +115,20 @@ template getGasPrice*(c: Computation): GasInt =
|
|||
else:
|
||||
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
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
when evmc_enabled:
|
||||
|
|
|
@ -611,6 +611,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
|
|||
ChainIdOp: fixed GasBase,
|
||||
SelfBalance: fixed GasLow,
|
||||
BaseFee: fixed GasBase,
|
||||
BlobHash: fixed GasVeryLow,
|
||||
|
||||
# 50s: Stack, Memory, Storage and Flow Operations
|
||||
Pop: fixed GasBase,
|
||||
|
|
|
@ -104,8 +104,9 @@ type
|
|||
ChainIdOp = 0x46, ## Get current chain’s EIP-155 unique identifier.
|
||||
SelfBalance = 0x47, ## Get current contract's balance.
|
||||
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, ## ..
|
||||
|
||||
# 50s: Stack, Memory, Storage and Flow Operations
|
||||
|
|
|
@ -17,6 +17,7 @@ import
|
|||
../../computation,
|
||||
../../stack,
|
||||
../../async/operations,
|
||||
../utils/utils_numeric,
|
||||
../op_codes,
|
||||
./oph_defs
|
||||
|
||||
|
@ -80,6 +81,18 @@ const
|
|||
k.cpt.stack.push:
|
||||
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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -157,6 +170,14 @@ const
|
|||
info: "Get current block's EIP-1559 base fee",
|
||||
exec: (prep: vm2OpIgnore,
|
||||
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))]
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -86,8 +86,7 @@ const
|
|||
Vm2OpShanghaiAndLater* =
|
||||
Vm2OpParisAndLater - {FkParis}
|
||||
|
||||
# TODO: fix this after EIP-1153 accepted in a fork
|
||||
Vm2Op1153AndLater* =
|
||||
Vm2OpCancunAndLater* =
|
||||
Vm2OpShanghaiAndLater - {FkShanghai}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -512,7 +512,7 @@ const
|
|||
post: vm2OpIgnore)),
|
||||
|
||||
(opCode: Tload, ## 0xb3, Load word from transient storage.
|
||||
forks: Vm2Op1153AndLater,
|
||||
forks: Vm2OpCancunAndLater,
|
||||
name: "tLoad",
|
||||
info: "Load word from transient storage",
|
||||
exec: (prep: vm2OpIgnore,
|
||||
|
@ -520,7 +520,7 @@ const
|
|||
post: vm2OpIgnore)),
|
||||
|
||||
(opCode: Tstore, ## 0xb4, Save word to transient storage.
|
||||
forks: Vm2Op1153AndLater,
|
||||
forks: Vm2OpCancunAndLater,
|
||||
name: "tStore",
|
||||
info: "Save word to transient storage",
|
||||
exec: (prep: vm2OpIgnore,
|
||||
|
|
|
@ -9,28 +9,31 @@
|
|||
# according to those terms.
|
||||
|
||||
import
|
||||
std/[macros],
|
||||
std/[math, macros],
|
||||
stew/results,
|
||||
"."/[types, blake2b_f, blscurve],
|
||||
./interpreter/[gas_meter, gas_costs, utils/utils_numeric],
|
||||
../errors, eth/[common, keys], chronicles,
|
||||
nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups],
|
||||
../common/evmforks,
|
||||
../core/eip4844,
|
||||
./modexp
|
||||
|
||||
|
||||
type
|
||||
PrecompileAddresses* = enum
|
||||
# Frontier to Spurious Dragron
|
||||
paEcRecover = 1
|
||||
paSha256
|
||||
paRipeMd160
|
||||
paIdentity
|
||||
paEcRecover = 0x01,
|
||||
paSha256 = 0x02,
|
||||
paRipeMd160 = 0x03,
|
||||
paIdentity = 0x04,
|
||||
# Byzantium and Constantinople
|
||||
paModExp
|
||||
paEcAdd
|
||||
paEcMul
|
||||
paPairing
|
||||
paModExp = 0x05,
|
||||
paEcAdd = 0x06,
|
||||
paEcMul = 0x07,
|
||||
paPairing = 0x08,
|
||||
# Istanbul
|
||||
paBlake2bf
|
||||
paBlake2bf = 0x09,
|
||||
# Berlin
|
||||
# EIP-2537: disabled
|
||||
# reason: not included in berlin
|
||||
|
@ -43,10 +46,36 @@ type
|
|||
# paBlsPairing
|
||||
# paBlsMapG1
|
||||
# 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
|
||||
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
|
||||
yield res
|
||||
|
||||
|
@ -643,21 +672,30 @@ proc blsMapG2*(c: Computation) =
|
|||
if not encodePoint(p, c.output):
|
||||
raise newException(ValidationError, "blsMapG2 encodePoint error")
|
||||
|
||||
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
|
||||
else: PrecompileAddresses.high
|
||||
proc pointEvaluation*(c: Computation) =
|
||||
# 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 |
|
||||
|
||||
template input: untyped =
|
||||
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.} =
|
||||
for i in 0..18:
|
||||
if computation.msg.codeAddress[i] != 0: return
|
||||
|
||||
let lb = computation.msg.codeAddress[19]
|
||||
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
|
||||
if lb in PrecompileAddresses.low.byte .. maxPrecompileAddr.byte:
|
||||
if validPrecompileAddr(lb, fork):
|
||||
result = true
|
||||
let precompile = PrecompileAddresses(lb)
|
||||
#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 paPairing: bn256ecPairing(computation, fork)
|
||||
of paBlake2bf: blake2bf(computation)
|
||||
of paPointEvaluation: pointEvaluation(computation)
|
||||
else: discard
|
||||
# EIP 2537: disabled
|
||||
# reason: not included in berlin
|
||||
# of paBlsG1Add: blsG1Add(computation)
|
||||
|
|
|
@ -23,7 +23,11 @@ import
|
|||
|
||||
{.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
|
||||
## is going to be executed
|
||||
vmState.txOrigin = origin
|
||||
|
@ -34,6 +38,7 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
|
|||
else:
|
||||
vmState.determineFork
|
||||
vmState.gasCosts = vmState.fork.forkToSchedule
|
||||
vmState.txVersionedHashes = @versionedHashes
|
||||
|
||||
# FIXME-awkwardFactoring: the factoring out of the pre and
|
||||
# post parts feels awkward to me, but for now I'd really like
|
||||
|
|
|
@ -52,6 +52,7 @@ type
|
|||
cumulativeGasUsed*: GasInt
|
||||
txOrigin* : EthAddress
|
||||
txGasPrice* : GasInt
|
||||
txVersionedHashes*: VersionedHashes
|
||||
gasCosts* : GasCosts
|
||||
fork* : EVMFork
|
||||
minerAddress* : EthAddress
|
||||
|
|
|
@ -569,8 +569,7 @@ const logProcs = {
|
|||
proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
||||
let
|
||||
tx = TxNode(parent)
|
||||
encodedTx = rlp.encode(tx.tx)
|
||||
txHash = keccakHash(encodedTx)
|
||||
txHash = rlpHash(tx.tx) # beware EIP-4844
|
||||
resp(txHash)
|
||||
|
||||
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)
|
||||
try:
|
||||
let data = hexToSeqByte(params[0].val.stringVal)
|
||||
let _ = decodeTx(data) # we want to know if it is a valid tx blob
|
||||
let txHash = keccakHash(data)
|
||||
let tx = decodeTx(data) # we want to know if it is a valid tx blob
|
||||
let txHash = rlpHash(tx) # beware EIP-4844
|
||||
resp(txHash)
|
||||
except CatchableError as em:
|
||||
return err("failed to process raw transaction: " & em.msg)
|
||||
|
|
|
@ -258,10 +258,9 @@ proc setupEthRpc*(
|
|||
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1)
|
||||
eip155 = com.isEIP155(com.syncCurrent)
|
||||
signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155)
|
||||
rlpTx = rlp.encode(signedTx)
|
||||
|
||||
txPool.add(signedTx)
|
||||
result = keccakHash(rlpTx).ethHashStr
|
||||
result = rlpHash(signedTx).ethHashStr
|
||||
|
||||
server.rpc("eth_sendRawTransaction") do(data: HexDataStr) -> EthHashStr:
|
||||
## Creates new message call transaction or a contract creation for signed transactions.
|
||||
|
@ -274,7 +273,7 @@ proc setupEthRpc*(
|
|||
signedTx = decodeTx(txBytes)
|
||||
|
||||
txPool.add(signedTx)
|
||||
result = keccakHash(txBytes).ethHashStr
|
||||
result = rlpHash(signedTx).ethHashStr
|
||||
|
||||
server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr:
|
||||
## 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 validTxs = newSeqOfCap[Transaction](txHashes.len)
|
||||
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
|
||||
validTxs.add txs[i]
|
||||
|
||||
|
|
|
@ -180,6 +180,7 @@ proc validateDifficulty(ctx: LegacySyncRef,
|
|||
return false
|
||||
|
||||
proc validateHeader(ctx: LegacySyncRef, header: BlockHeader,
|
||||
txs: openArray[Transaction],
|
||||
height = none(BlockNumber)): bool
|
||||
{.raises: [CatchableError].} =
|
||||
if header.parentHash == GENESIS_PARENT_HASH:
|
||||
|
@ -242,7 +243,7 @@ proc validateHeader(ctx: LegacySyncRef, header: BlockHeader,
|
|||
msg=res.error
|
||||
return false
|
||||
|
||||
res = com.validateEip4844Header(header)
|
||||
res = com.validateEip4844Header(header, parentHeader, txs)
|
||||
if res.isErr:
|
||||
trace "validate eip4844 error",
|
||||
msg=res.error
|
||||
|
@ -1052,7 +1053,7 @@ proc handleNewBlock(ctx: LegacySyncRef,
|
|||
number=blk.header.blockNumber
|
||||
return
|
||||
|
||||
if not ctx.validateHeader(blk.header):
|
||||
if not ctx.validateHeader(blk.header, blk.txs):
|
||||
error "invalid header from peer",
|
||||
peer, hash=short(blk.header.blockHash)
|
||||
return
|
||||
|
|
|
@ -458,10 +458,7 @@ p2pProtocol les(version = lesVersion,
|
|||
|
||||
var results: seq[TransactionStatusMsg]
|
||||
for t in transactions:
|
||||
let hash = t.rlpHash # TODO: this is not optimal, we can compute
|
||||
# the hash from the request bytes.
|
||||
# The RLP module can offer a helper Hashed[T]
|
||||
# to make this easy.
|
||||
let hash = t.rlpHash
|
||||
var s = ctx.getTransactionStatus(hash)
|
||||
if s.status == TransactionStatus.Unknown:
|
||||
ctx.addTransactions([t])
|
||||
|
|
|
@ -117,7 +117,7 @@ proc validateTxLegacy(tx: Transaction, fork: EVMFork) =
|
|||
isValid = isValid and tx.S < SECPK1_N div 2
|
||||
|
||||
if not isValid:
|
||||
raise newException(ValidationError, "Invalid transaction")
|
||||
raise newException(ValidationError, "Invalid legacy transaction")
|
||||
|
||||
proc validateTxEip2930(tx: Transaction) =
|
||||
var isValid = tx.V in {0'i64, 1'i64}
|
||||
|
@ -126,7 +126,27 @@ proc validateTxEip2930(tx: Transaction) =
|
|||
isValid = isValid and tx.R < SECPK1_N
|
||||
|
||||
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) =
|
||||
# parameters pass validation rules
|
||||
|
@ -144,7 +164,9 @@ proc validate*(tx: Transaction, fork: EVMFork) =
|
|||
case tx.txType
|
||||
of TxLegacy:
|
||||
validateTxLegacy(tx, fork)
|
||||
else:
|
||||
of TxEip4844:
|
||||
validateTxEip4844(tx)
|
||||
of TxEip2930, TxEip1559:
|
||||
validateTxEip2930(tx)
|
||||
|
||||
proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction =
|
||||
|
|
|
@ -16,6 +16,7 @@ import
|
|||
".."/[db/accounts_cache],
|
||||
../evm/async/operations,
|
||||
../common/evmforks,
|
||||
../core/eip4844,
|
||||
./host_types
|
||||
|
||||
when defined(evmc_enabled):
|
||||
|
@ -36,6 +37,7 @@ type
|
|||
value*: HostValue # Value sent from sender to recipient.
|
||||
input*: seq[byte] # Input data.
|
||||
accessList*: AccessList # EIP-2930 (Berlin) tx access list.
|
||||
versionedHashes*: VersionedHashes # EIP-4844 (Cancun) blob versioned hashes
|
||||
noIntrinsic*: bool # Don't charge intrinsic gas.
|
||||
noAccessList*: bool # Don't initialise EIP-2929 access list.
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
# of gas needed to send this transaction (but that is not actually used
|
||||
# for computation).
|
||||
let fork = vmState.fork
|
||||
var gas = gasFees[fork][GasTransaction]
|
||||
|
||||
# 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:
|
||||
gas += ACCESS_LIST_ADDRESS_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
|
||||
|
||||
proc initialAccessListEIP2929(call: CallParams) =
|
||||
|
@ -109,8 +118,8 @@ proc initialAccessListEIP2929(call: CallParams) =
|
|||
if vmState.fork >= FkShanghai:
|
||||
db.accessList(vmState.coinbase)
|
||||
|
||||
# TODO: Check this only adds the correct subset of precompiles.
|
||||
for c in activePrecompiles():
|
||||
# Adds the correct subset of precompiles.
|
||||
for c in activePrecompiles(vmState.fork):
|
||||
db.accessList(c)
|
||||
|
||||
# EIP2930 optional access list.
|
||||
|
@ -124,12 +133,13 @@ proc setupHost(call: CallParams): TransactionHost =
|
|||
vmState.setupTxContext(
|
||||
origin = call.origin.get(call.sender),
|
||||
gasPrice = call.gasPrice,
|
||||
versionedHashes = call.versionedHashes,
|
||||
forkOverride = call.forkOverride
|
||||
)
|
||||
|
||||
var intrinsicGas: GasInt = 0
|
||||
if not call.noIntrinsic:
|
||||
intrinsicGas = intrinsicGas(call, vmState.fork)
|
||||
intrinsicGas = intrinsicGas(call, vmState)
|
||||
|
||||
let host = TransactionHost(
|
||||
vmState: vmState,
|
||||
|
|
|
@ -29,6 +29,7 @@ type
|
|||
value* : Option[UInt256]
|
||||
data* : seq[byte]
|
||||
accessList* : AccessList
|
||||
versionedHashes*: VersionedHashes
|
||||
|
||||
proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
|
||||
globalGasCap: GasInt, baseFee: Option[UInt256],
|
||||
|
@ -73,7 +74,8 @@ proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
|
|||
gasPrice: gasPrice,
|
||||
value: cd.value.get(0.u256),
|
||||
input: cd.data,
|
||||
accessList: cd.accessList
|
||||
accessList: cd.accessList,
|
||||
versionedHashes:cd.versionedHashes
|
||||
)
|
||||
|
||||
proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, com: CommonRef): CallResult
|
||||
|
@ -152,7 +154,7 @@ proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, com: CommonRef, gasCa
|
|||
hi = gasCap
|
||||
|
||||
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
|
||||
proc executable(gasLimit: GasInt): bool
|
||||
|
@ -201,6 +203,9 @@ proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState,
|
|||
if tx.txType > TxLegacy:
|
||||
shallowCopy(result.accessList, tx.accessList)
|
||||
|
||||
if tx.txType >= TxEip4844:
|
||||
result.versionedHashes = tx.versionedHashes
|
||||
|
||||
proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams =
|
||||
result = CallParams(
|
||||
vmState: vmState,
|
||||
|
@ -219,6 +224,9 @@ proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState
|
|||
if tx.txType > TxLegacy:
|
||||
shallowCopy(result.accessList, tx.accessList)
|
||||
|
||||
if tx.txType >= TxEip4844:
|
||||
result.versionedHashes = tx.versionedHashes
|
||||
|
||||
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
let call = callParamsForTx(tx, sender, vmState, fork)
|
||||
|
|
|
@ -119,7 +119,10 @@ proc ecRecover*(tx: var Transaction): EcAddrResult =
|
|||
let txSig = tx.vrsSerialised
|
||||
if txSig.isErr:
|
||||
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 =
|
||||
## Variant of `ecRecover()` for call-by-value header.
|
||||
|
|
|
@ -42,6 +42,10 @@ type
|
|||
## database lookup failed
|
||||
"not found"
|
||||
|
||||
errTxEncError = ##\
|
||||
## TRansaction encoding error
|
||||
"tx enc error"
|
||||
|
||||
UtilsError* = ##\
|
||||
## Error message, tinned component + explanatory text (if any)
|
||||
(UtilsErrorType,string)
|
||||
|
|
|
@ -48,4 +48,5 @@ cliBuilder:
|
|||
./test_configuration,
|
||||
./test_keyed_queue_rlp,
|
||||
./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 =
|
||||
let privateKey = PrivateKey.fromHex("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d")[]
|
||||
let unsignedTx = Transaction(
|
||||
txType: TxLegacy,
|
||||
txType: TxEIP4844,
|
||||
nonce: 0,
|
||||
gasPrice: 1.GasInt,
|
||||
gasLimit: 500_000_000.GasInt,
|
||||
to: codeAddress.some,
|
||||
value: 500.u256,
|
||||
payload: payload
|
||||
payload: payload,
|
||||
versionedHashes: @[EMPTY_UNCLE_HASH, EMPTY_SHA3]
|
||||
)
|
||||
signTransaction(unsignedTx, privateKey, chainId, false)
|
||||
|
||||
|
|
|
@ -412,6 +412,7 @@ proc blockchainJsonMain*(debugMode = false) =
|
|||
testFixture(n, testStatusIMPL, debugMode = true, config.trace)
|
||||
|
||||
when isMainModule:
|
||||
import std/times
|
||||
var message: string
|
||||
|
||||
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)
|
||||
|
||||
when isMainModule:
|
||||
import std/times
|
||||
var message: string
|
||||
|
||||
let start = getTime()
|
||||
|
|
|
@ -374,5 +374,79 @@ proc opEnvMain*() =
|
|||
"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
|
||||
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:
|
||||
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 =
|
||||
parseHexOrInt[uint64](n[field].getStr()).ChainId
|
||||
|
||||
proc fromJson(T: type Hash256, n: JsonNode, field: string): Hash256 =
|
||||
var num = n[field].getStr()
|
||||
proc fromJson(T: type Hash256, n: JsonNode): Hash256 =
|
||||
var num = n.getStr()
|
||||
num.removePrefix("0x")
|
||||
if num.len < 64:
|
||||
num = repeat('0', 64 - num.len) & num
|
||||
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 =
|
||||
fromUnix(parseHexOrInt[int64](n[field].getStr()))
|
||||
|
||||
|
@ -99,6 +102,11 @@ proc fromJson(T: type Withdrawal, n: JsonNode): Withdrawal =
|
|||
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) =
|
||||
tx.gasLimit = x
|
||||
|
||||
|
@ -120,6 +128,9 @@ template `maxPriorityFeePerGas=`(tx: var Transaction, x: GasInt) =
|
|||
template `maxFeePerGas=`(tx: var Transaction, x: GasInt) =
|
||||
tx.maxFee = x
|
||||
|
||||
template `blobVersionedHashes=`(tx: var Transaction, x: VersionedHashes) =
|
||||
tx.versionedHashes = x
|
||||
|
||||
template required(o: untyped, T: type, oField: untyped) =
|
||||
const fName = astToStr(oField)
|
||||
if not n.hasKey(fName):
|
||||
|
@ -168,6 +179,8 @@ proc parseEnv*(ctx: var TransContext, n: JsonNode) =
|
|||
optional(ctx.env, UInt256, parentBaseFee)
|
||||
optional(ctx.env, GasInt, parentGasUsed)
|
||||
optional(ctx.env, GasInt, parentGasLimit)
|
||||
optional(ctx.env, uint64, parentDataGasUsed)
|
||||
optional(ctx.env, uint64, parentExcessDataGas)
|
||||
|
||||
if n.hasKey("blockHashes"):
|
||||
let w = n["blockHashes"]
|
||||
|
@ -216,6 +229,13 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
|
|||
required(tx, GasInt, maxPriorityFeePerGas)
|
||||
required(tx, GasInt, maxFeePerGas)
|
||||
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
|
||||
if n.hasKey("protected"):
|
||||
|
@ -231,7 +251,7 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
|
|||
proc parseTxLegacy(item: var Rlp): Result[Transaction, string] =
|
||||
try:
|
||||
var tx: Transaction
|
||||
item.readTxLegacy(tx)
|
||||
item.decodeTxLegacy(tx)
|
||||
return ok(tx)
|
||||
except RlpError as x:
|
||||
return err(x.msg)
|
||||
|
@ -240,7 +260,7 @@ proc parseTxTyped(item: var Rlp): Result[Transaction, string] =
|
|||
try:
|
||||
var tx: Transaction
|
||||
var rr = rlpFromBytes(item.read(Blob))
|
||||
rr.readTxTyped(tx)
|
||||
rr.decodeTxTyped(tx)
|
||||
return ok(tx)
|
||||
except RlpError as x:
|
||||
return err(x.msg)
|
||||
|
@ -420,3 +440,7 @@ proc `@@`*(x: ExecutionResult): JsonNode =
|
|||
result["currentBaseFee"] = @@(x.currentBaseFee)
|
||||
if x.withdrawalsRoot.isSome:
|
||||
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),
|
||||
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() =
|
||||
|
|
|
@ -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/core/pow/difficulty,
|
||||
../../nimbus/core/dao,
|
||||
../../nimbus/core/executor/[process_transaction, executor_helpers]
|
||||
../../nimbus/core/executor/[process_transaction, executor_helpers],
|
||||
../../nimbus/core/eip4844
|
||||
|
||||
import stew/byteutils
|
||||
const
|
||||
|
@ -203,6 +204,7 @@ proc exec(ctx: var TransContext,
|
|||
vmState.receipts = newSeqOfCap[Receipt](txList.len)
|
||||
vmState.cumulativeGasUsed = 0
|
||||
|
||||
var dataGasUsed = 0'u64
|
||||
for txIndex, txRes in txList:
|
||||
if txRes.isErr:
|
||||
rejected.add RejectedTx(
|
||||
|
@ -239,6 +241,7 @@ proc exec(ctx: var TransContext,
|
|||
rec, tx, sender, txIndex, gasUsed
|
||||
)
|
||||
includedTx.add tx
|
||||
dataGasUsed += tx.getTotalDataGas
|
||||
|
||||
# Add mining reward? (-1 means rewards are disabled)
|
||||
if stateReward.isSome and stateReward.get >= 0:
|
||||
|
@ -286,6 +289,10 @@ proc exec(ctx: var TransContext,
|
|||
withdrawalsRoot: header.withdrawalsRoot
|
||||
)
|
||||
|
||||
if fork >= FkCancun:
|
||||
result.result.dataGasUsed = some dataGasUsed
|
||||
result.result.excessDataGas = some calcExcessDataGas(vmState.parent)
|
||||
|
||||
template wrapException(body: untyped) =
|
||||
when wrapExceptionEnabled:
|
||||
try:
|
||||
|
@ -403,7 +410,9 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
|
|||
timestamp: ctx.env.parentTimestamp,
|
||||
difficulty: ctx.env.parentDifficulty.get(0.u256),
|
||||
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
|
||||
|
@ -419,6 +428,17 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
|
|||
if com.isShanghaiOrLater(ctx.env.currentTimestamp) and ctx.env.withdrawals.isNone:
|
||||
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 ctx.env.currentRandom.isNone:
|
||||
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
|
||||
# according to those terms.
|
||||
|
||||
import
|
||||
eth/common
|
||||
include eth/common/eth_types_rlp
|
||||
|
||||
from stew/objects
|
||||
import checkedEnumAssign
|
||||
# reexport private procs
|
||||
|
||||
# these procs are duplicates of nim-eth/eth_types_rlp.nim
|
||||
# both `readTxLegacy` and `readTxTyped` are exported here
|
||||
template decodeTxLegacy*(rlp: var Rlp, tx: var Transaction) =
|
||||
readTxLegacy(rlp, tx)
|
||||
|
||||
template read[T](rlp: var Rlp, val: var T)=
|
||||
val = rlp.read(type val)
|
||||
|
||||
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)
|
||||
template decodeTxTyped*(rlp: var Rlp, tx: var Transaction) =
|
||||
readTxTyped(rlp, tx)
|
||||
|
|
|
@ -44,6 +44,8 @@ type
|
|||
parentGasUsed*: Option[GasInt]
|
||||
parentGasLimit*: Option[GasInt]
|
||||
withdrawals*: Option[seq[Withdrawal]]
|
||||
parentDataGasUsed*: Option[uint64]
|
||||
parentExcessDataGas*: Option[uint64]
|
||||
|
||||
TxsType* = enum
|
||||
TxsNone
|
||||
|
@ -92,6 +94,8 @@ type
|
|||
gasUsed*: GasInt
|
||||
currentBaseFee*: Option[UInt256]
|
||||
withdrawalsRoot*: Option[Hash256]
|
||||
dataGasUsed*: Option[uint64]
|
||||
excessDataGas*: Option[uint64]
|
||||
|
||||
const
|
||||
ErrorEVM* = 2.T8NExitCode
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 6dacb2ca5ce4e5528924f56c87620f38e1d0c0d4
|
||||
Subproject commit 6b8a7b009eb94bfdac1410f243865984bdd7c4f2
|
Loading…
Reference in New Issue