Merge pull request #1988 from valentinewallace/subscribe-chans-rpc
rpc: Add SubscribeChannels RPC.
This commit is contained in:
19 files changed
+1984
-687
No files matched your search
@@ -0,0 +1,137 @@
|
|||||||
|
package channelnotifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/subscribe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChannelNotifier is a subsystem which all active, inactive, and closed channel
|
||||||
|
// events pipe through. It takes subscriptions for its events, and whenever
|
||||||
|
// it receives a new event it notifies its subscribers over the proper channel.
|
||||||
|
type ChannelNotifier struct {
|
||||||
|
started uint32
|
||||||
|
stopped uint32
|
||||||
|
|
||||||
|
ntfnServer *subscribe.Server
|
||||||
|
|
||||||
|
chanDB *channeldb.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenChannelEvent represents a new event where a channel goes from pending
|
||||||
|
// open to open.
|
||||||
|
type OpenChannelEvent struct {
|
||||||
|
// Channel is the channel that has become open.
|
||||||
|
Channel *channeldb.OpenChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveChannelEvent represents a new event where a channel becomes active.
|
||||||
|
type ActiveChannelEvent struct {
|
||||||
|
// ChannelPoint is the channelpoint for the newly active channel.
|
||||||
|
ChannelPoint *wire.OutPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// InactiveChannelEvent represents a new event where a channel becomes inactive.
|
||||||
|
type InactiveChannelEvent struct {
|
||||||
|
// ChannelPoint is the channelpoint for the newly inactive channel.
|
||||||
|
ChannelPoint *wire.OutPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosedChannelEvent represents a new event where a channel becomes closed.
|
||||||
|
type ClosedChannelEvent struct {
|
||||||
|
// CloseSummary is the summary of the channel close that has occurred.
|
||||||
|
CloseSummary *channeldb.ChannelCloseSummary
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new channel notifier. The ChannelNotifier gets channel
|
||||||
|
// events from peers and from the chain arbitrator, and dispatches them to
|
||||||
|
// its clients.
|
||||||
|
func New(chanDB *channeldb.DB) *ChannelNotifier {
|
||||||
|
return &ChannelNotifier{
|
||||||
|
ntfnServer: subscribe.NewServer(),
|
||||||
|
chanDB: chanDB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the ChannelNotifier and all goroutines it needs to carry out its task.
|
||||||
|
func (c *ChannelNotifier) Start() error {
|
||||||
|
if !atomic.CompareAndSwapUint32(&c.started, 0, 1) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("ChannelNotifier %v starting", c)
|
||||||
|
|
||||||
|
if err := c.ntfnServer.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop signals the notifier for a graceful shutdown.
|
||||||
|
func (c *ChannelNotifier) Stop() {
|
||||||
|
if !atomic.CompareAndSwapUint32(&c.stopped, 0, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ntfnServer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeChannelEvents returns a subscribe.Client that will receive updates
|
||||||
|
// any time the Server is made aware of a new event.
|
||||||
|
func (c *ChannelNotifier) SubscribeChannelEvents() (*subscribe.Client, error) {
|
||||||
|
return c.ntfnServer.Subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyOpenChannelEvent notifies the channelEventNotifier goroutine that a
|
||||||
|
// channel has gone from pending open to open.
|
||||||
|
func (c *ChannelNotifier) NotifyOpenChannelEvent(chanPoint wire.OutPoint) {
|
||||||
|
|
||||||
|
// Fetch the relevant channel from the database.
|
||||||
|
channel, err := c.chanDB.FetchChannel(chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to fetch open channel from the db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the open event to all channel event subscribers.
|
||||||
|
event := OpenChannelEvent{Channel: channel}
|
||||||
|
if err := c.ntfnServer.SendUpdate(event); err != nil {
|
||||||
|
log.Warnf("Unable to send open channel update: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyClosedChannelEvent notifies the channelEventNotifier goroutine that a
|
||||||
|
// channel has closed.
|
||||||
|
func (c *ChannelNotifier) NotifyClosedChannelEvent(chanPoint wire.OutPoint) {
|
||||||
|
// Fetch the relevant closed channel from the database.
|
||||||
|
closeSummary, err := c.chanDB.FetchClosedChannel(&chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to fetch closed channel summary from the db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the closed event to all channel event subscribers.
|
||||||
|
event := ClosedChannelEvent{CloseSummary: closeSummary}
|
||||||
|
if err := c.ntfnServer.SendUpdate(event); err != nil {
|
||||||
|
log.Warnf("Unable to send closed channel update: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyActiveChannelEvent notifies the channelEventNotifier goroutine that a
|
||||||
|
// channel is active.
|
||||||
|
func (c *ChannelNotifier) NotifyActiveChannelEvent(chanPoint wire.OutPoint) {
|
||||||
|
event := ActiveChannelEvent{ChannelPoint: &chanPoint}
|
||||||
|
if err := c.ntfnServer.SendUpdate(event); err != nil {
|
||||||
|
log.Warnf("Unable to send active channel update: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyInactiveChannelEvent notifies the channelEventNotifier goroutine that a
|
||||||
|
// channel is inactive.
|
||||||
|
func (c *ChannelNotifier) NotifyInactiveChannelEvent(chanPoint wire.OutPoint) {
|
||||||
|
event := InactiveChannelEvent{ChannelPoint: &chanPoint}
|
||||||
|
if err := c.ntfnServer.SendUpdate(event); err != nil {
|
||||||
|
log.Warnf("Unable to send inactive channel update: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package channelnotifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
"github.com/lightningnetwork/lnd/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
// log is a logger that is initialized with no output filters. This means the
|
||||||
|
// package will not perform any logging by default until the caller requests
|
||||||
|
// it.
|
||||||
|
var log btclog.Logger
|
||||||
|
|
||||||
|
// The default amount of logging is none.
|
||||||
|
func init() {
|
||||||
|
UseLogger(build.NewSubLogger("CHNF", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output. Logging output is disabled by
|
||||||
|
// default until UseLogger is called.
|
||||||
|
func DisableLog() {
|
||||||
|
UseLogger(btclog.Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info. This
|
||||||
|
// should be used in preference to SetLogWriter if the caller is also using
|
||||||
|
// btclog.
|
||||||
|
func UseLogger(logger btclog.Logger) {
|
||||||
|
log = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// logClosure is used to provide a closure over expensive logging operations so
|
||||||
|
// don't have to be performed when the logging level doesn't warrant it.
|
||||||
|
type logClosure func() string
|
||||||
|
|
||||||
|
// String invokes the underlying function and returns the result.
|
||||||
|
func (c logClosure) String() string {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLogClosure returns a new closure over a function that returns a string
|
||||||
|
// which itself provides a Stringer interface so that it can be used with the
|
||||||
|
// logging system.
|
||||||
|
func newLogClosure(c func() string) logClosure {
|
||||||
|
return logClosure(c)
|
||||||
|
}
|
||||||
@@ -141,6 +141,10 @@ type ChainArbitratorConfig struct {
|
|||||||
// the given payment hash. ErrInvoiceNotFound is returned if an invoice
|
// the given payment hash. ErrInvoiceNotFound is returned if an invoice
|
||||||
// is not found.
|
// is not found.
|
||||||
SettleInvoice func(lntypes.Hash, lnwire.MilliSatoshi) error
|
SettleInvoice func(lntypes.Hash, lnwire.MilliSatoshi) error
|
||||||
|
|
||||||
|
// NotifyClosedChannel is a function closure that the ChainArbitrator
|
||||||
|
// will use to notify the ChannelNotifier about a newly closed channel.
|
||||||
|
NotifyClosedChannel func(wire.OutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
||||||
@@ -245,10 +249,16 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
|
|||||||
return chanMachine.ForceClose()
|
return chanMachine.ForceClose()
|
||||||
},
|
},
|
||||||
MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted,
|
MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted,
|
||||||
MarkChannelClosed: channel.CloseChannel,
|
MarkChannelClosed: func(summary *channeldb.ChannelCloseSummary) error {
|
||||||
IsPendingClose: false,
|
if err := channel.CloseChannel(summary); err != nil {
|
||||||
ChainArbitratorConfig: c.cfg,
|
return err
|
||||||
ChainEvents: chanEvents,
|
}
|
||||||
|
c.cfg.NotifyClosedChannel(summary.ChanPoint)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
IsPendingClose: false,
|
||||||
|
ChainArbitratorConfig: c.cfg,
|
||||||
|
ChainEvents: chanEvents,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final component needed is an arbitrator log that the arbitrator
|
// The final component needed is an arbitrator log that the arbitrator
|
||||||
@@ -719,13 +729,7 @@ func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error
|
|||||||
// SubscribeChannelEvents returns a new active subscription for the set of
|
// SubscribeChannelEvents returns a new active subscription for the set of
|
||||||
// possible on-chain events for a particular channel. The struct can be used by
|
// possible on-chain events for a particular channel. The struct can be used by
|
||||||
// callers to be notified whenever an event that changes the state of the
|
// callers to be notified whenever an event that changes the state of the
|
||||||
// channel on-chain occurs. If syncDispatch is true, then the sender of the
|
// channel on-chain occurs.
|
||||||
// notification will wait until an error is sent over the ProcessACK before
|
|
||||||
// modifying any database state. This allows callers to request a reliable hand
|
|
||||||
// off.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): can be used later to provide RPC hook for all channel
|
|
||||||
// lifetimes
|
|
||||||
func (c *ChainArbitrator) SubscribeChannelEvents(
|
func (c *ChainArbitrator) SubscribeChannelEvents(
|
||||||
chanPoint wire.OutPoint) (*ChainEventSubscription, error) {
|
chanPoint wire.OutPoint) (*ChainEventSubscription, error) {
|
||||||
|
|
||||||
|
|||||||
@@ -333,6 +333,10 @@ type fundingConfig struct {
|
|||||||
// flood us with very small channels that would never really be usable
|
// flood us with very small channels that would never really be usable
|
||||||
// due to fees.
|
// due to fees.
|
||||||
MinChanSize btcutil.Amount
|
MinChanSize btcutil.Amount
|
||||||
|
|
||||||
|
// NotifyOpenChannelEvent informs the ChannelNotifier when channels
|
||||||
|
// transition from pending open to open.
|
||||||
|
NotifyOpenChannelEvent func(wire.OutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fundingManager acts as an orchestrator/bridge between the wallet's
|
// fundingManager acts as an orchestrator/bridge between the wallet's
|
||||||
@@ -1939,6 +1943,10 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inform the ChannelNotifier that the channel has transitioned from
|
||||||
|
// pending open to open.
|
||||||
|
f.cfg.NotifyOpenChannelEvent(completeChan.FundingOutpoint)
|
||||||
|
|
||||||
// TODO(roasbeef): ideally persistent state update for chan above
|
// TODO(roasbeef): ideally persistent state update for chan above
|
||||||
// should be abstracted
|
// should be abstracted
|
||||||
|
|
||||||
|
|||||||
@@ -353,8 +353,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
publTxChan <- txn
|
publTxChan <- txn
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
ZombieSweeperInterval: 1 * time.Hour,
|
ZombieSweeperInterval: 1 * time.Hour,
|
||||||
ReservationTimeout: 1 * time.Nanosecond,
|
ReservationTimeout: 1 * time.Nanosecond,
|
||||||
|
NotifyOpenChannelEvent: func(wire.OutPoint) {},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed creating fundingManager: %v", err)
|
t.Fatalf("failed creating fundingManager: %v", err)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package htlcswitch
|
package htlcswitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -58,6 +59,9 @@ type ChannelLink interface {
|
|||||||
// possible).
|
// possible).
|
||||||
HandleChannelUpdate(lnwire.Message)
|
HandleChannelUpdate(lnwire.Message)
|
||||||
|
|
||||||
|
// ChannelPoint returns the channel outpoint for the channel link.
|
||||||
|
ChannelPoint() *wire.OutPoint
|
||||||
|
|
||||||
// ChanID returns the channel ID for the channel link. The channel ID
|
// ChanID returns the channel ID for the channel link. The channel ID
|
||||||
// is a more compact representation of a channel's full outpoint.
|
// is a more compact representation of a channel's full outpoint.
|
||||||
ChanID() lnwire.ChannelID
|
ChanID() lnwire.ChannelID
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@@ -1743,6 +1744,12 @@ func (l *channelLink) Peer() lnpeer.Peer {
|
|||||||
return l.cfg.Peer
|
return l.cfg.Peer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChannelPoint returns the channel outpoint for the channel link.
|
||||||
|
// NOTE: Part of the ChannelLink interface.
|
||||||
|
func (l *channelLink) ChannelPoint() *wire.OutPoint {
|
||||||
|
return l.channel.ChannelPoint()
|
||||||
|
}
|
||||||
|
|
||||||
// ShortChanID returns the short channel ID for the channel link. The short
|
// ShortChanID returns the short channel ID for the channel link. The short
|
||||||
// channel ID encodes the exact location in the main chain that the original
|
// channel ID encodes the exact location in the main chain that the original
|
||||||
// funding output can be found.
|
// funding output can be found.
|
||||||
|
|||||||
+6
-3
@@ -162,9 +162,11 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
|
|||||||
FetchLastChannelUpdate: func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
|
FetchLastChannelUpdate: func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
Notifier: &mockNotifier{},
|
Notifier: &mockNotifier{},
|
||||||
FwdEventTicker: ticker.MockNew(DefaultFwdEventInterval),
|
FwdEventTicker: ticker.MockNew(DefaultFwdEventInterval),
|
||||||
LogEventTicker: ticker.MockNew(DefaultLogInterval),
|
LogEventTicker: ticker.MockNew(DefaultLogInterval),
|
||||||
|
NotifyActiveChannel: func(wire.OutPoint) {},
|
||||||
|
NotifyInactiveChannel: func(wire.OutPoint) {},
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(cfg, startingHeight)
|
return New(cfg, startingHeight)
|
||||||
@@ -673,6 +675,7 @@ func (f *mockChannelLink) ChanID() lnwire.ChannelID { return
|
|||||||
func (f *mockChannelLink) ShortChanID() lnwire.ShortChannelID { return f.shortChanID }
|
func (f *mockChannelLink) ShortChanID() lnwire.ShortChannelID { return f.shortChanID }
|
||||||
func (f *mockChannelLink) Bandwidth() lnwire.MilliSatoshi { return 99999999 }
|
func (f *mockChannelLink) Bandwidth() lnwire.MilliSatoshi { return 99999999 }
|
||||||
func (f *mockChannelLink) Peer() lnpeer.Peer { return f.peer }
|
func (f *mockChannelLink) Peer() lnpeer.Peer { return f.peer }
|
||||||
|
func (f *mockChannelLink) ChannelPoint() *wire.OutPoint { return &wire.OutPoint{} }
|
||||||
func (f *mockChannelLink) Stop() {}
|
func (f *mockChannelLink) Stop() {}
|
||||||
func (f *mockChannelLink) EligibleToForward() bool { return f.eligible }
|
func (f *mockChannelLink) EligibleToForward() bool { return f.eligible }
|
||||||
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
|
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
|
||||||
|
|||||||
@@ -175,6 +175,11 @@ type Config struct {
|
|||||||
// LogEventTicker is a signal instructing the htlcswitch to log
|
// LogEventTicker is a signal instructing the htlcswitch to log
|
||||||
// aggregate stats about it's forwarding during the last interval.
|
// aggregate stats about it's forwarding during the last interval.
|
||||||
LogEventTicker ticker.Ticker
|
LogEventTicker ticker.Ticker
|
||||||
|
|
||||||
|
// NotifyActiveChannel and NotifyInactiveChannel allow the link to tell
|
||||||
|
// the ChannelNotifier when channels become active and inactive.
|
||||||
|
NotifyActiveChannel func(wire.OutPoint)
|
||||||
|
NotifyInactiveChannel func(wire.OutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch is the central messaging bus for all incoming/outgoing HTLCs.
|
// Switch is the central messaging bus for all incoming/outgoing HTLCs.
|
||||||
@@ -1956,6 +1961,11 @@ func (s *Switch) addLiveLink(link ChannelLink) {
|
|||||||
s.interfaceIndex[peerPub] = make(map[lnwire.ChannelID]ChannelLink)
|
s.interfaceIndex[peerPub] = make(map[lnwire.ChannelID]ChannelLink)
|
||||||
}
|
}
|
||||||
s.interfaceIndex[peerPub][link.ChanID()] = link
|
s.interfaceIndex[peerPub][link.ChanID()] = link
|
||||||
|
|
||||||
|
// Inform the channel notifier if the link has become active.
|
||||||
|
if link.EligibleToForward() {
|
||||||
|
s.cfg.NotifyActiveChannel(*link.ChannelPoint())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLink is used to initiate the handling of the get link command. The
|
// GetLink is used to initiate the handling of the get link command. The
|
||||||
@@ -2031,6 +2041,9 @@ func (s *Switch) removeLink(chanID lnwire.ChannelID) ChannelLink {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inform the Channel Notifier about the link becoming inactive.
|
||||||
|
s.cfg.NotifyInactiveChannel(*link.ChannelPoint())
|
||||||
|
|
||||||
// Remove the channel from live link indexes.
|
// Remove the channel from live link indexes.
|
||||||
delete(s.pendingLinkIndex, link.ChanID())
|
delete(s.pendingLinkIndex, link.ChanID())
|
||||||
delete(s.linkIndex, link.ChanID())
|
delete(s.linkIndex, link.ChanID())
|
||||||
|
|||||||
+210
-10
@@ -5694,15 +5694,137 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBasicChannelCreation test multiple channel opening and closing.
|
// channelSubscription houses the proxied update and error chans for a node's
|
||||||
func testBasicChannelCreation(net *lntest.NetworkHarness, t *harnessTest) {
|
// channel subscriptions.
|
||||||
ctxb := context.Background()
|
type channelSubscription struct {
|
||||||
|
updateChan chan *lnrpc.ChannelEventUpdate
|
||||||
|
errChan chan error
|
||||||
|
quit chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribeChannelNotifications subscribes to channel updates and launches a
|
||||||
|
// goroutine that forwards these to the returned channel.
|
||||||
|
func subscribeChannelNotifications(ctxb context.Context, t *harnessTest,
|
||||||
|
node *lntest.HarnessNode) channelSubscription {
|
||||||
|
|
||||||
|
// We'll first start by establishing a notification client which will
|
||||||
|
// send us notifications upon channels becoming active, inactive or
|
||||||
|
// closed.
|
||||||
|
req := &lnrpc.ChannelEventSubscription{}
|
||||||
|
ctx, cancelFunc := context.WithCancel(ctxb)
|
||||||
|
|
||||||
|
chanUpdateClient, err := node.SubscribeChannelEvents(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create channel update client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll launch a goroutine that will be responsible for proxying all
|
||||||
|
// notifications recv'd from the client into the channel below.
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
quit := make(chan struct{})
|
||||||
|
chanUpdates := make(chan *lnrpc.ChannelEventUpdate, 20)
|
||||||
|
go func() {
|
||||||
|
defer cancelFunc()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
chanUpdate, err := chanUpdateClient.Recv()
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
select {
|
||||||
|
case errChan <- err:
|
||||||
|
case <-quit:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case chanUpdates <- chanUpdate:
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return channelSubscription{
|
||||||
|
updateChan: chanUpdates,
|
||||||
|
errChan: errChan,
|
||||||
|
quit: quit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyCloseUpdate is used to verify that a closed channel update is of the
|
||||||
|
// expected type.
|
||||||
|
func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate,
|
||||||
|
force bool, forceType lnrpc.ChannelCloseSummary_ClosureType) error {
|
||||||
|
|
||||||
|
// We should receive one inactive and one closed notification
|
||||||
|
// for each channel.
|
||||||
|
switch update := chanUpdate.Channel.(type) {
|
||||||
|
case *lnrpc.ChannelEventUpdate_InactiveChannel:
|
||||||
|
if chanUpdate.Type != lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL {
|
||||||
|
return fmt.Errorf("update type mismatch: expected %v, got %v",
|
||||||
|
lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL,
|
||||||
|
chanUpdate.Type)
|
||||||
|
}
|
||||||
|
case *lnrpc.ChannelEventUpdate_ClosedChannel:
|
||||||
|
if chanUpdate.Type !=
|
||||||
|
lnrpc.ChannelEventUpdate_CLOSED_CHANNEL {
|
||||||
|
return fmt.Errorf("update type mismatch: expected %v, got %v",
|
||||||
|
lnrpc.ChannelEventUpdate_CLOSED_CHANNEL,
|
||||||
|
chanUpdate.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch force {
|
||||||
|
case true:
|
||||||
|
if update.ClosedChannel.CloseType != forceType {
|
||||||
|
return fmt.Errorf("channel closure type mismatch: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
forceType,
|
||||||
|
update.ClosedChannel.CloseType)
|
||||||
|
}
|
||||||
|
case false:
|
||||||
|
if update.ClosedChannel.CloseType !=
|
||||||
|
lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE {
|
||||||
|
return fmt.Errorf("channel closure type "+
|
||||||
|
"mismatch: expected %v, got %v",
|
||||||
|
lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE,
|
||||||
|
update.ClosedChannel.CloseType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("channel update channel of wrong type, "+
|
||||||
|
"expected closed channel, got %T",
|
||||||
|
update)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBasicChannelCreationAndUpdates tests multiple channel opening and closing,
|
||||||
|
// and ensures that if a node is subscribed to channel updates they will be
|
||||||
|
// received correctly for both cooperative and force closed channels.
|
||||||
|
func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
ctxb := context.Background()
|
||||||
const (
|
const (
|
||||||
numChannels = 2
|
numChannels = 2
|
||||||
amount = maxBtcFundingAmount
|
amount = maxBtcFundingAmount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Let Bob subscribe to channel notifications.
|
||||||
|
bobChanSub := subscribeChannelNotifications(ctxb, t, net.Bob)
|
||||||
|
defer close(bobChanSub.quit)
|
||||||
|
|
||||||
// Open the channel between Alice and Bob, asserting that the
|
// Open the channel between Alice and Bob, asserting that the
|
||||||
// channel has been properly open on-chain.
|
// channel has been properly open on-chain.
|
||||||
chanPoints := make([]*lnrpc.ChannelPoint, numChannels)
|
chanPoints := make([]*lnrpc.ChannelPoint, numChannels)
|
||||||
@@ -5716,11 +5838,89 @@ func testBasicChannelCreation(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the channel between Alice and Bob, asserting that the
|
// Since each of the channels just became open, Bob should we receive an
|
||||||
// channel has been properly closed on-chain.
|
// open and an active notification for each channel.
|
||||||
for _, chanPoint := range chanPoints {
|
var numChannelUpds int
|
||||||
ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
|
for numChannelUpds < 2*numChannels {
|
||||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
select {
|
||||||
|
case update := <-bobChanSub.updateChan:
|
||||||
|
switch update.Type {
|
||||||
|
case lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL:
|
||||||
|
case lnrpc.ChannelEventUpdate_OPEN_CHANNEL:
|
||||||
|
default:
|
||||||
|
t.Fatalf("update type mismatch: expected open or active "+
|
||||||
|
"channel notification, got: %v", update.Type)
|
||||||
|
}
|
||||||
|
numChannelUpds++
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
t.Fatalf("timeout waiting for channel notifications, "+
|
||||||
|
"only received %d/%d chanupds", numChannelUpds,
|
||||||
|
numChannels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe Alice to channel updates so we can test that both remote
|
||||||
|
// and local force close notifications are received correctly.
|
||||||
|
aliceChanSub := subscribeChannelNotifications(ctxb, t, net.Alice)
|
||||||
|
defer close(aliceChanSub.quit)
|
||||||
|
|
||||||
|
// Close the channel between Alice and Bob, asserting that the channel
|
||||||
|
// has been properly closed on-chain.
|
||||||
|
for i, chanPoint := range chanPoints {
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
|
||||||
|
// Force close half of the channels.
|
||||||
|
force := i%2 == 0
|
||||||
|
closeChannelAndAssert(ctx, t, net, net.Alice, chanPoint, force)
|
||||||
|
if force {
|
||||||
|
cleanupForceClose(t, net, net.Alice, chanPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyCloseUpdatesReceived is used to verify that Alice and Bob
|
||||||
|
// receive the correct channel updates in order.
|
||||||
|
verifyCloseUpdatesReceived := func(sub channelSubscription,
|
||||||
|
forceType lnrpc.ChannelCloseSummary_ClosureType) error {
|
||||||
|
|
||||||
|
// Ensure one inactive and one closed notification is received for each
|
||||||
|
// closed channel.
|
||||||
|
numChannelUpds := 0
|
||||||
|
for numChannelUpds < 2*numChannels {
|
||||||
|
// Every other channel should be force closed.
|
||||||
|
force := (numChannelUpds/2)%2 == 0
|
||||||
|
|
||||||
|
select {
|
||||||
|
case chanUpdate := <-sub.updateChan:
|
||||||
|
err := verifyCloseUpdate(chanUpdate, force, forceType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
numChannelUpds++
|
||||||
|
case err := <-sub.errChan:
|
||||||
|
return err
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
return fmt.Errorf("timeout waiting for channel "+
|
||||||
|
"notifications, only received %d/%d "+
|
||||||
|
"chanupds", numChannelUpds, 2*numChannels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Bob receives all closed channel notifications. He should
|
||||||
|
// receive a remote force close notification for force closed channels.
|
||||||
|
if err := verifyCloseUpdatesReceived(bobChanSub,
|
||||||
|
lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE); err != nil {
|
||||||
|
t.Fatalf("errored verifying close updates: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Alice receives all closed channel notifications. She should
|
||||||
|
// receive a remote force close notification for force closed channels.
|
||||||
|
if err := verifyCloseUpdatesReceived(aliceChanSub,
|
||||||
|
lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE); err != nil {
|
||||||
|
t.Fatalf("errored verifying close updates: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12943,8 +13143,8 @@ var testsCases = []*testCase{
|
|||||||
test: testMultiHopOverPrivateChannels,
|
test: testMultiHopOverPrivateChannels,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple channel creation",
|
name: "multiple channel creation and update subscription",
|
||||||
test: testBasicChannelCreation,
|
test: testBasicChannelCreationAndUpdates,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invoice update subscription",
|
name: "invoice update subscription",
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (m *StatusRequest) Reset() { *m = StatusRequest{} }
|
|||||||
func (m *StatusRequest) String() string { return proto.CompactTextString(m) }
|
func (m *StatusRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*StatusRequest) ProtoMessage() {}
|
func (*StatusRequest) ProtoMessage() {}
|
||||||
func (*StatusRequest) Descriptor() ([]byte, []int) {
|
func (*StatusRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{0}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{0}
|
||||||
}
|
}
|
||||||
func (m *StatusRequest) XXX_Unmarshal(b []byte) error {
|
func (m *StatusRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_StatusRequest.Unmarshal(m, b)
|
return xxx_messageInfo_StatusRequest.Unmarshal(m, b)
|
||||||
@@ -65,7 +65,7 @@ func (m *StatusResponse) Reset() { *m = StatusResponse{} }
|
|||||||
func (m *StatusResponse) String() string { return proto.CompactTextString(m) }
|
func (m *StatusResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*StatusResponse) ProtoMessage() {}
|
func (*StatusResponse) ProtoMessage() {}
|
||||||
func (*StatusResponse) Descriptor() ([]byte, []int) {
|
func (*StatusResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{1}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{1}
|
||||||
}
|
}
|
||||||
func (m *StatusResponse) XXX_Unmarshal(b []byte) error {
|
func (m *StatusResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_StatusResponse.Unmarshal(m, b)
|
return xxx_messageInfo_StatusResponse.Unmarshal(m, b)
|
||||||
@@ -104,7 +104,7 @@ func (m *ModifyStatusRequest) Reset() { *m = ModifyStatusRequest{} }
|
|||||||
func (m *ModifyStatusRequest) String() string { return proto.CompactTextString(m) }
|
func (m *ModifyStatusRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ModifyStatusRequest) ProtoMessage() {}
|
func (*ModifyStatusRequest) ProtoMessage() {}
|
||||||
func (*ModifyStatusRequest) Descriptor() ([]byte, []int) {
|
func (*ModifyStatusRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{2}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{2}
|
||||||
}
|
}
|
||||||
func (m *ModifyStatusRequest) XXX_Unmarshal(b []byte) error {
|
func (m *ModifyStatusRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ModifyStatusRequest.Unmarshal(m, b)
|
return xxx_messageInfo_ModifyStatusRequest.Unmarshal(m, b)
|
||||||
@@ -141,7 +141,7 @@ func (m *ModifyStatusResponse) Reset() { *m = ModifyStatusResponse{} }
|
|||||||
func (m *ModifyStatusResponse) String() string { return proto.CompactTextString(m) }
|
func (m *ModifyStatusResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ModifyStatusResponse) ProtoMessage() {}
|
func (*ModifyStatusResponse) ProtoMessage() {}
|
||||||
func (*ModifyStatusResponse) Descriptor() ([]byte, []int) {
|
func (*ModifyStatusResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{3}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{3}
|
||||||
}
|
}
|
||||||
func (m *ModifyStatusResponse) XXX_Unmarshal(b []byte) error {
|
func (m *ModifyStatusResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ModifyStatusResponse.Unmarshal(m, b)
|
return xxx_messageInfo_ModifyStatusResponse.Unmarshal(m, b)
|
||||||
@@ -172,7 +172,7 @@ func (m *QueryScoresRequest) Reset() { *m = QueryScoresRequest{} }
|
|||||||
func (m *QueryScoresRequest) String() string { return proto.CompactTextString(m) }
|
func (m *QueryScoresRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*QueryScoresRequest) ProtoMessage() {}
|
func (*QueryScoresRequest) ProtoMessage() {}
|
||||||
func (*QueryScoresRequest) Descriptor() ([]byte, []int) {
|
func (*QueryScoresRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{4}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{4}
|
||||||
}
|
}
|
||||||
func (m *QueryScoresRequest) XXX_Unmarshal(b []byte) error {
|
func (m *QueryScoresRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_QueryScoresRequest.Unmarshal(m, b)
|
return xxx_messageInfo_QueryScoresRequest.Unmarshal(m, b)
|
||||||
@@ -210,7 +210,7 @@ func (m *QueryScoresResponse) Reset() { *m = QueryScoresResponse{} }
|
|||||||
func (m *QueryScoresResponse) String() string { return proto.CompactTextString(m) }
|
func (m *QueryScoresResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*QueryScoresResponse) ProtoMessage() {}
|
func (*QueryScoresResponse) ProtoMessage() {}
|
||||||
func (*QueryScoresResponse) Descriptor() ([]byte, []int) {
|
func (*QueryScoresResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{5}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{5}
|
||||||
}
|
}
|
||||||
func (m *QueryScoresResponse) XXX_Unmarshal(b []byte) error {
|
func (m *QueryScoresResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_QueryScoresResponse.Unmarshal(m, b)
|
return xxx_messageInfo_QueryScoresResponse.Unmarshal(m, b)
|
||||||
@@ -249,7 +249,7 @@ func (m *QueryScoresResponse_HeuristicResult) Reset() { *m = QueryScores
|
|||||||
func (m *QueryScoresResponse_HeuristicResult) String() string { return proto.CompactTextString(m) }
|
func (m *QueryScoresResponse_HeuristicResult) String() string { return proto.CompactTextString(m) }
|
||||||
func (*QueryScoresResponse_HeuristicResult) ProtoMessage() {}
|
func (*QueryScoresResponse_HeuristicResult) ProtoMessage() {}
|
||||||
func (*QueryScoresResponse_HeuristicResult) Descriptor() ([]byte, []int) {
|
func (*QueryScoresResponse_HeuristicResult) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_autopilot_52f30cf4d0055211, []int{5, 0}
|
return fileDescriptor_autopilot_7db7978f022d4696, []int{5, 0}
|
||||||
}
|
}
|
||||||
func (m *QueryScoresResponse_HeuristicResult) XXX_Unmarshal(b []byte) error {
|
func (m *QueryScoresResponse_HeuristicResult) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_QueryScoresResponse_HeuristicResult.Unmarshal(m, b)
|
return xxx_messageInfo_QueryScoresResponse_HeuristicResult.Unmarshal(m, b)
|
||||||
@@ -451,10 +451,10 @@ var _Autopilot_serviceDesc = grpc.ServiceDesc{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto.RegisterFile("autopilotrpc/autopilot.proto", fileDescriptor_autopilot_52f30cf4d0055211)
|
proto.RegisterFile("autopilotrpc/autopilot.proto", fileDescriptor_autopilot_7db7978f022d4696)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptor_autopilot_52f30cf4d0055211 = []byte{
|
var fileDescriptor_autopilot_7db7978f022d4696 = []byte{
|
||||||
// 391 bytes of a gzipped FileDescriptorProto
|
// 391 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4d, 0xaf, 0xd2, 0x40,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4d, 0xaf, 0xd2, 0x40,
|
||||||
0x14, 0xcd, 0x94, 0x58, 0xec, 0x05, 0xc5, 0x0c, 0x84, 0x34, 0x95, 0x45, 0xe9, 0xaa, 0x1b, 0xdb,
|
0x14, 0xcd, 0x94, 0x58, 0xec, 0x05, 0xc5, 0x0c, 0x84, 0x34, 0x95, 0x45, 0xe9, 0xaa, 0x1b, 0xdb,
|
||||||
|
|||||||
+916
-555
File diff suppressed because it is too large.
Load diff
@@ -359,6 +359,14 @@ service Lightning {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** lncli: `subscribechannelevents`
|
||||||
|
SubscribeChannelEvents creates a uni-directional stream from the server to
|
||||||
|
the client in which any updates relevant to the state of the channels are
|
||||||
|
sent over. Events include new active channels, inactive channels, and closed
|
||||||
|
channels.
|
||||||
|
*/
|
||||||
|
rpc SubscribeChannelEvents (ChannelEventSubscription) returns (stream ChannelEventUpdate);
|
||||||
|
|
||||||
/** lncli: `closedchannels`
|
/** lncli: `closedchannels`
|
||||||
ClosedChannels returns a description of all the closed channels that
|
ClosedChannels returns a description of all the closed channels that
|
||||||
this node was a participant in.
|
this node was a participant in.
|
||||||
@@ -1401,6 +1409,27 @@ message PendingChannelsResponse {
|
|||||||
repeated WaitingCloseChannel waiting_close_channels = 5 [ json_name = "waiting_close_channels" ];
|
repeated WaitingCloseChannel waiting_close_channels = 5 [ json_name = "waiting_close_channels" ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ChannelEventSubscription {
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChannelEventUpdate {
|
||||||
|
oneof channel {
|
||||||
|
Channel open_channel = 1 [ json_name = "open_channel" ];
|
||||||
|
ChannelCloseSummary closed_channel = 2 [ json_name = "closed_channel" ];
|
||||||
|
ChannelPoint active_channel = 3 [ json_name = "active_channel" ];
|
||||||
|
ChannelPoint inactive_channel = 4 [ json_name = "inactive_channel" ];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UpdateType {
|
||||||
|
OPEN_CHANNEL = 0;
|
||||||
|
CLOSED_CHANNEL = 1;
|
||||||
|
ACTIVE_CHANNEL = 2;
|
||||||
|
INACTIVE_CHANNEL = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateType type = 5 [ json_name = "type" ];
|
||||||
|
}
|
||||||
|
|
||||||
message WalletBalanceRequest {
|
message WalletBalanceRequest {
|
||||||
}
|
}
|
||||||
message WalletBalanceResponse {
|
message WalletBalanceResponse {
|
||||||
|
|||||||
@@ -1127,6 +1127,16 @@
|
|||||||
],
|
],
|
||||||
"default": "COOPERATIVE_CLOSE"
|
"default": "COOPERATIVE_CLOSE"
|
||||||
},
|
},
|
||||||
|
"ChannelEventUpdateUpdateType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"OPEN_CHANNEL",
|
||||||
|
"CLOSED_CHANNEL",
|
||||||
|
"ACTIVE_CHANNEL",
|
||||||
|
"INACTIVE_CHANNEL"
|
||||||
|
],
|
||||||
|
"default": "OPEN_CHANNEL"
|
||||||
|
},
|
||||||
"InvoiceInvoiceState": {
|
"InvoiceInvoiceState": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -1548,6 +1558,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcChannelEventUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"open_channel": {
|
||||||
|
"$ref": "#/definitions/lnrpcChannel"
|
||||||
|
},
|
||||||
|
"closed_channel": {
|
||||||
|
"$ref": "#/definitions/lnrpcChannelCloseSummary"
|
||||||
|
},
|
||||||
|
"active_channel": {
|
||||||
|
"$ref": "#/definitions/lnrpcChannelPoint"
|
||||||
|
},
|
||||||
|
"inactive_channel": {
|
||||||
|
"$ref": "#/definitions/lnrpcChannelPoint"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/ChannelEventUpdateUpdateType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcChannelFeeReport": {
|
"lnrpcChannelFeeReport": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
"github.com/lightningnetwork/lnd/discovery"
|
"github.com/lightningnetwork/lnd/discovery"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
@@ -80,6 +81,7 @@ var (
|
|||||||
wtwrLog = build.NewSubLogger("WTWR", backendLog.Logger)
|
wtwrLog = build.NewSubLogger("WTWR", backendLog.Logger)
|
||||||
ntfrLog = build.NewSubLogger("NTFR", backendLog.Logger)
|
ntfrLog = build.NewSubLogger("NTFR", backendLog.Logger)
|
||||||
irpcLog = build.NewSubLogger("IRPC", backendLog.Logger)
|
irpcLog = build.NewSubLogger("IRPC", backendLog.Logger)
|
||||||
|
chnfLog = build.NewSubLogger("CHNF", backendLog.Logger)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize package-global logger variables.
|
// Initialize package-global logger variables.
|
||||||
@@ -105,6 +107,7 @@ func init() {
|
|||||||
watchtower.UseLogger(wtwrLog)
|
watchtower.UseLogger(wtwrLog)
|
||||||
chainrpc.UseLogger(ntfrLog)
|
chainrpc.UseLogger(ntfrLog)
|
||||||
invoicesrpc.UseLogger(irpcLog)
|
invoicesrpc.UseLogger(irpcLog)
|
||||||
|
channelnotifier.UseLogger(chnfLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||||
@@ -136,6 +139,7 @@ var subsystemLoggers = map[string]btclog.Logger{
|
|||||||
"WTWR": wtwrLog,
|
"WTWR": wtwrLog,
|
||||||
"NTFR": ntfnLog,
|
"NTFR": ntfnLog,
|
||||||
"IRPC": irpcLog,
|
"IRPC": irpcLog,
|
||||||
|
"CHNF": chnfLog,
|
||||||
}
|
}
|
||||||
|
|
||||||
// initLogRotator initializes the logging rotator to write logs to logFile and
|
// initLogRotator initializes the logging rotator to write logs to logFile and
|
||||||
|
|||||||
+204
-92
@@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/autopilot"
|
"github.com/lightningnetwork/lnd/autopilot"
|
||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
@@ -245,6 +246,10 @@ var (
|
|||||||
Entity: "offchain",
|
Entity: "offchain",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
}},
|
}},
|
||||||
|
"/lnrpc.Lightning/SubscribeChannelEvents": {{
|
||||||
|
Entity: "offchain",
|
||||||
|
Action: "read",
|
||||||
|
}},
|
||||||
"/lnrpc.Lightning/ClosedChannels": {{
|
"/lnrpc.Lightning/ClosedChannels": {{
|
||||||
Entity: "offchain",
|
Entity: "offchain",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
@@ -2254,56 +2259,34 @@ func (r *rpcServer) ClosedChannels(ctx context.Context,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
nodePub := dbChannel.RemotePub
|
|
||||||
nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
|
|
||||||
|
|
||||||
var closeType lnrpc.ChannelCloseSummary_ClosureType
|
|
||||||
switch dbChannel.CloseType {
|
switch dbChannel.CloseType {
|
||||||
case channeldb.CooperativeClose:
|
case channeldb.CooperativeClose:
|
||||||
if filterResults && !in.Cooperative {
|
if filterResults && !in.Cooperative {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
closeType = lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE
|
|
||||||
case channeldb.LocalForceClose:
|
case channeldb.LocalForceClose:
|
||||||
if filterResults && !in.LocalForce {
|
if filterResults && !in.LocalForce {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
closeType = lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE
|
|
||||||
case channeldb.RemoteForceClose:
|
case channeldb.RemoteForceClose:
|
||||||
if filterResults && !in.RemoteForce {
|
if filterResults && !in.RemoteForce {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
closeType = lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE
|
|
||||||
case channeldb.BreachClose:
|
case channeldb.BreachClose:
|
||||||
if filterResults && !in.Breach {
|
if filterResults && !in.Breach {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
closeType = lnrpc.ChannelCloseSummary_BREACH_CLOSE
|
|
||||||
case channeldb.FundingCanceled:
|
case channeldb.FundingCanceled:
|
||||||
if filterResults && !in.FundingCanceled {
|
if filterResults && !in.FundingCanceled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
closeType = lnrpc.ChannelCloseSummary_FUNDING_CANCELED
|
|
||||||
case channeldb.Abandoned:
|
case channeldb.Abandoned:
|
||||||
if filterResults && !in.Abandoned {
|
if filterResults && !in.Abandoned {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
closeType = lnrpc.ChannelCloseSummary_ABANDONED
|
|
||||||
}
|
|
||||||
|
|
||||||
channel := &lnrpc.ChannelCloseSummary{
|
|
||||||
Capacity: int64(dbChannel.Capacity),
|
|
||||||
RemotePubkey: nodeID,
|
|
||||||
CloseHeight: dbChannel.CloseHeight,
|
|
||||||
CloseType: closeType,
|
|
||||||
ChannelPoint: dbChannel.ChanPoint.String(),
|
|
||||||
ChanId: dbChannel.ShortChanID.ToUint64(),
|
|
||||||
SettledBalance: int64(dbChannel.SettledBalance),
|
|
||||||
TimeLockedBalance: int64(dbChannel.TimeLockedBalance),
|
|
||||||
ChainHash: dbChannel.ChainHash.String(),
|
|
||||||
ClosingTxHash: dbChannel.ClosingTXID.String(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel := createRPCClosedChannel(dbChannel)
|
||||||
resp.Channels = append(resp.Channels, channel)
|
resp.Channels = append(resp.Channels, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2339,14 +2322,8 @@ func (r *rpcServer) ListChannels(ctx context.Context,
|
|||||||
|
|
||||||
for _, dbChannel := range dbChannels {
|
for _, dbChannel := range dbChannels {
|
||||||
nodePub := dbChannel.IdentityPub
|
nodePub := dbChannel.IdentityPub
|
||||||
nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
|
|
||||||
chanPoint := dbChannel.FundingOutpoint
|
chanPoint := dbChannel.FundingOutpoint
|
||||||
|
|
||||||
// With the channel point known, retrieve the network channel
|
|
||||||
// ID from the database.
|
|
||||||
var chanID uint64
|
|
||||||
chanID, _ = graph.ChannelID(&chanPoint)
|
|
||||||
|
|
||||||
var peerOnline bool
|
var peerOnline bool
|
||||||
if _, err := r.server.FindPeer(nodePub); err == nil {
|
if _, err := r.server.FindPeer(nodePub); err == nil {
|
||||||
peerOnline = true
|
peerOnline = true
|
||||||
@@ -2364,7 +2341,7 @@ func (r *rpcServer) ListChannels(ctx context.Context,
|
|||||||
// Next, we'll determine whether we should add this channel to
|
// Next, we'll determine whether we should add this channel to
|
||||||
// our list depending on the type of channels requested to us.
|
// our list depending on the type of channels requested to us.
|
||||||
isActive := peerOnline && linkActive
|
isActive := peerOnline && linkActive
|
||||||
isPublic := dbChannel.ChannelFlags&lnwire.FFAnnounceChannel != 0
|
channel := createRPCOpenChannel(r, graph, dbChannel, isActive)
|
||||||
|
|
||||||
// We'll only skip returning this channel if we were requested
|
// We'll only skip returning this channel if we were requested
|
||||||
// for a specific kind and this channel doesn't satisfy it.
|
// for a specific kind and this channel doesn't satisfy it.
|
||||||
@@ -2373,78 +2350,213 @@ func (r *rpcServer) ListChannels(ctx context.Context,
|
|||||||
continue
|
continue
|
||||||
case in.InactiveOnly && isActive:
|
case in.InactiveOnly && isActive:
|
||||||
continue
|
continue
|
||||||
case in.PublicOnly && !isPublic:
|
case in.PublicOnly && channel.Private:
|
||||||
continue
|
continue
|
||||||
case in.PrivateOnly && isPublic:
|
case in.PrivateOnly && !channel.Private:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// As this is required for display purposes, we'll calculate
|
|
||||||
// the weight of the commitment transaction. We also add on the
|
|
||||||
// estimated weight of the witness to calculate the weight of
|
|
||||||
// the transaction if it were to be immediately unilaterally
|
|
||||||
// broadcast.
|
|
||||||
localCommit := dbChannel.LocalCommitment
|
|
||||||
utx := btcutil.NewTx(localCommit.CommitTx)
|
|
||||||
commitBaseWeight := blockchain.GetTransactionWeight(utx)
|
|
||||||
commitWeight := commitBaseWeight + input.WitnessCommitmentTxWeight
|
|
||||||
|
|
||||||
localBalance := localCommit.LocalBalance
|
|
||||||
remoteBalance := localCommit.RemoteBalance
|
|
||||||
|
|
||||||
// As an artifact of our usage of mSAT internally, either party
|
|
||||||
// may end up in a state where they're holding a fractional
|
|
||||||
// amount of satoshis which can't be expressed within the
|
|
||||||
// actual commitment output. Since we round down when going
|
|
||||||
// from mSAT -> SAT, we may at any point be adding an
|
|
||||||
// additional SAT to miners fees. As a result, we display a
|
|
||||||
// commitment fee that accounts for this externally.
|
|
||||||
var sumOutputs btcutil.Amount
|
|
||||||
for _, txOut := range localCommit.CommitTx.TxOut {
|
|
||||||
sumOutputs += btcutil.Amount(txOut.Value)
|
|
||||||
}
|
|
||||||
externalCommitFee := dbChannel.Capacity - sumOutputs
|
|
||||||
|
|
||||||
channel := &lnrpc.Channel{
|
|
||||||
Active: isActive,
|
|
||||||
Private: !isPublic,
|
|
||||||
RemotePubkey: nodeID,
|
|
||||||
ChannelPoint: chanPoint.String(),
|
|
||||||
ChanId: chanID,
|
|
||||||
Capacity: int64(dbChannel.Capacity),
|
|
||||||
LocalBalance: int64(localBalance.ToSatoshis()),
|
|
||||||
RemoteBalance: int64(remoteBalance.ToSatoshis()),
|
|
||||||
CommitFee: int64(externalCommitFee),
|
|
||||||
CommitWeight: commitWeight,
|
|
||||||
FeePerKw: int64(localCommit.FeePerKw),
|
|
||||||
TotalSatoshisSent: int64(dbChannel.TotalMSatSent.ToSatoshis()),
|
|
||||||
TotalSatoshisReceived: int64(dbChannel.TotalMSatReceived.ToSatoshis()),
|
|
||||||
NumUpdates: localCommit.CommitHeight,
|
|
||||||
PendingHtlcs: make([]*lnrpc.HTLC, len(localCommit.Htlcs)),
|
|
||||||
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
|
|
||||||
Initiator: dbChannel.IsInitiator,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, htlc := range localCommit.Htlcs {
|
|
||||||
var rHash [32]byte
|
|
||||||
copy(rHash[:], htlc.RHash[:])
|
|
||||||
channel.PendingHtlcs[i] = &lnrpc.HTLC{
|
|
||||||
Incoming: htlc.Incoming,
|
|
||||||
Amount: int64(htlc.Amt.ToSatoshis()),
|
|
||||||
HashLock: rHash[:],
|
|
||||||
ExpirationHeight: htlc.RefundTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the Pending Htlc Amount to UnsettledBalance field.
|
|
||||||
channel.UnsettledBalance += channel.PendingHtlcs[i].Amount
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Channels = append(resp.Channels, channel)
|
resp.Channels = append(resp.Channels, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createRPCOpenChannel creates an *lnrpc.Channel from the *channeldb.Channel.
|
||||||
|
func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph,
|
||||||
|
dbChannel *channeldb.OpenChannel, isActive bool) *lnrpc.Channel {
|
||||||
|
|
||||||
|
nodePub := dbChannel.IdentityPub
|
||||||
|
nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
|
||||||
|
chanPoint := dbChannel.FundingOutpoint
|
||||||
|
|
||||||
|
// With the channel point known, retrieve the network channel
|
||||||
|
// ID from the database.
|
||||||
|
var chanID uint64
|
||||||
|
chanID, _ = graph.ChannelID(&chanPoint)
|
||||||
|
|
||||||
|
// Next, we'll determine whether the channel is public or not.
|
||||||
|
isPublic := dbChannel.ChannelFlags&lnwire.FFAnnounceChannel != 0
|
||||||
|
|
||||||
|
// As this is required for display purposes, we'll calculate
|
||||||
|
// the weight of the commitment transaction. We also add on the
|
||||||
|
// estimated weight of the witness to calculate the weight of
|
||||||
|
// the transaction if it were to be immediately unilaterally
|
||||||
|
// broadcast.
|
||||||
|
localCommit := dbChannel.LocalCommitment
|
||||||
|
utx := btcutil.NewTx(localCommit.CommitTx)
|
||||||
|
commitBaseWeight := blockchain.GetTransactionWeight(utx)
|
||||||
|
commitWeight := commitBaseWeight + input.WitnessCommitmentTxWeight
|
||||||
|
|
||||||
|
localBalance := localCommit.LocalBalance
|
||||||
|
remoteBalance := localCommit.RemoteBalance
|
||||||
|
|
||||||
|
// As an artifact of our usage of mSAT internally, either party
|
||||||
|
// may end up in a state where they're holding a fractional
|
||||||
|
// amount of satoshis which can't be expressed within the
|
||||||
|
// actual commitment output. Since we round down when going
|
||||||
|
// from mSAT -> SAT, we may at any point be adding an
|
||||||
|
// additional SAT to miners fees. As a result, we display a
|
||||||
|
// commitment fee that accounts for this externally.
|
||||||
|
var sumOutputs btcutil.Amount
|
||||||
|
for _, txOut := range localCommit.CommitTx.TxOut {
|
||||||
|
sumOutputs += btcutil.Amount(txOut.Value)
|
||||||
|
}
|
||||||
|
externalCommitFee := dbChannel.Capacity - sumOutputs
|
||||||
|
|
||||||
|
channel := &lnrpc.Channel{
|
||||||
|
Active: isActive,
|
||||||
|
Private: !isPublic,
|
||||||
|
RemotePubkey: nodeID,
|
||||||
|
ChannelPoint: chanPoint.String(),
|
||||||
|
ChanId: chanID,
|
||||||
|
Capacity: int64(dbChannel.Capacity),
|
||||||
|
LocalBalance: int64(localBalance.ToSatoshis()),
|
||||||
|
RemoteBalance: int64(remoteBalance.ToSatoshis()),
|
||||||
|
CommitFee: int64(externalCommitFee),
|
||||||
|
CommitWeight: commitWeight,
|
||||||
|
FeePerKw: int64(localCommit.FeePerKw),
|
||||||
|
TotalSatoshisSent: int64(dbChannel.TotalMSatSent.ToSatoshis()),
|
||||||
|
TotalSatoshisReceived: int64(dbChannel.TotalMSatReceived.ToSatoshis()),
|
||||||
|
NumUpdates: localCommit.CommitHeight,
|
||||||
|
PendingHtlcs: make([]*lnrpc.HTLC, len(localCommit.Htlcs)),
|
||||||
|
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
|
||||||
|
Initiator: dbChannel.IsInitiator,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, htlc := range localCommit.Htlcs {
|
||||||
|
var rHash [32]byte
|
||||||
|
copy(rHash[:], htlc.RHash[:])
|
||||||
|
channel.PendingHtlcs[i] = &lnrpc.HTLC{
|
||||||
|
Incoming: htlc.Incoming,
|
||||||
|
Amount: int64(htlc.Amt.ToSatoshis()),
|
||||||
|
HashLock: rHash[:],
|
||||||
|
ExpirationHeight: htlc.RefundTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the Pending Htlc Amount to UnsettledBalance field.
|
||||||
|
channel.UnsettledBalance += channel.PendingHtlcs[i].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRPCClosedChannel creates an *lnrpc.ClosedChannelSummary from a
|
||||||
|
// *channeldb.ChannelCloseSummary.
|
||||||
|
func createRPCClosedChannel(
|
||||||
|
dbChannel *channeldb.ChannelCloseSummary) *lnrpc.ChannelCloseSummary {
|
||||||
|
|
||||||
|
nodePub := dbChannel.RemotePub
|
||||||
|
nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
|
||||||
|
|
||||||
|
var closeType lnrpc.ChannelCloseSummary_ClosureType
|
||||||
|
switch dbChannel.CloseType {
|
||||||
|
case channeldb.CooperativeClose:
|
||||||
|
closeType = lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE
|
||||||
|
case channeldb.LocalForceClose:
|
||||||
|
closeType = lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE
|
||||||
|
case channeldb.RemoteForceClose:
|
||||||
|
closeType = lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE
|
||||||
|
case channeldb.BreachClose:
|
||||||
|
closeType = lnrpc.ChannelCloseSummary_BREACH_CLOSE
|
||||||
|
case channeldb.FundingCanceled:
|
||||||
|
closeType = lnrpc.ChannelCloseSummary_FUNDING_CANCELED
|
||||||
|
case channeldb.Abandoned:
|
||||||
|
closeType = lnrpc.ChannelCloseSummary_ABANDONED
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lnrpc.ChannelCloseSummary{
|
||||||
|
Capacity: int64(dbChannel.Capacity),
|
||||||
|
RemotePubkey: nodeID,
|
||||||
|
CloseHeight: dbChannel.CloseHeight,
|
||||||
|
CloseType: closeType,
|
||||||
|
ChannelPoint: dbChannel.ChanPoint.String(),
|
||||||
|
ChanId: dbChannel.ShortChanID.ToUint64(),
|
||||||
|
SettledBalance: int64(dbChannel.SettledBalance),
|
||||||
|
TimeLockedBalance: int64(dbChannel.TimeLockedBalance),
|
||||||
|
ChainHash: dbChannel.ChainHash.String(),
|
||||||
|
ClosingTxHash: dbChannel.ClosingTXID.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeChannelEvents returns a uni-directional stream (server -> client)
|
||||||
|
// for notifying the client of newly active, inactive or closed channels.
|
||||||
|
func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription,
|
||||||
|
updateStream lnrpc.Lightning_SubscribeChannelEventsServer) error {
|
||||||
|
|
||||||
|
channelEventSub, err := r.server.channelNotifier.SubscribeChannelEvents()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the resources for the client is cleaned up once either
|
||||||
|
// the server, or client exits.
|
||||||
|
defer channelEventSub.Cancel()
|
||||||
|
|
||||||
|
graph := r.server.chanDB.ChannelGraph()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// A new update has been sent by the channel router, we'll
|
||||||
|
// marshal it into the form expected by the gRPC client, then
|
||||||
|
// send it off to the client(s).
|
||||||
|
case e := <-channelEventSub.Updates():
|
||||||
|
var update *lnrpc.ChannelEventUpdate
|
||||||
|
switch event := e.(type) {
|
||||||
|
case channelnotifier.OpenChannelEvent:
|
||||||
|
channel := createRPCOpenChannel(r, graph,
|
||||||
|
event.Channel, true)
|
||||||
|
update = &lnrpc.ChannelEventUpdate{
|
||||||
|
Type: lnrpc.ChannelEventUpdate_OPEN_CHANNEL,
|
||||||
|
Channel: &lnrpc.ChannelEventUpdate_OpenChannel{
|
||||||
|
OpenChannel: channel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case channelnotifier.ClosedChannelEvent:
|
||||||
|
closedChannel := createRPCClosedChannel(event.CloseSummary)
|
||||||
|
update = &lnrpc.ChannelEventUpdate{
|
||||||
|
Type: lnrpc.ChannelEventUpdate_CLOSED_CHANNEL,
|
||||||
|
Channel: &lnrpc.ChannelEventUpdate_ClosedChannel{
|
||||||
|
ClosedChannel: closedChannel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case channelnotifier.ActiveChannelEvent:
|
||||||
|
update = &lnrpc.ChannelEventUpdate{
|
||||||
|
Type: lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL,
|
||||||
|
Channel: &lnrpc.ChannelEventUpdate_ActiveChannel{
|
||||||
|
ActiveChannel: &lnrpc.ChannelPoint{
|
||||||
|
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||||
|
FundingTxidBytes: event.ChannelPoint.Hash[:],
|
||||||
|
},
|
||||||
|
OutputIndex: event.ChannelPoint.Index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case channelnotifier.InactiveChannelEvent:
|
||||||
|
update = &lnrpc.ChannelEventUpdate{
|
||||||
|
Type: lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL,
|
||||||
|
Channel: &lnrpc.ChannelEventUpdate_InactiveChannel{
|
||||||
|
InactiveChannel: &lnrpc.ChannelPoint{
|
||||||
|
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||||
|
FundingTxidBytes: event.ChannelPoint.Hash[:],
|
||||||
|
},
|
||||||
|
OutputIndex: event.ChannelPoint.Index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected channel event update: %v", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateStream.Send(update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-r.quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// savePayment saves a successfully completed payment to the database for
|
// savePayment saves a successfully completed payment to the database for
|
||||||
// historical record keeping.
|
// historical record keeping.
|
||||||
func (r *rpcServer) savePayment(route *routing.Route,
|
func (r *rpcServer) savePayment(route *routing.Route,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/autopilot"
|
"github.com/lightningnetwork/lnd/autopilot"
|
||||||
"github.com/lightningnetwork/lnd/brontide"
|
"github.com/lightningnetwork/lnd/brontide"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
"github.com/lightningnetwork/lnd/discovery"
|
"github.com/lightningnetwork/lnd/discovery"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
@@ -145,6 +146,8 @@ type server struct {
|
|||||||
|
|
||||||
invoices *invoices.InvoiceRegistry
|
invoices *invoices.InvoiceRegistry
|
||||||
|
|
||||||
|
channelNotifier *channelnotifier.ChannelNotifier
|
||||||
|
|
||||||
witnessBeacon contractcourt.WitnessBeacon
|
witnessBeacon contractcourt.WitnessBeacon
|
||||||
|
|
||||||
breachArbiter *breachArbiter
|
breachArbiter *breachArbiter
|
||||||
@@ -274,6 +277,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
|
|
||||||
invoices: invoices.NewRegistry(chanDB, activeNetParams.Params),
|
invoices: invoices.NewRegistry(chanDB, activeNetParams.Params),
|
||||||
|
|
||||||
|
channelNotifier: channelnotifier.New(chanDB),
|
||||||
|
|
||||||
identityPriv: privKey,
|
identityPriv: privKey,
|
||||||
nodeSigner: netann.NewNodeSigner(privKey),
|
nodeSigner: netann.NewNodeSigner(privKey),
|
||||||
|
|
||||||
@@ -357,6 +362,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
htlcswitch.DefaultFwdEventInterval),
|
htlcswitch.DefaultFwdEventInterval),
|
||||||
LogEventTicker: ticker.New(
|
LogEventTicker: ticker.New(
|
||||||
htlcswitch.DefaultLogInterval),
|
htlcswitch.DefaultLogInterval),
|
||||||
|
NotifyActiveChannel: s.channelNotifier.NotifyActiveChannelEvent,
|
||||||
|
NotifyInactiveChannel: s.channelNotifier.NotifyInactiveChannelEvent,
|
||||||
}, uint32(currentHeight))
|
}, uint32(currentHeight))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -735,8 +742,9 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
DisableChannel: func(op wire.OutPoint) error {
|
DisableChannel: func(op wire.OutPoint) error {
|
||||||
return s.announceChanStatus(op, true)
|
return s.announceChanStatus(op, true)
|
||||||
},
|
},
|
||||||
Sweeper: s.sweeper,
|
Sweeper: s.sweeper,
|
||||||
SettleInvoice: s.invoices.SettleInvoice,
|
SettleInvoice: s.invoices.SettleInvoice,
|
||||||
|
NotifyClosedChannel: s.channelNotifier.NotifyClosedChannelEvent,
|
||||||
}, chanDB)
|
}, chanDB)
|
||||||
|
|
||||||
s.breachArbiter = newBreachArbiter(&BreachConfig{
|
s.breachArbiter = newBreachArbiter(&BreachConfig{
|
||||||
@@ -922,9 +930,10 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
// channel bandwidth.
|
// channel bandwidth.
|
||||||
return uint16(input.MaxHTLCNumber / 2)
|
return uint16(input.MaxHTLCNumber / 2)
|
||||||
},
|
},
|
||||||
ZombieSweeperInterval: 1 * time.Minute,
|
ZombieSweeperInterval: 1 * time.Minute,
|
||||||
ReservationTimeout: 10 * time.Minute,
|
ReservationTimeout: 10 * time.Minute,
|
||||||
MinChanSize: btcutil.Amount(cfg.MinChanSize),
|
MinChanSize: btcutil.Amount(cfg.MinChanSize),
|
||||||
|
NotifyOpenChannelEvent: s.channelNotifier.NotifyOpenChannelEvent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -986,6 +995,9 @@ func (s *server) Start() error {
|
|||||||
if err := s.cc.chainNotifier.Start(); err != nil {
|
if err := s.cc.chainNotifier.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.channelNotifier.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.sphinx.Start(); err != nil {
|
if err := s.sphinx.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1083,6 +1095,7 @@ func (s *server) Stop() error {
|
|||||||
s.authGossiper.Stop()
|
s.authGossiper.Stop()
|
||||||
s.chainArb.Stop()
|
s.chainArb.Stop()
|
||||||
s.sweeper.Stop()
|
s.sweeper.Stop()
|
||||||
|
s.channelNotifier.Stop()
|
||||||
s.cc.wallet.Shutdown()
|
s.cc.wallet.Shutdown()
|
||||||
s.cc.chainView.Stop()
|
s.cc.chainView.Stop()
|
||||||
s.connMgr.Stop()
|
s.connMgr.Stop()
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package subscribe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrServerShuttingDown is an error returned in case the server is in the
|
||||||
|
// process of shutting down.
|
||||||
|
var ErrServerShuttingDown = errors.New("subscription server shutting down")
|
||||||
|
|
||||||
|
// Client is used to get notified about updates the caller has subscribed to,
|
||||||
|
type Client struct {
|
||||||
|
// Cancel should be called in case the client no longer wants to
|
||||||
|
// subscribe for updates from the server.
|
||||||
|
Cancel func()
|
||||||
|
|
||||||
|
updates *queue.ConcurrentQueue
|
||||||
|
quit chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates returns a read-only channel where the updates the client has
|
||||||
|
// subscribed to will be delivered.
|
||||||
|
func (c *Client) Updates() <-chan interface{} {
|
||||||
|
return c.updates.ChanOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quit is a channel that will be closed in case the server decides to no
|
||||||
|
// longer deliver updates to this client.
|
||||||
|
func (c *Client) Quit() <-chan struct{} {
|
||||||
|
return c.quit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is a struct that manages a set of subscriptions and their
|
||||||
|
// corresponding clients. Any update will be delivered to all active clients.
|
||||||
|
type Server struct {
|
||||||
|
clientCounter uint64 // To be used atomically.
|
||||||
|
|
||||||
|
started uint32 // To be used atomically.
|
||||||
|
stopped uint32 // To be used atomically.
|
||||||
|
|
||||||
|
clients map[uint64]*Client
|
||||||
|
clientUpdates chan *clientUpdate
|
||||||
|
|
||||||
|
updates chan interface{}
|
||||||
|
|
||||||
|
quit chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientUpdate is an internal message sent to the subscriptionHandler to
|
||||||
|
// either register a new client for subscription or cancel an existing
|
||||||
|
// subscription.
|
||||||
|
type clientUpdate struct {
|
||||||
|
// cancel indicates if the update to the client is cancelling an
|
||||||
|
// existing client's subscription. If not then this update will be to
|
||||||
|
// subscribe a new client.
|
||||||
|
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
|
||||||
|
|
||||||
|
// client is the new client that will receive updates. Will be nil in
|
||||||
|
// case this is a cancallation update.
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer returns a new Server.
|
||||||
|
func NewServer() *Server {
|
||||||
|
return &Server{
|
||||||
|
clients: make(map[uint64]*Client),
|
||||||
|
clientUpdates: make(chan *clientUpdate),
|
||||||
|
updates: make(chan interface{}),
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the Server, making it ready to accept subscriptions and
|
||||||
|
// updates.
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
if !atomic.CompareAndSwapUint32(&s.started, 0, 1) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wg.Add(1)
|
||||||
|
go s.subscriptionHandler()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the server.
|
||||||
|
func (s *Server) Stop() error {
|
||||||
|
if !atomic.CompareAndSwapUint32(&s.stopped, 0, 1) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
close(s.quit)
|
||||||
|
s.wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe returns a Client that will receive updates any time the Server is
|
||||||
|
// made aware of a new event.
|
||||||
|
func (s *Server) Subscribe() (*Client, error) {
|
||||||
|
// We'll first atomically obtain the next ID for this client from the
|
||||||
|
// incrementing client ID counter.
|
||||||
|
clientID := atomic.AddUint64(&s.clientCounter, 1)
|
||||||
|
|
||||||
|
// Create the client that will be returned. The Cancel method is
|
||||||
|
// populated to send the cancellation intent to the
|
||||||
|
// subscriptionHandler.
|
||||||
|
client := &Client{
|
||||||
|
updates: queue.NewConcurrentQueue(20),
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
Cancel: func() {
|
||||||
|
select {
|
||||||
|
case s.clientUpdates <- &clientUpdate{
|
||||||
|
cancel: true,
|
||||||
|
clientID: clientID,
|
||||||
|
}:
|
||||||
|
case <-s.quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case s.clientUpdates <- &clientUpdate{
|
||||||
|
cancel: false,
|
||||||
|
clientID: clientID,
|
||||||
|
client: client,
|
||||||
|
}:
|
||||||
|
case <-s.quit:
|
||||||
|
return nil, ErrServerShuttingDown
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUpdate is called to send the passed update to all currently active
|
||||||
|
// subscription clients.
|
||||||
|
func (s *Server) SendUpdate(update interface{}) error {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case s.updates <- update:
|
||||||
|
return nil
|
||||||
|
case <-s.quit:
|
||||||
|
return ErrServerShuttingDown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscriptionHandler is the main handler for the Server. It will handle
|
||||||
|
// incoming updates and subscriptions, and forward the incoming updates to the
|
||||||
|
// registered clients.
|
||||||
|
//
|
||||||
|
// NOTE: MUST be run as a goroutine.
|
||||||
|
func (s *Server) subscriptionHandler() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
// If a client update is received, the either a new
|
||||||
|
// subscription becomes active, or we cancel and existing one.
|
||||||
|
case update := <-s.clientUpdates:
|
||||||
|
clientID := update.clientID
|
||||||
|
|
||||||
|
// In case this is a cancellation, stop the client's
|
||||||
|
// underlying queue, and remove the client from the set
|
||||||
|
// of active subscription clients.
|
||||||
|
if update.cancel {
|
||||||
|
client, ok := s.clients[update.clientID]
|
||||||
|
if ok {
|
||||||
|
client.updates.Stop()
|
||||||
|
close(client.quit)
|
||||||
|
delete(s.clients, clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this was not a cancellation, start the underlying
|
||||||
|
// queue and add the client to our set of subscription
|
||||||
|
// clients. It will be notified about any new updates
|
||||||
|
// the server receives.
|
||||||
|
update.client.updates.Start()
|
||||||
|
s.clients[update.clientID] = update.client
|
||||||
|
|
||||||
|
// A new update was received, forward it to all active clients.
|
||||||
|
case upd := <-s.updates:
|
||||||
|
for _, client := range s.clients {
|
||||||
|
select {
|
||||||
|
case client.updates.ChanIn() <- upd:
|
||||||
|
case <-client.quit:
|
||||||
|
case <-s.quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case the server is shutting down, stop the clients and
|
||||||
|
// close the quit channels to notify them.
|
||||||
|
case <-s.quit:
|
||||||
|
for _, client := range s.clients {
|
||||||
|
client.updates.Stop()
|
||||||
|
close(client.quit)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package subscribe_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/subscribe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSubscribe tests that the subscription clients receive the updates sent
|
||||||
|
// to them after they subscribe, and that cancelled clients don't get more
|
||||||
|
// updates.
|
||||||
|
func TestSubscribe(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
server := subscribe.NewServer()
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
t.Fatalf("unable to start server")
|
||||||
|
}
|
||||||
|
|
||||||
|
const numClients = 300
|
||||||
|
const numUpdates = 1000
|
||||||
|
|
||||||
|
var clients [numClients]*subscribe.Client
|
||||||
|
|
||||||
|
// Start by registering two thirds the clients.
|
||||||
|
for i := 0; i < numClients*2/3; i++ {
|
||||||
|
c, err := server.Subscribe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to subscribe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[i] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send half the updates.
|
||||||
|
for i := 0; i < numUpdates/2; i++ {
|
||||||
|
if err := server.SendUpdate(i); err != nil {
|
||||||
|
t.Fatalf("unable to send update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the rest of the clients.
|
||||||
|
for i := numClients * 2 / 3; i < numClients; i++ {
|
||||||
|
c, err := server.Subscribe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to subscribe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[i] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel one third of the clients.
|
||||||
|
for i := 0; i < numClients/3; i++ {
|
||||||
|
clients[i].Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the rest of the updates.
|
||||||
|
for i := numUpdates / 2; i < numUpdates; i++ {
|
||||||
|
if err := server.SendUpdate(i); err != nil {
|
||||||
|
t.Fatalf("unable to send update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now ensure the clients got the updates we expect.
|
||||||
|
for i, c := range clients {
|
||||||
|
|
||||||
|
var from, to int
|
||||||
|
switch {
|
||||||
|
|
||||||
|
// We expect the first third of the clients to quit, since they
|
||||||
|
// were cancelled.
|
||||||
|
case i < numClients/3:
|
||||||
|
select {
|
||||||
|
case <-c.Quit():
|
||||||
|
continue
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatalf("cancelled client %v did not quit", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next third should receive all updates.
|
||||||
|
case i < numClients*2/3:
|
||||||
|
from = 0
|
||||||
|
to = numUpdates
|
||||||
|
|
||||||
|
// And finally the last third should receive the last half of
|
||||||
|
// the updates.
|
||||||
|
default:
|
||||||
|
from = numUpdates / 2
|
||||||
|
to = numUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
for cnt := from; cnt < to; cnt++ {
|
||||||
|
select {
|
||||||
|
case upd := <-c.Updates():
|
||||||
|
j := upd.(int)
|
||||||
|
if j != cnt {
|
||||||
|
t.Fatalf("expected %v, got %v, for "+
|
||||||
|
"client %v", cnt, j, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatalf("did not receive expected update %v "+
|
||||||
|
"for client %v", cnt, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user