mirror of
https://github.com/luxfi/chains.git
synced 2026-07-27 03:39:41 +00:00
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:
+22
-36
@@ -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).
|
||||
//
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user