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 }