devnet-5: Implement EIP-7691: Blob throughput increase (#2957)

This commit is contained in:
andri lim 2024-12-20 16:12:16 +07:00 committed by GitHub
parent 1dff892995
commit 80f8b3c2b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 107 additions and 51 deletions

View File

@ -39,19 +39,19 @@ const
DATAHASH_START_ADDRESS* = toAddress(0x20000.u256)
DATAHASH_ADDRESS_COUNT* = 1000
func getMinExcessBlobGasForBlobGasPrice(data_gas_price: uint64): uint64 =
func getMinExcessBlobGasForBlobGasPrice(data_gas_price: uint64, electra: bool): uint64 =
var
current_excess_data_gas = 0'u64
current_data_gas_price = 1'u64
while current_data_gas_price < data_gas_price:
current_excess_data_gas += GAS_PER_BLOB.uint64
current_data_gas_price = getBlobBaseFee(current_excess_data_gas).truncate(uint64)
current_data_gas_price = getBlobBaseFee(current_excess_data_gas, electra).truncate(uint64)
return current_excess_data_gas
func getMinExcessBlobsForBlobGasPrice*(data_gas_price: uint64): uint64 =
return getMinExcessBlobGasForBlobGasPrice(data_gas_price) div GAS_PER_BLOB.uint64
func getMinExcessBlobsForBlobGasPrice*(data_gas_price: uint64, electra: bool): uint64 =
return getMinExcessBlobGasForBlobGasPrice(data_gas_price, electra) div GAS_PER_BLOB.uint64
proc addBlobTransaction*(pool: TestBlobTxPool, tx: PooledTransaction) =
let txHash = rlpHash(tx)

View File

@ -73,7 +73,7 @@ proc verifyPayload(step: NewPayloads,
excessBlobGas: Opt.some(parentExcessBlobGas),
blobGasUsed: Opt.some(parentBlobGasUsed)
)
expectedExcessBlobGas = calcExcessBlobGas(parent)
expectedExcessBlobGas = calcExcessBlobGas(parent, com.isPragueOrLater(payload.timestamp.EthTime))
if com.isCancunOrLater(payload.timestamp.EthTime):
if payload.excessBlobGas.isNone:
@ -96,7 +96,7 @@ proc verifyPayload(step: NewPayloads,
var
totalBlobCount = 0
expectedBlobGasPrice = getBlobBaseFee(expectedExcessBlobGas)
expectedBlobGasPrice = getBlobBaseFee(expectedExcessBlobGas, com.isPragueOrLater(payload.timestamp.EthTime))
for tx in blobTxsInPayload:
let blobCount = tx.versionedHashes.len

View File

@ -41,7 +41,7 @@ import
# Precalculate the first data gas cost increase
const
DATA_GAS_COST_INCREMENT_EXCEED_BLOBS = getMinExcessBlobsForBlobGasPrice(2).int
DATA_GAS_COST_INCREMENT_EXCEED_BLOBS = getMinExcessBlobsForBlobGasPrice(2, false).int
TARGET_BLOBS_PER_BLOCK = int(TARGET_BLOB_GAS_PER_BLOCK div GAS_PER_BLOB)
proc getGenesis(param: NetworkParams) =

View File

@ -92,10 +92,15 @@ const
GAS_PER_BLOB* = (1 shl 17).uint64 # 2^17
TARGET_BLOB_GAS_PER_BLOCK* = 393216
MIN_BLOB_GASPRICE* = 1'u64
BLOB_GASPRICE_UPDATE_FRACTION* = 3338477'u64
BLOB_BASE_FEE_UPDATE_FRACTION* = 3338477
MAX_BLOB_GAS_PER_BLOCK* = 786432
MAX_BLOBS_PER_BLOCK* = int(MAX_BLOB_GAS_PER_BLOCK div GAS_PER_BLOB)
MAX_BLOB_GAS_PER_BLOCK_ELECTRA* = 1179648
TARGET_BLOB_GAS_PER_BLOCK_ELECTRA* = 786432
BLOB_BASE_FEE_UPDATE_FRACTION_ELECTRA* = 5007716
MAX_BLOBS_PER_BLOCK_ELECTRA* = int(MAX_BLOB_GAS_PER_BLOCK_ELECTRA div GAS_PER_BLOB)
# EIP-4788 addresses
# BEACON_ROOTS_ADDRESS is the address where historical beacon roots are stored as per EIP-4788
BEACON_ROOTS_ADDRESS* = address"0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"

View File

@ -15,6 +15,7 @@ import
kzg4844/kzg,
results,
stint,
./eip7691,
../constants,
../common/common
@ -81,15 +82,16 @@ proc pointEvaluation*(input: openArray[byte]): Result[void, string] =
ok()
# calcExcessBlobGas implements calc_excess_data_gas from EIP-4844
proc calcExcessBlobGas*(parent: Header): uint64 =
proc calcExcessBlobGas*(parent: Header, electra: bool): uint64 =
let
excessBlobGas = parent.excessBlobGas.get(0'u64)
blobGasUsed = parent.blobGasUsed.get(0'u64)
targetBlobGasPerBlock = getTargetBlobGasPerBlock(electra)
if excessBlobGas + blobGasUsed < TARGET_BLOB_GAS_PER_BLOCK:
if excessBlobGas + blobGasUsed < targetBlobGasPerBlock:
0'u64
else:
excessBlobGas + blobGasUsed - TARGET_BLOB_GAS_PER_BLOCK
excessBlobGas + blobGasUsed - targetBlobGasPerBlock
# fakeExponential approximates factor * e ** (num / denom) using a taylor expansion
# as described in the EIP-4844 spec.
@ -113,17 +115,19 @@ proc getTotalBlobGas*(versionedHashesLen: int): uint64 =
GAS_PER_BLOB * versionedHashesLen.uint64
# getBlobBaseFee implements get_data_gas_price from EIP-4844
func getBlobBaseFee*(excessBlobGas: uint64): UInt256 =
func getBlobBaseFee*(excessBlobGas: uint64, electra: bool): UInt256 =
let blobBaseFeeUpdateFraction = getBlobBaseFeeUpdateFraction(electra).u256
fakeExponential(
MIN_BLOB_GASPRICE.u256,
excessBlobGas.u256,
BLOB_GASPRICE_UPDATE_FRACTION.u256
blobBaseFeeUpdateFraction
)
proc calcDataFee*(versionedHashesLen: int,
excessBlobGas: uint64): UInt256 =
excessBlobGas: uint64,
electra: bool): UInt256 =
getTotalBlobGas(versionedHashesLen).u256 *
getBlobBaseFee(excessBlobGas)
getBlobBaseFee(excessBlobGas, electra)
func blobGasUsed(txs: openArray[Transaction]): uint64 =
for tx in txs:
@ -150,13 +154,15 @@ func validateEip4844Header*(
return err("expect EIP-4844 excessBlobGas in block header")
let
electra = com.isPragueOrLater(header.timestamp)
headerBlobGasUsed = header.blobGasUsed.get()
blobGasUsed = blobGasUsed(txs)
headerExcessBlobGas = header.excessBlobGas.get
excessBlobGas = calcExcessBlobGas(parentHeader)
excessBlobGas = calcExcessBlobGas(parentHeader, electra)
maxBlobGasPerBlock = getMaxBlobGasPerBlock(electra)
if blobGasUsed > MAX_BLOB_GAS_PER_BLOCK:
return err("blobGasUsed " & $blobGasUsed & " exceeds maximum allowance " & $MAX_BLOB_GAS_PER_BLOCK)
if blobGasUsed > maxBlobGasPerBlock:
return err("blobGasUsed " & $blobGasUsed & " exceeds maximum allowance " & $maxBlobGasPerBlock)
if headerBlobGasUsed != blobGasUsed:
return err("calculated blobGas not equal header.blobGasUsed")

30
nimbus/core/eip7691.nim Normal file
View File

@ -0,0 +1,30 @@
# Nimbus
# Copyright (c) 2024 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.
{.push raises: [].}
import
../constants
func getMaxBlobGasPerBlock*(electra: bool): uint64 =
if electra: MAX_BLOB_GAS_PER_BLOCK_ELECTRA.uint64
else: MAX_BLOB_GAS_PER_BLOCK.uint64
func getTargetBlobGasPerBlock*(electra: bool): uint64 =
if electra: TARGET_BLOB_GAS_PER_BLOCK_ELECTRA.uint64
else: TARGET_BLOB_GAS_PER_BLOCK.uint64
func getBlobBaseFeeUpdateFraction*(electra: bool): uint64 =
if electra: BLOB_BASE_FEE_UPDATE_FRACTION_ELECTRA.uint64
else: BLOB_BASE_FEE_UPDATE_FRACTION.uint64
func getMaxBlobsPerBlock*(electra: bool): uint64 =
if electra: MAX_BLOBS_PER_BLOCK_ELECTRA.uint64
else: MAX_BLOBS_PER_BLOCK.uint64

View File

@ -22,6 +22,7 @@ import
../../evm/types,
../../constants,
../eip4844,
../eip7691,
../validate
# ------------------------------------------------------------------------------
@ -88,10 +89,12 @@ proc processTransactionImpl(
vmState.gasPool -= tx.gasLimit
# blobGasUsed will be added to vmState.blobGasUsed if the tx is ok.
let blobGasUsed = tx.getTotalBlobGas
if vmState.blobGasUsed + blobGasUsed > MAX_BLOB_GAS_PER_BLOCK:
let
blobGasUsed = tx.getTotalBlobGas
maxBlobGasPerBlock = getMaxBlobGasPerBlock(vmState.fork >= FkPrague)
if vmState.blobGasUsed + blobGasUsed > maxBlobGasPerBlock:
return err("blobGasUsed " & $blobGasUsed &
" exceeds maximum allowance " & $MAX_BLOB_GAS_PER_BLOCK)
" exceeds maximum allowance " & $maxBlobGasPerBlock)
# Actually, the eip-1559 reference does not mention an early exit.
#

View File

@ -123,7 +123,7 @@ proc setupVMState(com: CommonRef; parent: Header): BaseVMState =
prevRandao : pos.prevRandao,
difficulty : UInt256.zero(),
coinbase : pos.feeRecipient,
excessBlobGas: calcExcessBlobGas(parent),
excessBlobGas: calcExcessBlobGas(parent, com.isPragueOrLater(pos.timestamp)),
parentHash : parent.blockHash,
)

View File

@ -28,6 +28,7 @@ import
../../evm/types,
../eip4844,
../eip6110,
../eip7691,
"."/[tx_desc, tx_item, tx_tabs, tx_tabs/tx_status, tx_info],
tx_tasks/[tx_bucket]
@ -81,7 +82,7 @@ proc classifyValidatePacked(vmState: BaseVMState; item: TxItemRef): bool =
fork = vmState.fork
gasLimit = vmState.blockCtx.gasLimit
tx = item.tx.eip1559TxNormalization(baseFee.truncate(GasInt))
excessBlobGas = calcExcessBlobGas(vmState.parent)
excessBlobGas = calcExcessBlobGas(vmState.parent, vmState.fork >= FkPrague)
roDB.validateTransaction(
tx, item.sender, gasLimit, baseFee, excessBlobGas, fork).isOk
@ -210,12 +211,15 @@ proc vmExecGrabItem(pst: var TxPacker; item: TxItemRef): GrabResult
return ContinueWithNextAccount
# EIP-4844
if pst.numBlobPerBlock + item.tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK:
let maxBlobsPerBlob = getMaxBlobsPerBlock(vmState.fork >= FkPrague)
if (pst.numBlobPerBlock + item.tx.versionedHashes.len).uint64 > maxBlobsPerBlob:
return ContinueWithNextAccount
pst.numBlobPerBlock += item.tx.versionedHashes.len
let blobGasUsed = item.tx.getTotalBlobGas
if vmState.blobGasUsed + blobGasUsed > MAX_BLOB_GAS_PER_BLOCK:
let
blobGasUsed = item.tx.getTotalBlobGas
maxBlobGasPerBlock = getMaxBlobGasPerBlock(vmState.fork >= FkPrague)
if vmState.blobGasUsed + blobGasUsed > maxBlobGasPerBlock:
return ContinueWithNextAccount
vmState.blobGasUsed += blobGasUsed

View File

@ -21,7 +21,9 @@ import
chronicles,
eth/common/[transactions, keys]
import ../../../transaction
import
../../../transaction,
../../../common/evmforks
{.push raises: [].}
@ -112,7 +114,7 @@ proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool =
if item.tx.txType == TxEip4844:
let
excessBlobGas = xp.excessBlobGas
blobGasPrice = getBlobBaseFee(excessBlobGas)
blobGasPrice = getBlobBaseFee(excessBlobGas, xp.nextFork >= FkPrague)
if item.tx.maxFeePerBlobGas < blobGasPrice:
debug "invalid tx: maxFeePerBlobGas smaller than blobGasPrice",
maxFeePerBlobGas=item.tx.maxFeePerBlobGas,

View File

@ -18,7 +18,7 @@ import
../transaction/call_types,
../transaction,
../utils/utils,
"."/[dao, eip4844, eip7702, gaslimit, withdrawals],
"."/[dao, eip4844, eip7702, eip7691, gaslimit, withdrawals],
./pow/[difficulty, header],
stew/objects,
results
@ -260,8 +260,9 @@ proc validateTxBasic*(
if tx.versionedHashes.len == 0:
return err("invalid tx: there must be at least one blob")
if tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK:
return err(&"invalid tx: versioned hashes len exceeds MAX_BLOBS_PER_BLOCK={MAX_BLOBS_PER_BLOCK}. get={tx.versionedHashes.len}")
let maxBlobsPerBlob = getMaxBlobsPerBlock(fork >= FkPrague)
if tx.versionedHashes.len.uint64 > maxBlobsPerBlob:
return err(&"invalid tx: versioned hashes len exceeds MAX_BLOBS_PER_BLOCK={maxBlobsPerBlob}. get={tx.versionedHashes.len}")
for i, bv in tx.versionedHashes:
if bv.data[0] != VERSIONED_HASH_VERSION_KZG:
@ -348,7 +349,7 @@ proc validateTransaction*(
if tx.txType == TxEip4844:
# ensure that the user was willing to at least pay the current data gasprice
let blobGasPrice = getBlobBaseFee(excessBlobGas)
let blobGasPrice = getBlobBaseFee(excessBlobGas, fork >= FkPrague)
if tx.maxFeePerBlobGas < blobGasPrice:
return err("invalid tx: maxFeePerBlobGas smaller than blobGasPrice. " &
&"maxFeePerBlobGas={tx.maxFeePerBlobGas}, blobGasPrice={blobGasPrice}")

View File

@ -16,6 +16,7 @@ import
../transaction,
../common/common,
../core/eip4844,
../core/eip7691,
../core/chain/forked_chain
from ./rpc_types import
@ -98,13 +99,16 @@ func calcBaseFee(com: CommonRef, bc: BlockContent): UInt256 =
# the block field filled in, retrieves the block from the backend if not present yet and
# fills in the rest of the fields.
proc processBlock(oracle: Oracle, bc: BlockContent, percentiles: openArray[float64]): ProcessedFees =
let
electra = com.isPragueOrLater(bc.header.timestamp)
maxBlobGasPerBlock = getMaxBlobGasPerBlock(electra)
result = ProcessedFees(
baseFee: bc.header.baseFeePerGas.get(0.u256),
blobBaseFee: getBlobBaseFee(bc.header.excessBlobGas.get(0'u64)),
blobBaseFee: getBlobBaseFee(bc.header.excessBlobGas.get(0'u64), electra),
nextBaseFee: calcBaseFee(oracle.com, bc),
nextBlobBaseFee: getBlobBaseFee(calcExcessBlobGas(bc.header)),
nextBlobBaseFee: getBlobBaseFee(calcExcessBlobGas(bc.header, electra), electra),
gasUsedRatio: float64(bc.header.gasUsed) / float64(bc.header.gasLimit),
blobGasUsedRatio: float64(bc.header.blobGasUsed.get(0'u64)) / float64(MAX_BLOB_GAS_PER_BLOCK)
blobGasUsedRatio: float64(bc.header.blobGasUsed.get(0'u64)) / float64(maxBlobGasPerBlock)
)
if percentiles.len == 0:

View File

@ -56,8 +56,8 @@ proc calculateMedianGasPrice*(chain: ForkedChainRef): GasInt =
result = max(result, minGasPrice)
proc unsignedTx*(tx: TransactionArgs,
chain: ForkedChainRef,
defaultNonce: AccountNonce,
chain: ForkedChainRef,
defaultNonce: AccountNonce,
chainId: ChainId): Transaction =
var res: Transaction
@ -180,7 +180,7 @@ proc populateBlockObject*(blockHash: Hash32,
result.requestsHash = header.requestsHash
proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
txIndex: uint64, header: Header): ReceiptObject =
txIndex: uint64, header: Header, electra: bool): ReceiptObject =
let sender = tx.recoverSender()
var res = ReceiptObject()
res.transactionHash = tx.rlpHash
@ -236,7 +236,7 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
if tx.txType == TxEip4844:
res.blobGasUsed = Opt.some(Quantity(tx.versionedHashes.len.uint64 * GAS_PER_BLOB.uint64))
res.blobGasPrice = Opt.some(getBlobBaseFee(header.excessBlobGas.get(0'u64)))
res.blobGasPrice = Opt.some(getBlobBaseFee(header.excessBlobGas.get(0'u64), electra))
return res

View File

@ -353,7 +353,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, ctx: EthContext) =
let gasUsed = receipt.cumulativeGasUsed - prevGasUsed
prevGasUsed = receipt.cumulativeGasUsed
if idx == txDetails.index:
return populateReceipt(receipt, gasUsed, tx, txDetails.index, header)
return populateReceipt(receipt, gasUsed, tx, txDetails.index, header, api.com.isPragueOrLater(header.timestamp))
idx.inc
else:
# Receipt in memory
@ -366,7 +366,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, ctx: EthContext) =
if txid == idx:
return populateReceipt(
receipt, gasUsed, blkdesc.blk.transactions[txid], txid, blkdesc.blk.header
receipt, gasUsed, blkdesc.blk.transactions[txid], txid, blkdesc.blk.header,
api.com.isPragueOrLater(blkdesc.blk.header.timestamp)
)
idx.inc
@ -640,7 +641,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, ctx: EthContext) =
for receipt in receipts:
let gasUsed = receipt.cumulativeGasUsed - prevGasUsed
prevGasUsed = receipt.cumulativeGasUsed
recs.add populateReceipt(receipt, gasUsed, txs[index], index, header)
recs.add populateReceipt(receipt, gasUsed, txs[index], index, header, api.com.isPragueOrLater(header.timestamp))
inc index
return Opt.some(recs)
except CatchableError:
@ -666,7 +667,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, ctx: EthContext) =
if header.excessBlobGas.isNone:
raise newException(ValueError, "excessBlobGas missing from latest header")
let blobBaseFee =
getBlobBaseFee(header.excessBlobGas.get) * header.blobGasUsed.get.u256
getBlobBaseFee(header.excessBlobGas.get, api.com.isPragueOrLater(header.timestamp)) * header.blobGasUsed.get.u256
if blobBaseFee > high(uint64).u256:
raise newException(ValueError, "blobBaseFee is bigger than uint64.max")
return w3Qty blobBaseFee.truncate(uint64)

View File

@ -129,19 +129,19 @@ proc preExecComputation(vmState: BaseVMState, call: CallParams): int64 =
gasRefund
proc setupHost(call: CallParams, keepStack: bool): TransactionHost =
proc setupHost(call: CallParams, keepStack: bool): TransactionHost =
let vmState = call.vmState
vmState.txCtx = TxContext(
origin : call.origin.get(call.sender),
gasPrice : call.gasPrice,
versionedHashes: call.versionedHashes,
blobBaseFee : getBlobBaseFee(vmState.blockCtx.excessBlobGas),
blobBaseFee : getBlobBaseFee(vmState.blockCtx.excessBlobGas, vmState.fork >= FkPrague),
)
# reset global gasRefund counter each time
# EVM called for a new transaction
vmState.gasRefunded = 0
let
intrinsicGas = if call.noIntrinsic: 0.GasInt
else: intrinsicGas(call, vmState.fork)
@ -168,7 +168,7 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost =
host.msg.input_size = 0
host.msg.input_data = nil
CodeBytesRef.init(call.input)
else:
else:
if call.input.len > 0:
host.msg.input_size = call.input.len.csize_t
# Must copy the data so the `host.msg.input_data` pointer
@ -177,7 +177,7 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost =
host.msg.input_data = host.input[0].addr
getCallCode(host.vmState, host.msg.code_address.fromEvmc)
cMsg = hostToComputationMessage(host.msg)
host.computation = newComputation(vmState, keepStack, cMsg, code)
host.code = code
@ -231,7 +231,7 @@ proc prepareToRunComputation(host: TransactionHost, call: CallParams) =
# EIP-4844
if fork >= FkCancun:
let blobFee = calcDataFee(call.versionedHashes.len,
vmState.blockCtx.excessBlobGas)
vmState.blockCtx.excessBlobGas, fork >= FkPrague)
db.subBalance(call.sender, blobFee)
proc calculateAndPossiblyRefundGas(host: TransactionHost, call: CallParams): GasInt =

View File

@ -348,7 +348,7 @@ proc exec(ctx: TransContext,
if ctx.env.currentExcessBlobGas.isSome:
excessBlobGas = ctx.env.currentExcessBlobGas
elif ctx.env.parentExcessBlobGas.isSome and ctx.env.parentBlobGasUsed.isSome:
excessBlobGas = Opt.some calcExcessBlobGas(vmState.parent)
excessBlobGas = Opt.some calcExcessBlobGas(vmState.parent, vmState.fork >= FkPrague)
if excessBlobGas.isSome:
result.result.blobGasUsed = Opt.some vmState.blobGasUsed
@ -525,7 +525,7 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) =
# If it is not explicitly defined, but we have the parent values, we try
# to calculate it ourselves.
if parent.excessBlobGas.isSome and parent.blobGasUsed.isSome:
ctx.env.currentExcessBlobGas = Opt.some calcExcessBlobGas(parent)
ctx.env.currentExcessBlobGas = Opt.some calcExcessBlobGas(parent, com.isPragueOrLater(ctx.env.currentTimestamp))
let header = envToHeader(ctx.env)