Files
zeekay ec074f11aa evm, oraclevm, relayvm: a vmID nobody compared, and a policy no rollback could reach
The two shims re-exported luxfi/oracle's and luxfi/relay's private vmID
literals. constants.OracleVMID and constants.RelayVMID exist, agree with them,
and nothing made them agree. This repo builds the plugin binary whose FILENAME
is that id, so the shims now name the constant and their tests compare all
three; a drift upstream fails here instead of producing a chain no node starts.

cevm gets ErrNotLinked, so a caller can tell "this build has no C++ EVM" from
"this input is bad". cevm/parallel's sender-recovery step returned a hard error
for the first, which in a build without the library is every block, forever,
and CEVM_STRICT=0 -- the documented rollback to the Go EVM -- did not reach
that line. It now leaves by declineBlock like every other capability refusal.

Its receipt gate asked whether a transaction carried calldata. A contract
creation with empty init code carries none, so it was receipted as a value
transfer: no ContractAddress, and 21000 intrinsic gas against creation's 53000,
into the cumulative total the header commits to.

ExecuteBlock splits into shape / dispatch / assemble so the parity-critical
half needs no library to exercise. strictGPUEVM loses the sync.Once that fixed
its answer at the first decline and left both branches unreachable from one
process.

The native build referenced gpu_execute_block and gpu_free_result, which ABI v6
removed, so nothing behind lux_cevm_native compiled at all.

The three plugins print the version the VM reports rather than a second literal
beside it, and the vmID the binary must be installed under: a plugin under the
wrong filename is invisible and the chain simply never appears.

cevm/plugin.go is gone. It located a binary at ~/work/luxcpp/evm/build/bin/cevm,
a path that has never existed -- the tree is luxcpp/cevm -- claimed the lux CLI
and universe Makefiles used it, which neither does, and returned "cevm" from a
function called VMID. Its two tests skipped and asserted nothing.

.gitignore's bare `plugin` matched every <vm>/cmd/plugin DIRECTORY, so a new
file under one could not be added: git printed a hint and staged nothing.
mpcvm's and bridgevm's plugin tests are written, pass, and are not in the
repository.

Coverage: evm 0.0 -> 85.0, evm/cevm 71.1 -> 100.0, evm/cevm/parallel 0.0 -> 86.1,
oraclevm 0.0 -> 90.0, oraclevm/cmd/plugin 0.0 -> 72.2, relayvm/cmd/plugin 0.0 -> 72.2.
2026-08-28 15:01:34 -07:00

228 lines
8.2 KiB
Go

// Package cevm provides Go bindings to the C++ EVM (cevm) with GPU acceleration.
// Import this package to use the C++ EVM as a drop-in replacement for go-ethereum's EVM.
//
// The C++ EVM supports:
// - Block-STM parallel execution
// - GPU Keccak-256 state hashing (Metal/CUDA)
// - GPU batch ecrecover (Metal/CUDA)
// - GPU EVM opcode interpreter (Metal/CUDA)
// - ZAP VM plugin protocol (native)
//
// Build against the native library: CGO_ENABLED=1 go build -tags lux_cevm_native
// Build without it: go build (types only, no execution) — the default,
// and the only thing that works on a host lacking the lux-cevm bundle.
// Binary: the `cevm` binary in luxcpp/evm/build/bin/ is the Lux VM plugin.
//
// # Concurrency model
//
// 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
// []C.CGpuTx for its inputs and a fresh runtime.Pinner for its lifetime.
// The pinner pins the base address of every Go-owned []byte (tx.Data,
// tx.Code) that the C side dereferences, and is unpinned via defer
// after the C call returns — including on the error path.
//
// 2. The C result is freed via defer (gpu_free_result / gpu_free_result_v2)
// on every code path including failure. Gas/status arrays are copied
// into Go-owned slices before the result is freed.
//
// 3. The C++ engine uses a thread_local engine cache (one per OS thread
// reached by goroutines via cgo) for the Keccak hasher; per-instance
// MTLBuffer / CUDA context caches are mutex-protected on the C++ side.
// Two goroutines on different OS threads use independent kernel state.
//
// 4. The CPU path is fully reentrant: each call constructs a fresh
// cevm state and tears it down before returning.
//
// What is NOT safe:
// - Mutating the Transaction.Data or Transaction.Code slices while a
// concurrent ExecuteBlock call is reading them. The pinner only
// prevents GC moves; it does not provide read/write synchronization.
// - Sharing a *BlockResult between goroutines without external sync.
//
// # ABI version
//
// The Go module's ABIVersion constant is checked against the loaded
// library's gpu_abi_version() in init(). A mismatch panics at process
// start — that is intentional. A silent ABI mismatch produces wrong
// gas/state results and would corrupt consensus, so fail-fast is the
// only safe behaviour.
//
// Use Health() at startup to additionally verify each backend executes
// the canonical health-check battery (arithmetic, storage, hashing,
// memory, and the call bridge) without error.
package cevm
import (
"errors"
"fmt"
)
// ErrNotLinked is what every entry point that needs the native luxcpp library
// returns when this binary was built without it — the default build, and any
// build on a host lacking the lux-cevm bundle.
//
// It is a value rather than a message because a caller has to tell it apart
// from a bad input, and the two lead to opposite decisions: a build that
// cannot execute declines the block to whatever else can, while a block that
// cannot be executed is a hard failure. A caller that could only read the
// message either treated a missing library as a corrupt block, or — worse —
// fell back on a signature the library had rejected.
var ErrNotLinked = errors.New("cevm: native EVM not linked (rebuild with CGO_ENABLED=1 -tags=lux_cevm_native)")
// 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 (
// CPUSequential runs transactions one at a time on a single core.
CPUSequential Backend = 0
// CPUParallel uses Block-STM to run transactions across all cores.
CPUParallel Backend = 1
// GPUMetal offloads Keccak, ecrecover, and the EVM interpreter to Metal.
GPUMetal Backend = 2
// GPUCUDA offloads Keccak, ecrecover, and the EVM interpreter to CUDA.
GPUCUDA Backend = 3
)
// String returns the human-readable name of the backend.
func (b Backend) String() string {
switch b {
case CPUSequential:
return "cpu-sequential"
case CPUParallel:
return "cpu-parallel"
case GPUMetal:
return "gpu-metal"
case GPUCUDA:
return "gpu-cuda"
default:
return fmt.Sprintf("unknown(%d)", int(b))
}
}
// Transaction is a single EVM transaction to execute.
//
// When Code is non-empty AND a GPU backend is selected, the C++ EVM
// dispatches each tx through the parallel opcode interpreter (Metal:
// kernel::EvmKernelHost, CUDA: cuda::EvmKernel). When Code is empty, GPU
// backends use the scheduler-only Block-STM kernel.
type Transaction struct {
From [20]byte
To [20]byte
HasTo bool
Data []byte // Calldata
Code []byte // EVM bytecode (optional — required for real GPU execution)
GasLimit uint64
Value uint64
Nonce uint64
GasPrice uint64
}
// 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
TxCallNotSupported TxStatus = 5
)
// String returns a short label for the tx status.
func (s TxStatus) String() string {
switch s {
case TxOK:
return "ok"
case TxReturn:
return "return"
case TxRevert:
return "revert"
case TxOOG:
return "oog"
case TxError:
return "error"
case TxCallNotSupported:
return "call-not-supported"
default:
return fmt.Sprintf("status(%d)", int(s))
}
}
// 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 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
// behaviour.
//
// Field layout matches the C-side CBlockContext byte-for-byte: this struct
// is passed to the C ABI via direct memcpy, no field-by-field translation.
// Field order MUST match go_bridge.h CBlockContext exactly. Adding new
// fields requires bumping ABIVersion and the C-side EVM_GPU_ABI_VERSION
// in lockstep.
type BlockContext struct {
Origin [20]byte
GasPrice uint64
Timestamp uint64
Number uint64
Prevrandao [32]byte
GasLimit uint64
ChainID uint64
BaseFee uint64
BlobBaseFee uint64
Coinbase [20]byte
BlobHashes [8][32]byte
NumBlobHashes uint32
}
// ABIVersion is the C ABI version this Go module expects. Compare against
// the loaded library's gpu_abi_version() to detect version skew.
//
// 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 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
// the kernel CALL/CREATE path resolves targets on-device instead of
// returning CallNotSupported. V3 callers still see the same wire shape.
//
// StateAccount is one entry in the snapshot of touched accounts handed to
// 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).
//
// Address is canonical 20-byte big-endian. Balance is little-endian limbs
// (Balance[0] = low 64 bits). Code may be nil for EOAs — empty code is the
// EOA marker. CodeHash should be keccak256(code); the dispatcher does not
// recompute it because callers usually have it cached on the StateDB side.
type StateAccount struct {
Address [20]byte
Nonce uint64
Balance [4]uint64
Code []byte
CodeHash [32]byte
}