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