Files
node/vms/mpcvm/mpcvm_gpu_parity_test.go
T
zeekayandHanzo Dev d4323da799 node: rename thresholdvm → mpcvm; split M/F registries (LP-0130)
- vms/thresholdvm/ → vms/mpcvm/ (dir + inner files + package decl).
- node/vms.go optional-VM registry: drop ThresholdVMID row, add
  MPCVMID and FHEVMID rows (matching the new split).
- genesis/builder/registry.go: T-Chain entry → M-Chain (MPCVMID) +
  F-Chain (FHEVMID); registry_test.go parity assertions updated.
- genesis/builder/builder.go: TChainAliases → MChainAliases +
  FChainAliases; ThresholdVMID switch case split to MPCVMID / FHEVMID;
  optIn chain slice uses config.MChainGenesis + config.FChainGenesis
  (T-Chain slot retired).
- LLM.md: point at LP-0130 as canonical topology + fee spec; correct
  the quantumvm posture to service-only per LP-0130 §6 (Q-Chain has
  no user-payable blockspace).

Build + vet clean on chains/mpcvm/... + node/{vms/mpcvm,genesis,node}/...

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-03 01:23:03 -07:00

195 lines
5.8 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package mpcvm
// TestGPUBridgeCgoNocgoParity is the node-side mirror of the parity
// test in chains/mpcvm — proves that the re-exported GPUBackend
// method set produces byte-identical MPC ceremony state transitions
// regardless of build flavor (cgo / nocgo) and plugin presence.
//
// The node package is a thin alias layer over chains/mpcvm —
// type aliases on the wire structs and a re-exported GPUBackendInstance
// accessor — so the parity properties of the canonical bridge transfer
// directly. We re-run the four-op pipeline through the node-side
// surface to pin that the alias layer doesn't drop any state on the
// floor.
import (
"strings"
"testing"
chainsthreshold "github.com/luxfi/chains/mpcvm"
)
// TestGPUBridgeCgoNocgoParity runs a 3-of-5 FROST keygen ceremony
// through GPUBackendInstance() (the node-side accessor) and compares
// the resulting arena byte-for-byte to a fresh run via the canonical
// bridge in chains/mpcvm. Under cgo, the comparison is the
// upstream cgo bridge vs itself (both runs hit the same dispatcher).
// Under !cgo, the comparison is the upstream nocgo bridge vs itself.
// Either path proves the alias layer is transparent and the substrate
// transitions deterministically.
func TestGPUBridgeCgoNocgoParity(t *testing.T) {
const cid uint64 = 0xCEFA01CA001
const N = 16 // power of 2 for the open-addressing locator
var subject, seed [32]byte
for i := 0; i < 32; i++ {
subject[i] = byte(i + 1)
seed[i] = byte(0xA0 ^ i)
}
beginOps := []chainsthreshold.GPUCeremonyOp{
{
CeremonyID: cid,
DeadlineNs: 10_000_000_000,
Kind: 0, // kCeremonyOpBegin
CeremonyKind: 0, // kKindFrostKeygen → 3 rounds
Threshold: 3,
TotalParticipants: 5,
Subject: subject,
CeremonySeed: seed,
},
}
buildRound := func(round uint32) []chainsthreshold.GPUContributionOp {
ops := make([]chainsthreshold.GPUContributionOp, 5)
for h := uint32(0); h < 5; h++ {
var payload [384]byte
for k := 0; k < 16; k++ {
payload[k] = byte((round * 16) + h*4 + uint32(k))
}
ops[h] = chainsthreshold.GPUContributionOp{
CeremonyID: cid,
HolderAddr: uint64(0xDEAD0000 | h),
Round: round,
HolderIndex: h,
PayloadLen: 16,
Payload: payload,
}
}
return ops
}
r1 := buildRound(0)
r2 := buildRound(1)
r3 := buildRound(2)
desc1 := &GPUMPCVMRoundDescriptor{
ChainID: 0xABBA,
Round: 1,
TimestampNs: 1_000_000_000,
Epoch: 1,
CeremonyOpCount: 1,
ContributionOpCount: 5,
}
desc2 := &GPUMPCVMRoundDescriptor{
ChainID: 0xABBA,
Round: 2,
TimestampNs: 2_000_000_000,
Epoch: 1,
ContributionOpCount: 5,
}
desc3 := &GPUMPCVMRoundDescriptor{
ChainID: 0xABBA,
Round: 3,
TimestampNs: 3_000_000_000,
Epoch: 1,
ContributionOpCount: 5,
}
descClose := &GPUMPCVMRoundDescriptor{
ChainID: 0xABBA,
Round: 4,
TimestampNs: 4_000_000_000,
Epoch: 1,
ClosingFlag: 1,
}
// Run via node-side re-export.
runVia := func(t *testing.T) (
[]GPUCeremony,
[]GPUKeyShare,
[]GPUContribution,
GPUMPCVMState,
GPUMPCVMTransitionResult,
) {
t.Helper()
cer := make([]GPUCeremony, N)
keys := make([]GPUKeyShare, N)
con := make([]GPUContribution, N)
b := GPUBackendInstance() // nil-receiver-safe via fallback to CPU reference
if _, err := b.CeremonyApply(desc1, beginOps, cer); err != nil {
if strings.Contains(err.Error(), "GPU backend not available") {
t.Skip("GPU plugin not dlopened in this build; CPU-only path covered elsewhere")
}
t.Fatalf("CeremonyApply r0: %v", err)
}
if _, err := b.ContributionApply(desc1, r1, cer, con, 1); err != nil {
t.Fatalf("ContributionApply r0: %v", err)
}
if _, _, _, err := b.KeyShareApply(desc1, cer, keys, con, 1); err != nil {
t.Fatalf("KeyShareApply r0: %v", err)
}
if _, err := b.ContributionApply(desc2, r2, cer, con, 6); err != nil {
t.Fatalf("ContributionApply r1: %v", err)
}
if _, _, _, err := b.KeyShareApply(desc2, cer, keys, con, 1); err != nil {
t.Fatalf("KeyShareApply r1: %v", err)
}
if _, err := b.ContributionApply(desc3, r3, cer, con, 11); err != nil {
t.Fatalf("ContributionApply r2: %v", err)
}
if _, _, _, err := b.KeyShareApply(desc3, cer, keys, con, 1); err != nil {
t.Fatalf("KeyShareApply r2: %v", err)
}
var state GPUMPCVMState
res, err := b.MPCTransition(descClose, cer, keys, con, &state)
if err != nil {
t.Fatalf("MPCTransition: %v", err)
}
return cer, keys, con, state, *res
}
cerA, keysA, conA, stateA, resA := runVia(t)
cerB, keysB, conB, stateB, resB := runVia(t)
for i := range cerA {
if cerA[i] != cerB[i] {
t.Errorf("ceremony slot %d differs across runs:\nA=%+v\nB=%+v", i, cerA[i], cerB[i])
}
}
for i := range keysA {
if keysA[i] != keysB[i] {
t.Errorf("keyShare slot %d differs across runs:\nA=%+v\nB=%+v", i, keysA[i], keysB[i])
}
}
for i := range conA {
if conA[i] != conB[i] {
t.Errorf("contribution slot %d differs across runs:\nA=%+v\nB=%+v", i, conA[i], conB[i])
}
}
if stateA != stateB {
t.Errorf("state differs across runs:\nA=%+v\nB=%+v", stateA, stateB)
}
if resA != resB {
t.Errorf("result differs across runs:\nA=%+v\nB=%+v", resA, resB)
}
// Sanity — the ceremony MUST have finalized.
if stateA.FinalizedCeremonyCount != 1 {
t.Errorf("expected 1 finalized ceremony, got %d", stateA.FinalizedCeremonyCount)
}
if stateA.KeyShareCount != 5 {
t.Errorf("expected 5 emitted key shares, got %d", stateA.KeyShareCount)
}
var zero [32]byte
if stateA.MPCVMStateRoot == zero {
t.Errorf("mpcvm_state_root is zero — the fold produced no output")
}
}