cmd/evm: add enginetest command for direct engine fixture execution#34650
cmd/evm: add enginetest command for direct engine fixture execution#34650spencer-tb wants to merge 9 commits into
Conversation
29e3f4c to
00957ea
Compare
00957ea to
d46370a
Compare
| return engine.PayloadStatusV1{Status: engine.INVALID}, engineParamsErr("nil versionedHashes post-cancun") | ||
| case p.BeaconRoot == nil: | ||
| return engine.PayloadStatusV1{Status: engine.INVALID}, engineParamsErr("nil beaconRoot post-cancun") | ||
| case !h.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): |
There was a problem hiding this comment.
Will need to double check this for my own sanity -- for some reason I was expecting it to just say forks.Cancun
There was a problem hiding this comment.
This matches the real ConsensusAPI.NewPayloadV3 at api.go:204 which allows V3 for Cancun through BPO5. So maybe can be changed there too but not 100% certain :)
There was a problem hiding this comment.
Ah I see, seems I was remembering this:
go-ethereum/eth/catalyst/api.go
Line 708 in d8cb8a9
| if postCheck != nil { | ||
| defer postCheck(result, chain) | ||
| } |
There was a problem hiding this comment.
Minor nit: I think we may want to use a closure here becauseresult here would be evaluated at the callsite and not when the defer is triggered.
There was a problem hiding this comment.
Small self-contained example to elaborate:
package main
import "fmt"
func runBuggy(postCheck func(error, string)) (result error) {
chain := "some-chain"
// `result` is evaluated at the callsite, so its nil and not when the defer fires.
if postCheck != nil {
defer postCheck(result, chain)
}
result = fmt.Errorf("payload 3: expected VALID, got INVALID")
return result
}
func runFixed(postCheck func(error, string)) (result error) {
chain := "some-chain"
// closure captures `result` by reference, so it reads the final value when the defer fires.
if postCheck != nil {
defer func() { postCheck(result, chain) }()
}
result = fmt.Errorf("payload 3: expected VALID, got INVALID")
return result
}
func main() {
check := func(res error, chain string) {
fmt.Println(" postCheck got error:", res)
}
fmt.Println("buggy:")
runBuggy(check)
fmt.Println("fixed:")
runFixed(check)
}| var tests map[string]*tests.BlockTest | ||
| if err = json.Unmarshal(src, &tests); err != nil { | ||
| return nil, err | ||
| return nil, nil // Skip non-fixture JSON files |
There was a problem hiding this comment.
Just want to confirm that this also skips errors from malformed fixture files?
- Add lastBlockHash to blocktest/enginetest, lastPayloadStatus to enginetest - Remove stateRoot from blocktest/enginetest (only statetest has it) - Report validation/rejection error in `error` even when test passes, for negative tests (expected exceptions) - Enables EELS consume direct to map errors through ExceptionMapper and verify correct exception for every invalid test
|
I like the idea. I don't know if we need --worker though, we could just default to runtime.NumCPU() |
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
New `validate` CLI command for running EEST fixtures directly against client EVM binaries, replacing Hive for execution correctness testing. Usage: validate health # health check all clients validate engine --client geth # engine tests validate state --client besu # state tests validate block --client nethermind # block tests Features: - 7 clients: geth, besu, nethermind, erigon, reth, ethrex, nimbus - Per-type Pydantic result models: StateTestResult, BlockTestResult, EngineTestResult with type-specific fields - Exception matching: maps client error strings to EEST exception types via ExceptionMapper, verifies correct exception for every invalid test (--no-exception-check to disable) - Cross-validation: lastBlockHash against fixture, lastPayloadStatus (VALID/INVALID) for engine tests - validate.toml config for client binary paths with per-type overrides (state-bin, block-bin, engine-bin) - Auto bin-workers and xdist tuning per client - Bundled Frontier sanity fixtures for health checks - Shared validate_helpers.py for validation logic Client binary PRs: - geth: ethereum/go-ethereum#34650 - erigon: erigontech/erigon#20315 - besu: besu-eth/besu#10184 - nethermind: NethermindEth/nethermind#11035 - reth: paradigmxyz/reth#23361 - ethrex: lambdaclass/ethrex#6445 - nimbus: status-im/nimbus-eth1#4101 - revm: bluealloy/revm#3544 Tracking issue: ethereum#2319
|
There's a lot of log output when I just run this against a test dir: I'm not sure if the best solution is to suppress this entirely, or only dump log output for failing cases. The latter may be impossible. |
| } | ||
|
|
||
| // reportNDJSON prints one JSON object per result as it completes. | ||
| func reportNDJSON(r testResult) { |
There was a problem hiding this comment.
yeah lets remove it, i was trialing whether we could stream the json output using ndjson to pytest in ethereum/execution-specs#2622
| Alloc: t.json.Pre, | ||
| BaseFee: t.json.Genesis.BaseFeePerGas, | ||
| BlobGasUsed: t.json.Genesis.BlobGasUsed, | ||
| ExcessBlobGas: t.json.Genesis.ExcessBlobGas, |
There was a problem hiding this comment.
We need to set the SlotNumber here. And later when BALs are merged, the BAL hash
|
|
||
| // validateEnginePostState verifies the post-state accounts match the expected values. | ||
| // Mirrors BlockTest.validatePostState. | ||
| func validateEnginePostState(post types.GenesisAlloc, statedb *state.StateDB) error { |
There was a problem hiding this comment.
It's almost exactly the same as BlockTest.validatePostState. We should deduplicate them
| type engineHandler struct { | ||
| chain *core.BlockChain | ||
| invalidBlocksHits map[common.Hash]int | ||
| invalidTipsets map[common.Hash]*types.Header |
There was a problem hiding this comment.
The naming is kind of weird. invalidHeaders seems more appropriate.
| // newPayloadVersioned dispatches to the appropriate version-specific validation | ||
| // before calling the core newPayload logic. Mirrors NewPayloadV1-V5 in | ||
| // eth/catalyst/api.go. | ||
| func (h *engineHandler) newPayloadVersioned(p etNewPayload) (engine.PayloadStatusV1, error) { |
There was a problem hiding this comment.
Is there a reason to do validation here specific to the payload version? It feels like that's behavior we're trying to test here.
There was a problem hiding this comment.
Also (same line) newPayloadVersioned -> newPayloadVersion. typo fix
There was a problem hiding this comment.
Oh sorry, it's not calling the engine API handler. It's calling the engine API chasse that's added in this PR
There was a problem hiding this comment.
Would be nice if we could just call into existing engine API path instead of introducing a mirror of the logic here. I'm not sure what hurdles would need to be overcome to make that a possibility.
|
Assigned myself because I'm going to be pushing some changes to this to try improve where I can and pair down the size of the diff (e.g. #34650 (comment)) |
|
Just checking this now! Sorry for the referenced commit PR spam! Feel free to start a new PR based off of this one or push to my branch etc I mostly wanted to gauge whether this was actually feasible from all clients, so I'm glad it seems like a reasonable addition. We'd like to use: ethereum/execution-specs#2622 as the new default for running tests on clients. It will give us a much faster feedback loop across the board if we go this route :) |
|
Sorry for the late reply @spencer-tb ! Yes, I intend to finish this one. Will push updates to this branch when I get it to a state I am satisfied with. |
Summary
Add
evm enginetestcommand that runsblockchain_test_enginefixtures directly against a lightweight Engine API handler, without requiring Hive or full client startup. Also add--workersflag to all three test runners (enginetest,blocktest,statetest) for parallel fixture file processing.evm enginetestA new direct runner for Engine API test fixtures. Implements a lightweight engine handler that mirrors the core logic of
eth/catalyst.ConsensusAPI:NewPayloadV1-V5parameter validationExecutableDataToBlockpayload conversion and block insertion viaInsertBlockWithoutSetHeadForkchoiceUpdatedwithSetCanonicalhead management (including initial FCU to genesis)PayloadStatusV1responsesEngineAPIErrorcode validation against fixture expectations (errorCode,validationError)This exercises the actual engine code path (two-phase insert-then-canonicalize via forkchoice), not just
InsertChainlikeblocktest.Benchmarks
Tested against EEST v5.3.0 stable fixtures on Apple M-series.
For reference, Hive runs of the same test suite on the same geth version:
consume engine: 2h 48mconsume rlp: 4h 29mevm enginetest— exercises the same engine code paths asconsume engine(40,523 tests):evm blocktest— exercises the same execution paths asconsume rlp(43,924 tests):evm statetest(40,553 tests):Hive parity
Tested against v5.3.0 stable release — exact same 4 failures as Hive
consume engineon geth master:eip7002/test_system_contract_deployment[CancunToPragueAtTime15k-deploy_after_fork-nonzero_balance]eip7002/test_system_contract_deployment[CancunToPragueAtTime15k-deploy_after_fork-zero_balance]eip7251/test_system_contract_deployment[CancunToPragueAtTime15k-deploy_after_fork-nonzero_balance]eip7251/test_system_contract_deployment[CancunToPragueAtTime15k-deploy_after_fork-zero_balance]Usage