Files
node/vms/rpcchainvm/zap/client_initialize_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

134 lines
4.6 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package zap
import (
"context"
"errors"
"net"
"strings"
"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"
)
// TestInitialize_SuccessResponseNotMistakenForError pins the response decode.
// Treating every response with MsgResponseFlag set — that is, ALL responses —
// as an error, and printing the binary InitializeResponse payload as the error
// string, yields "vm error: <unprintable bytes>" and a chain that never starts
// while the plugin reports "VM initialized successfully".
//
// What this test asserts: when the plugin returns a well-formed
// InitializeResponse (with MsgResponseFlag — the normal success encoding),
// the client decodes it instead of synthesizing a spurious error. The
// payload echoes back a known LastAcceptedID so we can confirm the client
// actually parsed the response rather than just not erroring.
//
// Why this matters: the transport already converts true error responses
// (MsgErrorFlag set) into a non-nil err from Conn.Call, so the client's
// only job after Call returns success is to decode the response. A second
// defense against "error responses" here guards a case the transport has
// already handled, and fires on every success.
func TestInitialize_SuccessResponseNotMistakenForError(t *testing.T) {
wantLastAccepted := ids.ID{0xa1, 0xcc, 0xa4, 0xae, 0xb9, 0x08, 0x14, 0x8c}
wantParent := ids.ID{}
addr, stop := startTestServer(t, zapwire.HandlerFunc(func(_ context.Context, msgType zapwire.MessageType, _ []byte) (zapwire.MessageType, []byte, error) {
if msgType != zapwire.MsgInitialize {
return 0, nil, errors.New("unexpected message")
}
resp := &zapwire.InitializeResponse{
LastAcceptedID: wantLastAccepted[:],
LastAcceptedParentID: wantParent[:],
Height: 0,
Bytes: []byte{},
Timestamp: time.Now().UnixNano(),
}
buf := zapwire.GetBuffer()
defer zapwire.PutBuffer(buf)
resp.Encode(buf)
out := make([]byte, len(buf.Bytes()))
copy(out, buf.Bytes())
return zapwire.MsgInitialize, out, nil
}))
defer stop()
conn, err := zapwire.Dial(context.Background(), addr, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer conn.Close()
c := NewClient(conn, log.NewNoOpLogger())
rt := &luxruntime.Runtime{NetworkID: 1337}
if err := c.Initialize(context.Background(), block.Init{
Runtime: rt,
Genesis: []byte("{}"),
}); err != nil {
t.Fatalf("Initialize returned error on success response: %v", err)
}
if c.lastAcceptedID != wantLastAccepted {
t.Fatalf("lastAcceptedID not parsed: got %s want %s",
c.lastAcceptedID, wantLastAccepted)
}
}
// TestInitialize_ErrorResponseSurfacedAsError confirms the OTHER half of
// the contract: when the plugin actually returns an error, the client
// reports it as an error (rather than swallowing it as a success). The
// transport translates an error response into a non-nil err out of
// Conn.Call, so the client's wrapping of that err is what we check here.
func TestInitialize_ErrorResponseSurfacedAsError(t *testing.T) {
addr, stop := startTestServer(t, zapwire.HandlerFunc(func(_ context.Context, _ zapwire.MessageType, _ []byte) (zapwire.MessageType, []byte, error) {
return 0, nil, errors.New("synthesized init failure")
}))
defer stop()
conn, err := zapwire.Dial(context.Background(), addr, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer conn.Close()
c := NewClient(conn, log.NewNoOpLogger())
rt := &luxruntime.Runtime{NetworkID: 1337}
gotErr := c.Initialize(context.Background(), block.Init{
Runtime: rt,
Genesis: []byte("{}"),
})
if gotErr == nil {
t.Fatalf("Initialize swallowed server error and returned nil")
}
if !strings.Contains(gotErr.Error(), "synthesized init failure") {
t.Fatalf("error from server not surfaced; got %q", gotErr.Error())
}
}
// startTestServer starts an in-process ZAP server on loopback and returns
// the dial-able address plus a teardown closure. Using net.Listen on
// 127.0.0.1:0 lets the OS pick a free port — no shared state across tests.
func startTestServer(t *testing.T, handler zapwire.Handler) (string, func()) {
t.Helper()
raw, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
listener := zapwire.NewListener(raw, nil)
srv := zapwire.NewServer(listener, handler)
ctx, cancel := context.WithCancel(context.Background())
go func() { _ = srv.Serve(ctx) }()
return raw.Addr().String(), func() {
cancel()
_ = listener.Close()
}
}