nimbus-eth1/tests/test_engine_api.nim
andri lim 7d3616e3d9
Refactor TxPool: leaner and simpler (#2973)
* Refactor TxPool: leaner and simpler
* Rewrite test_txpool

Reduce number of tables used, from 5 to 2. Reduce number of files.
If need to modify the price rule or other filters, now is far more easier because only one table to work with(sender/nonce).
And the other table is just a map from txHash to TxItemRef.

Removing transactions from txPool either because of producing new block or syncing became much easier.
Removing expired transactions also simple.
Explicit Tx Pending, Staged, or Packed status is removed. The status of the transactions can be inferred implicitly.
Developer new to TxPool can easily follow the logic.

But the most important is we can revive the test_txpool without dirty trick and remove usage of getCanonicalHead furthermore to prepare for better integration with ForkedChain.
2024-12-26 17:07:25 +07:00

318 lines
8.9 KiB
Nim

# 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.
import
std/times,
eth/common,
json_rpc/rpcclient,
json_rpc/rpcserver,
web3/engine_api,
web3/conversions,
web3/execution_types,
unittest2
import
../nimbus/rpc,
../nimbus/config,
../nimbus/core/chain,
../nimbus/core/tx_pool,
../nimbus/beacon/beacon_engine,
../nimbus/beacon/web3_eth_conv,
../hive_integration/nodocker/engine/engine_client
type
TestEnv = ref object
com : CommonRef
server : RpcHttpServer
client : RpcHttpClient
chain : ForkedChainRef
NewPayloadV4Params* = object
payload*: ExecutionPayload
expectedBlobVersionedHashes*: Opt[seq[Hash32]]
parentBeaconBlockRoot*: Opt[Hash32]
executionRequests*: Opt[seq[seq[byte]]]
TestSpec = object
name: string
fork: HardFork
genesisFile: string
testProc: proc(env: TestEnv): Result[void, string]
NewPayloadV4Params.useDefaultSerializationIn JrpcConv
const
defaultGenesisFile = "tests/customgenesis/engine_api_genesis.json"
mekongGenesisFile = "tests/customgenesis/mekong.json"
proc setupConfig(genesisFile: string): NimbusConf =
makeConfig(@[
"--custom-network:" & genesisFile,
"--listen-address: 127.0.0.1",
])
proc setupCom(conf: NimbusConf): CommonRef =
CommonRef.new(
newCoreDbRef DefaultDbMemory,
nil,
conf.networkId,
conf.networkParams
)
proc setupClient(port: Port): RpcHttpClient =
let client = newRpcHttpClient()
waitFor client.connect("127.0.0.1", port, false)
return client
proc setupEnv(envFork: HardFork = MergeFork,
genesisFile: string = defaultGenesisFile): TestEnv =
doAssert(envFork >= MergeFork)
let
conf = setupConfig(genesisFile)
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)
chain = ForkedChainRef.init(com)
txPool = TxPoolRef.new(chain)
let
server = newRpcHttpServerWithParams("127.0.0.1:0").valueOr:
echo "Failed to create rpc server: ", error
quit(QuitFailure)
beaconEngine = BeaconEngineRef.new(txPool)
serverApi = newServerAPI(txPool)
setupServerAPI(serverApi, server, newEthContext())
setupEngineAPI(beaconEngine, server)
server.start()
let
client = setupClient(server.localAddress[0].port)
TestEnv(
com : com,
server : server,
client : client,
chain : chain,
)
proc close(env: TestEnv) =
waitFor env.client.close()
waitFor env.server.closeWait()
proc runBasicCycleTest(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]()),
)
fcuRes = ? client.forkchoiceUpdated(Version.V1, update, Opt.some(attr))
payload = ? client.getPayload(Version.V1, fcuRes.payloadId.get)
npRes = ? client.newPayloadV1(payload.executionPayload)
discard ? client.forkchoiceUpdated(Version.V1, ForkchoiceStateV1(
headBlockHash: npRes.latestValidHash.get
))
let bn = ? client.blockNumber()
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(Version.V4, fcuRes.payloadId.get)
res = ? client.newPayloadV4(payload.executionPayload,
Opt.some(default(seq[Hash32])),
attr.parentBeaconBlockRoot,
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 newPayloadV4ParamsTest(env: TestEnv): Result[void, string] =
const
paramsFiles = [
"tests/engine_api/newPayloadV4_invalid_blockhash.json",
"tests/engine_api/newPayloadV4_requests_order.json"
]
for paramsFile in paramsFiles:
let
client = env.client
params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params)
res = ?client.newPayloadV4(
params.payload,
params.expectedBlobVersionedHashes,
params.parentBeaconBlockRoot,
params.executionRequests)
if res.status != PayloadExecutionStatus.syncing:
return err("res.status should equals to PayloadExecutionStatus.syncing")
if res.latestValidHash.isSome:
return err("lastestValidHash should empty")
if res.validationError.isSome:
return err("validationError should empty")
ok()
proc genesisShouldCanonicalTest(env: TestEnv): Result[void, string] =
const
paramsFile = "tests/engine_api/genesis_base_canonical.json"
let
client = env.client
params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params)
res = ? client.newPayloadV3(
params.payload,
params.expectedBlobVersionedHashes,
params.parentBeaconBlockRoot)
if res.status != PayloadExecutionStatus.valid:
return err("res.status should equals to PayloadExecutionStatus.valid")
if res.latestValidHash.isNone:
return err("lastestValidHash should not empty")
let
update = ForkchoiceStateV1(
headBlockHash: params.payload.blockHash,
safeBlockHash: params.payload.parentHash,
finalizedBlockHash: params.payload.parentHash,
)
fcuRes = ? client.forkchoiceUpdated(Version.V3, update)
if fcuRes.payloadStatus.status != PayloadExecutionStatus.valid:
return err("fcuRes.payloadStatus.status should equals to PayloadExecutionStatus.valid")
ok()
proc newPayloadV4InvalidRequests(env: TestEnv): Result[void, string] =
const
paramsFiles = [
"tests/engine_api/newPayloadV4_invalid_requests.json",
"tests/engine_api/newPayloadV4_empty_requests_data.json",
"tests/engine_api/newPayloadV4_invalid_requests_type.json",
"tests/engine_api/newPayloadV4_invalid_requests_order.json",
]
for paramsFile in paramsFiles:
let
client = env.client
params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params)
res = client.newPayloadV4(
params.payload,
params.expectedBlobVersionedHashes,
params.parentBeaconBlockRoot,
params.executionRequests)
if res.isOk:
return err("res should error")
if $engineApiInvalidParams notin res.error:
return err("invalid error code: " & res.error & " expect: " & $engineApiInvalidParams)
if "request" notin res.error:
return err("expect \"request\" in error message: " & res.error)
ok()
const testList = [
TestSpec(
name: "Basic cycle",
fork: MergeFork,
testProc: runBasicCycleTest
),
TestSpec(
name: "newPayloadV4",
fork: Prague,
testProc: runNewPayloadV4Test
),
TestSpec(
name: "newPayloadV4 params",
fork: Prague,
testProc: newPayloadV4ParamsTest
),
TestSpec(
name: "Genesis block hash should canonical",
fork: Cancun,
testProc: genesisShouldCanonicalTest,
genesisFile: mekongGenesisFile
),
TestSpec(
name: "newPayloadV4 invalid execution requests",
fork: Prague,
testProc: newPayloadV4InvalidRequests
),
]
proc engineApiMain*() =
suite "Engine API":
for z in testList:
test z.name:
let genesisFile = if z.genesisFile.len > 0:
z.genesisFile
else:
defaultGenesisFile
let env = setupEnv(z.fork, genesisFile)
let res = z.testProc(env)
if res.isErr:
debugEcho "FAILED TO EXECUTE ", z.name, ": ", res.error
check res.isOk
env.close()
when isMainModule:
engineApiMain()