2019-08-08 19:39:38 +02:00
|
|
|
// Package chanfitness monitors the behaviour of channels to provide insight
|
|
|
|
// into the health and performance of a channel. This is achieved by maintaining
|
|
|
|
// an event store which tracks events for each channel.
|
|
|
|
//
|
|
|
|
// Lifespan: the period that the channel has been known to the scoring system.
|
|
|
|
// Note that lifespan may not equal the channel's full lifetime because data is
|
|
|
|
// not currently persisted.
|
|
|
|
//
|
|
|
|
// Uptime: the total time within a given period that the channel's remote peer
|
|
|
|
// has been online.
|
|
|
|
package chanfitness
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2019-12-17 16:36:28 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2019-08-08 19:39:38 +02:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
|
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
2020-09-08 13:47:15 +02:00
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
2019-08-08 19:39:38 +02:00
|
|
|
"github.com/lightningnetwork/lnd/peernotifier"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
"github.com/lightningnetwork/lnd/subscribe"
|
|
|
|
)
|
|
|
|
|
2019-12-17 16:36:21 +01:00
|
|
|
var (
|
2020-09-08 13:47:12 +02:00
|
|
|
// errShuttingDown is returned when the store cannot respond to a query
|
|
|
|
// because it has received the shutdown signal.
|
2019-12-17 16:36:21 +01:00
|
|
|
errShuttingDown = errors.New("channel event store shutting down")
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// ErrChannelNotFound is returned when a query is made for a channel
|
|
|
|
// that the event store does not have knowledge of.
|
2019-12-17 16:36:21 +01:00
|
|
|
ErrChannelNotFound = errors.New("channel not found in event store")
|
|
|
|
)
|
2019-08-08 19:39:38 +02:00
|
|
|
|
|
|
|
// ChannelEventStore maintains a set of event logs for the node's channels to
|
|
|
|
// provide insight into the performance and health of channels.
|
|
|
|
type ChannelEventStore struct {
|
|
|
|
cfg *Config
|
|
|
|
|
2019-12-17 16:36:28 +01:00
|
|
|
// channels maps channel points to event logs.
|
|
|
|
channels map[wire.OutPoint]*chanEventLog
|
2019-08-08 19:39:38 +02:00
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// peers tracks the current online status of peers based on online
|
|
|
|
// and offline events.
|
2019-08-08 19:39:38 +02:00
|
|
|
peers map[route.Vertex]bool
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
// chanInfoRequests serves requests for information about our channel.
|
|
|
|
chanInfoRequests chan channelInfoRequest
|
2019-08-08 19:39:38 +02:00
|
|
|
|
|
|
|
quit chan struct{}
|
|
|
|
|
|
|
|
wg sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// Config provides the event store with functions required to monitor channel
|
|
|
|
// activity. All elements of the config must be non-nil for the event store to
|
|
|
|
// operate.
|
|
|
|
type Config struct {
|
2020-09-08 13:47:12 +02:00
|
|
|
// SubscribeChannelEvents provides a subscription client which provides
|
|
|
|
// a stream of channel events.
|
2020-09-08 13:47:13 +02:00
|
|
|
SubscribeChannelEvents func() (subscribe.Subscription, error)
|
2019-08-08 19:39:38 +02:00
|
|
|
|
|
|
|
// SubscribePeerEvents provides a subscription client which provides a
|
|
|
|
// stream of peer online/offline events.
|
2020-09-08 13:47:13 +02:00
|
|
|
SubscribePeerEvents func() (subscribe.Subscription, error)
|
2019-08-08 19:39:38 +02:00
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// GetOpenChannels provides a list of existing open channels which is
|
|
|
|
// used to populate the ChannelEventStore with a set of channels on
|
|
|
|
// startup.
|
2019-08-08 19:39:38 +02:00
|
|
|
GetOpenChannels func() ([]*channeldb.OpenChannel, error)
|
2020-09-08 13:47:15 +02:00
|
|
|
|
|
|
|
// Clock is the time source that the subsystem uses, provided here
|
|
|
|
// for ease of testing.
|
|
|
|
Clock clock.Clock
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
type channelInfoRequest struct {
|
2019-12-17 16:36:28 +01:00
|
|
|
channelPoint wire.OutPoint
|
2020-09-08 13:47:17 +02:00
|
|
|
responseChan chan channelInfoResponse
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
type channelInfoResponse struct {
|
|
|
|
info *ChannelInfo
|
|
|
|
err error
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewChannelEventStore initializes an event store with the config provided.
|
|
|
|
// Note that this function does not start the main event loop, Start() must be
|
|
|
|
// called.
|
|
|
|
func NewChannelEventStore(config *Config) *ChannelEventStore {
|
|
|
|
store := &ChannelEventStore{
|
|
|
|
cfg: config,
|
2019-12-17 16:36:28 +01:00
|
|
|
channels: make(map[wire.OutPoint]*chanEventLog),
|
2019-08-08 19:39:38 +02:00
|
|
|
peers: make(map[route.Vertex]bool),
|
2020-09-08 13:47:17 +02:00
|
|
|
chanInfoRequests: make(chan channelInfoRequest),
|
2019-08-08 19:39:38 +02:00
|
|
|
quit: make(chan struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
return store
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start adds all existing open channels to the event store and starts the main
|
|
|
|
// loop which records channel and peer events, and serves requests for
|
|
|
|
// information from the store. If this function fails, it cancels its existing
|
|
|
|
// subscriptions and returns an error.
|
|
|
|
func (c *ChannelEventStore) Start() error {
|
|
|
|
// Create a subscription to channel events.
|
|
|
|
channelClient, err := c.cfg.SubscribeChannelEvents()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a subscription to peer events. If an error occurs, cancel the
|
|
|
|
// existing subscription to channel events and return.
|
|
|
|
peerClient, err := c.cfg.SubscribePeerEvents()
|
|
|
|
if err != nil {
|
|
|
|
channelClient.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// cancel should be called to cancel all subscriptions if an error
|
|
|
|
// occurs.
|
2019-08-08 19:39:38 +02:00
|
|
|
cancel := func() {
|
|
|
|
channelClient.Cancel()
|
|
|
|
peerClient.Cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the existing set of channels to the event store. This is required
|
|
|
|
// because channel events will not be triggered for channels that exist
|
|
|
|
// at startup time.
|
|
|
|
channels, err := c.cfg.GetOpenChannels()
|
|
|
|
if err != nil {
|
|
|
|
cancel()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("Adding %v channels to event store", len(channels))
|
|
|
|
|
|
|
|
for _, ch := range channels {
|
|
|
|
peerKey, err := route.NewVertexFromBytes(
|
|
|
|
ch.IdentityPub.SerializeCompressed(),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
cancel()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// Add existing channels to the channel store with an initial
|
|
|
|
// peer online or offline event.
|
2019-12-17 16:36:28 +01:00
|
|
|
c.addChannel(ch.FundingOutpoint, peerKey)
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start a goroutine that consumes events from all subscriptions.
|
|
|
|
c.wg.Add(1)
|
|
|
|
go c.consume(&subscriptions{
|
|
|
|
channelUpdates: channelClient.Updates(),
|
|
|
|
peerUpdates: peerClient.Updates(),
|
|
|
|
cancel: cancel,
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop terminates all goroutines started by the event store.
|
|
|
|
func (c *ChannelEventStore) Stop() {
|
|
|
|
log.Info("Stopping event store")
|
|
|
|
|
|
|
|
// Stop the consume goroutine.
|
|
|
|
close(c.quit)
|
|
|
|
|
|
|
|
c.wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// addChannel adds a new channel to the ChannelEventStore's map of channels with
|
|
|
|
// an initial peer online state (if the peer is online). If the channel is
|
|
|
|
// already present in the map, the function returns early. This function should
|
|
|
|
// be called to add existing channels on startup and when open channel events
|
|
|
|
// are observed.
|
2019-12-17 16:36:28 +01:00
|
|
|
func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint,
|
|
|
|
peer route.Vertex) {
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// Check for the unexpected case where the channel is already in the
|
|
|
|
// store.
|
2019-12-17 16:36:28 +01:00
|
|
|
_, ok := c.channels[channelPoint]
|
2019-08-08 19:39:38 +02:00
|
|
|
if ok {
|
2020-09-08 13:47:12 +02:00
|
|
|
log.Errorf("Channel %v duplicated in channel store",
|
|
|
|
channelPoint)
|
2019-08-08 19:39:38 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-17 16:36:28 +01:00
|
|
|
// Create an event log for the channel.
|
2020-09-08 13:47:15 +02:00
|
|
|
eventLog := newEventLog(channelPoint, peer, c.cfg.Clock)
|
2019-08-08 19:39:38 +02:00
|
|
|
|
2020-01-07 15:32:47 +01:00
|
|
|
// If the peer is already online, add a peer online event to record
|
|
|
|
// the starting state of the peer.
|
|
|
|
if c.peers[peer] {
|
|
|
|
eventLog.add(peerOnlineEvent)
|
|
|
|
}
|
|
|
|
|
2019-12-17 16:36:28 +01:00
|
|
|
c.channels[channelPoint] = eventLog
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// closeChannel records a closed time for a channel, and returns early is the
|
|
|
|
// channel is not known to the event store.
|
2019-12-17 16:36:28 +01:00
|
|
|
func (c *ChannelEventStore) closeChannel(channelPoint wire.OutPoint) {
|
2020-09-08 13:47:12 +02:00
|
|
|
// Check for the unexpected case where the channel is unknown to the
|
|
|
|
// store.
|
2019-12-17 16:36:28 +01:00
|
|
|
eventLog, ok := c.channels[channelPoint]
|
2019-08-08 19:39:38 +02:00
|
|
|
if !ok {
|
2019-12-17 16:36:28 +01:00
|
|
|
log.Errorf("Close channel %v unknown to store", channelPoint)
|
2019-08-08 19:39:38 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
eventLog.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// peerEvent adds a peer online or offline event to all channels we currently
|
|
|
|
// have open with a peer.
|
|
|
|
func (c *ChannelEventStore) peerEvent(peer route.Vertex, event eventType) {
|
|
|
|
// Track current online status of peers in the channelEventStore.
|
|
|
|
c.peers[peer] = event == peerOnlineEvent
|
|
|
|
|
|
|
|
for _, eventLog := range c.channels {
|
|
|
|
if eventLog.peer == peer {
|
|
|
|
eventLog.add(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// subscriptions abstracts away from subscription clients to allow for mocking.
|
|
|
|
type subscriptions struct {
|
|
|
|
channelUpdates <-chan interface{}
|
|
|
|
peerUpdates <-chan interface{}
|
|
|
|
cancel func()
|
|
|
|
}
|
|
|
|
|
|
|
|
// consume is the event store's main loop. It consumes subscriptions to update
|
|
|
|
// the event store with channel and peer events, and serves requests for channel
|
|
|
|
// uptime and lifespan.
|
|
|
|
func (c *ChannelEventStore) consume(subscriptions *subscriptions) {
|
|
|
|
defer c.wg.Done()
|
|
|
|
defer subscriptions.cancel()
|
|
|
|
|
|
|
|
// Consume events until the channel is closed.
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
// Process channel opened and closed events.
|
|
|
|
case e := <-subscriptions.channelUpdates:
|
|
|
|
switch event := e.(type) {
|
2020-09-08 13:47:12 +02:00
|
|
|
// A new channel has been opened, we must add the
|
|
|
|
// channel to the store and record a channel open event.
|
2019-08-08 19:39:38 +02:00
|
|
|
case channelnotifier.OpenChannelEvent:
|
2020-09-08 13:47:12 +02:00
|
|
|
compressed := event.Channel.IdentityPub.SerializeCompressed()
|
2019-08-08 19:39:38 +02:00
|
|
|
peerKey, err := route.NewVertexFromBytes(
|
2020-09-08 13:47:12 +02:00
|
|
|
compressed,
|
2019-08-08 19:39:38 +02:00
|
|
|
)
|
|
|
|
if err != nil {
|
2020-09-08 13:47:12 +02:00
|
|
|
log.Errorf("Could not get vertex "+
|
|
|
|
"from: %v", compressed)
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
c.addChannel(
|
|
|
|
event.Channel.FundingOutpoint, peerKey,
|
|
|
|
)
|
2019-08-08 19:39:38 +02:00
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// A channel has been closed, we must remove the channel
|
|
|
|
// from the store and record a channel closed event.
|
2019-08-08 19:39:38 +02:00
|
|
|
case channelnotifier.ClosedChannelEvent:
|
2019-12-17 16:36:28 +01:00
|
|
|
c.closeChannel(event.CloseSummary.ChanPoint)
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process peer online and offline events.
|
|
|
|
case e := <-subscriptions.peerUpdates:
|
|
|
|
switch event := e.(type) {
|
2020-09-08 13:47:12 +02:00
|
|
|
// We have reestablished a connection with our peer,
|
|
|
|
// and should record an online event for any channels
|
|
|
|
// with that peer.
|
2019-08-08 19:39:38 +02:00
|
|
|
case peernotifier.PeerOnlineEvent:
|
|
|
|
c.peerEvent(event.PubKey, peerOnlineEvent)
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// We have lost a connection with our peer, and should
|
|
|
|
// record an offline event for any channels with that
|
|
|
|
// peer.
|
2019-08-08 19:39:38 +02:00
|
|
|
case peernotifier.PeerOfflineEvent:
|
|
|
|
c.peerEvent(event.PubKey, peerOfflineEvent)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serve all requests for channel lifetime.
|
2020-09-08 13:47:17 +02:00
|
|
|
case req := <-c.chanInfoRequests:
|
|
|
|
var resp channelInfoResponse
|
2019-08-08 19:39:38 +02:00
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
resp.info, resp.err = c.getChanInfo(req)
|
2019-08-08 19:39:38 +02:00
|
|
|
req.responseChan <- resp
|
|
|
|
|
|
|
|
// Exit if the store receives the signal to shutdown.
|
|
|
|
case <-c.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
// ChannelInfo provides the set of information that the event store has recorded
|
|
|
|
// for a channel.
|
|
|
|
type ChannelInfo struct {
|
|
|
|
// Lifetime is the total amount of time we have monitored the channel
|
|
|
|
// for.
|
|
|
|
Lifetime time.Duration
|
|
|
|
|
|
|
|
// Uptime is the total amount of time that the channel peer has been
|
|
|
|
// observed as online during the monitored lifespan.
|
|
|
|
Uptime time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetChanInfo gets all the information we have on a channel in the event store.
|
|
|
|
func (c *ChannelEventStore) GetChanInfo(channelPoint wire.OutPoint) (
|
|
|
|
*ChannelInfo, error) {
|
2019-12-17 16:36:28 +01:00
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
request := channelInfoRequest{
|
2019-12-17 16:36:28 +01:00
|
|
|
channelPoint: channelPoint,
|
2020-09-08 13:47:17 +02:00
|
|
|
responseChan: make(chan channelInfoResponse),
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
// Send a request for the channel's information to the main event loop,
|
|
|
|
// or return early with an error if the store has already received a
|
2020-09-08 13:47:12 +02:00
|
|
|
// shutdown signal.
|
2019-08-08 19:39:38 +02:00
|
|
|
select {
|
2020-09-08 13:47:17 +02:00
|
|
|
case c.chanInfoRequests <- request:
|
2019-08-08 19:39:38 +02:00
|
|
|
case <-c.quit:
|
2020-09-08 13:47:17 +02:00
|
|
|
return nil, errShuttingDown
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:12 +02:00
|
|
|
// Return the response we receive on the response channel or exit early
|
|
|
|
// if the store is instructed to exit.
|
2019-08-08 19:39:38 +02:00
|
|
|
select {
|
|
|
|
case resp := <-request.responseChan:
|
2020-09-08 13:47:17 +02:00
|
|
|
return resp.info, resp.err
|
2019-08-08 19:39:38 +02:00
|
|
|
|
|
|
|
case <-c.quit:
|
2020-09-08 13:47:17 +02:00
|
|
|
return nil, errShuttingDown
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
// getChanInfo collects channel information for a channel. It gets uptime over
|
|
|
|
// the full lifetime of the channel.
|
|
|
|
func (c *ChannelEventStore) getChanInfo(req channelInfoRequest) (*ChannelInfo,
|
|
|
|
error) {
|
2019-08-08 19:39:38 +02:00
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
// Look for the channel in our current set.
|
|
|
|
channel, ok := c.channels[req.channelPoint]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChannelNotFound
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
// If our channel is not closed, we want to calculate uptime until the
|
|
|
|
// present.
|
|
|
|
endTime := channel.closedAt
|
|
|
|
if endTime.IsZero() {
|
|
|
|
endTime = c.cfg.Clock.Now()
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-08 13:47:17 +02:00
|
|
|
uptime, err := channel.uptime(channel.openedAt, endTime)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|
2020-09-08 13:47:17 +02:00
|
|
|
|
|
|
|
return &ChannelInfo{
|
|
|
|
Lifetime: endTime.Sub(channel.openedAt),
|
|
|
|
Uptime: uptime,
|
|
|
|
}, nil
|
2019-08-08 19:39:38 +02:00
|
|
|
}
|