autopilot: split channel definition into LocalChannel/ChannelEdge

Since non-local channels won't have a balance field, we split the
definitions in anticipation of adding one.
This commit is contained in:
Johan T. Halseth 2020-10-02 13:56:43 +02:00
parent 3e1755ee99
commit d40cf6b592
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
14 changed files with 66 additions and 72 deletions

@ -65,11 +65,11 @@ type Config struct {
// channelState is a type that represents the set of active channels of the // channelState is a type that represents the set of active channels of the
// backing LN node that the Agent should be aware of. This type contains a few // backing LN node that the Agent should be aware of. This type contains a few
// helper utility methods. // helper utility methods.
type channelState map[lnwire.ShortChannelID]Channel type channelState map[lnwire.ShortChannelID]LocalChannel
// Channels returns a slice of all the active channels. // Channels returns a slice of all the active channels.
func (c channelState) Channels() []Channel { func (c channelState) Channels() []LocalChannel {
chans := make([]Channel, 0, len(c)) chans := make([]LocalChannel, 0, len(c))
for _, channel := range c { for _, channel := range c {
chans = append(chans, channel) chans = append(chans, channel)
} }
@ -163,7 +163,7 @@ type Agent struct {
// initiated, but haven't yet been confirmed as being fully opened. // initiated, but haven't yet been confirmed as being fully opened.
// This state is required as otherwise, we may go over our allotted // This state is required as otherwise, we may go over our allotted
// channel limit, or open multiple channels to the same node. // channel limit, or open multiple channels to the same node.
pendingOpens map[NodeID]Channel pendingOpens map[NodeID]LocalChannel
pendingMtx sync.Mutex pendingMtx sync.Mutex
quit chan struct{} quit chan struct{}
@ -174,10 +174,10 @@ type Agent struct {
// configuration and initial channel state. The initial channel state slice // configuration and initial channel state. The initial channel state slice
// should be populated with the set of Channels that are currently opened by // should be populated with the set of Channels that are currently opened by
// the backing Lightning Node. // the backing Lightning Node.
func New(cfg Config, initialState []Channel) (*Agent, error) { func New(cfg Config, initialState []LocalChannel) (*Agent, error) {
a := &Agent{ a := &Agent{
cfg: cfg, cfg: cfg,
chanState: make(map[lnwire.ShortChannelID]Channel), chanState: make(map[lnwire.ShortChannelID]LocalChannel),
quit: make(chan struct{}), quit: make(chan struct{}),
stateUpdates: make(chan interface{}), stateUpdates: make(chan interface{}),
balanceUpdates: make(chan *balanceUpdate, 1), balanceUpdates: make(chan *balanceUpdate, 1),
@ -187,7 +187,7 @@ func New(cfg Config, initialState []Channel) (*Agent, error) {
pendingOpenUpdates: make(chan *chanPendingOpenUpdate, 1), pendingOpenUpdates: make(chan *chanPendingOpenUpdate, 1),
failedNodes: make(map[NodeID]struct{}), failedNodes: make(map[NodeID]struct{}),
pendingConns: make(map[NodeID]struct{}), pendingConns: make(map[NodeID]struct{}),
pendingOpens: make(map[NodeID]Channel), pendingOpens: make(map[NodeID]LocalChannel),
} }
for _, c := range initialState { for _, c := range initialState {
@ -249,7 +249,7 @@ type nodeUpdates struct{}
// channel has been opened, either by the Agent itself (within the main // channel has been opened, either by the Agent itself (within the main
// controller loop), or by an external user to the system. // controller loop), or by an external user to the system.
type chanOpenUpdate struct { type chanOpenUpdate struct {
newChan Channel newChan LocalChannel
} }
// chanPendingOpenUpdate is a type of external state update that indicates a new // chanPendingOpenUpdate is a type of external state update that indicates a new
@ -294,7 +294,7 @@ func (a *Agent) OnNodeUpdates() {
// OnChannelOpen is a callback that should be executed each time a new channel // OnChannelOpen is a callback that should be executed each time a new channel
// is manually opened by the user or any system outside the autopilot agent. // is manually opened by the user or any system outside the autopilot agent.
func (a *Agent) OnChannelOpen(c Channel) { func (a *Agent) OnChannelOpen(c LocalChannel) {
a.wg.Add(1) a.wg.Add(1)
go func() { go func() {
defer a.wg.Done() defer a.wg.Done()
@ -356,7 +356,7 @@ func (a *Agent) OnHeuristicUpdate(h AttachmentHeuristic) {
// channels open to, with the other sets of nodes that should be removed from // channels open to, with the other sets of nodes that should be removed from
// consideration during heuristic selection. This ensures that the Agent doesn't // consideration during heuristic selection. This ensures that the Agent doesn't
// attempt to open any "duplicate" channels to the same node. // attempt to open any "duplicate" channels to the same node.
func mergeNodeMaps(c map[NodeID]Channel, func mergeNodeMaps(c map[NodeID]LocalChannel,
skips ...map[NodeID]struct{}) map[NodeID]struct{} { skips ...map[NodeID]struct{}) map[NodeID]struct{} {
numNodes := len(c) numNodes := len(c)
@ -380,11 +380,11 @@ func mergeNodeMaps(c map[NodeID]Channel,
// mergeChanState merges the Agent's set of active channels, with the set of // mergeChanState merges the Agent's set of active channels, with the set of
// channels awaiting confirmation. This ensures that the agent doesn't go over // channels awaiting confirmation. This ensures that the agent doesn't go over
// the prescribed channel limit or fund allocation limit. // the prescribed channel limit or fund allocation limit.
func mergeChanState(pendingChans map[NodeID]Channel, func mergeChanState(pendingChans map[NodeID]LocalChannel,
activeChans channelState) []Channel { activeChans channelState) []LocalChannel {
numChans := len(pendingChans) + len(activeChans) numChans := len(pendingChans) + len(activeChans)
totalChans := make([]Channel, 0, numChans) totalChans := make([]LocalChannel, 0, numChans)
totalChans = append(totalChans, activeChans.Channels()...) totalChans = append(totalChans, activeChans.Channels()...)
@ -549,7 +549,7 @@ func (a *Agent) controller() {
// openChans queries the agent's heuristic for a set of channel candidates, and // openChans queries the agent's heuristic for a set of channel candidates, and
// attempts to open channels to them. // attempts to open channels to them.
func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32, func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
totalChans []Channel) error { totalChans []LocalChannel) error {
// As channel size we'll use the maximum channel size available. // As channel size we'll use the maximum channel size available.
chanSize := a.cfg.Constraints.MaxChanSize() chanSize := a.cfg.Constraints.MaxChanSize()
@ -828,7 +828,7 @@ func (a *Agent) executeDirective(directive AttachmentDirective) {
// opens. We do this here to ensure we don't stall on selecting new // opens. We do this here to ensure we don't stall on selecting new
// peers if the connection attempt happens to take too long. // peers if the connection attempt happens to take too long.
delete(a.pendingConns, nodeID) delete(a.pendingConns, nodeID)
a.pendingOpens[nodeID] = Channel{ a.pendingOpens[nodeID] = LocalChannel{
Capacity: directive.ChanAmt, Capacity: directive.ChanAmt,
Node: nodeID, Node: nodeID,
} }

@ -13,7 +13,7 @@ type AgentConstraints interface {
// the first return value will represent the amount of additional funds // the first return value will represent the amount of additional funds
// available towards creating channels. The second return value is the // available towards creating channels. The second return value is the
// exact *number* of additional channels available. // exact *number* of additional channels available.
ChannelBudget(chans []Channel, balance btcutil.Amount) ( ChannelBudget(chans []LocalChannel, balance btcutil.Amount) (
btcutil.Amount, uint32) btcutil.Amount, uint32)
// MaxPendingOpens returns the maximum number of pending channel // MaxPendingOpens returns the maximum number of pending channel
@ -82,7 +82,7 @@ func NewConstraints(minChanSize, maxChanSize btcutil.Amount, chanLimit,
// additional channels available. // additional channels available.
// //
// Note: part of the AgentConstraints interface. // Note: part of the AgentConstraints interface.
func (h *agentConstraints) ChannelBudget(channels []Channel, func (h *agentConstraints) ChannelBudget(channels []LocalChannel,
funds btcutil.Amount) (btcutil.Amount, uint32) { funds btcutil.Amount) (btcutil.Amount, uint32) {
// If we're already over our maximum allowed number of channels, then // If we're already over our maximum allowed number of channels, then

@ -37,7 +37,7 @@ func TestConstraintsChannelBudget(t *testing.T) {
} }
testCases := []struct { testCases := []struct {
channels []Channel channels []LocalChannel
walletAmt btcutil.Amount walletAmt btcutil.Amount
needMore bool needMore bool
@ -47,7 +47,7 @@ func TestConstraintsChannelBudget(t *testing.T) {
// Many available funds, but already have too many active open // Many available funds, but already have too many active open
// channels. // channels.
{ {
[]Channel{ []LocalChannel{
{ {
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.Amount(prand.Int31()), Capacity: btcutil.Amount(prand.Int31()),
@ -70,7 +70,7 @@ func TestConstraintsChannelBudget(t *testing.T) {
// Ratio of funds in channels and total funds meets the // Ratio of funds in channels and total funds meets the
// threshold. // threshold.
{ {
[]Channel{ []LocalChannel{
{ {
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin), Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin),
@ -93,7 +93,7 @@ func TestConstraintsChannelBudget(t *testing.T) {
// recommended. We should also request 2 more channels as the // recommended. We should also request 2 more channels as the
// limit is 3. // limit is 3.
{ {
[]Channel{ []LocalChannel{
{ {
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin), Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin),
@ -113,7 +113,7 @@ func TestConstraintsChannelBudget(t *testing.T) {
// to be committed. We should only request a single additional // to be committed. We should only request a single additional
// channel as the limit is 3. // channel as the limit is 3.
{ {
[]Channel{ []LocalChannel{
{ {
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin), Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin),
@ -132,7 +132,7 @@ func TestConstraintsChannelBudget(t *testing.T) {
// Ratio of funds in channels and total funds is above the // Ratio of funds in channels and total funds is above the
// threshold. // threshold.
{ {
[]Channel{ []LocalChannel{
{ {
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin), Capacity: btcutil.Amount(btcutil.SatoshiPerBitcoin),

@ -19,7 +19,7 @@ type moreChansResp struct {
} }
type moreChanArg struct { type moreChanArg struct {
chans []Channel chans []LocalChannel
balance btcutil.Amount balance btcutil.Amount
} }
@ -29,7 +29,7 @@ type mockConstraints struct {
quit chan struct{} quit chan struct{}
} }
func (m *mockConstraints) ChannelBudget(chans []Channel, func (m *mockConstraints) ChannelBudget(chans []LocalChannel,
balance btcutil.Amount) (btcutil.Amount, uint32) { balance btcutil.Amount) (btcutil.Amount, uint32) {
if m.moreChanArgs != nil { if m.moreChanArgs != nil {
@ -76,7 +76,7 @@ type mockHeuristic struct {
type directiveArg struct { type directiveArg struct {
graph ChannelGraph graph ChannelGraph
amt btcutil.Amount amt btcutil.Amount
chans []Channel chans []LocalChannel
nodes map[NodeID]struct{} nodes map[NodeID]struct{}
} }
@ -84,7 +84,7 @@ func (m *mockHeuristic) Name() string {
return "mock" return "mock"
} }
func (m *mockHeuristic) NodeScores(g ChannelGraph, chans []Channel, func (m *mockHeuristic) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) { map[NodeID]*NodeScore, error) {
@ -154,7 +154,7 @@ type testContext struct {
sync.Mutex sync.Mutex
} }
func setup(t *testing.T, initialChans []Channel) (*testContext, func()) { func setup(t *testing.T, initialChans []LocalChannel) (*testContext, func()) {
t.Helper() t.Helper()
// First, we'll create all the dependencies that we'll need in order to // First, we'll create all the dependencies that we'll need in order to
@ -291,7 +291,7 @@ func TestAgentChannelOpenSignal(t *testing.T) {
// Next we'll signal a new channel being opened by the backing LN node, // Next we'll signal a new channel being opened by the backing LN node,
// with a capacity of 1 BTC. // with a capacity of 1 BTC.
newChan := Channel{ newChan := LocalChannel{
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.SatoshiPerBitcoin, Capacity: btcutil.SatoshiPerBitcoin,
} }
@ -432,7 +432,7 @@ func TestAgentChannelFailureSignal(t *testing.T) {
func TestAgentChannelCloseSignal(t *testing.T) { func TestAgentChannelCloseSignal(t *testing.T) {
t.Parallel() t.Parallel()
// We'll start the agent with two channels already being active. // We'll start the agent with two channels already being active.
initialChans := []Channel{ initialChans := []LocalChannel{
{ {
ChanID: randChanID(), ChanID: randChanID(),
Capacity: btcutil.SatoshiPerBitcoin, Capacity: btcutil.SatoshiPerBitcoin,

@ -70,7 +70,7 @@ func (c *WeightedCombAttachment) Name() string {
// is the maximum possible improvement in connectivity. // is the maximum possible improvement in connectivity.
// //
// NOTE: This is a part of the AttachmentHeuristic interface. // NOTE: This is a part of the AttachmentHeuristic interface.
func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []Channel, func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) { map[NodeID]*NodeScore, error) {

@ -80,7 +80,7 @@ func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string,
// not known will get a score of 0. // not known will get a score of 0.
// //
// NOTE: This is a part of the AttachmentHeuristic interface. // NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []Channel, func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) { map[NodeID]*NodeScore, error) {

@ -99,11 +99,8 @@ func (d dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
} }
edge := ChannelEdge{ edge := ChannelEdge{
Channel: Channel{
ChanID: lnwire.NewShortChanIDFromInt(ep.ChannelID), ChanID: lnwire.NewShortChanIDFromInt(ep.ChannelID),
Capacity: ei.Capacity, Capacity: ei.Capacity,
Node: NodeID(ep.Node.PubKeyBytes),
},
Peer: dbNode{ Peer: dbNode{
tx: tx, tx: tx,
node: ep.Node, node: ep.Node,
@ -264,19 +261,15 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
} }
return &ChannelEdge{ return &ChannelEdge{
Channel: Channel{
ChanID: chanID, ChanID: chanID,
Capacity: capacity, Capacity: capacity,
},
Peer: dbNode{ Peer: dbNode{
node: vertex1, node: vertex1,
}, },
}, },
&ChannelEdge{ &ChannelEdge{
Channel: Channel{
ChanID: chanID, ChanID: chanID,
Capacity: capacity, Capacity: capacity,
},
Peer: dbNode{ Peer: dbNode{
node: vertex2, node: vertex2,
}, },
@ -424,19 +417,16 @@ func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
} }
} }
channel := Channel{ edge1 := ChannelEdge{
ChanID: randChanID(), ChanID: randChanID(),
Capacity: capacity, Capacity: capacity,
}
edge1 := ChannelEdge{
Channel: channel,
Peer: vertex2, Peer: vertex2,
} }
vertex1.chans = append(vertex1.chans, edge1) vertex1.chans = append(vertex1.chans, edge1)
edge2 := ChannelEdge{ edge2 := ChannelEdge{
Channel: channel, ChanID: randChanID(),
Capacity: capacity,
Peer: vertex1, Peer: vertex1,
} }
vertex2.chans = append(vertex2.chans, edge2) vertex2.chans = append(vertex2.chans, edge2)

@ -36,10 +36,10 @@ type Node interface {
ForEachChannel(func(ChannelEdge) error) error ForEachChannel(func(ChannelEdge) error) error
} }
// Channel is a simple struct which contains relevant details of a particular // LocalChannel is a simple struct which contains relevant details of a
// channel within the channel graph. The fields in this struct may be used a // particular channel the local node has. The fields in this struct may be used
// signals for various AttachmentHeuristic implementations. // a signals for various AttachmentHeuristic implementations.
type Channel struct { type LocalChannel struct {
// ChanID is the short channel ID for this channel as defined within // ChanID is the short channel ID for this channel as defined within
// BOLT-0007. // BOLT-0007.
ChanID lnwire.ShortChannelID ChanID lnwire.ShortChannelID
@ -59,8 +59,12 @@ type Channel struct {
// edge within the graph. The existence of this reference to the connected node // edge within the graph. The existence of this reference to the connected node
// will allow callers to traverse the graph in an object-oriented manner. // will allow callers to traverse the graph in an object-oriented manner.
type ChannelEdge struct { type ChannelEdge struct {
// Channel contains the attributes of this channel. // ChanID is the short channel ID for this channel as defined within
Channel // BOLT-0007.
ChanID lnwire.ShortChannelID
// Capacity is the capacity of the channel expressed in satoshis.
Capacity btcutil.Amount
// Peer is the peer that this channel creates an edge to in the channel // Peer is the peer that this channel creates an edge to in the channel
// graph. // graph.
@ -136,7 +140,7 @@ type AttachmentHeuristic interface {
// //
// NOTE: A NodeID not found in the returned map is implicitly given a // NOTE: A NodeID not found in the returned map is implicitly given a
// score of 0. // score of 0.
NodeScores(g ChannelGraph, chans []Channel, NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) map[NodeID]*NodeScore, error)
} }

@ -23,7 +23,7 @@ type ManagerCfg struct {
// ChannelState is a function closure that returns the current set of // ChannelState is a function closure that returns the current set of
// channels managed by this node. // channels managed by this node.
ChannelState func() ([]Channel, error) ChannelState func() ([]LocalChannel, error)
// SubscribeTransactions is used to get a subscription for transactions // SubscribeTransactions is used to get a subscription for transactions
// relevant to this node's wallet. // relevant to this node's wallet.
@ -200,7 +200,7 @@ func (m *Manager) StartAgent() error {
chanID := lnwire.NewShortChanIDFromInt( chanID := lnwire.NewShortChanIDFromInt(
edgeUpdate.ChanID, edgeUpdate.ChanID,
) )
edge := Channel{ edge := LocalChannel{
ChanID: chanID, ChanID: chanID,
Capacity: edgeUpdate.Capacity, Capacity: edgeUpdate.Capacity,
Node: chanNode, Node: chanNode,
@ -292,7 +292,7 @@ func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) (
// If we want to take the local state into action when querying the // If we want to take the local state into action when querying the
// heuristics, we fetch it. If not we'll just pass an emply slice to // heuristics, we fetch it. If not we'll just pass an emply slice to
// the heuristic. // the heuristic.
var totalChans []Channel var totalChans []LocalChannel
var err error var err error
if localState { if localState {
// Fetch the current set of channels. // Fetch the current set of channels.

@ -78,7 +78,7 @@ func (p *PrefAttachment) Name() string {
// given to nodes already having high connectivity in the graph. // given to nodes already having high connectivity in the graph.
// //
// NOTE: This is a part of the AttachmentHeuristic interface. // NOTE: This is a part of the AttachmentHeuristic interface.
func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []Channel, func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) { map[NodeID]*NodeScore, error) {

@ -422,10 +422,10 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) {
// We'll simulate a channel update by adding the nodes // We'll simulate a channel update by adding the nodes
// to our set of channels. // to our set of channels.
var chans []Channel var chans []LocalChannel
for _, candidate := range scores { for _, candidate := range scores {
chans = append(chans, chans = append(chans,
Channel{ LocalChannel{
Node: candidate.NodeID, Node: candidate.NodeID,
}, },
) )

@ -50,7 +50,7 @@ func (g *TopCentrality) Name() string {
// As our current implementation of betweenness centrality is non-incremental, // As our current implementation of betweenness centrality is non-incremental,
// NodeScores will recalculate the centrality values on every call, which is // NodeScores will recalculate the centrality values on every call, which is
// slow for large graphs. // slow for large graphs.
func (g *TopCentrality) NodeScores(graph ChannelGraph, chans []Channel, func (g *TopCentrality) NodeScores(graph ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) { map[NodeID]*NodeScore, error) {

@ -16,9 +16,9 @@ func testTopCentrality(t *testing.T, graph testGraph,
topCentrality := NewTopCentrality() topCentrality := NewTopCentrality()
var channels []Channel var channels []LocalChannel
for _, ch := range channelsWith { for _, ch := range channelsWith {
channels = append(channels, Channel{ channels = append(channels, LocalChannel{
Node: NewNodeID(graphNodes[ch]), Node: NewNodeID(graphNodes[ch]),
}) })
} }

@ -249,7 +249,7 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
return &autopilot.ManagerCfg{ return &autopilot.ManagerCfg{
Self: self, Self: self,
PilotCfg: &pilotCfg, PilotCfg: &pilotCfg,
ChannelState: func() ([]autopilot.Channel, error) { ChannelState: func() ([]autopilot.LocalChannel, error) {
// We'll fetch the current state of open // We'll fetch the current state of open
// channels from the database to use as initial // channels from the database to use as initial
// state for the auto-pilot agent. // state for the auto-pilot agent.
@ -257,10 +257,10 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
if err != nil { if err != nil {
return nil, err return nil, err
} }
chanState := make([]autopilot.Channel, chanState := make([]autopilot.LocalChannel,
len(activeChannels)) len(activeChannels))
for i, channel := range activeChannels { for i, channel := range activeChannels {
chanState[i] = autopilot.Channel{ chanState[i] = autopilot.LocalChannel{
ChanID: channel.ShortChanID(), ChanID: channel.ShortChanID(),
Capacity: channel.Capacity, Capacity: channel.Capacity,
Node: autopilot.NewNodeID( Node: autopilot.NewNodeID(