mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
cevm's Go entry is gpu_execute_block (go_bridge.h, ABI 6); the gpu_execute_block_v4 and gpu_free_result_v2 this package called are gone from the library, so the native build did not link. The entry answers ok=0 for any block it does not run — any batch with code, and any refusal on balance, nonce, price, block gas limit, revision or hashes — and that block is the caller's. ExecuteBlock returns ErrDeclined for it, with no result, and the executor declines the block to the Go EVM: (nil, nil), counted. A decline is never an error, so CEVM_STRICT and ErrGPUEVMRequired are gone; the state processor fell through on that error anyway. Nothing wider than the 64-bit wire reaches cevm. A gas price or fee cap above 2^64-1 crossed as 0; it, a value, a base fee or a chain id that does not fit now declines the block, as does a tx the wire drops part of: an access list, a blob or set-code tx, a tip above its fee cap. Snapshot rows carry what the header asks for: keccak256 of nothing and the empty trie root for an account that does not exist — the StateDB answers zero, which cevm reads as saying nothing and declines — the real storage root otherwise, and a row for the coinbase. cevm_cgo.go checks the four C structs' sizes at compile time, so a header that grows a field cannot have it cross as zero unnoticed. The call into cevm is a field on the Executor, so the tests show a declined block going to the Go EVM and a value of 2^64 never reaching cevm without the library. Co-authored-by: Hanzo Dev <[email protected]>
245 lines
9.3 KiB
Go
245 lines
9.3 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) 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)")
|
|
|
|
// ErrDeclined is what ExecuteBlock returns when the library answers ok=0
|
|
// (go_bridge.h): the result is not the block's, and the caller runs the block
|
|
// on its own EVM. cevm declines any batch in which a transaction carries code,
|
|
// and any batch its value-transfer paths cannot run as the EVM does — a
|
|
// refusal on balance, nonce, price, block gas limit or revision, a snapshot
|
|
// row without its hashes, or a device that failed.
|
|
//
|
|
// A declined result carries no gas or status a caller may use, so ExecuteBlock
|
|
// returns none with it.
|
|
var ErrDeclined = errors.New("cevm: declined the block (ok=0); the caller runs it")
|
|
|
|
// BlockResult is what ExecuteBlock returns for a block cevm ran: per-tx gas
|
|
// and status. StateRoot is zero: gpu_execute_block roots no state.
|
|
type BlockResult struct {
|
|
StateRoot [32]byte
|
|
GasUsed []uint64
|
|
Status []TxStatus
|
|
TotalGas uint64
|
|
ExecTimeMs float64
|
|
Conflicts uint32
|
|
ReExecutions uint32
|
|
ABIVersion uint32
|
|
}
|
|
|
|
// Backend selects the C++ EVM execution mode.
|
|
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.
|
|
//
|
|
// cevm runs a batch only when it is plain value transfers on a GPU backend's
|
|
// value-transfer path: a batch in which any transaction carries Code is
|
|
// declined (ErrDeclined), because gpu_execute_block passes no host and would
|
|
// run that code as a message, on its whole limit, which is not the tx's gas.
|
|
//
|
|
// Value and GasPrice are 64-bit on the wire. A transaction whose value or
|
|
// price does not fit is the caller's to run; it never belongs in a batch.
|
|
type Transaction struct {
|
|
From [20]byte
|
|
To [20]byte
|
|
HasTo bool
|
|
Data []byte // Calldata
|
|
Code []byte // The recipient's code; any makes cevm decline the batch
|
|
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. The binding
|
|
// copies it into a CBlockContext field by field, and the C side memcpy's that
|
|
// into evm::gpu::BlockContext; the Go layout is pinned to the C one all the
|
|
// same (TestBlockContextIsTheWireLayout, and the size check in cevm_cgo.go).
|
|
// 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 (cevm_cgo.go / cevm_nocgo.go) is the C ABI this build links:
|
|
// go_bridge.h's EVM_GPU_ABI_VERSION, 6. v6 is one entry, gpu_execute_block,
|
|
// taking a block context and a state snapshot (CGpuStateAccount) and
|
|
// answering per-tx gas and status with ok, freed by gpu_free_result. The
|
|
// versioned _v2/_v3/_v4 entries are gone.
|
|
|
|
// StateAccount is one entry in the snapshot of touched accounts handed to
|
|
// ExecuteBlock, the state before the block. Fields mirror the C-side
|
|
// CGpuStateAccount (modulo the inline `Code` slice which the binding flattens
|
|
// into a single blob before crossing the cgo boundary).
|
|
//
|
|
// The snapshot must hold every account a transaction touches: cevm takes an
|
|
// account it lacks not to exist, so a transfer to a contract left out runs as
|
|
// a transfer to an empty account.
|
|
//
|
|
// 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 is keccak256(code), keccak256 of nothing
|
|
// (types.EmptyCodeHash) for an account with no code, including one that does
|
|
// not exist; zero says nothing, and cevm declines a batch whose sender or
|
|
// recipient row carries it. StorageRoot is the account's storage trie root,
|
|
// types.EmptyRootHash for an account with no storage.
|
|
type StateAccount struct {
|
|
Address [20]byte
|
|
Nonce uint64
|
|
Balance [4]uint64
|
|
Code []byte
|
|
CodeHash [32]byte
|
|
StorageRoot [32]byte
|
|
}
|