lnd/pilot.go
Wilmer Paulino e1a4657427
pilot: remove disconnection logic from chanController
In this commit, we remove the disconnection logic within the
chanController when failing to open a channel with a peer. We do this as
it's already done within the autopilot agent, where it should be, and
because it's possible that we were already connected to this node and we
happened to disconnect them anyway.
2018-08-29 02:06:02 -07:00

312 lines
8.5 KiB
Go

package main
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"
)
// 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.Agent instance based on the passed
// configuration struct. All interfaces needed to drive the pilot will be
// registered and launched.
func initAutoPilot(svr *server, cfg *autoPilotConfig) (*autopilot.Agent, error) {
atplLog.Infof("Instantiating autopilot with cfg: %v", spew.Sdump(cfg))
// First, we'll create the preferential attachment heuristic,
// initialized with the passed auto pilot configuration parameters.
prefAttachment := autopilot.NewConstrainedPrefAttachment(
btcutil.Amount(cfg.MinChannelSize),
btcutil.Amount(cfg.MaxChannelSize),
uint16(cfg.MaxChannels), cfg.Allocation,
)
// 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: prefAttachment,
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()),
MaxPendingOpens: 10,
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, fmt.Errorf("unable to connect "+
"to %x", target.SerializeCompressed())
}
return false, nil
},
DisconnectPeer: svr.DisconnectPeer,
}
// Next, 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
}
initialChanState := make([]autopilot.Channel, len(activeChannels))
for i, channel := range activeChannels {
initialChanState[i] = autopilot.Channel{
ChanID: channel.ShortChanID(),
Capacity: channel.Capacity,
Node: autopilot.NewNodeID(channel.IdentityPub),
}
}
// Now that we have all the initial dependencies, we can create the
// auto-pilot instance itself.
pilot, err := autopilot.New(pilotCfg, initialChanState)
if err != nil {
return nil, err
}
// Finally, we'll need to subscribe to two things: incoming
// transactions that modify the wallet's balance, and also any graph
// topology updates.
txnSubscription, err := svr.cc.wallet.SubscribeTransactions()
if err != nil {
return nil, err
}
graphSubscription, err := svr.chanRouter.SubscribeTopology()
if err != nil {
return nil, err
}
// We'll launch a goroutine to provide the agent with notifications
// whenever the balance of the wallet changes.
svr.wg.Add(2)
go func() {
defer txnSubscription.Cancel()
defer svr.wg.Done()
for {
select {
case <-txnSubscription.ConfirmedTransactions():
pilot.OnBalanceChange()
case <-svr.quit:
return
}
}
}()
go func() {
defer svr.wg.Done()
for {
select {
// We won't act upon new unconfirmed transaction, as
// we'll only use confirmed outputs when funding.
// However, we will still drain this request in order
// to avoid goroutine leaks, and ensure we promptly
// read from the channel if available.
case <-txnSubscription.UnconfirmedTransactions():
case <-svr.quit:
return
}
}
}()
// We'll also launch a goroutine to provide the agent with
// notifications for when the graph topology controlled by the node
// changes.
svr.wg.Add(1)
go func() {
defer graphSubscription.Cancel()
defer svr.wg.Done()
for {
select {
case topChange, ok := <-graphSubscription.TopologyChanges:
// If the router is shutting down, then we will
// as well.
if !ok {
return
}
for _, edgeUpdate := range topChange.ChannelEdgeUpdates {
// If this isn't an advertisement by
// the backing lnd node, then we'll
// continue as we only want to add
// channels that we've created
// ourselves.
if !edgeUpdate.AdvertisingNode.IsEqual(self) {
continue
}
// If this is indeed a channel we
// opened, then we'll convert it to the
// autopilot.Channel format, and notify
// the pilot of the new channel.
chanNode := autopilot.NewNodeID(
edgeUpdate.ConnectingNode,
)
chanID := lnwire.NewShortChanIDFromInt(
edgeUpdate.ChanID,
)
edge := autopilot.Channel{
ChanID: chanID,
Capacity: edgeUpdate.Capacity,
Node: chanNode,
}
pilot.OnChannelOpen(edge)
}
// For each closed channel, we'll obtain
// the chanID of the closed channel and send it
// to the pilot.
for _, chanClose := range topChange.ClosedChannels {
chanID := lnwire.NewShortChanIDFromInt(
chanClose.ChanID,
)
pilot.OnChannelClose(chanID)
}
// If new nodes were added to the graph, or nod
// information has changed, we'll poke autopilot
// to see if it can make use of them.
if len(topChange.NodeUpdates) > 0 {
pilot.OnNodeUpdates()
}
case <-svr.quit:
return
}
}
}()
return pilot, nil
}