package routing import ( "fmt" "image/color" "net" "sync" "sync/atomic" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" ) // TopologyClient represents an intent to receive notifications from the // channel router regarding changes to the topology of the channel graph. The // TopologyChanges channel will be sent upon with new updates to the channel // graph in real-time as they're encountered. type TopologyClient struct { // TopologyChanges is a receive only channel that new channel graph // updates will be sent over. // // TODO(roasbeef): chan for each update type instead? TopologyChanges <-chan *TopologyChange // Cancel is a function closure that should be executed when the client // wishes to cancel their notification intent. Doing so allows the // ChannelRouter to free up resources. Cancel func() } // topologyClientUpdate is a message sent to the channel router to either // register a new topology client or re-register an existing client. type topologyClientUpdate struct { // cancel indicates if the update to the client is cancelling an // existing client's notifications. If not then this update will be to // register a new set of notifications. cancel bool // clientID is the unique identifier for this client. Any further // updates (deleting or adding) to this notification client will be // dispatched according to the target clientID. clientID uint64 // ntfnChan is a *send-only* channel in which notifications should be // sent over from router -> client. ntfnChan chan<- *TopologyChange } // SubscribeTopology returns a new topology client which can be used by the // caller to receive notifications whenever a change in the channel graph // topology occurs. Changes that will be sent at notifications include: new // nodes appearing, node updating their attributes, new channels, channels // closing, and updates in the routing policies of a channel's directed edges. func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) { // If the router is not yet started, return an error to avoid a // deadlock waiting for it to handle the subscription request. if atomic.LoadUint32(&r.started) == 0 { return nil, fmt.Errorf("router not started") } // We'll first atomically obtain the next ID for this client from the // incrementing client ID counter. clientID := atomic.AddUint64(&r.ntfnClientCounter, 1) log.Debugf("New graph topology client subscription, client %v", clientID) ntfnChan := make(chan *TopologyChange, 10) select { case r.ntfnClientUpdates <- &topologyClientUpdate{ cancel: false, clientID: clientID, ntfnChan: ntfnChan, }: case <-r.quit: return nil, errors.New("ChannelRouter shutting down") } return &TopologyClient{ TopologyChanges: ntfnChan, Cancel: func() { select { case r.ntfnClientUpdates <- &topologyClientUpdate{ cancel: true, clientID: clientID, }: case <-r.quit: return } }, }, nil } // topologyClient is a data-structure use by the channel router to couple the // client's notification channel along with a special "exit" channel that can // be used to cancel all lingering goroutines blocked on a send to the // notification channel. type topologyClient struct { // ntfnChan is a send-only channel that's used to propagate // notification s from the channel router to an instance of a // topologyClient client. ntfnChan chan<- *TopologyChange // exit is a channel that is used internally by the channel router to // cancel any active un-consumed goroutine notifications. exit chan struct{} wg sync.WaitGroup } // notifyTopologyChange notifies all registered clients of a new change in // graph topology in a non-blocking. func (r *ChannelRouter) notifyTopologyChange(topologyDiff *TopologyChange) { r.RLock() defer r.RUnlock() numClients := len(r.topologyClients) if numClients == 0 { return } log.Tracef("Sending topology notification to %v clients %v", numClients, newLogClosure(func() string { return spew.Sdump(topologyDiff) }), ) for _, client := range r.topologyClients { client.wg.Add(1) go func(c *topologyClient) { defer c.wg.Done() select { // In this case we'll try to send the notification // directly to the upstream client consumer. case c.ntfnChan <- topologyDiff: // If the client cancels the notifications, then we'll // exit early. case <-c.exit: // Similarly, if the ChannelRouter itself exists early, // then we'll also exit ourselves. case <-r.quit: } }(client) } } // TopologyChange represents a new set of modifications to the channel graph. // Topology changes will be dispatched in real-time as the ChannelGraph // validates and process modifications to the authenticated channel graph. type TopologyChange struct { // NodeUpdates is a slice of nodes which are either new to the channel // graph, or have had their attributes updated in an authenticated // manner. NodeUpdates []*NetworkNodeUpdate // ChanelEdgeUpdates is a slice of channel edges which are either newly // opened and authenticated, or have had their routing policies // updated. ChannelEdgeUpdates []*ChannelEdgeUpdate // ClosedChannels contains a slice of close channel summaries which // described which block a channel was closed at, and also carry // supplemental information such as the capacity of the former channel. ClosedChannels []*ClosedChanSummary } // isEmpty returns true if the TopologyChange is empty. A TopologyChange is // considered empty, if it contains no *new* updates of any type. func (t *TopologyChange) isEmpty() bool { return len(t.NodeUpdates) == 0 && len(t.ChannelEdgeUpdates) == 0 && len(t.ClosedChannels) == 0 } // ClosedChanSummary is a summary of a channel that was detected as being // closed by monitoring the blockchain. Once a channel's funding point has been // spent, the channel will automatically be marked as closed by the // ChainNotifier. // // TODO(roasbeef): add nodes involved? type ClosedChanSummary struct { // ChanID is the short-channel ID which uniquely identifies the // channel. ChanID uint64 // Capacity was the total capacity of the channel before it was closed. Capacity btcutil.Amount // ClosedHeight is the height in the chain that the channel was closed // at. ClosedHeight uint32 // ChanPoint is the funding point, or the multi-sig utxo which // previously represented the channel. ChanPoint wire.OutPoint } // createCloseSummaries takes in a slice of channels closed at the target block // height and creates a slice of summaries which of each channel closure. func createCloseSummaries(blockHeight uint32, closedChans ...*channeldb.ChannelEdgeInfo) []*ClosedChanSummary { closeSummaries := make([]*ClosedChanSummary, len(closedChans)) for i, closedChan := range closedChans { closeSummaries[i] = &ClosedChanSummary{ ChanID: closedChan.ChannelID, Capacity: closedChan.Capacity, ClosedHeight: blockHeight, ChanPoint: closedChan.ChannelPoint, } } return closeSummaries } // NetworkNodeUpdate is an update for a node within the Lightning Network. A // NetworkNodeUpdate is sent out either when a new node joins the network, or a // node broadcasts a new update with a newer time stamp that supersedes its // old update. All updates are properly authenticated. type NetworkNodeUpdate struct { // Addresses is a slice of all the node's known addresses. Addresses []net.Addr // IdentityKey is the identity public key of the target node. This is // used to encrypt onion blobs as well as to authenticate any new // updates. IdentityKey *btcec.PublicKey // Alias is the alias or nick name of the node. Alias string // Color is the node's color in hex code format. Color string // Features holds the set of features the node supports. Features *lnwire.FeatureVector } // ChannelEdgeUpdate is an update for a new channel within the ChannelGraph. // This update is sent out once a new authenticated channel edge is discovered // within the network. These updates are directional, so if a channel is fully // public, then there will be two updates sent out: one for each direction // within the channel. Each update will carry that particular routing edge // policy for the channel direction. // // An edge is a channel in the direction of AdvertisingNode -> ConnectingNode. type ChannelEdgeUpdate struct { // ChanID is the unique short channel ID for the channel. This encodes // where in the blockchain the channel's funding transaction was // originally confirmed. ChanID uint64 // ChanPoint is the outpoint which represents the multi-sig funding // output for the channel. ChanPoint wire.OutPoint // Capacity is the capacity of the newly created channel. Capacity btcutil.Amount // MinHTLC is the minimum HTLC amount that this channel will forward. MinHTLC lnwire.MilliSatoshi // MaxHTLC is the maximum HTLC amount that this channel will forward. MaxHTLC lnwire.MilliSatoshi // BaseFee is the base fee that will charged for all HTLC's forwarded // across the this channel direction. BaseFee lnwire.MilliSatoshi // FeeRate is the fee rate that will be shared for all HTLC's forwarded // across this channel direction. FeeRate lnwire.MilliSatoshi // TimeLockDelta is the time-lock expressed in blocks that will be // added to outgoing HTLC's from incoming HTLC's. This value is the // difference of the incoming and outgoing HTLC's time-locks routed // through this hop. TimeLockDelta uint16 // AdvertisingNode is the node that's advertising this edge. AdvertisingNode *btcec.PublicKey // ConnectingNode is the node that the advertising node connects to. ConnectingNode *btcec.PublicKey // Disabled, if true, signals that the channel is unavailable to relay // payments. Disabled bool } // appendTopologyChange appends the passed update message to the passed // TopologyChange, properly identifying which type of update the message // constitutes. This function will also fetch any required auxiliary // information required to create the topology change update from the graph // database. func addToTopologyChange(graph *channeldb.ChannelGraph, update *TopologyChange, msg interface{}) error { switch m := msg.(type) { // Any node announcement maps directly to a NetworkNodeUpdate struct. // No further data munging or db queries are required. case *channeldb.LightningNode: pubKey, err := m.PubKey() if err != nil { return err } nodeUpdate := &NetworkNodeUpdate{ Addresses: m.Addresses, IdentityKey: pubKey, Alias: m.Alias, Color: EncodeHexColor(m.Color), Features: m.Features.Clone(), } nodeUpdate.IdentityKey.Curve = nil update.NodeUpdates = append(update.NodeUpdates, nodeUpdate) return nil // We ignore initial channel announcements as we'll only send out // updates once the individual edges themselves have been updated. case *channeldb.ChannelEdgeInfo: return nil // Any new ChannelUpdateAnnouncements will generate a corresponding // ChannelEdgeUpdate notification. case *channeldb.ChannelEdgePolicy: // We'll need to fetch the edge's information from the database // in order to get the information concerning which nodes are // being connected. edgeInfo, _, _, err := graph.FetchChannelEdgesByID(m.ChannelID) if err != nil { return errors.Errorf("unable fetch channel edge: %v", err) } // If the flag is one, then the advertising node is actually // the second node. sourceNode := edgeInfo.NodeKey1 connectingNode := edgeInfo.NodeKey2 if m.ChannelFlags&lnwire.ChanUpdateDirection == 1 { sourceNode = edgeInfo.NodeKey2 connectingNode = edgeInfo.NodeKey1 } aNode, err := sourceNode() if err != nil { return err } cNode, err := connectingNode() if err != nil { return err } edgeUpdate := &ChannelEdgeUpdate{ ChanID: m.ChannelID, ChanPoint: edgeInfo.ChannelPoint, TimeLockDelta: m.TimeLockDelta, Capacity: edgeInfo.Capacity, MinHTLC: m.MinHTLC, MaxHTLC: m.MaxHTLC, BaseFee: m.FeeBaseMSat, FeeRate: m.FeeProportionalMillionths, AdvertisingNode: aNode, ConnectingNode: cNode, Disabled: m.ChannelFlags&lnwire.ChanUpdateDisabled != 0, } edgeUpdate.AdvertisingNode.Curve = nil edgeUpdate.ConnectingNode.Curve = nil // TODO(roasbeef): add bit to toggle update.ChannelEdgeUpdates = append(update.ChannelEdgeUpdates, edgeUpdate) return nil default: return fmt.Errorf("unable to add to topology change, "+ "unknown message type %T", msg) } } // EncodeHexColor takes a color and returns it in hex code format. func EncodeHexColor(color color.RGBA) string { return fmt.Sprintf("#%02x%02x%02x", color.R, color.G, color.B) }