From 5e8e54083fb0b7ad1ffa2027e6b9b496aadb4a66 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 22 Nov 2018 23:18:09 +0100 Subject: [PATCH] autopilot/prefattach+interface: add API NodeScores This commit adds a new method NodeScores to the AttachementHeuristic interface. Its intended use is to score a set of nodes according to their preference as channel counterparties. The PrefAttach heuristic gets a NodeScores method that will score the ndoes according to their number of already existing channels, similar to what is done already in Select. --- autopilot/agent_test.go | 7 +++ autopilot/interface.go | 22 +++++++++ autopilot/prefattach.go | 106 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/autopilot/agent_test.go b/autopilot/agent_test.go index c5c07de4..0017eb45 100644 --- a/autopilot/agent_test.go +++ b/autopilot/agent_test.go @@ -93,6 +93,13 @@ func (m *mockHeuristic) Select(self *btcec.PublicKey, graph ChannelGraph, } } +func (m *mockHeuristic) NodeScores(g ChannelGraph, chans []Channel, + fundsAvailable btcutil.Amount, nodes map[NodeID]struct{}) ( + map[NodeID]*AttachmentDirective, error) { + + return nil, nil +} + var _ AttachmentHeuristic = (*mockHeuristic)(nil) type openChanIntent struct { diff --git a/autopilot/interface.go b/autopilot/interface.go index 35e447e7..492a2ba3 100644 --- a/autopilot/interface.go +++ b/autopilot/interface.go @@ -98,6 +98,10 @@ type AttachmentDirective struct { // Addrs is a list of addresses that the target peer may be reachable // at. Addrs []net.Addr + + // Score is the score given by the heuristic for opening a channel of + // the given size to this node. + Score float64 } // AttachmentHeuristic is one of the primary interfaces within this package. @@ -127,6 +131,24 @@ type AttachmentHeuristic interface { Select(self *btcec.PublicKey, graph ChannelGraph, amtToUse btcutil.Amount, numNewChans uint32, skipNodes map[NodeID]struct{}) ([]AttachmentDirective, error) + + // NodeScores is a method that given the current channel graph, current + // set of local channels and funds available, scores the given nodes + // according to the preference of opening a channel with them. The + // returned channel candidates maps the NodeID to an attachemnt + // directive containing a score and a channel size. + // + // The scores will be in the range [0, M], where 0 indicates no + // improvement in connectivity if a channel is opened to this node, + // while M is the maximum possible improvement in connectivity. The + // size of M is up to the implementation of this interface, so scores + // must be normalized if compared against other implementations. + // + // NOTE: A NodeID not found in the returned map is implicitly given a + // score of 0. + NodeScores(g ChannelGraph, chans []Channel, + fundsAvailable btcutil.Amount, nodes map[NodeID]struct{}) ( + map[NodeID]*AttachmentDirective, error) } // ChannelController is a simple interface that allows an auto-pilot agent to diff --git a/autopilot/prefattach.go b/autopilot/prefattach.go index d675ac98..59f89bb2 100644 --- a/autopilot/prefattach.go +++ b/autopilot/prefattach.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" prand "math/rand" + "net" "time" "github.com/btcsuite/btcd/btcec" @@ -249,3 +250,108 @@ func (p *ConstrainedPrefAttachment) Select(self *btcec.PublicKey, g ChannelGraph return nil, fmt.Errorf("err") } } + +// NodeScores is a method that given the current channel graph, current set of +// local channels and funds available, scores the given nodes according the the +// preference of opening a channel with them. +// +// The heuristic employed by this method is one that attempts to promote a +// scale-free network globally, via local attachment preferences for new nodes +// joining the network with an amount of available funds to be allocated to +// channels. Specifically, we consider the degree of each node (and the flow +// in/out of the node available via its open channels) and utilize the +// Barabási–Albert model to drive our recommended attachment heuristics. If +// implemented globally for each new participant, this results in a channel +// graph that is scale-free and follows a power law distribution with k=-3. +// +// The returned scores will be in the range [0.0, 1.0], where higher scores are +// given to nodes already having high connectivity in the graph. +// +// NOTE: This is a part of the AttachmentHeuristic interface. +func (p *ConstrainedPrefAttachment) NodeScores(g ChannelGraph, chans []Channel, + fundsAvailable btcutil.Amount, nodes map[NodeID]struct{}) ( + map[NodeID]*AttachmentDirective, error) { + + // Count the number of channels in the graph. We'll also count the + // number of channels as we go for the nodes we are interested in, and + // record their addresses found in the db. + var graphChans int + nodeChanNum := make(map[NodeID]int) + addresses := make(map[NodeID][]net.Addr) + if err := g.ForEachNode(func(n Node) error { + var nodeChans int + err := n.ForEachChannel(func(_ ChannelEdge) error { + nodeChans++ + graphChans++ + return nil + }) + if err != nil { + return err + } + + // If this node is not among our nodes to score, we can return + // early. + nID := NodeID(n.PubKey()) + if _, ok := nodes[nID]; !ok { + return nil + } + + // Otherwise we'll record the number of channels, and also + // populate the address in our channel candidates map. + nodeChanNum[nID] = nodeChans + addresses[nID] = n.Addrs() + + return nil + }); err != nil { + return nil, err + } + + // If there are no channels in the graph we cannot determine any + // preferences, so we return, indicating all candidates get a score of + // zero. + if graphChans == 0 { + return nil, nil + } + + existingPeers := make(map[NodeID]struct{}) + for _, c := range chans { + existingPeers[c.Node] = struct{}{} + } + + // For each node in the set of nodes, count their fraction of channels + // in the graph, and use that as the score. + candidates := make(map[NodeID]*AttachmentDirective) + for nID, nodeChans := range nodeChanNum { + // As channel size we'll use the maximum channel size available. + chanSize := p.constraints.MaxChanSize + if fundsAvailable-chanSize < 0 { + chanSize = fundsAvailable + } + + _, ok := existingPeers[nID] + + switch { + + // If the node is among or existing channel peers, we don't + // need another channel. + case ok: + continue + + // If the amount is too small, we don't want to attempt opening + // another channel. + case chanSize == 0 || chanSize < p.constraints.MinChanSize: + continue + } + + // Otherwise we score the node according to its fraction of + // channels in the graph. + score := float64(nodeChans) / float64(graphChans) + candidates[nID] = &AttachmentDirective{ + NodeID: nID, + ChanAmt: chanSize, + Score: score, + } + } + + return candidates, nil +}