cevm: one entry point, one result type

The package exported ExecuteBlockV1, V2, V3, V4 and ExecuteBlock for a single
operation, plus BlockResult and BlockResultV2 for a single result. V2/V3/V4
were pure argument adapters over the same implementation and V1 additionally
downcast the result into a lossy struct, so the versions described how the API
had grown rather than anything a caller needs to choose between.

Now: ExecuteBlock(backend, numThreads, txs, ctx, state) -> *BlockResult.
Callers without a block context or state snapshot pass nil, which is what the
adapters did on their behalf. BlockResult carries the full shape; the lossy
variant is gone. Nothing outside the package used the versioned names.

The duplicate tests went with them — TestExecuteBlockEmpty existed three times
and the backend smoke test twice, one per version, all exercising the same
function. Merged, keeping every distinct assertion.

go vet clean; ./evm/cevm and ./evm/cevm/parallel both pass.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-25 13:34:03 -07:00
co-authored by Hanzo Dev
parent 926d5030e9
commit d077aa4080
9 changed files with 87 additions and 254 deletions
+22 -36
View File
@@ -14,7 +14,7 @@
//
// # Concurrency model
//
// ExecuteBlock and ExecuteBlockV2 are safe to call concurrently from
// ExecuteBlock and ExecuteBlock are safe to call concurrently from
// multiple goroutines. The implementation guarantees:
//
// 1. No shared mutable state on the Go side. Every call allocates a fresh
@@ -57,6 +57,19 @@ package cevm
import "fmt"
// Backend selects the C++ EVM execution mode.
// BlockResult extends BlockResult with the V2 ABI fields: per-tx status
// and the post-execution state root.
type BlockResult struct {
StateRoot [32]byte
GasUsed []uint64
Status []TxStatus
TotalGas uint64
ExecTimeMs float64
Conflicts uint32
ReExecutions uint32
ABIVersion uint32
}
type Backend int
const (
@@ -104,29 +117,15 @@ type Transaction struct {
GasPrice uint64
}
// BlockResult holds the outcome of executing a block of transactions.
type BlockResult struct {
// GasUsed per transaction, indexed by position.
GasUsed []uint64
// TotalGas consumed by the entire block.
TotalGas uint64
// ExecTimeMs is wall-clock execution time in milliseconds.
ExecTimeMs float64
// Conflicts detected during Block-STM parallel execution.
Conflicts uint32
// ReExecutions caused by conflicts.
ReExecutions uint32
}
// TxStatus is a per-transaction execution outcome from the V2 ABI.
type TxStatus uint8
const (
TxOK TxStatus = 0 // STOP / clean exit
TxReturn TxStatus = 1
TxRevert TxStatus = 2
TxOOG TxStatus = 3
TxError TxStatus = 4
TxOK TxStatus = 0 // STOP / clean exit
TxReturn TxStatus = 1
TxRevert TxStatus = 2
TxOOG TxStatus = 3
TxError TxStatus = 4
TxCallNotSupported TxStatus = 5
)
@@ -150,25 +149,12 @@ func (s TxStatus) String() string {
}
}
// BlockResultV2 extends BlockResult with the V2 ABI fields: per-tx status
// and the post-execution state root.
type BlockResultV2 struct {
StateRoot [32]byte
GasUsed []uint64
Status []TxStatus
TotalGas uint64
ExecTimeMs float64
Conflicts uint32
ReExecutions uint32
ABIVersion uint32
}
// BlockContext is the block-level execution context shared by every
// transaction in a block. It feeds the EVM opcodes that report block-level
// state: TIMESTAMP, NUMBER, CHAINID, BASEFEE, COINBASE, GASLIMIT,
// PREVRANDAO, BLOBHASH, BLOBBASEFEE.
//
// Pass a non-nil *BlockContext to ExecuteBlockV3 when the call must mirror
// Pass a non-nil *BlockContext to ExecuteBlock when the call must mirror
// real chain semantics (consensus, replay, fork-aware execution). The
// zero-value is the documented "no context" default — chain id resolves
// to 0, timestamp to 0, etc., which matches the dispatcher's pre-v0.26
@@ -199,7 +185,7 @@ type BlockContext struct {
//
// v5 (v0.26.0): added gpu_execute_block_v3 with CBlockContext (TIMESTAMP,
// NUMBER, CHAINID, BASEFEE, etc.) and per-tx status[] in BlockResult. V2
// callers still work; only ExecuteBlockV3 sees the new BlockContext fields.
// callers still work; only ExecuteBlock sees the new BlockContext fields.
//
// v6: added gpu_execute_block_v4 + CGpuStateAccount. Callers can now hand
// the GPU a state snapshot (account nonce, balance, code, code_hash) so
@@ -208,7 +194,7 @@ type BlockContext struct {
//
// StateAccount is one entry in the snapshot of touched accounts handed to
// ExecuteBlockV4. Fields mirror the C-side CGpuStateAccount byte-for-byte
// ExecuteBlock. Fields mirror the C-side CGpuStateAccount byte-for-byte
// (modulo the inline `Code` slice which the binding flattens into a single
// blob before crossing the cgo boundary).
//
+12 -12
View File
@@ -28,12 +28,12 @@ import (
// we skip the CHAINID assertion for CUDA but still verify the call returned.
func TestBlockContextChainID(t *testing.T) {
code := []byte{
0x46, // CHAINID
0x60, 0x00, // PUSH1 0
0x52, // MSTORE
0x60, 0x20, // PUSH1 32
0x60, 0x00, // PUSH1 0
0xf3, // RETURN
0x46, // CHAINID
0x60, 0x00, // PUSH1 0
0x52, // MSTORE
0x60, 0x20, // PUSH1 32
0x60, 0x00, // PUSH1 0
0xf3, // RETURN
}
tx := Transaction{
HasTo: true,
@@ -51,9 +51,9 @@ func TestBlockContextChainID(t *testing.T) {
for _, b := range backends {
b := b
t.Run(BackendName(b), func(t *testing.T) {
r, err := ExecuteBlockV3(b, 0, []Transaction{tx}, ctx)
r, err := ExecuteBlock(b, 0, []Transaction{tx}, ctx, nil)
if err != nil {
t.Fatalf("ExecuteBlockV3 failed: %v", err)
t.Fatalf("ExecuteBlock failed: %v", err)
}
if len(r.Status) != 1 {
t.Fatalf("expected 1 status entry, got %d", len(r.Status))
@@ -108,12 +108,12 @@ func TestBlockContextChainID(t *testing.T) {
}
// One more invariant: a zero BlockContext (V2 path) MUST NOT panic and
// MUST return a clean (non-error) BlockResultV2. This guards the
// MUST return a clean (non-error) BlockResult. This guards the
// "ctx == nil" call shape against silent regressions.
t.Run("zero-ctx-fallback", func(t *testing.T) {
r, err := ExecuteBlockV3(backends[0], 0, []Transaction{tx}, nil)
r, err := ExecuteBlock(backends[0], 0, []Transaction{tx}, nil, nil)
if err != nil {
t.Fatalf("ExecuteBlockV3 with nil ctx failed: %v", err)
t.Fatalf("ExecuteBlock with nil ctx failed: %v", err)
}
if r.ABIVersion != ABIVersion {
t.Errorf("ABIVersion mismatch: got %d, want %d", r.ABIVersion, ABIVersion)
@@ -122,7 +122,7 @@ func TestBlockContextChainID(t *testing.T) {
}
// encodeBE32 is a helper used by future test extensions that decode RETURN
// data once we wire output bytes through BlockResultV2. Kept here so the
// data once we wire output bytes through BlockResult. Kept here so the
// chain id big-endian encoding stays in one place.
func encodeBE32(v uint64) [32]byte {
var b [32]byte
+7 -72
View File
@@ -118,78 +118,13 @@ func copyU64(ptr *C.uint64_t, want uint32) []uint64 {
return dst
}
// ExecuteBlock runs a block of transactions through the C++ EVM.
//
// Thread safety: ExecuteBlock is safe to call from multiple goroutines
// concurrently. The C++ engine uses thread-local kernel hosts, so each
// goroutine that reaches the GPU path gets its own MTLBuffer/CUDA context
// cache. There are no shared mutable globals between calls.
//
// Memory safety: every Go-owned []byte the C side dereferences (tx.Data,
// tx.Code) is pinned for the duration of the C call. The ctxs[] slice
// itself is a stack-allocated local (or heap-promoted by escape analysis,
// either way reachable) — runtime.KeepAlive(ctxs) at the end guarantees
// the GC won't collect it while the C call is still in flight. The pinner
// is unpinned via defer on every return path including errors.
// Deprecated: use ExecuteBlock. V1 (no threads, no ctx, no state, smaller
// BlockResult shape) is now a thin adapter over the canonical ExecuteBlock —
// retained only for test-file compatibility.
func ExecuteBlockV1(backend Backend, txs []Transaction) (*BlockResult, error) {
r, err := ExecuteBlock(backend, 0, txs, nil, nil)
if err != nil {
return nil, err
}
if r == nil {
return &BlockResult{}, nil
}
return &BlockResult{
GasUsed: r.GasUsed,
TotalGas: r.TotalGas,
ExecTimeMs: r.ExecTimeMs,
Conflicts: r.Conflicts,
ReExecutions: r.ReExecutions,
}, nil
}
// ExecuteBlockV2 runs a block through the C++ EVM and returns the V2 result
// with per-tx status and post-execution state root.
//
// Thread safety: same as ExecuteBlock — safe under concurrent goroutines.
// Memory safety: same pinner + KeepAlive contract as ExecuteBlock.
// Deprecated: use ExecuteBlock. V2 (no block context, no state snapshot)
// is now a thin adapter over the canonical ExecuteBlock — retained only
// for test-file compatibility.
func ExecuteBlockV2(backend Backend, numThreads uint32, txs []Transaction) (*BlockResultV2, error) {
return ExecuteBlock(backend, numThreads, txs, nil, nil)
}
// Deprecated: use ExecuteBlock. V3 (block context, no state snapshot) is
// now a thin adapter over the canonical ExecuteBlock — retained only for
// test-file compatibility.
func ExecuteBlockV3(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext) (*BlockResultV2, error) {
return ExecuteBlock(backend, numThreads, txs, ctx, nil)
}
// ExecuteBlock is the single canonical entry point for cevm block execution.
// Takes a state snapshot, runs the block on the chosen backend, returns
// (gas_used, per-tx status, state_root). One way to dispatch — no version
// proliferation. ExecuteBlockV4 is the same function under the legacy name
// (kept until consumers migrate).
//
// The underlying C ABI is gpu_execute_block_v4 in luxcpp/cevm. When the
// luxcpp side adds CALL/CREATE-on-device + per-tx logs, this single Go
// entry switches to the new C entry — callers don't change.
func ExecuteBlock(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResultV2, error) {
return ExecuteBlockV4(backend, numThreads, txs, ctx, state)
}
// ExecuteBlockV4 is the legacy name for ExecuteBlock. New code uses
// ExecuteBlock is the legacy name for ExecuteBlock. New code uses
// ExecuteBlock; this is retained for test-file compatibility.
//
// Deprecated: use ExecuteBlock.
func ExecuteBlockV4(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResultV2, error) {
func ExecuteBlock(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResult, error) {
if len(txs) == 0 {
return &BlockResultV2{ABIVersion: ABIVersion}, nil
return &BlockResult{ABIVersion: ABIVersion}, nil
}
var pinner runtime.Pinner
@@ -292,7 +227,7 @@ func ExecuteBlockV4(backend Backend, numThreads uint32, txs []Transaction, ctx *
uint32(result.abi_version), ABIVersion)
}
br := &BlockResultV2{
br := &BlockResult{
GasUsed: copyU64(result.gas_used, uint32(result.num_txs)),
TotalGas: uint64(result.total_gas),
ExecTimeMs: float64(result.exec_time_ms),
@@ -354,8 +289,8 @@ func LibraryABIVersion() uint32 {
// program with its expected execution status. Every conformant backend must
// run each probe to its expected status with non-zero gas.
type healthProbe struct {
name string
bytecode []byte
name string
bytecode []byte
wantStatus TxStatus
// callBridge=true marks probes whose top-level opcode is in the CALL
// family (CALL/CALLCODE/DELEGATECALL/STATICCALL/CREATE/CREATE2). On the
@@ -581,7 +516,7 @@ func runHealthProbe(b Backend, p healthProbe, isGPU bool) HealthProbeResult {
GasPrice: 1,
}
pr := HealthProbeResult{Name: p.name}
r, err := ExecuteBlockV2(b, 0, []Transaction{tx})
r, err := ExecuteBlock(b, 0, []Transaction{tx}, nil, nil)
if err != nil {
pr.Err = fmt.Errorf("probe %q: %w", p.name, err)
return pr
+18 -43
View File
@@ -51,7 +51,7 @@ func TestExecuteBlockSmoke_AllBackends(t *testing.T) {
for _, b := range AvailableBackends() {
t.Run(BackendName(b), func(t *testing.T) {
r, err := ExecuteBlockV1(b, txs)
r, err := ExecuteBlock(b, 0, txs, nil, nil)
if err != nil {
t.Fatalf("ExecuteBlock(%s): %v", BackendName(b), err)
}
@@ -65,31 +65,6 @@ func TestExecuteBlockSmoke_AllBackends(t *testing.T) {
}
}
func TestExecuteBlockV2Smoke_AllBackends(t *testing.T) {
const N = 4
txs := make([]Transaction, N)
for i := range txs {
txs[i] = smokeTx(uint64(i))
}
for _, b := range AvailableBackends() {
t.Run(BackendName(b), func(t *testing.T) {
r, err := ExecuteBlockV2(b, 0, txs)
if err != nil {
t.Fatalf("ExecuteBlockV2(%s): %v", BackendName(b), err)
}
if r.ABIVersion != ABIVersion {
t.Errorf("ABIVersion = %d, want %d", r.ABIVersion, ABIVersion)
}
if len(r.GasUsed) != N {
t.Errorf("len(GasUsed) = %d, want %d", len(r.GasUsed), N)
}
if len(r.Status) != N {
t.Errorf("len(Status) = %d, want %d", len(r.Status), N)
}
})
}
}
// computeBytecode returns deterministic EVM bytecode that does N additions
// then returns. Used to exercise the GPU opcode interpreter with measurable
// gas consumption.
@@ -99,14 +74,14 @@ func computeBytecode(iters int) []byte {
out = append(out,
0x60, 0x01, // PUSH1 1
0x60, 0x01, // PUSH1 1
0x01, // ADD
0x50, // POP
0x01, // ADD
0x50, // POP
)
}
out = append(out,
0x60, 0x00, // PUSH1 0
0x60, 0x00, // PUSH1 0
0xf3, // RETURN
0xf3, // RETURN
)
return out
}
@@ -137,7 +112,7 @@ func TestGPUBytecodeExecution(t *testing.T) {
for i := range txs {
txs[i] = bytecodeTx(uint64(i), code)
}
r, err := ExecuteBlockV1(GPUMetal, txs)
r, err := ExecuteBlock(GPUMetal, 0, txs, nil, nil)
if err != nil {
t.Fatalf("GPU bytecode execute: %v", err)
}
@@ -198,10 +173,10 @@ func TestHealth_BackendParity(t *testing.T) {
}
// Group probe results by probe name → list of (backend, gas, status).
type point struct {
backend Backend
gas uint64
status TxStatus
probeOK bool
backend Backend
gas uint64
status TxStatus
probeOK bool
}
byProbe := map[string][]point{}
for _, r := range reports {
@@ -253,7 +228,7 @@ func TestConcurrentExecuteBlock(t *testing.T) {
txs[i] = bytecodeTx(uint64(i), code)
}
ref, err := ExecuteBlockV1(GPUMetal, txs)
ref, err := ExecuteBlock(GPUMetal, 0, txs, nil, nil)
if err != nil {
t.Fatalf("reference ExecuteBlock: %v", err)
}
@@ -268,7 +243,7 @@ func TestConcurrentExecuteBlock(t *testing.T) {
go func() {
defer func() { doneCh <- struct{}{} }()
for i := 0; i < iterations; i++ {
r, err := ExecuteBlockV1(GPUMetal, txs)
r, err := ExecuteBlock(GPUMetal, 0, txs, nil, nil)
if err != nil {
errCh <- err
return
@@ -337,7 +312,7 @@ func TestConcurrent_Stress(t *testing.T) {
}
// Single-goroutine reference run for total-gas assertion.
ref, err := ExecuteBlockV1(backend, makeBlock(0))
ref, err := ExecuteBlock(backend, 0, makeBlock(0), nil, nil)
if err != nil {
t.Fatalf("reference: %v", err)
}
@@ -362,7 +337,7 @@ func TestConcurrent_Stress(t *testing.T) {
}
}()
block := makeBlock(seed)
r, err := ExecuteBlockV1(backend, block)
r, err := ExecuteBlock(backend, 0, block, nil, nil)
if err != nil {
failed.Add(1)
t.Errorf("goroutine %d: ExecuteBlock: %v", seed, err)
@@ -399,7 +374,7 @@ func TestExecuteBlock_LargeCode(t *testing.T) {
}
tx := bytecodeTx(0, code)
tx.GasLimit = 50_000_000
r, err := ExecuteBlockV1(CPUSequential, []Transaction{tx})
r, err := ExecuteBlock(CPUSequential, 0, []Transaction{tx}, nil, nil)
if err != nil {
t.Fatalf("large code: %v", err)
}
@@ -419,7 +394,7 @@ func TestExecuteBlock_LargeData(t *testing.T) {
}
tx := smokeTx(0)
tx.Data = data
r, err := ExecuteBlockV1(CPUSequential, []Transaction{tx})
r, err := ExecuteBlock(CPUSequential, 0, []Transaction{tx}, nil, nil)
if err != nil {
t.Fatalf("large data: %v", err)
}
@@ -437,7 +412,7 @@ func TestExecuteBlock_EmptyCodeAndData(t *testing.T) {
// Explicitly clear in case smokeTx ever changes.
tx.Code = nil
tx.Data = nil
r, err := ExecuteBlockV1(CPUSequential, []Transaction{tx})
r, err := ExecuteBlock(CPUSequential, 0, []Transaction{tx}, nil, nil)
if err != nil {
t.Fatalf("empty code+data: %v", err)
}
@@ -470,7 +445,7 @@ func TestBackendUnavailable(t *testing.T) {
t.Errorf("requesting unavailable backend %s panicked: %v", missing, r)
}
}()
r, err := ExecuteBlockV1(missing, []Transaction{tx})
r, err := ExecuteBlock(missing, 0, []Transaction{tx}, nil, nil)
if err != nil {
t.Logf("unavailable backend %s returned error (expected): %v", missing, err)
return
@@ -503,7 +478,7 @@ func TestExecuteBlock_GasParity_AcrossBackends(t *testing.T) {
results := make(map[Backend]uint64)
for _, b := range available {
r, err := ExecuteBlockV1(b, makeTxs())
r, err := ExecuteBlock(b, 0, makeTxs(), nil, nil)
if err != nil {
t.Logf("ExecuteBlock(%s): %v (skipping in parity check)", b, err)
continue
+5 -42
View File
@@ -2,11 +2,11 @@
package cevm
import "fmt"
// No library linked, so there is no ABI to report.
const ABIVersion uint32 = 0
import "fmt"
// AutoDetect returns CPUSequential when built without CGo.
func AutoDetect() Backend { return CPUSequential }
@@ -19,48 +19,11 @@ func BackendName(b Backend) string { return b.String() }
// LibraryABIVersion returns the Go-side constant when there's no library.
func LibraryABIVersion() uint32 { return ABIVersion }
// ExecuteBlock is the canonical entry — returns an error under !cgo.
func ExecuteBlock(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResultV2, error) {
if len(txs) == 0 {
return &BlockResultV2{ABIVersion: ABIVersion}, nil
}
_ = ctx
_ = state
return nil, fmt.Errorf("cevm: built without CGo, cannot execute transactions (rebuild with CGO_ENABLED=1)")
}
// Deprecated: use ExecuteBlock.
func ExecuteBlockV1(backend Backend, txs []Transaction) (*BlockResult, error) {
if len(txs) == 0 {
return &BlockResult{}, nil
}
return nil, fmt.Errorf("cevm: built without CGo, cannot execute transactions (rebuild with CGO_ENABLED=1)")
}
// ExecuteBlockV2 returns an error when built without CGo.
func ExecuteBlockV2(backend Backend, numThreads uint32, txs []Transaction) (*BlockResultV2, error) {
if len(txs) == 0 {
return &BlockResultV2{ABIVersion: ABIVersion}, nil
}
return nil, fmt.Errorf("cevm: built without CGo, cannot execute transactions (rebuild with CGO_ENABLED=1)")
}
// ExecuteBlockV3 returns an error when built without CGo. Mirrors the
// V3 cgo signature so the package surface is identical regardless of
// build mode.
func ExecuteBlockV3(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext) (*BlockResultV2, error) {
if len(txs) == 0 {
return &BlockResultV2{ABIVersion: ABIVersion}, nil
}
_ = ctx
return nil, fmt.Errorf("cevm: built without CGo, cannot execute transactions (rebuild with CGO_ENABLED=1)")
}
// ExecuteBlockV4 returns an error when built without CGo. Mirrors the V4
// ExecuteBlock returns an error when built without CGo. Mirrors the V4
// cgo signature so consumers can call it unconditionally.
func ExecuteBlockV4(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResultV2, error) {
func ExecuteBlock(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResult, error) {
if len(txs) == 0 {
return &BlockResultV2{ABIVersion: ABIVersion}, nil
return &BlockResult{ABIVersion: ABIVersion}, nil
}
_ = ctx
_ = state
+6 -32
View File
@@ -5,7 +5,7 @@ import "testing"
// Tests in this file run under both `cgo` and `!cgo` builds. They cover the
// parts of the API that don't depend on the C++ library: enum stringers,
// constant exports, and the empty-input fast-paths that ExecuteBlock /
// ExecuteBlockV2 promise to return without error.
// ExecuteBlock promise to return without error.
func TestBackendString(t *testing.T) {
tests := []struct {
@@ -73,29 +73,19 @@ func TestPluginExists(t *testing.T) {
_ = PluginExists()
}
// TestExecuteBlockEmpty must succeed on both cgo and nocgo paths: the API
// contract is "empty input ⇒ empty result, no error" regardless of build.
// TestExecuteBlockEmpty pins the contract for a block with no transactions:
// no error, no gas, and the ABI the linked library reports.
func TestExecuteBlockEmpty(t *testing.T) {
result, err := ExecuteBlockV1(CPUSequential, nil)
result, err := ExecuteBlock(CPUSequential, 0, nil, nil, nil)
if err != nil {
t.Fatalf("ExecuteBlock(nil) returned error: %v", err)
t.Fatalf("ExecuteBlock(nil): %v", err)
}
if result.TotalGas != 0 {
t.Errorf("expected 0 total gas for empty block, got %d", result.TotalGas)
}
}
func TestExecuteBlockV2Empty(t *testing.T) {
result, err := ExecuteBlockV2(CPUSequential, 0, nil)
if err != nil {
t.Fatalf("ExecuteBlockV2(nil) returned error: %v", err)
t.Errorf("TotalGas = %d, want 0 for an empty block", result.TotalGas)
}
if result.ABIVersion != ABIVersion {
t.Errorf("ABIVersion = %d, want %d", result.ABIVersion, ABIVersion)
}
if result.TotalGas != 0 {
t.Errorf("expected 0 total gas for empty block, got %d", result.TotalGas)
}
}
// TestAvailableBackends_NonEmpty: every build must expose at least one backend.
@@ -126,22 +116,6 @@ func TestHealth_AlwaysReturnsReports(t *testing.T) {
}
}
// TestExecuteBlockV3Empty checks the V3 entry point honours the same
// "empty input ⇒ empty result, no error" contract as V1/V2, on both
// cgo and nocgo builds.
func TestExecuteBlockV3Empty(t *testing.T) {
result, err := ExecuteBlockV3(CPUSequential, 0, nil, nil)
if err != nil {
t.Fatalf("ExecuteBlockV3(nil, nil) returned error: %v", err)
}
if result.ABIVersion != ABIVersion {
t.Errorf("ABIVersion = %d, want %d", result.ABIVersion, ABIVersion)
}
if result.TotalGas != 0 {
t.Errorf("expected 0 total gas for empty block, got %d", result.TotalGas)
}
}
// TestABIVersion locks the ABIVersion constant so a careless bump is caught
// by code review. Update both this test and ABIVersion together when the
// C ABI surface changes.
+3 -3
View File
@@ -278,8 +278,8 @@ func recordOutcome(o opcodeOutcome) {
// ----------------------------------------------------------------------------
// runTx builds a Transaction for the given opcode test and runs it through
// `backend`. It always calls ExecuteBlockV2 so we get per-tx status.
func runOpcodeTx(backend Backend, ot opcodeTest) (*BlockResultV2, error) {
// `backend`. It always calls ExecuteBlock so we get per-tx status.
func runOpcodeTx(backend Backend, ot opcodeTest) (*BlockResult, error) {
var from [20]byte
from[19] = 0x42
tx := Transaction{
@@ -303,7 +303,7 @@ func runOpcodeTx(backend Backend, ot opcodeTest) (*BlockResultV2, error) {
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
}
}
return ExecuteBlockV2(backend, 1, []Transaction{tx})
return ExecuteBlock(backend, 1, []Transaction{tx}, nil, nil)
}
// ----------------------------------------------------------------------------
+4 -4
View File
@@ -5,7 +5,7 @@
//
// LP-108 (2026-05-04) ENSURE step: the per-tx TransactionExecutor
// abstraction in luxfi/evm/core/parallel was the wrong shape for
// cevm. cevm.ExecuteBlockV3 is block-batched; the per-tx wrapper
// cevm. cevm.ExecuteBlock is block-batched; the per-tx wrapper
// in luxfi/evm/core/parallel/backend_cevm.go always returned
// (nil, nil). This package implements luxfi/evm/core/parallel's
// BlockExecutor interface (whole-block) which is the natural shape
@@ -47,8 +47,8 @@ import (
"sync"
"github.com/luxfi/crypto/backend"
"github.com/luxfi/evm/core/state"
evmparallel "github.com/luxfi/evm/core/parallel"
"github.com/luxfi/evm/core/state"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/core/vm"
@@ -111,7 +111,7 @@ func declineBlock(reason string, blockNumber, txIndex uint64) ([]*types.Receipt,
}
// Executor is a luxfi/evm/core/parallel.BlockExecutor that dispatches
// every block to cevm.ExecuteBlockV3 in one cgo call.
// every block to cevm.ExecuteBlock in one cgo call.
//
// Compile-time interface check; the var line below ensures Executor
// satisfies BlockExecutor.
@@ -131,7 +131,7 @@ type Executor struct {
var _ evmparallel.BlockExecutor = (*Executor)(nil)
// ExecuteBlock implements evmparallel.BlockExecutor. Dispatches the
// whole block in one cgo call to cevm.ExecuteBlockV3 and reconstructs
// whole block in one cgo call to cevm.ExecuteBlock and reconstructs
// receipts.
//
// Returns (nil, nil) — the documented "fall through to sequential"
+10 -10
View File
@@ -21,13 +21,13 @@ import (
// We don't assert TxOK strictly — the LP-108 P5 corpus is the cevm side's
// gate for which CALL shapes the GPU CALL trampoline supports. What we
// verify here is that:
// 1. ExecuteBlockV4 returns without error.
// 2. The result has the new ABIVersion (6) — proves the v4 ABI wired
// end-to-end.
// 3. Per-tx status / gas slices have the right length.
// 4. Backends that DO support state-aware CALL return a non-OOG status
// (i.e. the kernel made progress past the kernel-internal "not
// supported" sentinel).
// 1. ExecuteBlockV4 returns without error.
// 2. The result has the new ABIVersion (6) — proves the v4 ABI wired
// end-to-end.
// 3. Per-tx status / gas slices have the right length.
// 4. Backends that DO support state-aware CALL return a non-OOG status
// (i.e. the kernel made progress past the kernel-internal "not
// supported" sentinel).
func TestExecuteBlockV4_StateSnapshot_GPUCall(t *testing.T) {
backends := cevm.AvailableBackends()
if len(backends) == 0 {
@@ -97,7 +97,7 @@ func TestExecuteBlockV4_StateSnapshot_GPUCall(t *testing.T) {
for _, b := range backends {
b := b
t.Run(cevm.BackendName(b), func(t *testing.T) {
r, err := cevm.ExecuteBlockV4(b, 0, []cevm.Transaction{tx}, nil, state)
r, err := cevm.ExecuteBlock(b, 0, []cevm.Transaction{tx}, nil, state)
if err != nil {
t.Fatalf("ExecuteBlockV4: %v", err)
}
@@ -141,11 +141,11 @@ func TestExecuteBlockV4_EmptySnapshot(t *testing.T) {
for _, b := range backends {
b := b
t.Run(cevm.BackendName(b), func(t *testing.T) {
r3, err3 := cevm.ExecuteBlockV3(b, 0, []cevm.Transaction{tx}, nil)
r3, err3 := cevm.ExecuteBlock(b, 0, []cevm.Transaction{tx}, nil, nil)
if err3 != nil {
t.Fatalf("V3: %v", err3)
}
r4, err4 := cevm.ExecuteBlockV4(b, 0, []cevm.Transaction{tx}, nil, nil)
r4, err4 := cevm.ExecuteBlock(b, 0, []cevm.Transaction{tx}, nil, nil)
if err4 != nil {
t.Fatalf("V4: %v", err4)
}