2020-10-08 13:17:50 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-01-15 10:02:59 +00:00
|
|
|
"bytes"
|
2021-08-24 07:43:21 +00:00
|
|
|
"errors"
|
2021-09-20 16:13:17 +00:00
|
|
|
"fmt"
|
2021-01-15 10:02:59 +00:00
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
netmapV2 "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
2020-10-08 13:17:50 +00:00
|
|
|
netmapGRPC "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
|
2021-09-20 16:13:17 +00:00
|
|
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
2021-11-28 12:06:07 +00:00
|
|
|
nodeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/node"
|
2021-02-19 08:01:37 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
2021-12-28 10:39:42 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/metrics"
|
2022-01-31 11:58:55 +00:00
|
|
|
nmClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap"
|
2020-10-21 09:26:16 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
|
2020-10-21 15:12:31 +00:00
|
|
|
netmapEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap"
|
2021-06-23 09:50:40 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/network"
|
2020-10-08 13:17:50 +00:00
|
|
|
netmapTransportGRPC "github.com/nspcc-dev/neofs-node/pkg/network/transport/netmap/grpc"
|
2021-01-13 13:46:39 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/services/control"
|
2020-10-08 13:17:50 +00:00
|
|
|
netmapService "github.com/nspcc-dev/neofs-node/pkg/services/netmap"
|
2021-11-10 07:08:33 +00:00
|
|
|
netmapSDK "github.com/nspcc-dev/neofs-sdk-go/netmap"
|
2021-11-28 12:06:07 +00:00
|
|
|
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
2020-10-21 15:12:31 +00:00
|
|
|
"go.uber.org/atomic"
|
2020-10-21 16:26:38 +00:00
|
|
|
"go.uber.org/zap"
|
2020-10-08 13:17:50 +00:00
|
|
|
)
|
|
|
|
|
2020-10-21 15:12:31 +00:00
|
|
|
// primary solution of local network state dump.
|
|
|
|
type networkState struct {
|
|
|
|
epoch *atomic.Uint64
|
2021-06-11 10:55:11 +00:00
|
|
|
|
|
|
|
controlNetStatus atomic.Value // control.NetmapStatus
|
|
|
|
|
|
|
|
nodeInfo atomic.Value // *netmapSDK.NodeInfo
|
2021-12-28 10:39:42 +00:00
|
|
|
|
|
|
|
metrics *metrics.StorageMetrics
|
2020-10-21 15:12:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newNetworkState() *networkState {
|
|
|
|
return &networkState{
|
|
|
|
epoch: atomic.NewUint64(0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *networkState) CurrentEpoch() uint64 {
|
|
|
|
return s.epoch.Load()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *networkState) setCurrentEpoch(v uint64) {
|
|
|
|
s.epoch.Store(v)
|
2021-12-28 10:39:42 +00:00
|
|
|
if s.metrics != nil {
|
|
|
|
s.metrics.SetEpoch(v)
|
|
|
|
}
|
2020-10-21 15:12:31 +00:00
|
|
|
}
|
|
|
|
|
2021-06-11 10:55:11 +00:00
|
|
|
func (s *networkState) setNodeInfo(ni *netmapSDK.NodeInfo) {
|
|
|
|
s.nodeInfo.Store(ni)
|
|
|
|
|
|
|
|
var ctrlNetSt control.NetmapStatus
|
|
|
|
|
|
|
|
switch ni.State() {
|
|
|
|
default:
|
|
|
|
ctrlNetSt = control.NetmapStatus_STATUS_UNDEFINED
|
|
|
|
case netmapSDK.NodeStateOnline:
|
|
|
|
ctrlNetSt = control.NetmapStatus_ONLINE
|
|
|
|
case netmapSDK.NodeStateOffline:
|
|
|
|
ctrlNetSt = control.NetmapStatus_OFFLINE
|
|
|
|
}
|
|
|
|
|
|
|
|
s.controlNetStatus.Store(ctrlNetSt)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *networkState) controlNetmapStatus() control.NetmapStatus {
|
|
|
|
return s.controlNetStatus.Load().(control.NetmapStatus)
|
|
|
|
}
|
2020-10-08 13:17:50 +00:00
|
|
|
|
2021-06-11 10:55:11 +00:00
|
|
|
func (s *networkState) getNodeInfo() *netmapSDK.NodeInfo {
|
|
|
|
return s.nodeInfo.Load().(*netmapSDK.NodeInfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
func nodeKeyFromNetmap(c *cfg) []byte {
|
|
|
|
return c.cfgNetmap.state.getNodeInfo().PublicKey()
|
|
|
|
}
|
|
|
|
|
2021-06-22 16:00:00 +00:00
|
|
|
func (c *cfg) iterateNetworkAddresses(f func(string) bool) {
|
|
|
|
c.cfgNetmap.state.getNodeInfo().IterateAddresses(f)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cfg) addressNum() int {
|
|
|
|
return c.cfgNetmap.state.getNodeInfo().NumberOfAddresses()
|
2021-06-11 10:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func initNetmapService(c *cfg) {
|
2021-06-23 09:50:40 +00:00
|
|
|
network.WriteToNodeInfo(c.localAddr, &c.cfgNodeInfo.localInfo)
|
2021-05-31 08:55:38 +00:00
|
|
|
c.cfgNodeInfo.localInfo.SetPublicKey(c.key.PublicKey().Bytes())
|
2021-06-11 10:55:11 +00:00
|
|
|
c.cfgNodeInfo.localInfo.SetAttributes(parseAttributes(c.appCfg)...)
|
|
|
|
c.cfgNodeInfo.localInfo.SetState(netmapSDK.NodeStateOffline)
|
2020-10-08 13:17:50 +00:00
|
|
|
|
2021-11-28 12:06:07 +00:00
|
|
|
readSubnetCfg(c)
|
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
if c.cfgMorph.client == nil {
|
|
|
|
initMorphComponents(c)
|
|
|
|
}
|
|
|
|
|
2021-10-20 14:28:37 +00:00
|
|
|
initNetmapState(c)
|
|
|
|
|
2021-06-22 17:25:18 +00:00
|
|
|
server := netmapTransportGRPC.New(
|
|
|
|
netmapService.NewSignService(
|
|
|
|
&c.key.PrivateKey,
|
|
|
|
netmapService.NewResponseService(
|
|
|
|
netmapService.NewExecutionService(
|
|
|
|
c,
|
|
|
|
c.apiVersion,
|
|
|
|
&netInfo{
|
2021-09-20 16:13:17 +00:00
|
|
|
netState: c.cfgNetmap.state,
|
|
|
|
magic: c.cfgMorph.client,
|
|
|
|
netCfg: c.cfgNetmap.wrapper.IterateConfigParameters,
|
|
|
|
msPerBlockRdr: c.cfgMorph.client.MsPerBlock,
|
2021-06-22 17:25:18 +00:00
|
|
|
},
|
2020-10-08 13:17:50 +00:00
|
|
|
),
|
2021-06-22 17:25:18 +00:00
|
|
|
c.respSvc,
|
2020-10-08 13:17:50 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
2020-10-21 15:12:31 +00:00
|
|
|
|
2021-06-22 17:25:18 +00:00
|
|
|
for _, srv := range c.cfgGRPC.servers {
|
|
|
|
netmapGRPC.RegisterNetmapServiceServer(srv, server)
|
|
|
|
}
|
|
|
|
|
2020-10-21 15:12:31 +00:00
|
|
|
addNewEpochNotificationHandler(c, func(ev event.Event) {
|
|
|
|
c.cfgNetmap.state.setCurrentEpoch(ev.(netmapEvent.NewEpoch).EpochNumber())
|
|
|
|
})
|
2020-10-30 12:57:49 +00:00
|
|
|
|
2021-05-12 09:27:12 +00:00
|
|
|
addNewEpochAsyncNotificationHandler(c, func(ev event.Event) {
|
2021-08-24 07:43:21 +00:00
|
|
|
if !c.needBootstrap() || c.cfgNetmap.reBoostrapTurnedOff.Load() { // fixes #470
|
2021-05-12 09:27:12 +00:00
|
|
|
return
|
|
|
|
}
|
2021-04-21 13:26:04 +00:00
|
|
|
|
2021-05-12 09:27:12 +00:00
|
|
|
n := ev.(netmapEvent.NewEpoch).EpochNumber()
|
2020-10-30 12:57:49 +00:00
|
|
|
|
2021-05-12 09:27:12 +00:00
|
|
|
const reBootstrapInterval = 2
|
|
|
|
|
|
|
|
if (n-c.cfgNetmap.startEpoch)%reBootstrapInterval == 0 {
|
2021-06-11 10:55:11 +00:00
|
|
|
err := c.bootstrap()
|
2021-05-12 09:27:12 +00:00
|
|
|
if err != nil {
|
|
|
|
c.log.Warn("can't send re-bootstrap tx", zap.Error(err))
|
2020-10-30 12:57:49 +00:00
|
|
|
}
|
2021-05-12 09:27:12 +00:00
|
|
|
}
|
|
|
|
})
|
2021-01-15 10:02:59 +00:00
|
|
|
|
2021-04-15 10:24:43 +00:00
|
|
|
addNewEpochAsyncNotificationHandler(c, func(ev event.Event) {
|
2021-01-15 10:02:59 +00:00
|
|
|
e := ev.(netmapEvent.NewEpoch).EpochNumber()
|
|
|
|
|
2021-01-25 08:23:06 +00:00
|
|
|
ni, err := c.netmapLocalNodeState(e)
|
2021-01-15 10:02:59 +00:00
|
|
|
if err != nil {
|
2021-01-25 08:23:06 +00:00
|
|
|
c.log.Error("could not update node state on new epoch",
|
2021-01-15 10:02:59 +00:00
|
|
|
zap.Uint64("epoch", e),
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-25 08:23:06 +00:00
|
|
|
c.handleLocalNodeInfo(ni)
|
2021-01-15 10:02:59 +00:00
|
|
|
})
|
2021-09-30 19:11:20 +00:00
|
|
|
|
|
|
|
if c.cfgMorph.notaryEnabled {
|
|
|
|
addNewEpochAsyncNotificationHandler(c, func(ev event.Event) {
|
|
|
|
_, err := makeNotaryDeposit(c)
|
|
|
|
if err != nil {
|
|
|
|
c.log.Error("could not make notary deposit",
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-10-08 13:17:50 +00:00
|
|
|
}
|
|
|
|
|
2021-11-28 12:06:07 +00:00
|
|
|
func readSubnetCfg(c *cfg) {
|
|
|
|
var subnetCfg nodeconfig.SubnetConfig
|
|
|
|
|
|
|
|
subnetCfg.Init(*c.appCfg)
|
|
|
|
|
|
|
|
var (
|
|
|
|
id subnetid.ID
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
subnetCfg.IterateSubnets(func(idTxt string) {
|
|
|
|
err = id.UnmarshalText([]byte(idTxt))
|
|
|
|
fatalOnErrDetails("parse subnet entry", err)
|
|
|
|
|
|
|
|
c.cfgNodeInfo.localInfo.EnterSubnet(id)
|
|
|
|
})
|
|
|
|
|
|
|
|
if subnetCfg.ExitZero() {
|
|
|
|
subnetid.MakeZero(&id)
|
|
|
|
c.cfgNodeInfo.localInfo.ExitSubnet(id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-20 14:28:37 +00:00
|
|
|
// bootstrapNode adds current node to the Network map.
|
|
|
|
// Must be called after initNetmapService.
|
2020-10-08 13:17:50 +00:00
|
|
|
func bootstrapNode(c *cfg) {
|
2021-08-24 07:43:21 +00:00
|
|
|
if c.needBootstrap() {
|
|
|
|
err := c.bootstrap()
|
|
|
|
fatalOnErrDetails("bootstrap error", err)
|
|
|
|
}
|
2020-10-08 13:17:50 +00:00
|
|
|
}
|
2020-10-21 09:26:16 +00:00
|
|
|
|
|
|
|
func addNetmapNotificationHandler(c *cfg, sTyp string, h event.Handler) {
|
|
|
|
typ := event.TypeFromString(sTyp)
|
|
|
|
|
|
|
|
if c.cfgNetmap.subscribers == nil {
|
|
|
|
c.cfgNetmap.subscribers = make(map[event.Type][]event.Handler, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.cfgNetmap.subscribers[typ] = append(c.cfgNetmap.subscribers[typ], h)
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:24:17 +00:00
|
|
|
func setNetmapNotificationParser(c *cfg, sTyp string, p event.NotificationParser) {
|
2020-10-21 09:26:16 +00:00
|
|
|
typ := event.TypeFromString(sTyp)
|
|
|
|
|
|
|
|
if c.cfgNetmap.parsers == nil {
|
2021-08-12 15:24:17 +00:00
|
|
|
c.cfgNetmap.parsers = make(map[event.Type]event.NotificationParser, 1)
|
2020-10-21 09:26:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.cfgNetmap.parsers[typ] = p
|
|
|
|
}
|
|
|
|
|
2021-10-20 14:28:37 +00:00
|
|
|
// initNetmapState inits current Network map state.
|
|
|
|
// Must be called after Morph components initialization.
|
|
|
|
func initNetmapState(c *cfg) {
|
2020-10-21 16:26:38 +00:00
|
|
|
epoch, err := c.cfgNetmap.wrapper.Epoch()
|
2021-05-31 05:11:23 +00:00
|
|
|
fatalOnErrDetails("could not initialize current epoch number", err)
|
2020-10-21 16:26:38 +00:00
|
|
|
|
2021-01-25 08:23:06 +00:00
|
|
|
ni, err := c.netmapLocalNodeState(epoch)
|
2021-05-31 05:11:23 +00:00
|
|
|
fatalOnErrDetails("could not init network state", err)
|
2021-01-15 10:02:59 +00:00
|
|
|
|
|
|
|
c.log.Info("initial network state",
|
|
|
|
zap.Uint64("epoch", epoch),
|
2021-01-25 08:23:06 +00:00
|
|
|
zap.Stringer("state", ni.State()),
|
2020-10-21 16:26:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
c.cfgNetmap.state.setCurrentEpoch(epoch)
|
2021-05-12 09:27:12 +00:00
|
|
|
c.cfgNetmap.startEpoch = epoch
|
2021-06-11 10:55:11 +00:00
|
|
|
c.cfgNetmap.state.setNodeInfo(ni)
|
2020-10-21 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
func (c *cfg) netmapLocalNodeState(epoch uint64) (*netmapSDK.NodeInfo, error) {
|
2021-01-15 10:02:59 +00:00
|
|
|
// calculate current network state
|
|
|
|
nm, err := c.cfgNetmap.wrapper.GetNetMapByEpoch(epoch)
|
|
|
|
if err != nil {
|
2021-01-25 08:23:06 +00:00
|
|
|
return nil, err
|
2021-01-15 10:02:59 +00:00
|
|
|
}
|
|
|
|
|
2021-01-25 08:23:06 +00:00
|
|
|
return c.localNodeInfoFromNetmap(nm), nil
|
2021-01-15 10:02:59 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
func (c *cfg) localNodeInfoFromNetmap(nm *netmapSDK.Netmap) *netmapSDK.NodeInfo {
|
2021-01-15 10:02:59 +00:00
|
|
|
for _, n := range nm.Nodes {
|
2021-05-31 08:55:38 +00:00
|
|
|
if bytes.Equal(n.PublicKey(), c.key.PublicKey().Bytes()) {
|
2021-01-25 08:23:06 +00:00
|
|
|
return n.NodeInfo
|
2021-01-15 10:02:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-25 08:23:06 +00:00
|
|
|
return nil
|
2021-01-15 10:02:59 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 12:53:58 +00:00
|
|
|
// addNewEpochNotificationHandler adds handler that will be executed synchronously
|
2020-10-21 09:26:16 +00:00
|
|
|
func addNewEpochNotificationHandler(c *cfg, h event.Handler) {
|
|
|
|
addNetmapNotificationHandler(c, newEpochNotification, h)
|
|
|
|
}
|
2021-01-11 11:57:01 +00:00
|
|
|
|
2021-04-13 12:53:58 +00:00
|
|
|
// addNewEpochAsyncNotificationHandler adds handler that will be executed asynchronously via netmap workerPool
|
|
|
|
func addNewEpochAsyncNotificationHandler(c *cfg, h event.Handler) {
|
|
|
|
addNetmapNotificationHandler(
|
|
|
|
c,
|
|
|
|
newEpochNotification,
|
|
|
|
event.WorkerPoolHandler(
|
|
|
|
c.cfgNetmap.workerPool,
|
|
|
|
h,
|
|
|
|
c.log,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-08-24 07:43:21 +00:00
|
|
|
var errRelayBootstrap = errors.New("setting netmap status is forbidden in relay mode")
|
|
|
|
|
2021-11-09 15:54:03 +00:00
|
|
|
var errNodeMaintenance = errors.New("node is in maintenance mode")
|
|
|
|
|
2021-01-15 11:53:41 +00:00
|
|
|
func (c *cfg) SetNetmapStatus(st control.NetmapStatus) error {
|
2021-11-09 15:54:03 +00:00
|
|
|
if st == control.NetmapStatus_MAINTENANCE {
|
|
|
|
return c.cfgObject.cfgLocalStorage.localStorage.BlockExecution(errNodeMaintenance)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.cfgObject.cfgLocalStorage.localStorage.ResumeExecution()
|
|
|
|
if err != nil {
|
|
|
|
c.log.Error("failed to resume local object operations",
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-08-24 07:43:21 +00:00
|
|
|
if !c.needBootstrap() {
|
|
|
|
return errRelayBootstrap
|
|
|
|
}
|
|
|
|
|
2021-01-15 11:53:41 +00:00
|
|
|
if st == control.NetmapStatus_ONLINE {
|
2021-04-21 13:26:04 +00:00
|
|
|
c.cfgNetmap.reBoostrapTurnedOff.Store(false)
|
2021-06-11 10:55:11 +00:00
|
|
|
return c.bootstrap()
|
2021-01-15 11:53:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
var apiState netmapSDK.NodeState
|
2021-01-15 11:53:41 +00:00
|
|
|
|
|
|
|
if st == control.NetmapStatus_OFFLINE {
|
2021-02-19 08:01:37 +00:00
|
|
|
apiState = netmapSDK.NodeStateOffline
|
2021-01-15 11:53:41 +00:00
|
|
|
}
|
|
|
|
|
2021-04-21 13:26:04 +00:00
|
|
|
c.cfgNetmap.reBoostrapTurnedOff.Store(true)
|
|
|
|
|
2022-01-31 11:58:55 +00:00
|
|
|
prm := nmClient.UpdatePeerPrm{}
|
2021-11-10 11:05:51 +00:00
|
|
|
|
|
|
|
prm.SetKey(c.key.PublicKey().Bytes())
|
|
|
|
prm.SetState(apiState)
|
|
|
|
|
|
|
|
return c.cfgNetmap.wrapper.UpdatePeerState(prm)
|
2021-01-15 11:53:41 +00:00
|
|
|
}
|
2021-02-19 08:01:37 +00:00
|
|
|
|
|
|
|
type netInfo struct {
|
|
|
|
netState netmap.State
|
|
|
|
|
|
|
|
magic interface {
|
2021-08-26 08:17:31 +00:00
|
|
|
MagicNumber() (uint64, error)
|
2021-02-19 08:01:37 +00:00
|
|
|
}
|
2021-09-20 16:13:17 +00:00
|
|
|
|
|
|
|
netCfg func(func(key, value []byte) error) error
|
|
|
|
|
|
|
|
msPerBlockRdr func() (int64, error)
|
2021-02-19 08:01:37 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 16:13:17 +00:00
|
|
|
func (n *netInfo) Dump(ver *refs.Version) (*netmapV2.NetworkInfo, error) {
|
2021-08-26 08:17:31 +00:00
|
|
|
magic, err := n.magic.MagicNumber()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
ni := new(netmapV2.NetworkInfo)
|
|
|
|
ni.SetCurrentEpoch(n.netState.CurrentEpoch())
|
2021-08-26 08:17:31 +00:00
|
|
|
ni.SetMagicNumber(magic)
|
2021-02-19 08:01:37 +00:00
|
|
|
|
2021-09-20 16:13:17 +00:00
|
|
|
if mjr := ver.GetMajor(); mjr > 2 || mjr == 2 && ver.GetMinor() > 9 {
|
|
|
|
msPerBlock, err := n.msPerBlockRdr()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("ms per block: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
ps []*netmapV2.NetworkParameter
|
|
|
|
netCfg netmapV2.NetworkConfig
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := n.netCfg(func(key, value []byte) error {
|
|
|
|
var p netmapV2.NetworkParameter
|
|
|
|
|
|
|
|
p.SetKey(key)
|
|
|
|
p.SetValue(value)
|
|
|
|
|
|
|
|
ps = append(ps, &p)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, fmt.Errorf("network config: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
netCfg.SetParameters(ps...)
|
|
|
|
|
|
|
|
ni.SetNetworkConfig(&netCfg)
|
|
|
|
ni.SetMsPerBlock(msPerBlock)
|
|
|
|
}
|
|
|
|
|
2021-02-19 08:01:37 +00:00
|
|
|
return ni, nil
|
|
|
|
}
|