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

121 lines
3.5 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package oraclevm
import (
"errors"
"testing"
"github.com/luxfi/accel"
"github.com/luxfi/log"
)
// A host built around nothing defers its failure to whichever method the
// operator calls first, arbitrarily far from the mistake. NewAccelHost refuses
// at the point the mistake is made.
func TestNoVMIsRefusedAtConstruction(t *testing.T) {
h, err := NewAccelHost(nil)
if !errors.Is(err, ErrNilVM) {
t.Fatalf("NewAccelHost(nil) error = %v, want ErrNilVM", err)
}
if h != nil {
t.Fatalf("NewAccelHost(nil) returned a host (%p) alongside its refusal", h)
}
}
// The host wraps the VM it was given and holds a session for it. The embedded
// *VM is the promoted field every method of the VM reaches through, so a host
// that dropped it would answer for a different VM than the operator built.
func TestTheHostHoldsTheVMItWasGiven(t *testing.T) {
vm := newVM(t)
h, err := NewAccelHost(vm)
if err != nil {
t.Fatalf("NewAccelHost: %v", err)
}
t.Cleanup(func() { _ = h.Close() })
if h.VM != vm {
t.Fatalf("host wraps %p, want the VM it was given, %p", h.VM, vm)
}
if h.Session() == nil {
t.Fatal("Session() = nil; the host exists to carry one")
}
// The session is named for the chain, so per-VM GPU accounting attributes
// its work to O-Chain and not to whichever VM allocated first.
if got := h.Session().ID(); got != "oraclevm" {
t.Errorf("session id = %q, want %q", got, "oraclevm")
}
}
// Options reach the session. Without this, NewAccelHost's variadic parameter
// could be dropped on the floor and every caller would silently get the
// documented default instead of what it asked for.
func TestOptionsReachTheSession(t *testing.T) {
h, err := NewAccelHost(newVM(t), accel.WithPriority(accel.PriorityHigh))
if err != nil {
t.Fatalf("NewAccelHost: %v", err)
}
t.Cleanup(func() { _ = h.Close() })
if got := h.Session().Priority(); got != accel.PriorityHigh {
t.Errorf("session priority = %v, want %v", got, accel.PriorityHigh)
}
// And the documented default when nothing is asked for.
d, err := NewAccelHost(newVM(t))
if err != nil {
t.Fatalf("NewAccelHost: %v", err)
}
t.Cleanup(func() { _ = d.Close() })
if got := d.Session().Priority(); got != accel.PriorityNormal {
t.Errorf("default session priority = %v, want %v", got, accel.PriorityNormal)
}
}
// Close releases the session and leaves the VM alone: the host owns the GPU
// session, not the chain.
func TestCloseReleasesTheSessionAndNotTheVM(t *testing.T) {
vm := newVM(t)
h, err := NewAccelHost(vm)
if err != nil {
t.Fatalf("NewAccelHost: %v", err)
}
if err := h.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
if !h.Session().IsClosed() {
t.Fatal("Close returned nil but the session is still open")
}
if h.VM != vm {
t.Fatal("Close dropped the wrapped VM")
}
}
// A host with no session — the zero value, reachable through composite literal
// construction — closes without dereferencing it. Nothing to release is not an
// error.
func TestClosingAHostWithNoSessionIsNotAnError(t *testing.T) {
var h AccelHost
if err := h.Close(); err != nil {
t.Fatalf("Close() on a host with no session = %v, want nil", err)
}
if h.Session() != nil {
t.Fatal("Session() on a host with no session returned one")
}
}
// newVM builds an O-Chain VM through the Factory the node uses.
func newVM(t *testing.T) *VM {
t.Helper()
raw, err := (&Factory{}).New(log.Root())
if err != nil {
t.Fatalf("Factory.New: %v", err)
}
return raw.(*VM)
}