github.com/decred/dcrlnd@v0.7.6/autopilot/manager.go (about)

     1  package autopilot
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
     8  	"github.com/decred/dcrd/wire"
     9  	"github.com/decred/dcrlnd/lnwallet"
    10  	"github.com/decred/dcrlnd/lnwire"
    11  	"github.com/decred/dcrlnd/routing"
    12  )
    13  
    14  // ManagerCfg houses a set of values and methods that is passed to the Manager
    15  // for it to properly manage its autopilot agent.
    16  type ManagerCfg struct {
    17  	// Self is the public key of the lnd instance. It is used to making
    18  	// sure the autopilot is not opening channels to itself.
    19  	Self *secp256k1.PublicKey
    20  
    21  	// PilotCfg is the config of the autopilot agent managed by the
    22  	// Manager.
    23  	PilotCfg *Config
    24  
    25  	// ChannelState is a function closure that returns the current set of
    26  	// channels managed by this node.
    27  	ChannelState func() ([]LocalChannel, error)
    28  
    29  	// ChannelInfo is a function closure that returns the channel managed
    30  	// by the node given by the passed channel point.
    31  	ChannelInfo func(wire.OutPoint) (*LocalChannel, error)
    32  
    33  	// SubscribeTransactions is used to get a subscription for transactions
    34  	// relevant to this node's wallet.
    35  	SubscribeTransactions func() (lnwallet.TransactionSubscription, error)
    36  
    37  	// SubscribeTopology is used to get a subscription for topology changes
    38  	// on the network.
    39  	SubscribeTopology func() (*routing.TopologyClient, error)
    40  }
    41  
    42  // Manager is struct that manages an autopilot agent, making it possible to
    43  // enable and disable it at will, and hand it relevant external information.
    44  // It implements the autopilot grpc service, which is used to get data about
    45  // the running autopilot, and gives it relevant information.
    46  type Manager struct {
    47  	started sync.Once
    48  	stopped sync.Once
    49  
    50  	cfg *ManagerCfg
    51  
    52  	// pilot is the current autopilot agent. It will be nil if the agent is
    53  	// disabled.
    54  	pilot *Agent
    55  
    56  	quit chan struct{}
    57  	wg   sync.WaitGroup
    58  	sync.Mutex
    59  }
    60  
    61  // NewManager creates a new instance of the Manager from the passed config.
    62  func NewManager(cfg *ManagerCfg) (*Manager, error) {
    63  	return &Manager{
    64  		cfg:  cfg,
    65  		quit: make(chan struct{}),
    66  	}, nil
    67  }
    68  
    69  // Start starts the Manager.
    70  func (m *Manager) Start() error {
    71  	m.started.Do(func() {})
    72  	return nil
    73  }
    74  
    75  // Stop stops the Manager. If an autopilot agent is active, it will also be
    76  // stopped.
    77  func (m *Manager) Stop() error {
    78  	m.stopped.Do(func() {
    79  		if err := m.StopAgent(); err != nil {
    80  			log.Errorf("Unable to stop pilot: %v", err)
    81  		}
    82  
    83  		close(m.quit)
    84  		m.wg.Wait()
    85  	})
    86  	return nil
    87  }
    88  
    89  // IsActive returns whether the autopilot agent is currently active.
    90  func (m *Manager) IsActive() bool {
    91  	m.Lock()
    92  	defer m.Unlock()
    93  
    94  	return m.pilot != nil
    95  }
    96  
    97  // StartAgent creates and starts an autopilot agent from the Manager's
    98  // config.
    99  func (m *Manager) StartAgent() error {
   100  	m.Lock()
   101  	defer m.Unlock()
   102  
   103  	// Already active.
   104  	if m.pilot != nil {
   105  		return nil
   106  	}
   107  
   108  	// Next, we'll fetch the current state of open channels from the
   109  	// database to use as initial state for the auto-pilot agent.
   110  	initialChanState, err := m.cfg.ChannelState()
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// Now that we have all the initial dependencies, we can create the
   116  	// auto-pilot instance itself.
   117  	pilot, err := New(*m.cfg.PilotCfg, initialChanState)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	if err := pilot.Start(); err != nil {
   123  		return err
   124  	}
   125  
   126  	// Finally, we'll need to subscribe to two things: incoming
   127  	// transactions that modify the wallet's balance, and also any graph
   128  	// topology updates.
   129  	txnSubscription, err := m.cfg.SubscribeTransactions()
   130  	if err != nil {
   131  		pilot.Stop()
   132  		return err
   133  	}
   134  	graphSubscription, err := m.cfg.SubscribeTopology()
   135  	if err != nil {
   136  		txnSubscription.Cancel()
   137  		pilot.Stop()
   138  		return err
   139  	}
   140  
   141  	m.pilot = pilot
   142  
   143  	// We'll launch a goroutine to provide the agent with notifications
   144  	// whenever the balance of the wallet changes.
   145  	// TODO(halseth): can lead to panic if in process of shutting down.
   146  	m.wg.Add(1)
   147  	go func() {
   148  		defer txnSubscription.Cancel()
   149  		defer m.wg.Done()
   150  
   151  		for {
   152  			select {
   153  			case <-txnSubscription.ConfirmedTransactions():
   154  				pilot.OnBalanceChange()
   155  
   156  			// We won't act upon new unconfirmed transaction, as
   157  			// we'll only use confirmed outputs when funding.
   158  			// However, we will still drain this request in order
   159  			// to avoid goroutine leaks, and ensure we promptly
   160  			// read from the channel if available.
   161  			case <-txnSubscription.UnconfirmedTransactions():
   162  			case <-pilot.quit:
   163  				return
   164  			case <-m.quit:
   165  				return
   166  			}
   167  		}
   168  
   169  	}()
   170  
   171  	// We'll also launch a goroutine to provide the agent with
   172  	// notifications for when the graph topology controlled by the node
   173  	// changes.
   174  	m.wg.Add(1)
   175  	go func() {
   176  		defer graphSubscription.Cancel()
   177  		defer m.wg.Done()
   178  
   179  		for {
   180  			select {
   181  			case topChange, ok := <-graphSubscription.TopologyChanges:
   182  				// If the router is shutting down, then we will
   183  				// as well.
   184  				if !ok {
   185  					return
   186  				}
   187  
   188  				for _, edgeUpdate := range topChange.ChannelEdgeUpdates {
   189  					// If this isn't an advertisement by
   190  					// the backing lnd node, then we'll
   191  					// continue as we only want to add
   192  					// channels that we've created
   193  					// ourselves.
   194  					if !edgeUpdate.AdvertisingNode.IsEqual(m.cfg.Self) {
   195  						continue
   196  					}
   197  
   198  					// If this is indeed a channel we
   199  					// opened, then we'll convert it to the
   200  					// autopilot.Channel format, and notify
   201  					// the pilot of the new channel.
   202  					cp := edgeUpdate.ChanPoint
   203  					edge, err := m.cfg.ChannelInfo(cp)
   204  					if err != nil {
   205  						log.Errorf("Unable to fetch "+
   206  							"channel info for %v: "+
   207  							"%v", cp, err)
   208  						continue
   209  					}
   210  
   211  					pilot.OnChannelOpen(*edge)
   212  				}
   213  
   214  				// For each closed channel, we'll obtain
   215  				// the chanID of the closed channel and send it
   216  				// to the pilot.
   217  				for _, chanClose := range topChange.ClosedChannels {
   218  					chanID := lnwire.NewShortChanIDFromInt(
   219  						chanClose.ChanID,
   220  					)
   221  
   222  					pilot.OnChannelClose(chanID)
   223  				}
   224  
   225  				// If new nodes were added to the graph, or
   226  				// node information has changed, we'll poke
   227  				// autopilot to see if it can make use of them.
   228  				if len(topChange.NodeUpdates) > 0 {
   229  					pilot.OnNodeUpdates()
   230  				}
   231  
   232  			case <-pilot.quit:
   233  				return
   234  			case <-m.quit:
   235  				return
   236  			}
   237  		}
   238  	}()
   239  
   240  	log.Debugf("Manager started autopilot agent")
   241  
   242  	return nil
   243  }
   244  
   245  // StopAgent stops any active autopilot agent.
   246  func (m *Manager) StopAgent() error {
   247  	m.Lock()
   248  	defer m.Unlock()
   249  
   250  	// Not active, so we can return early.
   251  	if m.pilot == nil {
   252  		return nil
   253  	}
   254  
   255  	if err := m.pilot.Stop(); err != nil {
   256  		return err
   257  	}
   258  
   259  	// Make sure to nil the current agent, indicating it is no longer
   260  	// active.
   261  	m.pilot = nil
   262  
   263  	log.Debugf("Manager stopped autopilot agent")
   264  
   265  	return nil
   266  }
   267  
   268  // QueryHeuristics queries the available autopilot heuristics for node scores.
   269  func (m *Manager) QueryHeuristics(nodes []NodeID, localState bool) (
   270  	HeuristicScores, error) {
   271  
   272  	m.Lock()
   273  	defer m.Unlock()
   274  
   275  	n := make(map[NodeID]struct{})
   276  	for _, node := range nodes {
   277  		n[node] = struct{}{}
   278  	}
   279  
   280  	log.Debugf("Querying heuristics for %d nodes", len(n))
   281  	return m.queryHeuristics(n, localState)
   282  }
   283  
   284  // HeuristicScores is an alias for a map that maps heuristic names to a map of
   285  // scores for pubkeys.
   286  type HeuristicScores map[string]map[NodeID]float64
   287  
   288  // queryHeuristics gets node scores from all available simple heuristics, and
   289  // the agent's current active heuristic.
   290  //
   291  // NOTE: Must be called with the manager's lock.
   292  func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) (
   293  	HeuristicScores, error) {
   294  
   295  	// If we want to take the local state into action when querying the
   296  	// heuristics, we fetch it. If not we'll just pass an emply slice to
   297  	// the heuristic.
   298  	var totalChans []LocalChannel
   299  	var err error
   300  	if localState {
   301  		// Fetch the current set of channels.
   302  		totalChans, err = m.cfg.ChannelState()
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  
   307  		// If the agent is active, we can merge the channel state with
   308  		// the channels pending open.
   309  		if m.pilot != nil {
   310  			m.pilot.chanStateMtx.Lock()
   311  			m.pilot.pendingMtx.Lock()
   312  			totalChans = mergeChanState(
   313  				m.pilot.pendingOpens, m.pilot.chanState,
   314  			)
   315  			m.pilot.pendingMtx.Unlock()
   316  			m.pilot.chanStateMtx.Unlock()
   317  		}
   318  	}
   319  
   320  	// As channel size we'll use the maximum size.
   321  	chanSize := m.cfg.PilotCfg.Constraints.MaxChanSize()
   322  
   323  	// We'll start by getting the scores from each available sub-heuristic,
   324  	// in addition the current agent heuristic.
   325  	var heuristics []AttachmentHeuristic
   326  	heuristics = append(heuristics, availableHeuristics...)
   327  	heuristics = append(heuristics, m.cfg.PilotCfg.Heuristic)
   328  
   329  	report := make(HeuristicScores)
   330  	for _, h := range heuristics {
   331  		name := h.Name()
   332  
   333  		// If the agent heuristic is among the simple heuristics it
   334  		// might get queried more than once. As an optimization we'll
   335  		// just skip it the second time.
   336  		if _, ok := report[name]; ok {
   337  			continue
   338  		}
   339  
   340  		s, err := h.NodeScores(
   341  			m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes,
   342  		)
   343  		if err != nil {
   344  			return nil, fmt.Errorf("unable to get sub score: %v",
   345  				err)
   346  		}
   347  
   348  		log.Debugf("Heuristic \"%v\" scored %d nodes", name, len(s))
   349  
   350  		scores := make(map[NodeID]float64)
   351  		for nID, score := range s {
   352  			scores[nID] = score.Score
   353  		}
   354  
   355  		report[name] = scores
   356  	}
   357  
   358  	return report, nil
   359  }
   360  
   361  // SetNodeScores is used to set the scores of the given heuristic, if it is
   362  // active, and ScoreSettable.
   363  func (m *Manager) SetNodeScores(name string, scores map[NodeID]float64) error {
   364  	m.Lock()
   365  	defer m.Unlock()
   366  
   367  	// It must be ScoreSettable to be available for external
   368  	// scores.
   369  	s, ok := m.cfg.PilotCfg.Heuristic.(ScoreSettable)
   370  	if !ok {
   371  		return fmt.Errorf("current heuristic doesn't support " +
   372  			"external scoring")
   373  	}
   374  
   375  	// Heuristic was found, set its node scores.
   376  	applied, err := s.SetNodeScores(name, scores)
   377  	if err != nil {
   378  		return err
   379  	}
   380  
   381  	if !applied {
   382  		return fmt.Errorf("heuristic with name %v not found", name)
   383  	}
   384  
   385  	// If the autopilot agent is active, notify about the updated
   386  	// heuristic.
   387  	if m.pilot != nil {
   388  		m.pilot.OnHeuristicUpdate(m.cfg.PilotCfg.Heuristic)
   389  	}
   390  
   391  	return nil
   392  }