Files
zeekay 2faf6ed0df The wire is ZAP: no codec, no marshal, no parallel wallet
github.com/luxfi/codec is gone from this module — no import, no call, no
require. Nothing here marshals or unmarshals a transaction any more.

WHY IT BROKE. codec deleted its jsonrpc subpackage (1b67d6a, "delete dead
subpackages") while nine files imported it. It looked dead because the
PUBLISHED [email protected] still ships it, so only a workspace build — which
resolves codec from the local checkout — ever saw the break.

THE WIRE. A tx already carries its own ZAP encoding, so the codec was
re-deriving what the tx could hand over:
  txs.Codec.Marshal(txs.CodecVersion, &tx.Unsigned)  ->  tx.Unsigned.Bytes()
                                                          txs.UnsignedBytes(tx.Unsigned)
  txs.Codec.Marshal(txs.CodecVersion, tx)            ->  tx.Initialize()
  txs.Codec.Unmarshal(txBytes, &tx)                  ->  txs.Parse(txBytes)
  codec.Unmarshal(utxoBytes, &utxo)                  ->  utxo.ParseUTXO(...)
These are the same calls the node makes in txs.Tx.Sign. AddAllUTXOs no longer
takes a codec.Manager, and SortTransferableOutputs / state.Sort /
SortOperations no longer get one — upstream dropped the parameter.

PARALLEL IMPLEMENTATIONS, DELETED. wallet/chain/{p,x} carried a second copy of
the builder and signer beside the subpackages that already held them —
p/signer_visitor.go and p/signer/visitor.go were both 399 lines of the same
thing, and each defect had to be fixed twice. The flat files are gone; the
package keeps the facade it already had in wallet_exports.go, which re-exports
the subpackage. -4787 lines.

wallet/chain/c is deleted. Its whole reason to exist was a codec.Manager
scaffold for C-chain atomic-tx wire that nothing runs: NewWallet was passed
nil for it, and the builder was commented out. The two symbols anything used
move to where they are read — Account, a balance and a nonce off an EVM
client, and EVMKeychain, the geth-address half of a keychain. Wallet no longer
advertises a C() that only ever returned nil.

ALIGNED. XAssetID -> UTXOAssetID, matching runtime.Runtime and netrunner, which
already expected it. Keychain GetEth -> GetByEVM, EthAddrs -> EVMAddrs. warp
UnsignedMessage and Message collapsed into one flat Message, so a signature is
an Envelope rather than a second message type. netrunner's HealthResponse is
read as a field: the getter form is protobuf, behind //go:build grpc, and the
default build is ZAP-native.

VERIFIED on a machine with the native libraries: go build ./... exits 0 with no
codec require in go.mod, and go test ./... is 23 ok. Three packages still fail
to build their tests — profiles, wallet/keychain, wallet/primary — and all
three fail identically on the tree before this change: Consensus,
VerifyHybridSecp256k1MLDSA and isXChainNotEnabled are referenced by tests and
defined nowhere. evm failed there too and now passes.
2026-08-28 22:25:12 -07:00

182 lines
5.0 KiB
Go

// Copyright (C) 2020-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
// Package netrunner provides integration with the Lux netrunner tool
// for managing test networks and blockchain deployments.
package netrunner
import (
"context"
"errors"
"fmt"
"time"
log "github.com/luxfi/log"
"github.com/luxfi/netrunner/client"
"github.com/luxfi/netrunner/rpcpb"
)
// Client wraps the netrunner client with additional functionality
type Client struct {
client client.Client
logger log.Logger
config *Config
}
// Config holds configuration for the netrunner client
type Config struct {
Endpoint string
DialTimeout time.Duration
LogLevel string
}
// DefaultConfig returns default netrunner configuration
func DefaultConfig() *Config {
return &Config{
Endpoint: "localhost:8080",
DialTimeout: 30 * time.Second,
LogLevel: "info",
}
}
// NewClient creates a new netrunner client
func NewClient(config *Config, logger log.Logger) (*Client, error) {
if config == nil {
config = DefaultConfig()
}
if logger == nil {
logger = log.NewNoOpLogger()
}
// Create netrunner client
netrunnerClient, err := client.New(client.Config{
Endpoint: config.Endpoint,
DialTimeout: config.DialTimeout,
}, logger)
if err != nil {
return nil, fmt.Errorf("failed to create netrunner client: %w", err)
}
return &Client{
client: netrunnerClient,
logger: logger,
config: config,
}, nil
}
// Start starts a new network with the given configuration
func (c *Client) Start(ctx context.Context, execPath string, opts ...client.OpOption) (*rpcpb.StartResponse, error) {
c.logger.Info("starting network", "execPath", execPath)
resp, err := c.client.Start(ctx, execPath, opts...)
if err != nil {
return nil, fmt.Errorf("failed to start network: %w", err)
}
c.logger.Info("network started", "clusterInfo", resp.ClusterInfo)
return resp, nil
}
// Stop stops the running network
func (c *Client) Stop(ctx context.Context) error {
c.logger.Info("stopping network")
_, err := c.client.Stop(ctx)
if err != nil {
return fmt.Errorf("failed to stop network: %w", err)
}
c.logger.Info("network stopped")
return nil
}
// Health checks the health of the network
func (c *Client) Health(ctx context.Context) (*rpcpb.HealthResponse, error) {
return c.client.Health(ctx)
}
// Status returns the current network status
func (c *Client) Status(ctx context.Context) (*rpcpb.StatusResponse, error) {
return c.client.Status(ctx)
}
// URIs returns the URIs of all nodes in the network
func (c *Client) URIs(ctx context.Context) ([]string, error) {
return c.client.URIs(ctx)
}
// CreateChains creates new blockchains with the given specifications
func (c *Client) CreateChains(ctx context.Context, specs []*rpcpb.BlockchainSpec) (*rpcpb.CreateBlockchainsResponse, error) {
c.logger.Info("creating blockchains", "count", len(specs))
resp, err := c.client.CreateChains(ctx, specs)
if err != nil {
return nil, fmt.Errorf("failed to create blockchains: %w", err)
}
c.logger.Info("blockchains created")
return resp, nil
}
// AddNode adds a new node to the network
func (c *Client) AddNode(ctx context.Context, name string, execPath string, opts ...client.OpOption) (*rpcpb.AddNodeResponse, error) {
c.logger.Info("adding node", "name", name)
resp, err := c.client.AddNode(ctx, name, execPath, opts...)
if err != nil {
return nil, fmt.Errorf("failed to add node: %w", err)
}
c.logger.Info("node added", "clusterInfo", resp.ClusterInfo)
return resp, nil
}
// RemoveNode removes a node from the network
func (c *Client) RemoveNode(ctx context.Context, name string) error {
c.logger.Info("removing node", "name", name)
_, err := c.client.RemoveNode(ctx, name)
if err != nil {
return fmt.Errorf("failed to remove node: %w", err)
}
c.logger.Info("node removed", "name", name)
return nil
}
// RestartNode restarts a node in the network
func (c *Client) RestartNode(ctx context.Context, name string, opts ...client.OpOption) error {
c.logger.Info("restarting node", "name", name)
_, err := c.client.RestartNode(ctx, name, opts...)
if err != nil {
return fmt.Errorf("failed to restart node: %w", err)
}
c.logger.Info("node restarted", "name", name)
return nil
}
// WaitForHealthy waits for all nodes in the network to be healthy
func (c *Client) WaitForHealthy(ctx context.Context, timeout time.Duration) error {
c.logger.Info("waiting for network to be healthy", "timeout", timeout)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return errors.New("timeout waiting for network to be healthy")
case <-ticker.C:
health, err := c.Health(ctx)
if err != nil {
c.logger.Debug("health check failed", "error", err)
continue
}
// Check if health response indicates healthy
if health.ClusterInfo != nil {
c.logger.Info("network is healthy")
return nil
}
}
}
}
// Close closes the netrunner client connection
func (c *Client) Close() error {
return c.client.Close()
}