package lnd import ( "errors" "fmt" "net" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tor" ) // validateAtplConfig is a helper method that makes sure the passed // configuration is sane. Currently it checks that the heuristic configuration // makes sense. In case the config is valid, it will return a list of // WeightedHeuristics that can be combined for use with the autopilot agent. func validateAtplCfg(cfg *lncfg.AutoPilot) ([]*autopilot.WeightedHeuristic, error) { var ( heuristicsStr string sum float64 heuristics []*autopilot.WeightedHeuristic ) // Create a help text that we can return in case the config is not // correct. for _, a := range autopilot.AvailableHeuristics { heuristicsStr += fmt.Sprintf(" '%v' ", a.Name()) } availStr := fmt.Sprintf("Available heuristics are: [%v]", heuristicsStr) // We'll go through the config and make sure all the heuristics exists, // and that the sum of their weights is 1.0. for name, weight := range cfg.Heuristic { a, ok := autopilot.AvailableHeuristics[name] if !ok { // No heuristic matching this config option was found. return nil, fmt.Errorf("heuristic %v not available. %v", name, availStr) } // If this heuristic was among the registered ones, we add it // to the list we'll give to the agent, and keep track of the // sum of weights. heuristics = append( heuristics, &autopilot.WeightedHeuristic{ Weight: weight, AttachmentHeuristic: a, }, ) sum += weight } // Check found heuristics. We must have at least one to operate. if len(heuristics) == 0 { return nil, fmt.Errorf("no active heuristics: %v", availStr) } if sum != 1.0 { return nil, fmt.Errorf("heuristic weights must sum to 1.0") } return heuristics, nil } // chanController is an implementation of the autopilot.ChannelController // interface that's backed by a running lnd instance. type chanController struct { server *server private bool minConfs int32 confTarget uint32 chanMinHtlcIn lnwire.MilliSatoshi netParams chainreg.BitcoinNetParams } // OpenChannel opens a channel to a target peer, with a capacity of the // specified amount. This function should un-block immediately after the // funding transaction that marks the channel open has been broadcast. func (c *chanController) OpenChannel(target *btcec.PublicKey, amt btcutil.Amount) error { // With the connection established, we'll now establish our connection // to the target peer, waiting for the first update before we exit. feePerKw, err := c.server.cc.FeeEstimator.EstimateFeePerKW( c.confTarget, ) if err != nil { return err } // Construct the open channel request and send it to the server to begin // the funding workflow. req := &funding.InitFundingMsg{ TargetPubkey: target, ChainHash: *c.netParams.GenesisHash, SubtractFees: true, LocalFundingAmt: amt, PushAmt: 0, MinHtlcIn: c.chanMinHtlcIn, FundingFeePerKw: feePerKw, Private: c.private, RemoteCsvDelay: 0, MinConfs: c.minConfs, MaxValueInFlight: 0, } updateStream, errChan := c.server.OpenChannel(req) select { case err := <-errChan: return err case <-updateStream: return nil case <-c.server.quit: return nil } } func (c *chanController) CloseChannel(chanPoint *wire.OutPoint) error { return nil } // A compile time assertion to ensure chanController meets the // autopilot.ChannelController interface. var _ autopilot.ChannelController = (*chanController)(nil) // initAutoPilot initializes a new autopilot.ManagerCfg to manage an autopilot. // Agent instance based on the passed configuration structs. The agent and all // interfaces needed to drive it won't be launched before the Manager's // StartAgent method is called. func initAutoPilot(svr *server, cfg *lncfg.AutoPilot, minHTLCIn lnwire.MilliSatoshi, netParams chainreg.BitcoinNetParams) ( *autopilot.ManagerCfg, error) { atplLog.Infof("Instantiating autopilot with active=%v, "+ "max_channels=%d, allocation=%f, min_chan_size=%d, "+ "max_chan_size=%d, private=%t, min_confs=%d, conf_target=%d", cfg.Active, cfg.MaxChannels, cfg.Allocation, cfg.MinChannelSize, cfg.MaxChannelSize, cfg.Private, cfg.MinConfs, cfg.ConfTarget) // Set up the constraints the autopilot heuristics must adhere to. atplConstraints := autopilot.NewConstraints( btcutil.Amount(cfg.MinChannelSize), btcutil.Amount(cfg.MaxChannelSize), uint16(cfg.MaxChannels), 10, cfg.Allocation, ) heuristics, err := validateAtplCfg(cfg) if err != nil { return nil, err } weightedAttachment, err := autopilot.NewWeightedCombAttachment( heuristics..., ) if err != nil { return nil, err } // With the heuristic itself created, we can now populate the remainder // of the items that the autopilot agent needs to perform its duties. self := svr.identityECDH.PubKey() pilotCfg := autopilot.Config{ Self: self, Heuristic: weightedAttachment, ChanController: &chanController{ server: svr, private: cfg.Private, minConfs: cfg.MinConfs, confTarget: cfg.ConfTarget, chanMinHtlcIn: minHTLCIn, netParams: netParams, }, WalletBalance: func() (btcutil.Amount, error) { return svr.cc.Wallet.ConfirmedBalance( cfg.MinConfs, lnwallet.DefaultAccountName, ) }, Graph: autopilot.ChannelGraphFromDatabase(svr.graphDB), Constraints: atplConstraints, ConnectToPeer: func(target *btcec.PublicKey, addrs []net.Addr) (bool, error) { // First, we'll check if we're already connected to the // target peer. If we are, we can exit early. Otherwise, // we'll need to establish a connection. if _, err := svr.FindPeer(target); err == nil { return true, nil } // We can't establish a channel if no addresses were // provided for the peer. if len(addrs) == 0 { return false, errors.New("no addresses specified") } atplLog.Tracef("Attempting to connect to %x", target.SerializeCompressed()) lnAddr := &lnwire.NetAddress{ IdentityKey: target, ChainNet: netParams.Net, } // We'll attempt to successively connect to each of the // advertised IP addresses until we've either exhausted // the advertised IP addresses, or have made a // connection. var connected bool for _, addr := range addrs { switch addr.(type) { case *net.TCPAddr, *tor.OnionAddr: lnAddr.Address = addr default: return false, fmt.Errorf("unknown "+ "address type %T", addr) } err := svr.ConnectToPeer( lnAddr, false, svr.cfg.ConnectionTimeout, ) if err != nil { // If we weren't able to connect to the // peer at this address, then we'll move // onto the next. continue } connected = true break } // If we weren't able to establish a connection at all, // then we'll error out. if !connected { return false, errors.New("exhausted all " + "advertised addresses") } return false, nil }, DisconnectPeer: svr.DisconnectPeer, } // Create and return the autopilot.ManagerCfg that administrates this // agent-pilot instance. return &autopilot.ManagerCfg{ Self: self, PilotCfg: &pilotCfg, ChannelState: func() ([]autopilot.LocalChannel, error) { // We'll fetch the current state of open // channels from the database to use as initial // state for the auto-pilot agent. activeChannels, err := svr.chanStateDB.FetchAllChannels() if err != nil { return nil, err } chanState := make([]autopilot.LocalChannel, len(activeChannels)) for i, channel := range activeChannels { localCommit := channel.LocalCommitment balance := localCommit.LocalBalance.ToSatoshis() chanState[i] = autopilot.LocalChannel{ ChanID: channel.ShortChanID(), Balance: balance, Node: autopilot.NewNodeID( channel.IdentityPub, ), } } return chanState, nil }, ChannelInfo: func(chanPoint wire.OutPoint) ( *autopilot.LocalChannel, error) { channel, err := svr.chanStateDB.FetchChannel(nil, chanPoint) if err != nil { return nil, err } localCommit := channel.LocalCommitment return &autopilot.LocalChannel{ ChanID: channel.ShortChanID(), Balance: localCommit.LocalBalance.ToSatoshis(), Node: autopilot.NewNodeID(channel.IdentityPub), }, nil }, SubscribeTransactions: svr.cc.Wallet.SubscribeTransactions, SubscribeTopology: svr.chanRouter.SubscribeTopology, }, nil }