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

118 lines
4.4 KiB
Go

// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"github.com/luxfi/chains/relayvm"
"github.com/luxfi/log"
"github.com/luxfi/vm/chain"
"github.com/luxfi/vm/rpc/runtime"
)
// The `version` subcommand prints the id the binary must be INSTALLED as, not
// just the software version.
//
// The two facts fail differently and an operator needs both. A wrong software
// version starts a chain that misbehaves; a wrong FILENAME starts nothing at
// all — the node's registry resolves a CreateChainTx's vmID by looking for a
// file with that name, so a correctly built plugin under the wrong name is
// invisible, and the symptom is a chain that never appears with no error
// anywhere. Printing the id here puts it where an operator already looks.
func TestVersionNamesTheIdTheBinaryMustBeInstalledAs(t *testing.T) {
var out, errOut bytes.Buffer
if code := run(context.Background(), []string{"version"}, &out, &errOut); code != 0 {
t.Fatalf("version exited %d, want 0; stderr=%q", code, errOut.String())
}
got := out.String()
if !strings.Contains(got, relayvm.VMID.String()) {
t.Errorf("version output %q does not name the vmID %s the plugin must be installed as",
got, relayvm.VMID)
}
if errOut.Len() != 0 {
t.Errorf("version wrote to stderr: %q", errOut.String())
}
}
// The version printed is the VM's own answer, not a literal beside it. The
// plugin used to print a hardcoded "Relay-VM/1.0.0" while the VM answered with
// its own "1.0.0" from luxfi/relay; the two agreed by coincidence, and the
// first bump of either would have left the one an operator reads — the
// plugin's — wrong.
func TestTheVersionIsTheVMsOwn(t *testing.T) {
raw, err := (&relayvm.Factory{}).New(log.Root())
if err != nil {
t.Fatalf("Factory.New: %v", err)
}
want, err := raw.(chain.ChainVM).Version(context.Background())
if err != nil {
t.Fatalf("VM.Version: %v", err)
}
var out, errOut bytes.Buffer
if code := run(context.Background(), []string{"version"}, &out, &errOut); code != 0 {
t.Fatalf("version exited %d, want 0; stderr=%q", code, errOut.String())
}
if !strings.Contains(out.String(), want) {
t.Errorf("version output %q does not carry the VM's own version %q", out.String(), want)
}
}
// Anything that is not `version` serves, and serving needs a node. Started by
// hand there is no engine address in the environment, so the plugin says why it
// is not serving and exits non-zero.
//
// The exit code is the load-bearing half. A plugin that fails to serve and
// exits 0 looks to the node's runtime like a plugin that ran and finished, so
// the chain never starts and nothing reports a failure — the same silent
// absence a misnamed binary produces.
func TestAPluginWithNoNodeToServeSaysSoAndFails(t *testing.T) {
t.Setenv(runtime.EngineAddressKey, "")
var out, errOut bytes.Buffer
if code := run(context.Background(), nil, &out, &errOut); code != 1 {
t.Fatalf("run with no engine address exited %d, want 1", code)
}
if !strings.Contains(errOut.String(), "relayvm plugin:") {
t.Errorf("stderr %q does not name the plugin as the source of the failure", errOut.String())
}
if !strings.Contains(errOut.String(), runtime.EngineAddressKey) {
t.Errorf("stderr %q does not name the missing environment variable %q",
errOut.String(), runtime.EngineAddressKey)
}
}
// An empty argument list and an argument that is not `version` take the same
// path: there is one subcommand, and everything else is "serve".
func TestOnlyVersionIsASubcommand(t *testing.T) {
t.Setenv(runtime.EngineAddressKey, "")
for _, args := range [][]string{nil, {}, {"serve"}, {"Version"}, {"--version"}} {
var out, errOut bytes.Buffer
if code := run(context.Background(), args, &out, &errOut); code != 1 {
t.Errorf("run(%q) exited %d, want 1 (it must have tried to serve)", args, code)
}
if out.Len() != 0 {
t.Errorf("run(%q) wrote %q to stdout; only `version` reports on stdout", args, out.String())
}
}
}
// fail is the one place a refusal is reported, so the message names the plugin
// and the code is non-zero whatever went wrong.
func TestEveryRefusalIsAttributedAndNonZero(t *testing.T) {
var w bytes.Buffer
if code := fail(&w, errors.New("disk fell off")); code != 1 {
t.Fatalf("fail() = %d, want 1", code)
}
if got := w.String(); got != "relayvm plugin: disk fell off\n" {
t.Fatalf("fail() wrote %q", got)
}
}