Files
node/vms/thresholdvm/thresholdvm_gpu_test.go
T
Hanzo AI 8385394b51 thresholdvm: re-export GPU substrate bridge from chains/thresholdvm
Thin re-export layer so consumers importing the legacy node path
github.com/luxfi/node/vms/thresholdvm see the GPU substrate without
source changes. ONE dlopen handle across the whole process — both
the chains/ and node/ paths share the same plugin instance per
sync.Once in chains/thresholdvm/backend.go.

Files (under vms/thresholdvm/):
- thresholdvm_gpu.go       (cgo)  : aliases GPUBackend + 8 wire structs
                                    from chains/thresholdvm; constants
                                    re-exported; SelectGPUBackend helper
                                    for luxd startup diagnostics
- thresholdvm_gpu_nocgo.go (!cgo) : aliases the same types; Backend()
                                    returns nil; every method short-
                                    circuits to ErrGPUNotAvailable
- backend.go               (cgo)  : SelectGPUBackend() — one-line
                                    diagnostic string in the cevm.go
                                    pattern (chains/evm/backend_cgo.go)
- thresholdvm_gpu_test.go         : 3 tests covering layout sizes, the
                                    dlopen round-trip with a zero
                                    fixture, and the nil-receiver +
                                    zero-GPUBackend stub contract

Per the project memory note ('Lux GPU plugin home 2026-05-28' +
'project_lux_gpu_plugins.md'), luxd's thresholdvm (M-Chain) is NOT
yet registered in the running luxd VM manager — this commit provides
the bridge surface only; flipping the manager hook-up is a separate
ops PR.

Constraints honored:
- Existing thresholdvm.go re-export (chains/thresholdvm alias wrapper)
  NOT modified — bridge is opt-in alongside it
- Both build flavors compile: go build + CGO_ENABLED=0 go build
- Tests pass: 3/3 under cgo, 3/3 under nocgo, race-clean
2026-06-05 15:36:26 -07:00

108 lines
3.9 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package thresholdvm
import (
"errors"
"testing"
"unsafe"
)
// TestNodeGPULayoutSizes pins the re-exported wire structs to the same
// device-side __align__(16) values declared in
// ~/work/lux-private/gpu-kernels/ops/mpcvm/cuda/mpcvm_kernels_common.cuh.
//
// Even though the structs are type aliases to chains/thresholdvm, this
// test sits at the node import boundary — if upstream sizes drift the
// node-side surface MUST also fail loud rather than miscompile.
func TestNodeGPULayoutSizes(t *testing.T) {
cases := []struct {
name string
want uintptr
got uintptr
}{
{"GPUCeremony", 128, unsafe.Sizeof(GPUCeremony{})},
{"GPUKeyShare", 368, unsafe.Sizeof(GPUKeyShare{})},
{"GPUContribution", 432, unsafe.Sizeof(GPUContribution{})},
{"GPUMPCVMState", 160, unsafe.Sizeof(GPUMPCVMState{})},
{"GPUMPCVMRoundDescriptor", 96, unsafe.Sizeof(GPUMPCVMRoundDescriptor{})},
{"GPUCeremonyOp", 96, unsafe.Sizeof(GPUCeremonyOp{})},
{"GPUContributionOp", 416, unsafe.Sizeof(GPUContributionOp{})},
{"GPUMPCVMTransitionResult", 176, unsafe.Sizeof(GPUMPCVMTransitionResult{})},
}
for _, c := range cases {
if c.got != c.want {
t.Errorf("%s: sizeof=%d want=%d", c.name, c.got, c.want)
}
}
}
// TestNodeGPUBackendRoundTrip is the dlopen round-trip required by the
// task spec. Two acceptable outcomes:
//
// 1. Plugin resolved: GPUBackendInstance() != nil && IsAvailable(); a
// zero-fixture CeremonyApply against the best backend returns rc=0
// (no ops processed) with no error.
//
// 2. Plugin absent: GPUBackendInstance() == nil; nil-receiver methods
// return ErrGPUNotAvailable. SelectGPUBackend reports the CPU-only
// diagnostic string.
//
// Both outcomes count as passing — the bridge correctly surfaces GPU
// state through the public node API.
func TestNodeGPUBackendRoundTrip(t *testing.T) {
b, diag := SelectGPUBackend()
t.Logf("SelectGPUBackend: %s", diag)
if b == nil {
// Plugin-absent path. The nil receiver contract must hold —
// every method returns ErrGPUNotAvailable, no panic.
_, err := (*GPUBackend)(nil).CeremonyApply(nil, nil, nil)
if !errors.Is(err, ErrGPUNotAvailable) {
t.Fatalf("nil receiver CeremonyApply: want ErrGPUNotAvailable, got %v", err)
}
return
}
// Plugin-resolved path. Zero fixture: 1-slot ceremony arena, no ops.
// The kernel walks zero ops and returns applied=0 with rc=0. Any
// non-zero rc means a real launcher failure.
desc := &GPUMPCVMRoundDescriptor{
ChainID: 0xCAFEBABE,
Round: 1,
TimestampNs: 1700000000_000000000,
Epoch: 1,
}
ceremonies := make([]GPUCeremony, 1)
applied, err := b.CeremonyApply(desc, nil, ceremonies)
if err != nil {
t.Fatalf("CeremonyApply(zero): %v", err)
}
if applied != 0 {
t.Errorf("CeremonyApply(zero): applied=%d want=0", applied)
}
}
// TestNodeGPUStubContract verifies the nocgo-equivalent contract on a
// zero-value GPUBackend: IsAvailable()==false and every state-machine
// method returns ErrGPUNotAvailable. The same contract holds under cgo
// when no plugin is dlopened — making this test build-flavor-independent.
func TestNodeGPUStubContract(t *testing.T) {
var b GPUBackend
if b.IsAvailable() {
t.Fatal("zero GPUBackend.IsAvailable() must be false")
}
if _, err := b.CeremonyApply(nil, nil, nil); !errors.Is(err, ErrGPUNotAvailable) {
t.Errorf("CeremonyApply: want ErrGPUNotAvailable, got %v", err)
}
if _, _, _, err := b.KeyShareApply(nil, nil, nil, nil, 0); !errors.Is(err, ErrGPUNotAvailable) {
t.Errorf("KeyShareApply: want ErrGPUNotAvailable, got %v", err)
}
if _, err := b.ContributionApply(nil, nil, nil, nil, 0); !errors.Is(err, ErrGPUNotAvailable) {
t.Errorf("ContributionApply: want ErrGPUNotAvailable, got %v", err)
}
if _, err := b.MPCTransition(nil, nil, nil, nil, nil); !errors.Is(err, ErrGPUNotAvailable) {
t.Errorf("MPCTransition: want ErrGPUNotAvailable, got %v", err)
}
}