package lnd import ( "fmt" "net" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" ) // addrSource is an interface that allow us to get the addresses for a target // node. We'll need this in order to be able to properly proxy the // notifications to create SCBs. type addrSource interface { // AddrsForNode returns all known addresses for the target node public // key. AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr, error) } // channelNotifier is an implementation of the chanbackup.ChannelNotifier // interface using the existing channelnotifier.ChannelNotifier struct. This // implementation allows us to satisfy all the dependencies of the // chanbackup.SubSwapper struct. type channelNotifier struct { // chanNotifier is the based channel notifier that we'll proxy requests // from. chanNotifier *channelnotifier.ChannelNotifier // addrs is an implementation of the addrSource interface that allows // us to get the latest set of addresses for a given node. We'll need // this to be able to create an SCB for new channels. addrs addrSource } // SubscribeChans requests a new channel subscription relative to the initial // set of known channels. We use the knownChans as a synchronization point to // ensure that the chanbackup.SubSwapper does not miss any channel open or // close events in the period between when it's created, and when it requests // the channel subscription. // // NOTE: This is part of the chanbackup.ChannelNotifier interface. func (c *channelNotifier) SubscribeChans(startingChans map[wire.OutPoint]struct{}) ( *chanbackup.ChannelSubscription, error) { ltndLog.Infof("Channel backup proxy channel notifier starting") // TODO(roasbeef): read existing set of chans and diff quit := make(chan struct{}) chanUpdates := make(chan chanbackup.ChannelEvent, 1) // sendChanOpenUpdate is a closure that sends a ChannelEvent to the // chanUpdates channel to inform subscribers about new pending or // confirmed channels. sendChanOpenUpdate := func(newOrPendingChan *channeldb.OpenChannel) { nodeAddrs, err := c.addrs.AddrsForNode( newOrPendingChan.IdentityPub, ) if err != nil { pub := newOrPendingChan.IdentityPub ltndLog.Errorf("unable to fetch addrs for %x: %v", pub.SerializeCompressed(), err) } chanEvent := chanbackup.ChannelEvent{ NewChans: []chanbackup.ChannelWithAddrs{ { OpenChannel: newOrPendingChan, Addrs: nodeAddrs, }, }, } select { case chanUpdates <- chanEvent: case <-quit: return } } // In order to adhere to the interface, we'll proxy the events from the // channel notifier to the sub-swapper in a format it understands. go func() { // First, we'll subscribe to the primary channel notifier so we can // obtain events for new opened/closed channels. chanSubscription, err := c.chanNotifier.SubscribeChannelEvents() if err != nil { panic(fmt.Sprintf("unable to subscribe to chans: %v", err)) } defer chanSubscription.Cancel() for { select { // A new event has been sent by the chanNotifier, we'll // filter out the events we actually care about and // send them to the sub-swapper. case e := <-chanSubscription.Updates(): // TODO(roasbeef): batch dispatch ntnfs switch event := e.(type) { // A new channel has been opened and is still // pending. We can still create a backup, even // if the final channel ID is not yet available. case channelnotifier.PendingOpenChannelEvent: pendingChan := event.PendingChannel sendChanOpenUpdate(pendingChan) // A new channel has been confirmed, we'll // obtain the node address, then send to the // sub-swapper. case channelnotifier.OpenChannelEvent: sendChanOpenUpdate(event.Channel) // An existing channel has been closed, we'll // send only the chanPoint of the closed // channel to the sub-swapper. case channelnotifier.ClosedChannelEvent: chanPoint := event.CloseSummary.ChanPoint closeType := event.CloseSummary.CloseType // Because we see the contract as closed // once our local force close TX // confirms, the channel arbitrator // already fires on this event. But // because our funds can be in limbo for // up to 2 weeks worst case we don't // want to remove the crucial info we // need for sweeping that time locked // output before we've actually done so. if closeType == channeldb.LocalForceClose { ltndLog.Debugf("Channel %v "+ "was force closed by "+ "us, not removing "+ "from channel backup "+ "until fully resolved", chanPoint) continue } chanEvent := chanbackup.ChannelEvent{ ClosedChans: []wire.OutPoint{ chanPoint, }, } select { case chanUpdates <- chanEvent: case <-quit: return } // A channel was fully resolved on chain. This // should only really interest us if it was a // locally force closed channel where we didn't // remove the channel already when the close // event was fired. case channelnotifier.FullyResolvedChannelEvent: chanEvent := chanbackup.ChannelEvent{ ClosedChans: []wire.OutPoint{ *event.ChannelPoint, }, } select { case chanUpdates <- chanEvent: case <-quit: return } } // The cancel method has been called, signalling us to // exit case <-quit: return } } }() return &chanbackup.ChannelSubscription{ ChanUpdates: chanUpdates, Cancel: func() { close(quit) }, }, nil } // A compile-time constraint to ensure channelNotifier implements // chanbackup.ChannelNotifier. var _ chanbackup.ChannelNotifier = (*channelNotifier)(nil)