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

     1  package dcrlnd
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  
     8  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
     9  	"github.com/decred/dcrd/dcrutil/v4"
    10  	"github.com/decred/dcrd/wire"
    11  	"github.com/decred/dcrlnd/autopilot"
    12  	"github.com/decred/dcrlnd/chainreg"
    13  	"github.com/decred/dcrlnd/funding"
    14  	"github.com/decred/dcrlnd/lncfg"
    15  	"github.com/decred/dcrlnd/lnwallet"
    16  	"github.com/decred/dcrlnd/lnwire"
    17  	"github.com/decred/dcrlnd/tor"
    18  )
    19  
    20  // validateAtplConfig is a helper method that makes sure the passed
    21  // configuration is sane. Currently it checks that the heuristic configuration
    22  // makes sense. In case the config is valid, it will return a list of
    23  // WeightedHeuristics that can be combined for use with the autopilot agent.
    24  func validateAtplCfg(cfg *lncfg.AutoPilot) ([]*autopilot.WeightedHeuristic,
    25  	error) {
    26  
    27  	var (
    28  		heuristicsStr string
    29  		sum           float64
    30  		heuristics    []*autopilot.WeightedHeuristic
    31  	)
    32  
    33  	// Create a help text that we can return in case the config is not
    34  	// correct.
    35  	for _, a := range autopilot.AvailableHeuristics {
    36  		heuristicsStr += fmt.Sprintf(" '%v' ", a.Name())
    37  	}
    38  	availStr := fmt.Sprintf("Available heuristics are: [%v]", heuristicsStr)
    39  
    40  	// We'll go through the config and make sure all the heuristics exists,
    41  	// and that the sum of their weights is 1.0.
    42  	for name, weight := range cfg.Heuristic {
    43  		a, ok := autopilot.AvailableHeuristics[name]
    44  		if !ok {
    45  			// No heuristic matching this config option was found.
    46  			return nil, fmt.Errorf("heuristic %v not available. %v",
    47  				name, availStr)
    48  		}
    49  
    50  		// If this heuristic was among the registered ones, we add it
    51  		// to the list we'll give to the agent, and keep track of the
    52  		// sum of weights.
    53  		heuristics = append(
    54  			heuristics,
    55  			&autopilot.WeightedHeuristic{
    56  				Weight:              weight,
    57  				AttachmentHeuristic: a,
    58  			},
    59  		)
    60  		sum += weight
    61  	}
    62  
    63  	// Check found heuristics. We must have at least one to operate.
    64  	if len(heuristics) == 0 {
    65  		return nil, fmt.Errorf("no active heuristics: %v", availStr)
    66  	}
    67  
    68  	if sum != 1.0 {
    69  		return nil, fmt.Errorf("heuristic weights must sum to 1.0")
    70  	}
    71  	return heuristics, nil
    72  }
    73  
    74  // chanController is an implementation of the autopilot.ChannelController
    75  // interface that's backed by a running lnd instance.
    76  type chanController struct {
    77  	server        *server
    78  	private       bool
    79  	minConfs      int32
    80  	confTarget    uint32
    81  	chanMinHtlcIn lnwire.MilliAtom
    82  	netParams     chainreg.DecredNetParams
    83  }
    84  
    85  // OpenChannel opens a channel to a target peer, with a capacity of the
    86  // specified amount. This function should un-block immediately after the
    87  // funding transaction that marks the channel open has been broadcast.
    88  func (c *chanController) OpenChannel(target *secp256k1.PublicKey,
    89  	amt dcrutil.Amount) error {
    90  
    91  	// With the connection established, we'll now establish our connection
    92  	// to the target peer, waiting for the first update before we exit.
    93  	feePerKB, err := c.server.cc.FeeEstimator.EstimateFeePerKB(
    94  		c.confTarget,
    95  	)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	// Construct the open channel request and send it to the server to begin
   101  	// the funding workflow.
   102  	req := &funding.InitFundingMsg{
   103  		TargetPubkey:     target,
   104  		ChainHash:        c.netParams.GenesisHash,
   105  		SubtractFees:     true,
   106  		LocalFundingAmt:  amt,
   107  		PushAmt:          0,
   108  		MinHtlcIn:        c.chanMinHtlcIn,
   109  		FundingFeePerKB:  feePerKB,
   110  		Private:          c.private,
   111  		RemoteCsvDelay:   0,
   112  		MinConfs:         c.minConfs,
   113  		MaxValueInFlight: 0,
   114  	}
   115  
   116  	updateStream, errChan := c.server.OpenChannel(req)
   117  	select {
   118  	case err := <-errChan:
   119  		return err
   120  	case <-updateStream:
   121  		return nil
   122  	case <-c.server.quit:
   123  		return nil
   124  	}
   125  }
   126  
   127  func (c *chanController) CloseChannel(chanPoint *wire.OutPoint) error {
   128  	return nil
   129  }
   130  
   131  // A compile time assertion to ensure chanController meets the
   132  // autopilot.ChannelController interface.
   133  var _ autopilot.ChannelController = (*chanController)(nil)
   134  
   135  // initAutoPilot initializes a new autopilot.ManagerCfg to manage an autopilot.
   136  // Agent instance based on the passed configuration structs. The agent and all
   137  // interfaces needed to drive it won't be launched before the Manager's
   138  // StartAgent method is called.
   139  func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
   140  	minHTLCIn lnwire.MilliAtom, netParams chainreg.DecredNetParams) (
   141  	*autopilot.ManagerCfg, error) {
   142  
   143  	atplLog.Infof("Instantiating autopilot with active=%v, "+
   144  		"max_channels=%d, allocation=%f, min_chan_size=%d, "+
   145  		"max_chan_size=%d, private=%t, min_confs=%d, conf_target=%d",
   146  		cfg.Active, cfg.MaxChannels, cfg.Allocation, cfg.MinChannelSize,
   147  		cfg.MaxChannelSize, cfg.Private, cfg.MinConfs, cfg.ConfTarget)
   148  
   149  	// Set up the constraints the autopilot heuristics must adhere to.
   150  	atplConstraints := autopilot.NewConstraints(
   151  		dcrutil.Amount(cfg.MinChannelSize),
   152  		dcrutil.Amount(cfg.MaxChannelSize),
   153  		uint16(cfg.MaxChannels),
   154  		10,
   155  		cfg.Allocation,
   156  	)
   157  	heuristics, err := validateAtplCfg(cfg)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	weightedAttachment, err := autopilot.NewWeightedCombAttachment(
   163  		heuristics...,
   164  	)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	// With the heuristic itself created, we can now populate the remainder
   170  	// of the items that the autopilot agent needs to perform its duties.
   171  	self := svr.identityECDH.PubKey()
   172  	pilotCfg := autopilot.Config{
   173  		Self:      self,
   174  		Heuristic: weightedAttachment,
   175  		ChanController: &chanController{
   176  			server:        svr,
   177  			private:       cfg.Private,
   178  			minConfs:      cfg.MinConfs,
   179  			confTarget:    cfg.ConfTarget,
   180  			chanMinHtlcIn: minHTLCIn,
   181  			netParams:     netParams,
   182  		},
   183  		WalletBalance: func() (dcrutil.Amount, error) {
   184  			return svr.cc.Wallet.ConfirmedBalance(
   185  				cfg.MinConfs, lnwallet.DefaultAccountName,
   186  			)
   187  		},
   188  		Graph:       autopilot.ChannelGraphFromDatabase(svr.graphDB),
   189  		Constraints: atplConstraints,
   190  		ConnectToPeer: func(target *secp256k1.PublicKey, addrs []net.Addr) (bool, error) {
   191  			// First, we'll check if we're already connected to the
   192  			// target peer. If we are, we can exit early. Otherwise,
   193  			// we'll need to establish a connection.
   194  			if _, err := svr.FindPeer(target); err == nil {
   195  				return true, nil
   196  			}
   197  
   198  			// We can't establish a channel if no addresses were
   199  			// provided for the peer.
   200  			if len(addrs) == 0 {
   201  				return false, errors.New("no addresses specified")
   202  			}
   203  
   204  			atplLog.Tracef("Attempting to connect to %x",
   205  				target.SerializeCompressed())
   206  
   207  			lnAddr := &lnwire.NetAddress{
   208  				IdentityKey: target,
   209  				ChainNet:    netParams.Net,
   210  			}
   211  
   212  			// We'll attempt to successively connect to each of the
   213  			// advertised IP addresses until we've either exhausted
   214  			// the advertised IP addresses, or have made a
   215  			// connection.
   216  			var connected bool
   217  			for _, addr := range addrs {
   218  				switch addr.(type) {
   219  				case *net.TCPAddr, *tor.OnionAddr:
   220  					lnAddr.Address = addr
   221  				default:
   222  					return false, fmt.Errorf("unknown "+
   223  						"address type %T", addr)
   224  				}
   225  
   226  				err := svr.ConnectToPeer(
   227  					lnAddr, false, svr.cfg.ConnectionTimeout,
   228  				)
   229  				if err != nil {
   230  					// If we weren't able to connect to the
   231  					// peer at this address, then we'll move
   232  					// onto the next.
   233  					continue
   234  				}
   235  
   236  				connected = true
   237  				break
   238  			}
   239  
   240  			// If we weren't able to establish a connection at all,
   241  			// then we'll error out.
   242  			if !connected {
   243  				return false, errors.New("exhausted all " +
   244  					"advertised addresses")
   245  			}
   246  
   247  			return false, nil
   248  		},
   249  		DisconnectPeer: svr.DisconnectPeer,
   250  	}
   251  
   252  	// Create and return the autopilot.ManagerCfg that administrates this
   253  	// agent-pilot instance.
   254  	return &autopilot.ManagerCfg{
   255  		Self:     self,
   256  		PilotCfg: &pilotCfg,
   257  		ChannelState: func() ([]autopilot.LocalChannel, error) {
   258  			// We'll fetch the current state of open
   259  			// channels from the database to use as initial
   260  			// state for the auto-pilot agent.
   261  			activeChannels, err := svr.chanStateDB.FetchAllChannels()
   262  			if err != nil {
   263  				return nil, err
   264  			}
   265  			chanState := make([]autopilot.LocalChannel,
   266  				len(activeChannels))
   267  			for i, channel := range activeChannels {
   268  				localCommit := channel.LocalCommitment
   269  				balance := localCommit.LocalBalance.ToAtoms()
   270  
   271  				chanState[i] = autopilot.LocalChannel{
   272  					ChanID:  channel.ShortChanID(),
   273  					Balance: balance,
   274  					Node: autopilot.NewNodeID(
   275  						channel.IdentityPub,
   276  					),
   277  				}
   278  			}
   279  
   280  			return chanState, nil
   281  		},
   282  		ChannelInfo: func(chanPoint wire.OutPoint) (
   283  			*autopilot.LocalChannel, error) {
   284  
   285  			channel, err := svr.chanStateDB.FetchChannel(nil, chanPoint)
   286  			if err != nil {
   287  				return nil, err
   288  			}
   289  
   290  			localCommit := channel.LocalCommitment
   291  			return &autopilot.LocalChannel{
   292  				ChanID:  channel.ShortChanID(),
   293  				Balance: localCommit.LocalBalance.ToAtoms(),
   294  				Node:    autopilot.NewNodeID(channel.IdentityPub),
   295  			}, nil
   296  		},
   297  		SubscribeTransactions: svr.cc.Wallet.SubscribeTransactions,
   298  		SubscribeTopology:     svr.chanRouter.SubscribeTopology,
   299  	}, nil
   300  }