Files
node/vms/rpcchainvm/zap/client_quasar_test.go
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

163 lines
6.5 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package zap
import (
"context"
"net/http"
"sync"
"testing"
"time"
zapwire "github.com/luxfi/api/zap"
"github.com/luxfi/consensus/engine/chain/block"
"github.com/luxfi/ids"
"github.com/luxfi/log"
luxruntime "github.com/luxfi/runtime"
"github.com/luxfi/version"
rpc "github.com/luxfi/vm/rpc"
)
// These tests exercise the REAL cross-process export path that the live deploy
// uses — node rpcchainvm *Client → ZAP wire → luxfi/vm/rpc server → the
// wrapped VM — which is where the export tier had been silently dormant (the
// node held the *Client, whose method set did not carry the Quasar calls, so the
// chain manager's capability probe failed and the observer never fired). The
// fake VM here stands in for the C-Chain EVM's concrete *VM: the server asserts
// the SAME structural capability interface against it that it asserts against
// *evm.VM, so this proves the plumbing end to end without the full EVM stack.
// baseFakeVM is a minimal block.ChainVM with a NON-nil GetBlock (so the server's
// handleInitialize succeeds) and NO export capability.
type baseFakeVM struct{}
func (baseFakeVM) Initialize(context.Context, block.Init) error { return nil }
func (baseFakeVM) BuildBlock(context.Context) (block.Block, error) { return &fakeBlock{}, nil }
func (baseFakeVM) ParseBlock(context.Context, []byte) (block.Block, error) { return &fakeBlock{}, nil }
func (baseFakeVM) GetBlock(context.Context, ids.ID) (block.Block, error) { return &fakeBlock{}, nil }
func (baseFakeVM) Shutdown(context.Context) error { return nil }
func (baseFakeVM) NewHTTPHandler(context.Context) (http.Handler, error) { return nil, nil }
func (baseFakeVM) SetState(context.Context, uint32) error { return nil }
func (baseFakeVM) Version(context.Context) (string, error) { return "fake", nil }
func (baseFakeVM) Connected(context.Context, ids.NodeID, *version.Application) error {
return nil
}
func (baseFakeVM) Disconnected(context.Context, ids.NodeID) error { return nil }
func (baseFakeVM) HealthCheck(context.Context) (block.HealthCheckResult, error) {
var r block.HealthCheckResult
return r, nil
}
func (baseFakeVM) GetBlockIDAtHeight(context.Context, uint64) (ids.ID, error) {
return ids.Empty, nil
}
func (baseFakeVM) SetPreference(context.Context, ids.ID) error { return nil }
func (baseFakeVM) LastAccepted(context.Context) (ids.ID, error) { return ids.Empty, nil }
func (baseFakeVM) WaitForEvent(context.Context) (block.Message, error) {
var m block.Message
return m, nil
}
var _ block.ChainVM = baseFakeVM{}
// exportFakeVM adds the OPTIONAL export capability — the shape of the C-Chain EVM.
type exportFakeVM struct {
baseFakeVM
mu sync.Mutex
height uint64
}
func (v *exportFakeVM) SetLastQuasarFinalized(h uint64) {
v.mu.Lock()
v.height = h
v.mu.Unlock()
}
func (v *exportFakeVM) LastQuasarHeight() uint64 {
v.mu.Lock()
defer v.mu.Unlock()
return v.height
}
type fakeBlock struct{}
func (*fakeBlock) ID() ids.ID { return ids.Empty }
func (*fakeBlock) Parent() ids.ID { return ids.Empty }
func (*fakeBlock) ParentID() ids.ID { return ids.Empty }
func (*fakeBlock) Height() uint64 { return 0 }
func (*fakeBlock) Timestamp() time.Time { return time.Unix(0, 0) }
func (*fakeBlock) Status() uint8 { return 0 }
func (*fakeBlock) Bytes() []byte { return []byte{} }
func (*fakeBlock) Verify(context.Context) error { return nil }
func (*fakeBlock) Accept(context.Context) error { return nil }
func (*fakeBlock) Reject(context.Context) error { return nil }
var _ block.Block = (*fakeBlock)(nil)
// newQuasarClient stands up the REAL luxfi/vm/rpc server (via NewZAPHandler)
// wrapping vm, connects a REAL node *Client over an in-process ZAP link, and runs
// the real Initialize handshake (which is where the capability is advertised).
func newQuasarClient(t *testing.T, vm block.ChainVM) *Client {
t.Helper()
addr, stop := startTestServer(t, rpc.NewZAPHandler(vm, log.NewNoOpLogger()))
t.Cleanup(stop)
conn, err := zapwire.Dial(context.Background(), addr, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
t.Cleanup(func() { _ = conn.Close() })
c := NewClient(conn, log.NewNoOpLogger())
rt := &luxruntime.Runtime{NetworkID: 1337, ChainDataDir: t.TempDir()}
if err := c.Initialize(context.Background(), block.Init{Runtime: rt, Genesis: []byte("{}")}); err != nil {
t.Fatalf("Initialize: %v", err)
}
return c
}
// TestQuasarExport_CrossProcess_ReachesVM is the integration guard for the
// deploy-blocking gap: with an export-capable VM behind the plugin boundary, the
// node *Client (a) captures the capability from the Initialize handshake, (b)
// forwards a pushed export height ACROSS the wire into the VM, and (c) reads the
// VM's height back across the wire. Before the fix the *Client carried none of
// this and the whole export tier stuck at genesis in the real plugin deploy.
func TestQuasarExport_CrossProcess_ReachesVM(t *testing.T) {
vm := &exportFakeVM{}
c := newQuasarClient(t, vm)
if !c.SupportsQuasarExport() {
t.Fatal("client did not capture CapQuasarExport from the Initialize handshake")
}
// Push crosses the wire and reaches the concrete VM (what the chain manager's
// QuasarObserver does on every ⅔-stake export-frontier advance).
c.SetLastQuasarFinalized(42)
if got := vm.LastQuasarHeight(); got != 42 {
t.Fatalf("SetLastQuasarFinalized did not reach the VM across the boundary: got %d want 42", got)
}
// Read-back crosses the wire (what the chain manager's boot re-seed does).
if got := c.LastQuasarHeight(); got != 42 {
t.Fatalf("LastQuasarHeight did not round-trip the VM's height: got %d want 42", got)
}
}
// TestQuasarExport_CrossProcess_NonCapableGraceful proves the OTHER half of the
// capability gate: a generic plugin (no export methods) advertises nothing, so
// the *Client reports not-capable and the export calls are graceful no-ops — the
// chain manager therefore leaves the observer unwired and the chain runs
// Nova-only, with no per-finalization cross-process traffic.
func TestQuasarExport_CrossProcess_NonCapableGraceful(t *testing.T) {
c := newQuasarClient(t, baseFakeVM{})
if c.SupportsQuasarExport() {
t.Fatal("generic VM must not advertise CapQuasarExport")
}
// No panic, no RPC, sentinel height.
c.SetLastQuasarFinalized(42)
if got := c.LastQuasarHeight(); got != 0 {
t.Fatalf("non-capable client must report 0 (empty frontier), got %d", got)
}
}