Skip to content

Commit

Permalink
devnet-5: Implement EIP-7691: Blob throughput increase (#2957)
Browse files Browse the repository at this point in the history
  • Loading branch information
jangko authored Dec 20, 2024
1 parent 1dff892 commit 80f8b3c
Show file tree
Hide file tree
Showing 16 changed files with 107 additions and 51 deletions.
8 changes: 4 additions & 4 deletions hive_integration/nodocker/engine/cancun/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions hive_integration/nodocker/engine/cancun/step_newpayloads.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion hive_integration/nodocker/engine/cancun_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
7 changes: 6 additions & 1 deletion nimbus/constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
26 changes: 16 additions & 10 deletions nimbus/core/eip4844.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import
kzg4844/kzg,
results,
stint,
./eip7691,
../constants,
../common/common

Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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")
Expand Down
30 changes: 30 additions & 0 deletions nimbus/core/eip7691.nim
Original file line number Diff line number Diff line change
@@ -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
9 changes: 6 additions & 3 deletions nimbus/core/executor/process_transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import
../../evm/types,
../../constants,
../eip4844,
../eip7691,
../validate

# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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.
#
Expand Down
2 changes: 1 addition & 1 deletion nimbus/core/tx_pool/tx_desc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
12 changes: 8 additions & 4 deletions nimbus/core/tx_pool/tx_packer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
6 changes: 4 additions & 2 deletions nimbus/core/tx_pool/tx_tasks/tx_classify.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import
chronicles,
eth/common/[transactions, keys]

import ../../../transaction
import
../../../transaction,
../../../common/evmforks

{.push raises: [].}

Expand Down Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions nimbus/core/validate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}")
Expand Down
10 changes: 7 additions & 3 deletions nimbus/rpc/oracle.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import
../transaction,
../common/common,
../core/eip4844,
../core/eip7691,
../core/chain/forked_chain

from ./rpc_types import
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions nimbus/rpc/rpc_utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
9 changes: 5 additions & 4 deletions nimbus/rpc/server_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 80f8b3c

Please sign in to comment.