cevm

package
v1.3.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: BSD-3-Clause Imports: 10 Imported by: 0

README

cevm — C++ EVM execution backend

The CGO bridge to the C++ EVM in ~/work/luxcpp/cevm. Selected at link time via build tags from chains/evm/backend_{cgo,nocgo}.go.

This is not a standalone VM. It is the GPU-accelerated execution backend for C-Chain when built with LUX_CGO=1. The pure-Go fallback (luxfi/geth) is used when LUX_CGO=0.

Layout

chains/evm/
├── main.go                 # luxd plugin entry
├── backend_cgo.go          # cgo backend selector (LUX_CGO=1)
├── backend_nocgo.go        # pure-Go selector (LUX_CGO=0)
└── cevm/                   # this package — C++ FFI shim
    ├── cevm.go             # Go API
    ├── cevm_cgo.go         # cgo bindings (linked against luxcpp libs)
    ├── cevm_nocgo.go       # no-op stubs for !cgo builds
    └── plugin.go           # plugin registration glue

Linking

cevm_cgo.go references ${SRCDIR}/../../../../luxcpp/... for headers and libs. From ~/work/lux/chains/evm/cevm/ that resolves to ~/work/luxcpp/. For module-cache builds, see accel's fetch-luxcpp.sh pattern (TODO: port the same approach here).

Performance

When CGO is enabled and luxcpp libs are available:

  • ~3–5× the throughput of pure-Go EVM
  • SIMD opcode dispatch (AVX2/NEON)
  • GPU batch operations via Block-STM (CUDA/Metal)

Provenance

Folded into chains/evm from the previously-standalone luxfi/cevm repo on 2026-04-30. cevm is internal-only — no operator daemon. C-Chain runs in luxd. See ~/work/lux/chains/PLUGGABLE.md for the canonicalization pattern that governs the rest of the chain VMs.

Documentation

Overview

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 with CGo: CGO_ENABLED=1 go build -tags cgo Build without CGo: CGO_ENABLED=0 go build (types only, no execution) Binary: the `cevm` binary in luxcpp/evm/build/bin/ is the Lux VM plugin.

Concurrency model

ExecuteBlock and ExecuteBlockV2 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.

Index

Constants

View Source
const ABIVersion uint32 = 6

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 ExecuteBlockV3 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.

Variables

This section is empty.

Functions

func BackendName

func BackendName(b Backend) string

BackendName returns the human-readable name of a backend as reported by the C++ library (which is authoritative).

func BatchRecoverSenders

func BatchRecoverSenders(txs types.Transactions, signer types.Signer) ([]common.Address, error)

BatchRecoverSenders recovers the sender address for every transaction in the input slice in a single cgo dispatch into the luxcpp/crypto first-party secp256k1 ecrecover pipeline.

The output slice is the same length as txs and indexed positionally — i.e. out[i] is the sender of txs[i]. Per-tx behaviour matches types.Sender byte-for-byte; that contract is enforced by the parity test in cevm_secp256k1_parity_test.go.

On any per-tx recovery failure the call returns an error naming the offending tx index. This matches the existing parallel.Executor behaviour (Stage 1 in parallel/parallel.go) which errors on the first failed sender rather than mixing successful and failed senders mid-block.

The recovered addresses are written into each tx's sigCache via types.CacheSender, so subsequent calls to types.Sender(signer, tx) return the cached value and skip recomputation.

func LibraryABIVersion

func LibraryABIVersion() uint32

LibraryABIVersion returns the ABI version reported by the loaded library. Useful for diagnostics when binaries and shared libs may drift.

func PluginExists

func PluginExists() bool

PluginExists reports whether the cevm plugin binary is present on disk.

func PluginPath

func PluginPath() string

PluginPath returns the absolute path to the cevm VM plugin binary. Used by lux CLI and universe Makefiles to locate the built plugin.

func VMID

func VMID() string

VMID returns the VM ID for the cevm plugin. This is the identifier used by Lux subnet configuration to reference this VM in the plugin directory.

Types

type Backend

type Backend int

Backend selects the C++ EVM execution mode.

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
)

func AutoDetect

func AutoDetect() Backend

AutoDetect returns the best available backend for this machine.

func AvailableBackends

func AvailableBackends() []Backend

AvailableBackends returns the list of backends compiled and detected at runtime by the loaded library.

func (Backend) String

func (b Backend) String() string

String returns the human-readable name of the backend.

type BlockContext

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
}

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 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 BlockResult

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
}

BlockResult holds the outcome of executing a block of transactions.

func ExecuteBlockV1 added in v1.2.7

func ExecuteBlockV1(backend Backend, txs []Transaction) (*BlockResult, error)

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.

type BlockResultV2

type BlockResultV2 struct {
	StateRoot    [32]byte
	GasUsed      []uint64
	Status       []TxStatus
	TotalGas     uint64
	ExecTimeMs   float64
	Conflicts    uint32
	ReExecutions uint32
	ABIVersion   uint32
}

BlockResultV2 extends BlockResult with the V2 ABI fields: per-tx status and the post-execution state root.

func ExecuteBlock

func ExecuteBlock(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResultV2, error)

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 ExecuteBlockV2

func ExecuteBlockV2(backend Backend, numThreads uint32, txs []Transaction) (*BlockResultV2, error)

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 ExecuteBlockV3 deprecated

func ExecuteBlockV3(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext) (*BlockResultV2, error)

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 ExecuteBlockV4 deprecated

func ExecuteBlockV4(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResultV2, error)

ExecuteBlockV4 is the legacy name for ExecuteBlock. New code uses ExecuteBlock; this is retained for test-file compatibility.

Deprecated: use ExecuteBlock.

type HealthProbeResult

type HealthProbeResult struct {
	Name    string
	OK      bool
	GasUsed uint64
	Status  TxStatus
	Err     error
}

HealthProbeResult is the outcome of a single probe on a single backend.

type HealthReport

type HealthReport struct {
	Backend      Backend
	Name         string
	OK           bool
	Err          error
	Probe        string // first failing probe name, empty when OK
	ProbesRun    int
	ProbeResults []HealthProbeResult
	// Aggregate stats — sum of gas across probes, time of the last probe.
	GasUsed  uint64
	Status   TxStatus
	ExecTime float64
}

HealthReport is the per-backend result of Health(). It aggregates the per-probe results into a single OK / not-OK signal: a backend is healthy iff every probe ran to its expected status with non-zero gas.

func Health

func Health() []HealthReport

Health runs a battery of canonical bytecode programs through every backend the loaded library exposes and returns a per-backend report. Use at process start to fail-fast on misconfigured GPUs (driver missing, library mismatch, device permissions, kernel coverage gaps). Returns nil only if the runtime cannot enumerate backends at all.

The battery covers:

  • arithmetic (ADD/POP) — strict gas parity required across backends
  • storage (SSTORE / SLOAD) — strict gas parity required
  • hashing (KECCAK256) — non-zero gas required, parity not strict
  • memory ops (MSTORE / MLOAD / MCOPY) — non-zero gas required, parity not strict
  • the CALL bridge (CALL with a constant target) — must complete cleanly

A backend is reported OK iff every probe executed to its expected status with non-zero gas AND its gas matches every other backend on the strict- parity probes. A failure sets Err and Probe to identify the offending case.

type StateAccount

type StateAccount struct {
	Address  [20]byte
	Nonce    uint64
	Balance  [4]uint64
	Code     []byte
	CodeHash [32]byte
}

StateAccount is one entry in the snapshot of touched accounts handed to ExecuteBlockV4. 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 Transaction

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
}

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 TxStatus

type TxStatus uint8

TxStatus is a per-transaction execution outcome from the V2 ABI.

const (
	TxOK               TxStatus = 0 // STOP / clean exit
	TxReturn           TxStatus = 1
	TxRevert           TxStatus = 2
	TxOOG              TxStatus = 3
	TxError            TxStatus = 4
	TxCallNotSupported TxStatus = 5
)

func (TxStatus) String

func (s TxStatus) String() string

String returns a short label for the tx status.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL