mirror of
https://github.com/luxfi/netrunner.git
synced 2026-07-27 00:04:23 +00:00
Per the project rule 'ZAP internal, ZIP edge': netrunner's 26-method
ControlService + PingService move off google.golang.org/grpc and
grpc-gateway onto a luxfi/zap envelope. Default builds now have ZERO
google.golang.org/grpc transitive deps.
New package: netrunner/zaprpc/
- protocol.go: MsgType (uint8 range, encoded in ZAP flags upper byte).
One stable wire ID per method. Append-only.
- dispatch.go: typed Bind[Req, Resp](d, msg, handler) — register a
'func(ctx, *Req) (*Resp, error)' handler. JSON encode/decode +
envelope framing happen here, handlers stay business-logic-only.
- server.go: thin wrapper around zap.Node hosting a Dispatcher.
- client.go: typed Call[Req, Resp](ctx, c, msg, req) — same DX as the
old gRPC client but transport is ZAP. Includes connect-retry around
the boot race.
server/bind.go: one-shot registration of all 26 RPC methods onto a
Dispatcher. The canonical map of 'what this server exposes'.
server/server.go: gRPC server + grpc-gateway HTTP bridge are gone.
Run() now starts the ZAP server and waits for rootCtx.Done. The
'soldier-on' isClientCanceled helper goes with the gRPC stream code.
client/client.go: full rewrite. Same Client interface (28 methods),
implementation is now one-line zaprpc.Call per method. StreamStatus
becomes a client-driven Status() poll on the requested interval —
ZAP has no native server-streaming, but the observable contract
(channel of ClusterInfo) is unchanged.
Deleted:
- rpcpb/rpc_grpc.pb.go (gRPC service stubs)
- rpcpb/rpc.pb.gw.go (grpc-gateway HTTP bridge)
The rpcpb message types (rpc.pb.go) stay — they're plain Go structs
with json: tags, used as the JSON payload format inside ZAP envelopes.
A future cleanup pass can replace them with hand-written structs to
drop google.golang.org/protobuf from the dep tree too.
go.mod cleanup: drop github.com/grpc-ecosystem/grpc-gateway/v2,
google.golang.org/grpc, google.golang.org/genproto/googleapis/api.
Pin luxfi/proto v1.0.0 (was luxfi/protocol v0.0.4 — stale rename
artifact). Add luxfi/zap v0.2.0.
Tests: zaprpc/zaprpc_test.go round-trips request/response and proves
the error path propagates through the envelope.
No backwards compat — forwards perfection per the project rule.
477 lines
17 KiB
Go
477 lines
17 KiB
Go
// Copyright (C) 2021-2026, Lux Industries Inc. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package client is the netrunner control-plane RPC client. Wire is ZAP
|
|
// (luxfi/zap) — no gRPC, no grpc-gateway.
|
|
package client
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
log "github.com/luxfi/log"
|
|
"github.com/luxfi/netrunner/local"
|
|
"github.com/luxfi/netrunner/rpcpb"
|
|
"github.com/luxfi/netrunner/zaprpc"
|
|
)
|
|
|
|
type Config struct {
|
|
// Endpoint is "host:port" of the netrunner ZAP server, e.g. "127.0.0.1:8546".
|
|
Endpoint string
|
|
DialTimeout time.Duration
|
|
}
|
|
|
|
type Client interface {
|
|
Ping(ctx context.Context) (*rpcpb.PingResponse, error)
|
|
RPCVersion(ctx context.Context) (*rpcpb.RPCVersionResponse, error)
|
|
Start(ctx context.Context, execPath string, opts ...OpOption) (*rpcpb.StartResponse, error)
|
|
// CreateChains creates chains with VMs and genesis (uses rpcpb.CreateBlockchains)
|
|
CreateChains(ctx context.Context, chainSpecs []*rpcpb.BlockchainSpec) (*rpcpb.CreateBlockchainsResponse, error)
|
|
// CreateParticipantGroups creates participant groups (uses rpcpb.CreateChains)
|
|
CreateParticipantGroups(ctx context.Context, participantsSpecs []*rpcpb.ChainSpec) (*rpcpb.CreateChainsResponse, error)
|
|
TransformElasticChains(ctx context.Context, elasticChainSpecs []*rpcpb.ElasticChainSpec) (*rpcpb.TransformElasticChainsResponse, error)
|
|
AddPermissionlessValidator(ctx context.Context, validatorSpec []*rpcpb.PermissionlessValidatorSpec) (*rpcpb.AddPermissionlessValidatorResponse, error)
|
|
RemoveChainValidator(ctx context.Context, validatorSpec []*rpcpb.RemoveChainValidatorSpec) (*rpcpb.RemoveChainValidatorResponse, error)
|
|
Health(ctx context.Context) (*rpcpb.HealthResponse, error)
|
|
WaitForHealthy(ctx context.Context) (*rpcpb.WaitForHealthyResponse, error)
|
|
URIs(ctx context.Context) ([]string, error)
|
|
Status(ctx context.Context) (*rpcpb.StatusResponse, error)
|
|
StreamStatus(ctx context.Context, pushInterval time.Duration) (<-chan *rpcpb.ClusterInfo, error)
|
|
RemoveNode(ctx context.Context, name string) (*rpcpb.RemoveNodeResponse, error)
|
|
PauseNode(ctx context.Context, name string) (*rpcpb.PauseNodeResponse, error)
|
|
ResumeNode(ctx context.Context, name string) (*rpcpb.ResumeNodeResponse, error)
|
|
RestartNode(ctx context.Context, name string, opts ...OpOption) (*rpcpb.RestartNodeResponse, error)
|
|
AddNode(ctx context.Context, name string, execPath string, opts ...OpOption) (*rpcpb.AddNodeResponse, error)
|
|
Stop(ctx context.Context) (*rpcpb.StopResponse, error)
|
|
AttachPeer(ctx context.Context, nodeName string) (*rpcpb.AttachPeerResponse, error)
|
|
SendOutboundMessage(ctx context.Context, nodeName string, peerID string, op uint32, msgBody []byte) (*rpcpb.SendOutboundMessageResponse, error)
|
|
Close() error
|
|
SaveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error)
|
|
SaveHotSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error)
|
|
LoadSnapshot(ctx context.Context, snapshotName string, opts ...OpOption) (*rpcpb.LoadSnapshotResponse, error)
|
|
RemoveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.RemoveSnapshotResponse, error)
|
|
GetSnapshotNames(ctx context.Context) ([]string, error)
|
|
}
|
|
|
|
type client struct {
|
|
cfg Config
|
|
logger log.Logger
|
|
|
|
zc *zaprpc.Client
|
|
|
|
closed chan struct{}
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
func New(cfg Config, logger log.Logger) (Client, error) {
|
|
if cfg.DialTimeout == 0 {
|
|
cfg.DialTimeout = 10 * time.Second
|
|
}
|
|
log.Debug("dialing netrunner ZAP server", log.String("endpoint", cfg.Endpoint))
|
|
|
|
zc, err := zaprpc.NewClient(zaprpc.ClientConfig{
|
|
NodeID: "netrunner-client",
|
|
ServerAddr: cfg.Endpoint,
|
|
DialTimeout: cfg.DialTimeout,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &client{
|
|
cfg: cfg,
|
|
logger: logger,
|
|
zc: zc,
|
|
closed: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
func (c *client) Ping(ctx context.Context) (*rpcpb.PingResponse, error) {
|
|
c.logger.Info("ping")
|
|
return zaprpc.Call[rpcpb.PingRequest, rpcpb.PingResponse](ctx, c.zc, zaprpc.MsgPing, &rpcpb.PingRequest{})
|
|
}
|
|
|
|
func (c *client) RPCVersion(ctx context.Context) (*rpcpb.RPCVersionResponse, error) {
|
|
c.logger.Info("rpc version")
|
|
return zaprpc.Call[rpcpb.RPCVersionRequest, rpcpb.RPCVersionResponse](ctx, c.zc, zaprpc.MsgRPCVersion, &rpcpb.RPCVersionRequest{})
|
|
}
|
|
|
|
func (c *client) Start(ctx context.Context, execPath string, opts ...OpOption) (*rpcpb.StartResponse, error) {
|
|
ret := &Op{numNodes: local.DefaultNumNodes}
|
|
ret.applyOpts(opts)
|
|
|
|
req := &rpcpb.StartRequest{
|
|
ExecPath: execPath,
|
|
NumNodes: &ret.numNodes,
|
|
ChainConfigs: ret.chainConfigs,
|
|
UpgradeConfigs: ret.upgradeConfigs,
|
|
ChainConfigFiles: ret.chainConfigs,
|
|
}
|
|
if ret.trackChains != "" {
|
|
req.WhitelistedChains = &ret.trackChains
|
|
}
|
|
if ret.rootDataDir != "" {
|
|
req.RootDataDir = &ret.rootDataDir
|
|
}
|
|
if ret.pluginDir != "" {
|
|
req.PluginDir = ret.pluginDir
|
|
}
|
|
if len(ret.chainSpecs) > 0 {
|
|
req.BlockchainSpecs = ret.chainSpecs
|
|
}
|
|
if ret.globalNodeConfig != "" {
|
|
req.GlobalNodeConfig = &ret.globalNodeConfig
|
|
}
|
|
if ret.customNodeConfigs != nil {
|
|
req.CustomNodeConfigs = ret.customNodeConfigs
|
|
}
|
|
req.ReassignPortsIfUsed = &ret.reassignPortsIfUsed
|
|
req.DynamicPorts = &ret.dynamicPorts
|
|
|
|
c.logger.Info("start")
|
|
return zaprpc.Call[rpcpb.StartRequest, rpcpb.StartResponse](ctx, c.zc, zaprpc.MsgStart, req)
|
|
}
|
|
|
|
func (c *client) CreateChains(ctx context.Context, chainSpecs []*rpcpb.BlockchainSpec) (*rpcpb.CreateBlockchainsResponse, error) {
|
|
c.logger.Info("create chains")
|
|
return zaprpc.Call[rpcpb.CreateBlockchainsRequest, rpcpb.CreateBlockchainsResponse](ctx, c.zc, zaprpc.MsgCreateBlockchains, &rpcpb.CreateBlockchainsRequest{BlockchainSpecs: chainSpecs})
|
|
}
|
|
|
|
func (c *client) CreateParticipantGroups(ctx context.Context, participantsSpecs []*rpcpb.ChainSpec) (*rpcpb.CreateChainsResponse, error) {
|
|
c.logger.Info("create participant groups")
|
|
return zaprpc.Call[rpcpb.CreateChainsRequest, rpcpb.CreateChainsResponse](ctx, c.zc, zaprpc.MsgCreateChains, &rpcpb.CreateChainsRequest{ChainSpecs: participantsSpecs})
|
|
}
|
|
|
|
func (c *client) TransformElasticChains(ctx context.Context, elasticChainSpecs []*rpcpb.ElasticChainSpec) (*rpcpb.TransformElasticChainsResponse, error) {
|
|
c.logger.Info("transform chains")
|
|
return zaprpc.Call[rpcpb.TransformElasticChainsRequest, rpcpb.TransformElasticChainsResponse](ctx, c.zc, zaprpc.MsgTransformElasticChains, &rpcpb.TransformElasticChainsRequest{ElasticChainSpec: elasticChainSpecs})
|
|
}
|
|
|
|
func (c *client) AddPermissionlessValidator(ctx context.Context, validatorSpec []*rpcpb.PermissionlessValidatorSpec) (*rpcpb.AddPermissionlessValidatorResponse, error) {
|
|
c.logger.Info("add permissionless validators to elastic chains")
|
|
return zaprpc.Call[rpcpb.AddPermissionlessValidatorRequest, rpcpb.AddPermissionlessValidatorResponse](ctx, c.zc, zaprpc.MsgAddPermissionlessValidator, &rpcpb.AddPermissionlessValidatorRequest{ValidatorSpec: validatorSpec})
|
|
}
|
|
|
|
func (c *client) RemoveChainValidator(ctx context.Context, validatorSpec []*rpcpb.RemoveChainValidatorSpec) (*rpcpb.RemoveChainValidatorResponse, error) {
|
|
c.logger.Info("remove chain validator")
|
|
return zaprpc.Call[rpcpb.RemoveChainValidatorRequest, rpcpb.RemoveChainValidatorResponse](ctx, c.zc, zaprpc.MsgRemoveChainValidator, &rpcpb.RemoveChainValidatorRequest{ValidatorSpec: validatorSpec})
|
|
}
|
|
|
|
func (c *client) Health(ctx context.Context) (*rpcpb.HealthResponse, error) {
|
|
c.logger.Info("health")
|
|
return zaprpc.Call[rpcpb.HealthRequest, rpcpb.HealthResponse](ctx, c.zc, zaprpc.MsgHealth, &rpcpb.HealthRequest{})
|
|
}
|
|
|
|
func (c *client) WaitForHealthy(ctx context.Context) (*rpcpb.WaitForHealthyResponse, error) {
|
|
c.logger.Info("wait for healthy")
|
|
return zaprpc.Call[rpcpb.WaitForHealthyRequest, rpcpb.WaitForHealthyResponse](ctx, c.zc, zaprpc.MsgWaitForHealthy, &rpcpb.WaitForHealthyRequest{})
|
|
}
|
|
|
|
func (c *client) URIs(ctx context.Context) ([]string, error) {
|
|
c.logger.Info("uris")
|
|
resp, err := zaprpc.Call[rpcpb.URIsRequest, rpcpb.URIsResponse](ctx, c.zc, zaprpc.MsgURIs, &rpcpb.URIsRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.Uris, nil
|
|
}
|
|
|
|
func (c *client) Status(ctx context.Context) (*rpcpb.StatusResponse, error) {
|
|
c.logger.Info("status")
|
|
return zaprpc.Call[rpcpb.StatusRequest, rpcpb.StatusResponse](ctx, c.zc, zaprpc.MsgStatus, &rpcpb.StatusRequest{})
|
|
}
|
|
|
|
// StreamStatus emits one ClusterInfo per pushInterval into the returned
|
|
// channel until ctx or the client is closed. ZAP doesn't model
|
|
// server-streams natively, so this is a client-driven Status() poll —
|
|
// observably identical to the previous gRPC server-streaming RPC,
|
|
// strictly less plumbing.
|
|
func (c *client) StreamStatus(ctx context.Context, pushInterval time.Duration) (<-chan *rpcpb.ClusterInfo, error) {
|
|
if pushInterval <= 0 {
|
|
pushInterval = time.Second
|
|
}
|
|
ch := make(chan *rpcpb.ClusterInfo, 1)
|
|
go func() {
|
|
defer close(ch)
|
|
ticker := time.NewTicker(pushInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
resp, err := c.Status(ctx)
|
|
if err != nil {
|
|
c.logger.Debug("status poll error", log.Err(err))
|
|
return
|
|
}
|
|
select {
|
|
case ch <- resp.ClusterInfo:
|
|
case <-ctx.Done():
|
|
return
|
|
case <-c.closed:
|
|
return
|
|
}
|
|
select {
|
|
case <-ticker.C:
|
|
case <-ctx.Done():
|
|
return
|
|
case <-c.closed:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return ch, nil
|
|
}
|
|
|
|
func (c *client) Stop(ctx context.Context) (*rpcpb.StopResponse, error) {
|
|
c.logger.Info("stop")
|
|
return zaprpc.Call[rpcpb.StopRequest, rpcpb.StopResponse](ctx, c.zc, zaprpc.MsgStop, &rpcpb.StopRequest{})
|
|
}
|
|
|
|
func (c *client) AddNode(ctx context.Context, name string, execPath string, opts ...OpOption) (*rpcpb.AddNodeResponse, error) {
|
|
ret := &Op{}
|
|
ret.applyOpts(opts)
|
|
|
|
req := &rpcpb.AddNodeRequest{
|
|
Name: name,
|
|
ExecPath: execPath,
|
|
NodeConfig: &ret.globalNodeConfig,
|
|
ChainConfigs: ret.chainConfigs,
|
|
UpgradeConfigs: ret.upgradeConfigs,
|
|
ChainConfigFiles: ret.chainConfigs,
|
|
}
|
|
if ret.pluginDir != "" {
|
|
req.PluginDir = ret.pluginDir
|
|
}
|
|
|
|
c.logger.Info("add node", log.String("name", name))
|
|
return zaprpc.Call[rpcpb.AddNodeRequest, rpcpb.AddNodeResponse](ctx, c.zc, zaprpc.MsgAddNode, req)
|
|
}
|
|
|
|
func (c *client) RemoveNode(ctx context.Context, name string) (*rpcpb.RemoveNodeResponse, error) {
|
|
c.logger.Info("remove node", log.String("name", name))
|
|
return zaprpc.Call[rpcpb.RemoveNodeRequest, rpcpb.RemoveNodeResponse](ctx, c.zc, zaprpc.MsgRemoveNode, &rpcpb.RemoveNodeRequest{Name: name})
|
|
}
|
|
|
|
func (c *client) PauseNode(ctx context.Context, name string) (*rpcpb.PauseNodeResponse, error) {
|
|
c.logger.Info("pause node", log.String("name", name))
|
|
return zaprpc.Call[rpcpb.PauseNodeRequest, rpcpb.PauseNodeResponse](ctx, c.zc, zaprpc.MsgPauseNode, &rpcpb.PauseNodeRequest{Name: name})
|
|
}
|
|
|
|
func (c *client) ResumeNode(ctx context.Context, name string) (*rpcpb.ResumeNodeResponse, error) {
|
|
c.logger.Info("resume node", log.String("name", name))
|
|
return zaprpc.Call[rpcpb.ResumeNodeRequest, rpcpb.ResumeNodeResponse](ctx, c.zc, zaprpc.MsgResumeNode, &rpcpb.ResumeNodeRequest{Name: name})
|
|
}
|
|
|
|
func (c *client) RestartNode(ctx context.Context, name string, opts ...OpOption) (*rpcpb.RestartNodeResponse, error) {
|
|
ret := &Op{}
|
|
ret.applyOpts(opts)
|
|
|
|
req := &rpcpb.RestartNodeRequest{Name: name}
|
|
if ret.execPath != "" {
|
|
req.ExecPath = &ret.execPath
|
|
}
|
|
if ret.pluginDir != "" {
|
|
req.PluginDir = ret.pluginDir
|
|
}
|
|
if ret.trackChains != "" {
|
|
req.WhitelistedChains = &ret.trackChains
|
|
}
|
|
req.ChainConfigs = ret.chainConfigs
|
|
req.UpgradeConfigs = ret.upgradeConfigs
|
|
req.ChainConfigFiles = ret.chainConfigs
|
|
|
|
c.logger.Info("restart node", log.String("name", name))
|
|
return zaprpc.Call[rpcpb.RestartNodeRequest, rpcpb.RestartNodeResponse](ctx, c.zc, zaprpc.MsgRestartNode, req)
|
|
}
|
|
|
|
func (c *client) AttachPeer(ctx context.Context, nodeName string) (*rpcpb.AttachPeerResponse, error) {
|
|
c.logger.Info("attaching peer", log.String("name", nodeName))
|
|
return zaprpc.Call[rpcpb.AttachPeerRequest, rpcpb.AttachPeerResponse](ctx, c.zc, zaprpc.MsgAttachPeer, &rpcpb.AttachPeerRequest{NodeName: nodeName})
|
|
}
|
|
|
|
func (c *client) SendOutboundMessage(ctx context.Context, nodeName string, peerID string, op uint32, msgBody []byte) (*rpcpb.SendOutboundMessageResponse, error) {
|
|
c.logger.Info("sending outbound message", log.String("name", nodeName), log.String("peer-ID", peerID))
|
|
return zaprpc.Call[rpcpb.SendOutboundMessageRequest, rpcpb.SendOutboundMessageResponse](ctx, c.zc, zaprpc.MsgSendOutboundMessage, &rpcpb.SendOutboundMessageRequest{
|
|
NodeName: nodeName,
|
|
PeerId: peerID,
|
|
Op: op,
|
|
Bytes: msgBody,
|
|
})
|
|
}
|
|
|
|
func (c *client) SaveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error) {
|
|
c.logger.Info("save snapshot", log.String("snapshot-name", snapshotName))
|
|
return zaprpc.Call[rpcpb.SaveSnapshotRequest, rpcpb.SaveSnapshotResponse](ctx, c.zc, zaprpc.MsgSaveSnapshot, &rpcpb.SaveSnapshotRequest{SnapshotName: snapshotName})
|
|
}
|
|
|
|
func (c *client) SaveHotSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error) {
|
|
c.logger.Info("save hot snapshot", log.String("snapshot-name", snapshotName))
|
|
return zaprpc.Call[rpcpb.SaveSnapshotRequest, rpcpb.SaveSnapshotResponse](ctx, c.zc, zaprpc.MsgSaveHotSnapshot, &rpcpb.SaveSnapshotRequest{SnapshotName: snapshotName})
|
|
}
|
|
|
|
func (c *client) LoadSnapshot(ctx context.Context, snapshotName string, opts ...OpOption) (*rpcpb.LoadSnapshotResponse, error) {
|
|
c.logger.Info("load snapshot", log.String("snapshot-name", snapshotName))
|
|
ret := &Op{}
|
|
ret.applyOpts(opts)
|
|
req := rpcpb.LoadSnapshotRequest{
|
|
SnapshotName: snapshotName,
|
|
ChainConfigs: ret.chainConfigs,
|
|
UpgradeConfigs: ret.upgradeConfigs,
|
|
ChainConfigFiles: ret.chainConfigs,
|
|
}
|
|
if ret.execPath != "" {
|
|
req.ExecPath = &ret.execPath
|
|
}
|
|
if ret.pluginDir != "" {
|
|
req.PluginDir = ret.pluginDir
|
|
}
|
|
if ret.rootDataDir != "" {
|
|
req.RootDataDir = &ret.rootDataDir
|
|
}
|
|
if ret.globalNodeConfig != "" {
|
|
req.GlobalNodeConfig = &ret.globalNodeConfig
|
|
}
|
|
req.ReassignPortsIfUsed = &ret.reassignPortsIfUsed
|
|
return zaprpc.Call[rpcpb.LoadSnapshotRequest, rpcpb.LoadSnapshotResponse](ctx, c.zc, zaprpc.MsgLoadSnapshot, &req)
|
|
}
|
|
|
|
func (c *client) RemoveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.RemoveSnapshotResponse, error) {
|
|
c.logger.Info("remove snapshot", log.String("snapshot-name", snapshotName))
|
|
return zaprpc.Call[rpcpb.RemoveSnapshotRequest, rpcpb.RemoveSnapshotResponse](ctx, c.zc, zaprpc.MsgRemoveSnapshot, &rpcpb.RemoveSnapshotRequest{SnapshotName: snapshotName})
|
|
}
|
|
|
|
func (c *client) GetSnapshotNames(ctx context.Context) ([]string, error) {
|
|
c.logger.Info("get snapshot names")
|
|
resp, err := zaprpc.Call[rpcpb.GetSnapshotNamesRequest, rpcpb.GetSnapshotNamesResponse](ctx, c.zc, zaprpc.MsgGetSnapshotNames, &rpcpb.GetSnapshotNamesRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.SnapshotNames, nil
|
|
}
|
|
|
|
func (c *client) Close() error {
|
|
c.closeOnce.Do(func() {
|
|
close(c.closed)
|
|
})
|
|
return c.zc.Close()
|
|
}
|
|
|
|
type Op struct {
|
|
numNodes uint32
|
|
execPath string
|
|
trackChains string
|
|
globalNodeConfig string
|
|
rootDataDir string
|
|
pluginDir string
|
|
chainSpecs []*rpcpb.BlockchainSpec
|
|
customNodeConfigs map[string]string
|
|
numChains uint32
|
|
chainConfigs map[string]string
|
|
upgradeConfigs map[string]string
|
|
pChainConfigs map[string]string
|
|
reassignPortsIfUsed bool
|
|
dynamicPorts bool
|
|
}
|
|
|
|
type OpOption func(*Op)
|
|
|
|
func (op *Op) applyOpts(opts []OpOption) {
|
|
for _, opt := range opts {
|
|
opt(op)
|
|
}
|
|
}
|
|
|
|
func WithGlobalNodeConfig(nodeConfig string) OpOption {
|
|
return func(op *Op) {
|
|
op.globalNodeConfig = nodeConfig
|
|
}
|
|
}
|
|
|
|
func WithNumNodes(numNodes uint32) OpOption {
|
|
return func(op *Op) {
|
|
op.numNodes = numNodes
|
|
}
|
|
}
|
|
|
|
func WithExecPath(execPath string) OpOption {
|
|
return func(op *Op) {
|
|
op.execPath = execPath
|
|
}
|
|
}
|
|
|
|
func WithWhitelistedChains(trackChains string) OpOption {
|
|
return func(op *Op) {
|
|
op.trackChains = trackChains
|
|
}
|
|
}
|
|
|
|
func WithTrackChains(trackChains string) OpOption {
|
|
return func(op *Op) {
|
|
op.trackChains = trackChains
|
|
}
|
|
}
|
|
|
|
func WithRootDataDir(rootDataDir string) OpOption {
|
|
return func(op *Op) {
|
|
op.rootDataDir = rootDataDir
|
|
}
|
|
}
|
|
|
|
func WithPluginDir(pluginDir string) OpOption {
|
|
return func(op *Op) {
|
|
op.pluginDir = pluginDir
|
|
}
|
|
}
|
|
|
|
// WithChainSpecs sets the chain specifications for creating chains with VMs
|
|
func WithChainSpecs(chainSpecs []*rpcpb.BlockchainSpec) OpOption {
|
|
return func(op *Op) {
|
|
op.chainSpecs = chainSpecs
|
|
}
|
|
}
|
|
|
|
// Map from chain name to its configuration json contents.
|
|
func WithChainConfigs(chainConfigs map[string]string) OpOption {
|
|
return func(op *Op) {
|
|
op.chainConfigs = chainConfigs
|
|
}
|
|
}
|
|
|
|
// Map from chain name to its upgrade json contents.
|
|
func WithUpgradeConfigs(upgradeConfigs map[string]string) OpOption {
|
|
return func(op *Op) {
|
|
op.upgradeConfigs = upgradeConfigs
|
|
}
|
|
}
|
|
|
|
// Map from chain id to its configuration json contents.
|
|
func WithChainConfigFiles(chainConfigs map[string]string) OpOption {
|
|
return func(op *Op) {
|
|
op.chainConfigs = chainConfigs
|
|
}
|
|
}
|
|
|
|
// Map from node name to its custom node config
|
|
func WithCustomNodeConfigs(customNodeConfigs map[string]string) OpOption {
|
|
return func(op *Op) {
|
|
op.customNodeConfigs = customNodeConfigs
|
|
}
|
|
}
|
|
|
|
func WithNumChains(numChains uint32) OpOption {
|
|
return func(op *Op) {
|
|
op.numChains = numChains
|
|
}
|
|
}
|
|
|
|
func WithReassignPortsIfUsed(reassignPortsIfUsed bool) OpOption {
|
|
return func(op *Op) {
|
|
op.reassignPortsIfUsed = reassignPortsIfUsed
|
|
}
|
|
}
|
|
|
|
func WithDynamicPorts(dynamicPorts bool) OpOption {
|
|
return func(op *Op) {
|
|
op.dynamicPorts = dynamicPorts
|
|
}
|
|
}
|