Fixes related to executionRequests of Pectra (#2787)

This commit is contained in:
andri lim 2024-10-26 18:10:54 +07:00 committed by GitHub
parent 6b2d341ebb
commit 738cb277cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 161 additions and 42 deletions

View File

@ -92,6 +92,7 @@ proc getPayload*(client: RpcClient,
blockValue: Opt.some(x.blockValue),
blobsBundle: Opt.some(x.blobsBundle),
shouldOverrideBuilder: Opt.some(x.shouldOverrideBuilder),
executionRequests: Opt.some(x.executionRequests),
))
elif version == Version.V3:
let x = client.getPayloadV3(payloadId).valueOr:

View File

@ -190,7 +190,11 @@ proc forkchoiceUpdated*(ben: BeaconEngineRef,
raise invalidAttr(error)
let id = computePayloadId(blockHash, attrs)
ben.put(id, bundle.blockValue, bundle.executionPayload, bundle.blobsBundle)
ben.put(id,
bundle.blockValue,
bundle.executionPayload,
bundle.blobsBundle,
bundle.executionRequests)
info "Created payload for sealing",
id = id.toHex,

View File

@ -34,47 +34,48 @@ func validateVersionedHashed(payload: ExecutionPayload,
return false
true
template validateVersion(com, timestamp, version, apiVersion) =
template validateVersion(com, timestamp, payloadVersion, apiVersion) =
if apiVersion == Version.V4:
if not com.isPragueOrLater(timestamp):
raise unsupportedFork("newPayloadV4 expect payload timestamp fall within Prague")
if com.isPragueOrLater(timestamp):
if version != Version.V3:
if payloadVersion != Version.V3:
raise invalidParams("if timestamp is Prague or later, " &
"payload must be ExecutionPayloadV3, got ExecutionPayload" & $version)
"payload must be ExecutionPayloadV3, got ExecutionPayload" & $payloadVersion)
if apiVersion == Version.V3:
if not com.isCancunOrLater(timestamp):
raise unsupportedFork("newPayloadV3 expect payload timestamp fall within Cancun")
if com.isCancunOrLater(timestamp):
if version != Version.V3:
if payloadVersion != Version.V3:
raise invalidParams("if timestamp is Cancun or later, " &
"payload must be ExecutionPayloadV3, got ExecutionPayload" & $version)
"payload must be ExecutionPayloadV3, got ExecutionPayload" & $payloadVersion)
elif com.isShanghaiOrLater(timestamp):
if version != Version.V2:
if payloadVersion != Version.V2:
raise invalidParams("if timestamp is Shanghai or later, " &
"payload must be ExecutionPayloadV2, got ExecutionPayload" & $version)
"payload must be ExecutionPayloadV2, got ExecutionPayload" & $payloadVersion)
elif version != Version.V1:
elif payloadVersion != Version.V1:
raise invalidParams("if timestamp is earlier than Shanghai, " &
"payload must be ExecutionPayloadV1, got ExecutionPayload" & $version)
"payload must be ExecutionPayloadV1, got ExecutionPayload" & $payloadVersion)
if apiVersion >= Version.V3:
if version != apiVersion:
if apiVersion == Version.V3 or apiVersion == Version.V4:
# both newPayloadV3 and newPayloadV4 expect ExecutionPayloadV3
if payloadVersion != Version.V3:
raise invalidParams("newPayload" & $apiVersion &
" expect ExecutionPayload" & $apiVersion &
" but got ExecutionPayload" & $version)
" expect ExecutionPayload3" &
" but got ExecutionPayload" & $payloadVersion)
template validatePayload(apiVersion, version, payload, executionRequests) =
if version >= Version.V2:
template validatePayload(apiVersion, payloadVersion, payload) =
if payloadVersion >= Version.V2:
if payload.withdrawals.isNone:
raise invalidParams("newPayload" & $apiVersion &
"withdrawals is expected from execution payload")
if apiVersion >= Version.V3 or version >= Version.V3:
if apiVersion >= Version.V3 or payloadVersion >= Version.V3:
if payload.blobGasUsed.isNone:
raise invalidParams("newPayload" & $apiVersion &
"blobGasUsed is expected from execution payload")
@ -82,11 +83,6 @@ template validatePayload(apiVersion, version, payload, executionRequests) =
raise invalidParams("newPayload" & $apiVersion &
"excessBlobGas is expected from execution payload")
if apiVersion >= Version.V4 or version >= Version.V4:
if executionRequests.isNone:
raise invalidParams("newPayload" & $apiVersion &
"executionRequests is expected from execution payload")
proc newPayload*(ben: BeaconEngineRef,
apiVersion: Version,
payload: ExecutionPayload,
@ -103,6 +99,11 @@ proc newPayload*(ben: BeaconEngineRef,
if beaconRoot.isNone:
raise invalidParams("newPayloadV3 expect beaconRoot but got none")
if apiVersion >= Version.V4:
if executionRequests.isNone:
raise invalidParams("newPayload" & $apiVersion &
": executionRequests is expected from execution payload")
let
com = ben.com
db = com.db
@ -110,7 +111,7 @@ proc newPayload*(ben: BeaconEngineRef,
version = payload.version
requestsHash = calcRequestsHash(executionRequests)
validatePayload(apiVersion, version, payload, executionRequests)
validatePayload(apiVersion, version, payload)
validateVersion(com, timestamp, version, apiVersion)
var blk = ethBlock(payload, beaconRoot, requestsHash)

View File

@ -121,6 +121,12 @@ func put*(ben: BeaconEngineRef, id: Bytes8,
blobsBundle: Opt[BlobsBundleV1]) =
ben.queue.put(id, blockValue, payload, blobsBundle)
func put*(ben: BeaconEngineRef, id: Bytes8,
blockValue: UInt256, payload: ExecutionPayload,
blobsBundle: Opt[BlobsBundleV1],
executionRequests: Opt[array[3, seq[byte]]]) =
ben.queue.put(id, blockValue, payload, blobsBundle, executionRequests)
func put*(ben: BeaconEngineRef, id: Bytes8,
blockValue: UInt256, payload: SomeExecutionPayload,
blobsBundle: Opt[BlobsBundleV1]) =
@ -185,14 +191,15 @@ func get*(ben: BeaconEngineRef, id: Bytes8,
# Public functions
# ------------------------------------------------------------------------------
type ExecutionPayloadAndBlobsBundle* = object
type AssembledExecutionPayload* = object
executionPayload*: ExecutionPayload
blobsBundle*: Opt[BlobsBundleV1]
blockValue*: UInt256
executionRequests*: Opt[array[3, seq[byte]]]
proc generatePayload*(ben: BeaconEngineRef,
attrs: PayloadAttributes):
Result[ExecutionPayloadAndBlobsBundle, string] =
Result[AssembledExecutionPayload, string] =
wrapException:
let
xp = ben.txPool
@ -231,10 +238,11 @@ proc generatePayload*(ben: BeaconEngineRef,
proofs: blobData.proofs.mapIt it.Web3KZGProof,
blobs: blobData.blobs.mapIt it.Web3Blob)
ok ExecutionPayloadAndBlobsBundle(
ok AssembledExecutionPayload(
executionPayload: executionPayload(bundle.blk),
blobsBundle: blobsBundle,
blockValue: bundle.blockValue)
blockValue: bundle.blockValue,
executionRequests: bundle.executionRequests)
func setInvalidAncestor*(ben: BeaconEngineRef, header: Header, blockHash: Hash32) =
ben.invalidBlocksHits[blockHash] = 1

View File

@ -152,6 +152,16 @@ proc procBlkEpilogue(
clearEmptyAccount = vmState.com.isSpuriousOrLater(header.number),
clearCache = true)
var
withdrawalReqs: seq[byte]
consolidationReqs: seq[byte]
if header.requestsHash.isSome:
# Execute EIP-7002 and EIP-7251 before calculating stateRoot
# because they will alter the state
withdrawalReqs = processDequeueWithdrawalRequests(vmState)
consolidationReqs = processDequeueConsolidationRequests(vmState)
if not skipValidation:
let stateDB = vmState.stateDB
if header.stateRoot != stateDB.rootHash:
@ -181,8 +191,6 @@ proc procBlkEpilogue(
if header.requestsHash.isSome:
let
depositReqs = ?parseDepositLogs(vmState.allLogs)
withdrawalReqs = processDequeueWithdrawalRequests(vmState)
consolidationReqs = processDequeueConsolidationRequests(vmState)
requestsHash = calcRequestsHashInsertType(depositReqs, withdrawalReqs, consolidationReqs)
if header.requestsHash.get != requestsHash:

View File

@ -460,15 +460,16 @@ func com*(xp: TxPoolRef): CommonRef =
## Getter
xp.vmState.com
type EthBlockAndBlobsBundle* = object
type AssembledBlock* = object
blk*: EthBlock
blobsBundle*: Opt[BlobsBundle]
blockValue*: UInt256
executionRequests*: Opt[array[3, seq[byte]]]
proc assembleBlock*(
xp: TxPoolRef,
someBaseFee: bool = false
): Result[EthBlockAndBlobsBundle, string] {.gcsafe,raises: [CatchableError].} =
): Result[AssembledBlock, string] {.gcsafe,raises: [CatchableError].} =
## Getter, retrieves a packed block ready for mining and signing depending
## on the internally cached block chain head, the txs in the pool and some
## tuning parameters. The following block header fields are left
@ -520,10 +521,17 @@ proc assembleBlock*(
# make sure baseFee always has something
blk.header.baseFeePerGas = Opt.some(blk.header.baseFeePerGas.get(0.u256))
ok EthBlockAndBlobsBundle(
let executionRequestsOpt =
if com.isPragueOrLater(blk.header.timestamp):
Opt.some(pst.executionRequests)
else:
Opt.none(array[3, seq[byte]])
ok AssembledBlock(
blk: blk,
blobsBundle: blobsBundleOpt,
blockValue: pst.blockValue)
blockValue: pst.blockValue,
executionRequests: executionRequestsOpt)
# core/tx_pool.go(474): func (pool SetGasPrice,*TxPool) Stats() (int, int) {
# core/tx_pool.go(1728): func (t *txLookup) Count() int {

View File

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

View File

@ -26,6 +26,7 @@ import
../../evm/state,
../../evm/types,
../eip4844,
../eip6110,
"."/[tx_desc, tx_item, tx_tabs, tx_tabs/tx_status, tx_info],
tx_tasks/[tx_bucket]
@ -42,6 +43,9 @@ type
stateRoot: Hash32
receiptsRoot: Hash32
logsBloom: Bloom
withdrawalReqs: seq[byte]
consolidationReqs: seq[byte]
depositReqs: seq[byte]
GrabResult = enum
FetchNextItem
@ -187,6 +191,11 @@ proc vmExecInit(xp: TxPoolRef): Result[TxPacker, string]
xp.vmState.processBeaconBlockRoot(beaconRoot).isOkOr:
return err(error)
# EIP-2935
if xp.nextFork >= FkPrague:
xp.vmState.processParentBlockHash(xp.vmState.blockCtx.parentHash).isOkOr:
return err(error)
ok(packer)
proc vmExecGrabItem(pst: var TxPacker; item: TxItemRef): GrabResult
@ -203,7 +212,7 @@ proc vmExecGrabItem(pst: var TxPacker; item: TxItemRef): GrabResult
if pst.numBlobPerBlock + item.tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK:
return ContinueWithNextAccount
pst.numBlobPerBlock += item.tx.versionedHashes.len
let blobGasUsed = item.tx.getTotalBlobGas
if vmState.blobGasUsed + blobGasUsed > MAX_BLOB_GAS_PER_BLOCK:
return ContinueWithNextAccount
@ -245,7 +254,7 @@ proc vmExecGrabItem(pst: var TxPacker; item: TxItemRef): GrabResult
FetchNextItem
proc vmExecCommit(pst: var TxPacker) =
proc vmExecCommit(pst: var TxPacker): Result[void, string] =
let
vmState = pst.vmState
stateDB = vmState.stateDB
@ -255,6 +264,12 @@ proc vmExecCommit(pst: var TxPacker) =
for withdrawal in vmState.com.pos.withdrawals:
stateDB.addBalance(withdrawal.address, withdrawal.weiAmount)
# EIP-6110, EIP-7002, EIP-7251
if vmState.fork >= FkPrague:
pst.withdrawalReqs = processDequeueWithdrawalRequests(vmState)
pst.consolidationReqs = processDequeueConsolidationRequests(vmState)
pst.depositReqs = ?parseDepositLogs(vmState.allLogs)
# Finish up, then vmState.stateDB.rootHash may be accessed
stateDB.persist(clearEmptyAccount = vmState.fork >= FkSpurious)
@ -265,7 +280,7 @@ proc vmExecCommit(pst: var TxPacker) =
pst.receiptsRoot = vmState.receipts.calcReceiptsRoot
pst.logsBloom = vmState.receipts.createBloom
pst.stateRoot = vmState.stateDB.rootHash
ok()
# ------------------------------------------------------------------------------
# Public functions
@ -293,7 +308,7 @@ proc packerVmExec*(xp: TxPoolRef): Result[TxPacker, string]
if rc == ContinueWithNextAccount:
break account # continue with next account
pst.vmExecCommit()
?pst.vmExecCommit()
ok(pst)
# Block chain will roll back automatically
@ -305,7 +320,7 @@ proc assembleHeader*(pst: TxPacker): Header =
pos = com.pos
result = Header(
parentHash: vmState.parent.blockHash,
parentHash: vmState.blockCtx.parentHash,
ommersHash: EMPTY_UNCLE_HASH,
coinbase: pos.feeRecipient,
stateRoot: pst.stateRoot,
@ -330,9 +345,25 @@ proc assembleHeader*(pst: TxPacker): Header =
result.blobGasUsed = Opt.some vmState.blobGasUsed
result.excessBlobGas = Opt.some vmState.blockCtx.excessBlobGas
if com.isPragueOrLater(pos.timestamp):
let requestsHash = calcRequestsHashInsertType(pst.depositReqs,
pst.withdrawalReqs, pst.consolidationReqs)
result.requestsHash = Opt.some(requestsHash)
func blockValue*(pst: TxPacker): UInt256 =
pst.blockValue
func executionRequests*(pst: TxPacker): array[3, seq[byte]] =
result[0] = newSeqOfCap[byte](pst.depositReqs.len+1)
result[0].add 0x00.byte
result[0].add pst.depositReqs
result[1] = newSeqOfCap[byte](pst.withdrawalReqs.len+1)
result[1].add 0x01.byte
result[1].add pst.withdrawalReqs
result[2] = newSeqOfCap[byte](pst.consolidationReqs.len+1)
result[2].add 0x02.byte
result[2].add pst.consolidationReqs
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -56,6 +56,8 @@ proc debug*(h: Header): string =
result.add "excessBlobGas : " & $h.excessBlobGas.get() & "\n"
if h.parentBeaconBlockRoot.isSome:
result.add "beaconRoot : " & $h.parentBeaconBlockRoot.get() & "\n"
if h.requestsHash.isSome:
result.add "requestsHash : " & $h.requestsHash.get() & "\n"
result.add "blockHash : " & $blockHash(h) & "\n"
proc dumpAccounts*(vmState: BaseVMState): JsonNode =

View File

@ -53,9 +53,22 @@ proc setupClient(port: Port): RpcHttpClient =
waitFor client.connect("127.0.0.1", port, false)
return client
proc setupEnv(): TestEnv =
proc setupEnv(envFork: HardFork = MergeFork): TestEnv =
doAssert(envFork >= MergeFork)
let
conf = setupConfig()
if envFork >= Shanghai:
conf.networkParams.config.shanghaiTime = Opt.some(0.EthTime)
if envFork >= Cancun:
conf.networkParams.config.cancunTime = Opt.some(0.EthTime)
if envFork >= Prague:
conf.networkParams.config.pragueTime = Opt.some(0.EthTime)
let
com = setupCom(conf)
head = com.db.getCanonicalHead()
chain = newForkedChain(com, head)
@ -91,7 +104,7 @@ proc close(env: TestEnv) =
waitFor env.client.close()
waitFor env.server.closeWait()
proc runTest(env: TestEnv): Result[void, string] =
proc runBasicCycleTest(env: TestEnv): Result[void, string] =
let
client = env.client
header = ? client.latestHeader()
@ -116,14 +129,56 @@ proc runTest(env: TestEnv): Result[void, string] =
if bn != 1:
return err("Expect returned block number: 1, got: " & $bn)
ok()
proc runNewPayloadV4Test(env: TestEnv): Result[void, string] =
let
client = env.client
header = ? client.latestHeader()
update = ForkchoiceStateV1(
headBlockHash: header.blockHash
)
time = getTime().toUnix
attr = PayloadAttributes(
timestamp: w3Qty(time + 1),
prevRandao: default(Bytes32),
suggestedFeeRecipient: default(Address),
withdrawals: Opt.some(newSeq[WithdrawalV1]()),
parentBeaconBlockRoot: Opt.some(default(Hash32))
)
fcuRes = ? client.forkchoiceUpdated(Version.V3, update, Opt.some(attr))
payload = ? client.getPayload(fcuRes.payloadId.get, Version.V4)
res = ? client.newPayload(Version.V4,
payload.executionPayload,
Opt.some(default(Hash32)),
payload.executionRequests)
if res.status != PayloadExecutionStatus.valid:
return err("res.status should equals to PayloadExecutionStatus.valid")
if res.latestValidHash.isNone or
res.latestValidHash.get != payload.executionPayload.blockHash:
return err("lastestValidHash mismatch")
if res.validationError.isSome:
return err("validationError should empty")
ok()
proc engineApiMain*() =
suite "Engine API":
test "Basic cycle":
let env = setupEnv()
let res = env.runTest()
let res = env.runBasicCycleTest()
if res.isErr:
debugEcho "FAILED TO EXECUTE TEST: ", res.error
check res.isOk
env.close()
test "newPayloadV4":
let env = setupEnv(Prague)
let res = env.runNewPayloadV4Test()
if res.isErr:
debugEcho "FAILED TO EXECUTE TEST: ", res.error
check res.isOk