You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
7.7 KiB
268 lines
7.7 KiB
package lnd |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"net" |
|
|
|
"github.com/btcsuite/btcd/btcec" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/davecgh/go-spew/spew" |
|
"github.com/lightningnetwork/lnd/autopilot" |
|
"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 *autoPilotConfig) ([]*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("Avaiblable heuristcs 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 |
|
} |
|
|
|
// 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(3) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// TODO(halseth): make configurable? |
|
minHtlc := lnwire.NewMSatFromSatoshis(1) |
|
|
|
// Construct the open channel request and send it to the server to begin |
|
// the funding workflow. |
|
req := &openChanReq{ |
|
targetPubkey: target, |
|
chainHash: *activeNetParams.GenesisHash, |
|
localFundingAmt: amt, |
|
pushAmt: 0, |
|
minHtlc: minHtlc, |
|
fundingFeePerKw: feePerKw, |
|
private: c.private, |
|
remoteCsvDelay: 0, |
|
minConfs: c.minConfs, |
|
} |
|
|
|
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 |
|
} |
|
func (c *chanController) SpliceIn(chanPoint *wire.OutPoint, |
|
amt btcutil.Amount) (*autopilot.Channel, error) { |
|
return nil, nil |
|
} |
|
func (c *chanController) SpliceOut(chanPoint *wire.OutPoint, |
|
amt btcutil.Amount) (*autopilot.Channel, error) { |
|
return nil, 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 struct. 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 *autoPilotConfig) (*autopilot.ManagerCfg, error) { |
|
atplLog.Infof("Instantiating autopilot with cfg: %v", spew.Sdump(cfg)) |
|
|
|
// 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.identityPriv.PubKey() |
|
pilotCfg := autopilot.Config{ |
|
Self: self, |
|
Heuristic: weightedAttachment, |
|
ChanController: &chanController{ |
|
server: svr, |
|
private: cfg.Private, |
|
minConfs: cfg.MinConfs, |
|
}, |
|
WalletBalance: func() (btcutil.Amount, error) { |
|
return svr.cc.wallet.ConfirmedBalance(cfg.MinConfs) |
|
}, |
|
Graph: autopilot.ChannelGraphFromDatabase(svr.chanDB.ChannelGraph()), |
|
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: activeNetParams.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) |
|
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.Channel, 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.chanDB.FetchAllChannels() |
|
if err != nil { |
|
return nil, err |
|
} |
|
chanState := make([]autopilot.Channel, |
|
len(activeChannels)) |
|
for i, channel := range activeChannels { |
|
chanState[i] = autopilot.Channel{ |
|
ChanID: channel.ShortChanID(), |
|
Capacity: channel.Capacity, |
|
Node: autopilot.NewNodeID( |
|
channel.IdentityPub), |
|
} |
|
} |
|
|
|
return chanState, nil |
|
}, |
|
SubscribeTransactions: svr.cc.wallet.SubscribeTransactions, |
|
SubscribeTopology: svr.chanRouter.SubscribeTopology, |
|
}, nil |
|
}
|
|
|