mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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
108 lines
3.9 KiB
Go
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)
|
|
}
|
|
}
|