From e9baf0e4a22706f1ec33afc3163a4f1880af4f3e Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Mon, 15 Feb 2021 21:13:50 -0800 Subject: [PATCH 01/14] multi: add bool param to channel enable/disable methods Add a new boolean parameter without changing any existing functionality. The parameter will be used to indicate whether a request to update a channel's status was made manually by a user (currently always false). --- netann/chan_status_manager.go | 28 +++++++++++++++++++--------- netann/chan_status_manager_test.go | 4 ++-- peer/brontide.go | 10 +++++++--- server.go | 4 +++- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/netann/chan_status_manager.go b/netann/chan_status_manager.go index 612b8a7f4..93a4e98a0 100644 --- a/netann/chan_status_manager.go +++ b/netann/chan_status_manager.go @@ -223,16 +223,20 @@ func (m *ChanStatusManager) Stop() error { // channel's peer has lasted at least the ChanEnableTimeout. Failure to do so // may result in behavior that deviates from the expected behavior of the state // machine. -func (m *ChanStatusManager) RequestEnable(outpoint wire.OutPoint) error { - return m.submitRequest(m.enableRequests, outpoint) +func (m *ChanStatusManager) RequestEnable(outpoint wire.OutPoint, + manual bool) error { + + return m.submitRequest(m.enableRequests, outpoint, manual) } // RequestDisable submits a request to immediately disable a channel identified // by the provided outpoint. If the channel is already disabled, no action will // be taken. Otherwise, a new announcement will be signed with the disabled bit // set and broadcast to the network. -func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint) error { - return m.submitRequest(m.disableRequests, outpoint) +func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint, + manual bool) error { + + return m.submitRequest(m.disableRequests, outpoint, manual) } // statusRequest is passed to the statusManager to request a change in status @@ -240,6 +244,7 @@ func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint) error { // request through one of the enableRequests or disableRequests channels. type statusRequest struct { outpoint wire.OutPoint + manual bool errChan chan error } @@ -248,10 +253,11 @@ type statusRequest struct { // reqChan passed in, which can be either of the enableRequests or // disableRequests channels. func (m *ChanStatusManager) submitRequest(reqChan chan statusRequest, - outpoint wire.OutPoint) error { + outpoint wire.OutPoint, manual bool) error { req := statusRequest{ outpoint: outpoint, + manual: manual, errChan: make(chan error, 1), } @@ -285,11 +291,11 @@ func (m *ChanStatusManager) statusManager() { // Process any requests to mark channel as enabled. case req := <-m.enableRequests: - req.errChan <- m.processEnableRequest(req.outpoint) + req.errChan <- m.processEnableRequest(req.outpoint, req.manual) // Process any requests to mark channel as disabled. case req := <-m.disableRequests: - req.errChan <- m.processDisableRequest(req.outpoint) + req.errChan <- m.processDisableRequest(req.outpoint, req.manual) // Use long-polling to detect when channels become inactive. case <-m.statusSampleTicker.C: @@ -317,7 +323,9 @@ func (m *ChanStatusManager) statusManager() { // ErrEnableInactiveChan will be returned. An update will be broadcast only if // the channel is currently disabled, otherwise no update will be sent on the // network. -func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint) error { +func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint, + manual bool) error { + curState, err := m.getOrInitChanStatus(outpoint) if err != nil { return err @@ -362,7 +370,9 @@ func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint) error { // ChanStatusDisabled. An update will only be sent if the channel has a status // other than ChanStatusEnabled, otherwise no update will be sent on the // network. -func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint) error { +func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint, + manual bool) error { + curState, err := m.getOrInitChanStatus(outpoint) if err != nil { return err diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 2d1554432..096afd002 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -428,7 +428,7 @@ func (h *testHarness) assertDisables(channels []*channeldb.OpenChannel, expErr e func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error) { h.t.Helper() - err := h.mgr.RequestEnable(outpoint) + err := h.mgr.RequestEnable(outpoint, false) if err != expErr { h.t.Fatalf("expected enable error: %v, got %v", expErr, err) } @@ -439,7 +439,7 @@ func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error) { func (h *testHarness) assertDisable(outpoint wire.OutPoint, expErr error) { h.t.Helper() - err := h.mgr.RequestDisable(outpoint) + err := h.mgr.RequestDisable(outpoint, false) if err != expErr { h.t.Fatalf("expected disable error: %v, got %v", expErr, err) } diff --git a/peer/brontide.go b/peer/brontide.go index fda508112..3377a754f 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -2285,7 +2285,7 @@ func (p *Brontide) reenableActiveChannels() { // disabled bit to false and send out a new ChannelUpdate. If this // channel is already active, the update won't be sent. for _, chanPoint := range activePublicChans { - err := p.cfg.ChanStatusMgr.RequestEnable(chanPoint) + err := p.cfg.ChanStatusMgr.RequestEnable(chanPoint, false) if err != nil { peerLog.Errorf("Unable to enable channel %v: %v", chanPoint, err) @@ -2360,7 +2360,9 @@ func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) ( Channel: channel, UnregisterChannel: p.cfg.Switch.RemoveLink, BroadcastTx: p.cfg.Wallet.PublishTransaction, - DisableChannel: p.cfg.ChanStatusMgr.RequestDisable, + DisableChannel: func(chanPoint wire.OutPoint) error { + return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false) + }, Disconnect: func() error { return p.cfg.DisconnectPeer(p.IdentityKey()) }, @@ -2476,7 +2478,9 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { Channel: channel, UnregisterChannel: p.cfg.Switch.RemoveLink, BroadcastTx: p.cfg.Wallet.PublishTransaction, - DisableChannel: p.cfg.ChanStatusMgr.RequestDisable, + DisableChannel: func(chanPoint wire.OutPoint) error { + return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false) + }, Disconnect: func() error { return p.cfg.DisconnectPeer(p.IdentityKey()) }, diff --git a/server.go b/server.go index ead9014aa..ac8ccc3e0 100644 --- a/server.go +++ b/server.go @@ -965,7 +965,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return ErrServerShuttingDown } }, - DisableChannel: s.chanStatusMgr.RequestDisable, + DisableChannel: func(chanPoint wire.OutPoint) error { + return s.chanStatusMgr.RequestDisable(chanPoint, false) + }, Sweeper: s.sweeper, Registry: s.invoices, NotifyClosedChannel: s.channelNotifier.NotifyClosedChannelEvent, From c40d291488e2180c3234a856aa0b7d1f93923ea3 Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Mon, 15 Feb 2021 21:56:45 -0800 Subject: [PATCH 02/14] netann: add ChanStatusManuallyDisabled state Add new value for ChanStatus without changing any existing functionality. The new value indicates that a user manually requested disabling a channel. --- netann/channel_state.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/netann/channel_state.go b/netann/channel_state.go index 7f095e60e..123a586b8 100644 --- a/netann/channel_state.go +++ b/netann/channel_state.go @@ -26,6 +26,18 @@ const ( // ChanStatusDisabled indicates that the channel's last announcement has // the disabled bit set. ChanStatusDisabled + + // ChanStatusManuallyDisabled indicates that the channel's last + // announcement had the disabled bit set, and that a user manually + // requested disabling the channel. Channels in this state will ignore + // automatic / background attempts to re-enable the channel. + // + // Note that there's no corresponding ChanStatusManuallyEnabled state + // because even if a user manually requests enabling a channel, we still + // DO want to allow automatic / background processes to disable it. + // Otherwise, the network might be cluttered with channels that are + // advertised as enabled, but don't actually work or even exist. + ChanStatusManuallyDisabled ) // ChannelState describes the ChanStatusManager's view of a channel, and @@ -63,6 +75,14 @@ func (s *channelStates) markDisabled(outpoint wire.OutPoint) { } } +// markManuallyDisabled creates a channelState using +// ChanStatusManuallyDisabled. +func (s *channelStates) markManuallyDisabled(outpoint wire.OutPoint) { + (*s)[outpoint] = ChannelState{ + Status: ChanStatusManuallyDisabled, + } +} + // markPendingDisabled creates a channelState using ChanStatusPendingDisabled // and sets the ChannelState's SendDisableTime to sendDisableTime. func (s *channelStates) markPendingDisabled(outpoint wire.OutPoint, From 542c89ad5d6892e9f67b44b14ee6561e3336d97a Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 00:29:29 -0800 Subject: [PATCH 03/14] netann: update channel status transitions to handle manual requests --- netann/chan_status_manager.go | 89 +++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/netann/chan_status_manager.go b/netann/chan_status_manager.go index 93a4e98a0..1f67feb90 100644 --- a/netann/chan_status_manager.go +++ b/netann/chan_status_manager.go @@ -29,6 +29,12 @@ var ( // the time of the request. ErrEnableInactiveChan = errors.New("unable to enable channel which " + "is not currently active") + + // ErrEnableManuallyDisabledChan signals that an automatic / background + // request to enable a channel could not be completed because the channel + // was manually disabled. + ErrEnableManuallyDisabledChan = errors.New("unable to enable channel " + + "which was manually disabled") ) // ChanStatusConfig holds parameters and resources required by the @@ -219,6 +225,9 @@ func (m *ChanStatusManager) Stop() error { // channel is found to be disabled, a new announcement will be signed with the // disabled bit cleared and broadcast to the network. // +// If the channel was manually disabled and RequestEnable is called with +// manual = false, then the request will be ignored. +// // NOTE: RequestEnable should only be called after a stable connection with the // channel's peer has lasted at least the ChanEnableTimeout. Failure to do so // may result in behavior that deviates from the expected behavior of the state @@ -233,6 +242,21 @@ func (m *ChanStatusManager) RequestEnable(outpoint wire.OutPoint, // by the provided outpoint. If the channel is already disabled, no action will // be taken. Otherwise, a new announcement will be signed with the disabled bit // set and broadcast to the network. +// +// The channel state will be changed to either ChanStatusDisabled or +// ChanStatusManuallyDisabled, depending on the passed-in value of manual. In +// particular, note the following state transitions: +// +// current state | manual | new state +// --------------------------------------------------- +// Disabled | false | Disabled +// ManuallyDisabled | false | ManuallyDisabled (*) +// Disabled | true | ManuallyDisabled +// ManuallyDisabled | true | ManuallyDisabled +// +// (*) If a channel was manually disabled, subsequent automatic / background +// requests to disable the channel do not change the fact that the channel +// was manually disabled. func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint, manual bool) error { @@ -317,12 +341,18 @@ func (m *ChanStatusManager) statusManager() { } } -// processEnableRequest attempts to enable the given outpoint. If the method -// returns nil, the status of the channel in chanStates will be -// ChanStatusEnabled. If the channel is not active at the time of the request, -// ErrEnableInactiveChan will be returned. An update will be broadcast only if -// the channel is currently disabled, otherwise no update will be sent on the -// network. +// processEnableRequest attempts to enable the given outpoint. +// +// * If the channel is not active at the time of the request, +// ErrEnableInactiveChan will be returned. +// * If the channel was in the ManuallyDisabled state and manual = false, +// the request will be ignored and ErrEnableManuallyDisabledChan will be +// returned. +// * Otherwise, the status of the channel in chanStates will be +// ChanStatusEnabled and the method will return nil. +// +// An update will be broadcast only if the channel is currently disabled, +// otherwise no update will be sent on the network. func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint, manual bool) error { @@ -351,6 +381,12 @@ func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint, "disable", outpoint) // We'll sign a new update if the channel is still disabled. + case ChanStatusManuallyDisabled: + if !manual { + return ErrEnableManuallyDisabledChan + } + fallthrough + case ChanStatusDisabled: log.Infof("Announcing channel(%v) enabled", outpoint) @@ -366,10 +402,12 @@ func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint, } // processDisableRequest attempts to disable the given outpoint. If the method -// returns nil, the status of the channel in chanStates will be -// ChanStatusDisabled. An update will only be sent if the channel has a status -// other than ChanStatusEnabled, otherwise no update will be sent on the -// network. +// returns nil, the status of the channel in chanStates will be either +// ChanStatusDisabled or ChanStatusManuallyDisabled, depending on the +// passed-in value of manual. +// +// An update will only be sent if the channel has a status other than +// ChanStatusEnabled, otherwise no update will be sent on the network. func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint, manual bool) error { @@ -378,15 +416,8 @@ func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint, return err } - switch curState.Status { - - // Channel is already disabled, nothing to do. - case ChanStatusDisabled: - return nil - - // We'll sign a new update disabling the channel if the current status - // is enabled or pending-inactive. - case ChanStatusEnabled, ChanStatusPendingDisabled: + status := curState.Status + if status == ChanStatusEnabled || status == ChanStatusPendingDisabled { log.Infof("Announcing channel(%v) disabled [requested]", outpoint) @@ -396,13 +427,19 @@ func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint, } } - // If the disable was requested via the manager's public interface, we - // will remove the output from our map of channel states. Typically this - // signals that the channel is being closed, so this frees up the space - // in the map. If for some reason the channel isn't closed, the state - // will be repopulated on subsequent calls to RequestEnable or - // RequestDisable via a db lookup, or on startup. - delete(m.chanStates, outpoint) + // Typically, a request to disable a channel via the manager's public + // interface signals that the channel is being closed. + // + // If we don't need to keep track of a manual request to disable the + // channel, then we can remove the outpoint to free up space in the map + // of channel states. If for some reason the channel isn't closed, the + // state will be repopulated on subsequent calls to the manager's public + // interface via a db lookup, or on startup. + if manual { + m.chanStates.markManuallyDisabled(outpoint) + } else if status != ChanStatusManuallyDisabled { + delete(m.chanStates, outpoint) + } return nil } From dbabc2e696921b8deb8d0ada59044ac0bcf34b12 Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 00:56:37 -0800 Subject: [PATCH 04/14] netann: add bool param to chan_status_manager_test Add boolean parameter for test functions without changing any existing functionality. All current tests set manual = false, but Future tests can set manual = true to test manual requests to update channel state. --- netann/chan_status_manager_test.go | 48 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 096afd002..9f19bb225 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -405,30 +405,36 @@ func (h *testHarness) markInactive(channels []*channeldb.OpenChannel) { // assertEnables requests enables for all of the passed channels, and asserts // that the errors returned from RequestEnable matches expErr. -func (h *testHarness) assertEnables(channels []*channeldb.OpenChannel, expErr error) { +func (h *testHarness) assertEnables(channels []*channeldb.OpenChannel, expErr error, + manual bool) { + h.t.Helper() for _, channel := range channels { - h.assertEnable(channel.FundingOutpoint, expErr) + h.assertEnable(channel.FundingOutpoint, expErr, manual) } } // assertDisables requests disables for all of the passed channels, and asserts // that the errors returned from RequestDisable matches expErr. -func (h *testHarness) assertDisables(channels []*channeldb.OpenChannel, expErr error) { +func (h *testHarness) assertDisables(channels []*channeldb.OpenChannel, expErr error, + manual bool) { + h.t.Helper() for _, channel := range channels { - h.assertDisable(channel.FundingOutpoint, expErr) + h.assertDisable(channel.FundingOutpoint, expErr, manual) } } // assertEnable requests an enable for the given outpoint, and asserts that the // returned error matches expErr. -func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error) { +func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error, + manual bool) { + h.t.Helper() - err := h.mgr.RequestEnable(outpoint, false) + err := h.mgr.RequestEnable(outpoint, manual) if err != expErr { h.t.Fatalf("expected enable error: %v, got %v", expErr, err) } @@ -436,10 +442,12 @@ func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error) { // assertDisable requests a disable for the given outpoint, and asserts that the // returned error matches expErr. -func (h *testHarness) assertDisable(outpoint wire.OutPoint, expErr error) { +func (h *testHarness) assertDisable(outpoint wire.OutPoint, expErr error, + manual bool) { + h.t.Helper() - err := h.mgr.RequestDisable(outpoint, false) + err := h.mgr.RequestDisable(outpoint, manual) if err != expErr { h.t.Fatalf("expected disable error: %v, got %v", expErr, err) } @@ -548,7 +556,7 @@ var stateMachineTests = []stateMachineTest{ startEnabled: false, fn: func(h testHarness) { // Request enables for all channels. - h.assertEnables(h.graph.chans(), nil) + h.assertEnables(h.graph.chans(), nil, false) // Expect to see them all enabled on the network. h.assertUpdates( h.graph.chans(), true, h.safeDisableTimeout, @@ -561,7 +569,7 @@ var stateMachineTests = []stateMachineTest{ startEnabled: true, fn: func(h testHarness) { // Request disables for all channels. - h.assertDisables(h.graph.chans(), nil) + h.assertDisables(h.graph.chans(), nil, false) // Expect to see them all disabled on the network. h.assertUpdates( h.graph.chans(), false, h.safeDisableTimeout, @@ -574,7 +582,7 @@ var stateMachineTests = []stateMachineTest{ startEnabled: true, fn: func(h testHarness) { // Request enables for already enabled channels. - h.assertEnables(h.graph.chans(), nil) + h.assertEnables(h.graph.chans(), nil, false) // Manager shouldn't send out any updates. h.assertNoUpdates(h.safeDisableTimeout) }, @@ -585,7 +593,7 @@ var stateMachineTests = []stateMachineTest{ startEnabled: false, fn: func(h testHarness) { // Request disables for already enabled channels. - h.assertDisables(h.graph.chans(), nil) + h.assertDisables(h.graph.chans(), nil, false) // Manager shouldn't sent out any updates. h.assertNoUpdates(h.safeDisableTimeout) }, @@ -616,7 +624,7 @@ var stateMachineTests = []stateMachineTest{ // Simulate reconnect by making channels active. h.markActive(h.graph.chans()) // Request that all channels be reenabled. - h.assertEnables(h.graph.chans(), nil) + h.assertEnables(h.graph.chans(), nil, false) // Pending disable should have been canceled, and // no updates sent. Channels remain enabled on the // network. @@ -642,7 +650,7 @@ var stateMachineTests = []stateMachineTest{ // Request enable of inactive channels, expect error // indicating that channel was not active. h.assertEnables( - h.graph.chans(), netann.ErrEnableInactiveChan, + h.graph.chans(), netann.ErrEnableInactiveChan, false, ) // No updates should be sent as a result of the failure. h.assertNoUpdates(h.safeDisableTimeout) @@ -662,7 +670,7 @@ var stateMachineTests = []stateMachineTest{ // Request that they be enabled, which should return an // error as the graph doesn't have an edge for them. h.assertEnables( - unknownChans, channeldb.ErrEdgeNotFound, + unknownChans, channeldb.ErrEdgeNotFound, false, ) // No updates should be sent as a result of the failure. h.assertNoUpdates(h.safeDisableTimeout) @@ -682,7 +690,7 @@ var stateMachineTests = []stateMachineTest{ // Request that they be disabled, which should return an // error as the graph doesn't have an edge for them. h.assertDisables( - unknownChans, channeldb.ErrEdgeNotFound, + unknownChans, channeldb.ErrEdgeNotFound, false, ) // No updates should be sent as a result of the failure. h.assertNoUpdates(h.safeDisableTimeout) @@ -712,7 +720,7 @@ var stateMachineTests = []stateMachineTest{ // Check that trying to enable the channel with unknown // edges results in a failure. - h.assertEnables(newChans, channeldb.ErrEdgeNotFound) + h.assertEnables(newChans, channeldb.ErrEdgeNotFound, false) // Now, insert edge policies for the channel into the // graph, starting with the channel enabled, and mark @@ -731,7 +739,7 @@ var stateMachineTests = []stateMachineTest{ // Finally, assert that enabling the channel doesn't // return an error now that everything is in place. - h.assertEnables(newChans, nil) + h.assertEnables(newChans, nil, false) }, }, { @@ -759,7 +767,7 @@ var stateMachineTests = []stateMachineTest{ // Check that trying to enable the channel with unknown // edges results in a failure. - h.assertDisables(rmChans, channeldb.ErrEdgeNotFound) + h.assertDisables(rmChans, channeldb.ErrEdgeNotFound, false) }, }, { @@ -777,7 +785,7 @@ var stateMachineTests = []stateMachineTest{ // Check that trying to enable the channel with unknown // edges results in a failure. - h.assertDisables(rmChans, nil) + h.assertDisables(rmChans, nil, false) // Since the channels are still in the graph, we expect // these channels to be disabled on the network. From e1709f20ebc1379a42fe554ee25fde8e1780a114 Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 01:03:04 -0800 Subject: [PATCH 05/14] netann: add tests for manually enabling/disabling channels --- netann/chan_status_manager_test.go | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 9f19bb225..e9ff61d62 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -799,6 +799,57 @@ var stateMachineTests = []stateMachineTest{ h.assertNoUpdates(h.safeDisableTimeout) }, }, + { + name: "request manual enable", + startActive: true, + startEnabled: false, + fn: func(h testHarness) { + // Request manual enables for all channels. + h.assertEnables(h.graph.chans(), nil, true) + + // Expect to see them all enabled on the network. + h.assertUpdates( + h.graph.chans(), true, h.safeDisableTimeout, + ) + + // Subsequent request disables with manual = false should succeed. + h.assertDisables( + h.graph.chans(), nil, false, + ) + + // Expect to see them all disabled on the network again. + h.assertUpdates( + h.graph.chans(), false, h.safeDisableTimeout, + ) + }, + }, + { + name: "request manual disable", + startActive: true, + startEnabled: true, + fn: func(h testHarness) { + // Request manual disables for all channels. + h.assertDisables(h.graph.chans(), nil, true) + + // Expect to see them all disabled on the network. + h.assertUpdates( + h.graph.chans(), false, h.safeDisableTimeout, + ) + + // Request enables with manual = false should fail. + h.assertEnables( + h.graph.chans(), netann.ErrEnableManuallyDisabledChan, false, + ) + + // Request enables with manual = true should succeed. + h.assertEnables(h.graph.chans(), nil, true) + + // Expect to see them all enabled on the network again. + h.assertUpdates( + h.graph.chans(), true, h.safeDisableTimeout, + ) + }, + }, } // TestChanStatusManagerStateMachine tests the possible state transitions that From 59bd617c976576df28a984ea06b0008f1010e17b Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 09:42:37 -0800 Subject: [PATCH 06/14] netann: add RequestAuto for restoring auto chan state management If a channel was manually disabled, subsequent automatic / background requests to update that channel's state will be ignored. A RequestAuto call restores automatic / background state management and indicates that such requests should no longer be ignored. --- netann/chan_status_manager.go | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/netann/chan_status_manager.go b/netann/chan_status_manager.go index 1f67feb90..b0188625b 100644 --- a/netann/chan_status_manager.go +++ b/netann/chan_status_manager.go @@ -111,6 +111,10 @@ type ChanStatusManager struct { // primary event loop. disableRequests chan statusRequest + // autoRequests pipes external requests to restore automatic channel + // state management into the primary event loop. + autoRequests chan statusRequest + // statusSampleTicker fires at the interval prescribed by // ChanStatusSampleInterval to check if channels in chanStates have // become inactive. @@ -154,6 +158,7 @@ func NewChanStatusManager(cfg *ChanStatusConfig) (*ChanStatusManager, error) { statusSampleTicker: time.NewTicker(cfg.ChanStatusSampleInterval), enableRequests: make(chan statusRequest), disableRequests: make(chan statusRequest), + autoRequests: make(chan statusRequest), quit: make(chan struct{}), }, nil } @@ -263,6 +268,13 @@ func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint, return m.submitRequest(m.disableRequests, outpoint, manual) } +// RequestAuto submits a request to restore automatic channel state management. +// If the channel is in the state ChanStatusManuallyDisabled, it will be moved +// back to the state ChanStatusDisabled. Otherwise, no action will be taken. +func (m *ChanStatusManager) RequestAuto(outpoint wire.OutPoint) error { + return m.submitRequest(m.autoRequests, outpoint, true) +} + // statusRequest is passed to the statusManager to request a change in status // for a particular channel point. The exact action is governed by passing the // request through one of the enableRequests or disableRequests channels. @@ -321,6 +333,10 @@ func (m *ChanStatusManager) statusManager() { case req := <-m.disableRequests: req.errChan <- m.processDisableRequest(req.outpoint, req.manual) + // Process any requests to restore automatic channel state management. + case req := <-m.autoRequests: + req.errChan <- m.processAutoRequest(req.outpoint) + // Use long-polling to detect when channels become inactive. case <-m.statusSampleTicker.C: // First, do a sweep and mark any ChanStatusEnabled @@ -444,6 +460,27 @@ func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint, return nil } +// processAutoRequest attempts to restore automatic channel state management +// for the given outpoint. If the method returns nil, the state of the channel +// will no longer be ChanStatusManuallyDisabled (currently the only state in +// which automatic / background requests are ignored). +// +// No update will be sent on the network. +func (m *ChanStatusManager) processAutoRequest(outpoint wire.OutPoint) error { + curState, err := m.getOrInitChanStatus(outpoint) + if err != nil { + return err + } + + if curState.Status == ChanStatusManuallyDisabled { + log.Debugf("Restoring automatic control for manually disabled "+ + "channel(%v)", outpoint) + + m.chanStates.markDisabled(outpoint) + } + return nil +} + // markPendingInactiveChannels performs a sweep of the database's active // channels and determines which, if any, should have a disable announcement // scheduled. Once an active channel is determined to be pending-inactive, one From a259317d72a528d4fd0671b9c1fef29374732f2b Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 09:55:22 -0800 Subject: [PATCH 07/14] netann: add test for RequestAuto --- netann/chan_status_manager_test.go | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index e9ff61d62..0b79597dc 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -427,6 +427,18 @@ func (h *testHarness) assertDisables(channels []*channeldb.OpenChannel, expErr e } } +// assertAutos requests auto state management for all of the passed channels, and +// asserts that the errors returned from RequestAuto matches expErr. +func (h *testHarness) assertAutos(channels []*channeldb.OpenChannel, + expErr error) { + + h.t.Helper() + + for _, channel := range channels { + h.assertAuto(channel.FundingOutpoint, expErr) + } +} + // assertEnable requests an enable for the given outpoint, and asserts that the // returned error matches expErr. func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error, @@ -453,6 +465,17 @@ func (h *testHarness) assertDisable(outpoint wire.OutPoint, expErr error, } } +// assertAuto requests auto state management for the given outpoint, and asserts +// that the returned error matches expErr. +func (h *testHarness) assertAuto(outpoint wire.OutPoint, expErr error) { + h.t.Helper() + + err := h.mgr.RequestAuto(outpoint) + if err != expErr { + h.t.Fatalf("expected error: %v, got %v", expErr, err) + } +} + // assertNoUpdates waits for the specified duration, and asserts that no updates // are announced on the network. func (h *testHarness) assertNoUpdates(duration time.Duration) { @@ -844,6 +867,35 @@ var stateMachineTests = []stateMachineTest{ // Request enables with manual = true should succeed. h.assertEnables(h.graph.chans(), nil, true) + // Expect to see them all enabled on the network again. + h.assertUpdates( + h.graph.chans(), true, h.safeDisableTimeout, + ) + }, + }, + { + name: "restore auto", + startActive: true, + startEnabled: true, + fn: func(h testHarness) { + // Request manual disables for all channels. + h.assertDisables(h.graph.chans(), nil, true) + + // Expect to see them all disabled on the network. + h.assertUpdates( + h.graph.chans(), false, h.safeDisableTimeout, + ) + + // Request enables with manual = false should fail. + h.assertEnables( + h.graph.chans(), netann.ErrEnableManuallyDisabledChan, false, + ) + + // Request enables with manual = false should succeed after + // restoring auto state management. + h.assertAutos(h.graph.chans(), nil) + h.assertEnables(h.graph.chans(), nil, false) + // Expect to see them all enabled on the network again. h.assertUpdates( h.graph.chans(), true, h.safeDisableTimeout, From 4e4f4bc194982c3e26b56bb8fb977e1730f32726 Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Fri, 12 Feb 2021 22:47:15 -0800 Subject: [PATCH 08/14] routerrpc: add UpdateChanStatus RPC to Router service Update router.proto and rest-annotations.yaml, recompile protos, add a stub implementation to routerrpc.Server. --- lnrpc/rest-annotations.yaml | 3 + lnrpc/routerrpc/router.pb.go | 521 ++++++++++++++++++---------- lnrpc/routerrpc/router.proto | 24 ++ lnrpc/routerrpc/router.swagger.json | 31 ++ lnrpc/routerrpc/router_server.go | 7 + 5 files changed, 406 insertions(+), 180 deletions(-) diff --git a/lnrpc/rest-annotations.yaml b/lnrpc/rest-annotations.yaml index 927dd63ee..10e68963e 100644 --- a/lnrpc/rest-annotations.yaml +++ b/lnrpc/rest-annotations.yaml @@ -226,6 +226,9 @@ http: # deprecated, no REST endpoint - selector: routerrpc.HtlcInterceptor # request streaming RPC, REST not supported + - selector: routerrpc.UpdateChanStatus + post: "/v2/router/updatechanstatus" + body: "*" # signrpc/signer.proto - selector: signrpc.Signer.SignOutputRaw diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index d28f43651..b4a5a235b 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -197,6 +197,34 @@ func (ResolveHoldForwardAction) EnumDescriptor() ([]byte, []int) { return fileDescriptor_7a0613f69d37b0a5, []int{2} } +type ChanStatusAction int32 + +const ( + ChanStatusAction_ENABLE ChanStatusAction = 0 + ChanStatusAction_DISABLE ChanStatusAction = 1 + ChanStatusAction_AUTO ChanStatusAction = 2 +) + +var ChanStatusAction_name = map[int32]string{ + 0: "ENABLE", + 1: "DISABLE", + 2: "AUTO", +} + +var ChanStatusAction_value = map[string]int32{ + "ENABLE": 0, + "DISABLE": 1, + "AUTO": 2, +} + +func (x ChanStatusAction) String() string { + return proto.EnumName(ChanStatusAction_name, int32(x)) +} + +func (ChanStatusAction) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{3} +} + type HtlcEvent_EventType int32 const ( @@ -2259,10 +2287,89 @@ func (m *ForwardHtlcInterceptResponse) GetPreimage() []byte { return nil } +type UpdateChanStatusRequest struct { + ChanPoint *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` + Action ChanStatusAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ChanStatusAction" json:"action,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateChanStatusRequest) Reset() { *m = UpdateChanStatusRequest{} } +func (m *UpdateChanStatusRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateChanStatusRequest) ProtoMessage() {} +func (*UpdateChanStatusRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{32} +} + +func (m *UpdateChanStatusRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateChanStatusRequest.Unmarshal(m, b) +} +func (m *UpdateChanStatusRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateChanStatusRequest.Marshal(b, m, deterministic) +} +func (m *UpdateChanStatusRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateChanStatusRequest.Merge(m, src) +} +func (m *UpdateChanStatusRequest) XXX_Size() int { + return xxx_messageInfo_UpdateChanStatusRequest.Size(m) +} +func (m *UpdateChanStatusRequest) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateChanStatusRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateChanStatusRequest proto.InternalMessageInfo + +func (m *UpdateChanStatusRequest) GetChanPoint() *lnrpc.ChannelPoint { + if m != nil { + return m.ChanPoint + } + return nil +} + +func (m *UpdateChanStatusRequest) GetAction() ChanStatusAction { + if m != nil { + return m.Action + } + return ChanStatusAction_ENABLE +} + +type UpdateChanStatusResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateChanStatusResponse) Reset() { *m = UpdateChanStatusResponse{} } +func (m *UpdateChanStatusResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateChanStatusResponse) ProtoMessage() {} +func (*UpdateChanStatusResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7a0613f69d37b0a5, []int{33} +} + +func (m *UpdateChanStatusResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateChanStatusResponse.Unmarshal(m, b) +} +func (m *UpdateChanStatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateChanStatusResponse.Marshal(b, m, deterministic) +} +func (m *UpdateChanStatusResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateChanStatusResponse.Merge(m, src) +} +func (m *UpdateChanStatusResponse) XXX_Size() int { + return xxx_messageInfo_UpdateChanStatusResponse.Size(m) +} +func (m *UpdateChanStatusResponse) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateChanStatusResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateChanStatusResponse proto.InternalMessageInfo + func init() { proto.RegisterEnum("routerrpc.FailureDetail", FailureDetail_name, FailureDetail_value) proto.RegisterEnum("routerrpc.PaymentState", PaymentState_name, PaymentState_value) proto.RegisterEnum("routerrpc.ResolveHoldForwardAction", ResolveHoldForwardAction_name, ResolveHoldForwardAction_value) + proto.RegisterEnum("routerrpc.ChanStatusAction", ChanStatusAction_name, ChanStatusAction_value) proto.RegisterEnum("routerrpc.HtlcEvent_EventType", HtlcEvent_EventType_name, HtlcEvent_EventType_value) proto.RegisterType((*SendPaymentRequest)(nil), "routerrpc.SendPaymentRequest") proto.RegisterMapType((map[uint64][]byte)(nil), "routerrpc.SendPaymentRequest.DestCustomRecordsEntry") @@ -2298,191 +2405,199 @@ func init() { proto.RegisterType((*ForwardHtlcInterceptRequest)(nil), "routerrpc.ForwardHtlcInterceptRequest") proto.RegisterMapType((map[uint64][]byte)(nil), "routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry") proto.RegisterType((*ForwardHtlcInterceptResponse)(nil), "routerrpc.ForwardHtlcInterceptResponse") + proto.RegisterType((*UpdateChanStatusRequest)(nil), "routerrpc.UpdateChanStatusRequest") + proto.RegisterType((*UpdateChanStatusResponse)(nil), "routerrpc.UpdateChanStatusResponse") } func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) } var fileDescriptor_7a0613f69d37b0a5 = []byte{ - // 2854 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x59, 0x4b, 0x77, 0xdb, 0xc6, - 0x15, 0x0e, 0x48, 0x8a, 0x22, 0x2f, 0x5f, 0xd0, 0x50, 0xb6, 0x58, 0xca, 0x0f, 0x86, 0x79, 0x98, - 0x75, 0x13, 0xd9, 0x51, 0x7b, 0x92, 0xb4, 0x79, 0x34, 0x14, 0x09, 0x59, 0xb0, 0x29, 0x52, 0x19, - 0x52, 0x4e, 0x9c, 0x2c, 0xa6, 0x10, 0x39, 0x14, 0x51, 0xe1, 0xc1, 0x02, 0x43, 0xdb, 0xca, 0xaa, - 0xdd, 0xf5, 0xf4, 0x97, 0x74, 0xd5, 0x5f, 0xd0, 0x73, 0xda, 0x4d, 0x37, 0xfd, 0x13, 0xdd, 0xf6, - 0x17, 0x74, 0xdd, 0x33, 0x83, 0x01, 0x08, 0x48, 0x94, 0xe4, 0x3e, 0x36, 0x36, 0xf1, 0xdd, 0x6f, - 0xee, 0xdc, 0xb9, 0x73, 0xef, 0x9d, 0x3b, 0x23, 0xb8, 0xed, 0xb9, 0x0b, 0x46, 0x3d, 0x6f, 0x3e, - 0x7e, 0x14, 0xfc, 0xda, 0x99, 0x7b, 0x2e, 0x73, 0x51, 0x3e, 0xc2, 0xeb, 0x79, 0x6f, 0x3e, 0x0e, - 0xd0, 0xe6, 0xdf, 0xd6, 0x01, 0x0d, 0xa9, 0x33, 0x39, 0x32, 0xce, 0x6d, 0xea, 0x30, 0x4c, 0x7f, - 0xb3, 0xa0, 0x3e, 0x43, 0x08, 0x32, 0x13, 0xea, 0xb3, 0x9a, 0xd2, 0x50, 0x5a, 0x45, 0x2c, 0x7e, - 0x23, 0x15, 0xd2, 0x86, 0xcd, 0x6a, 0xa9, 0x86, 0xd2, 0x4a, 0x63, 0xfe, 0x13, 0xfd, 0x08, 0x72, - 0x86, 0xcd, 0x88, 0xed, 0x1b, 0xac, 0x56, 0x14, 0xf0, 0xba, 0x61, 0xb3, 0x43, 0xdf, 0x60, 0xe8, - 0x6d, 0x28, 0xce, 0x03, 0x95, 0x64, 0x66, 0xf8, 0xb3, 0x5a, 0x5a, 0x28, 0x2a, 0x48, 0xec, 0xc0, - 0xf0, 0x67, 0xa8, 0x05, 0xea, 0xd4, 0x74, 0x0c, 0x8b, 0x8c, 0x2d, 0xf6, 0x92, 0x4c, 0xa8, 0xc5, - 0x8c, 0x5a, 0xa6, 0xa1, 0xb4, 0xd6, 0x70, 0x59, 0xe0, 0x1d, 0x8b, 0xbd, 0xec, 0x72, 0x34, 0xae, - 0xcc, 0x98, 0x4c, 0xbc, 0xda, 0x66, 0x42, 0x59, 0x7b, 0x32, 0xf1, 0xd0, 0x03, 0xa8, 0x84, 0x14, - 0x2f, 0x58, 0x43, 0x6d, 0xad, 0xa1, 0xb4, 0xf2, 0xb8, 0x3c, 0x4f, 0xae, 0xec, 0x01, 0x54, 0x98, - 0x69, 0x53, 0x77, 0xc1, 0x88, 0x4f, 0xc7, 0xae, 0x33, 0xf1, 0x6b, 0xd9, 0x60, 0x52, 0x09, 0x0f, - 0x03, 0x14, 0x35, 0xa1, 0x34, 0xa5, 0x94, 0x58, 0xa6, 0x6d, 0x32, 0xc2, 0x57, 0xb8, 0x2e, 0x56, - 0x58, 0x98, 0x52, 0xda, 0xe3, 0xd8, 0xd0, 0x60, 0xe8, 0x5d, 0x28, 0x2f, 0x39, 0xc2, 0x0d, 0x25, - 0x41, 0x2a, 0x86, 0x24, 0xe1, 0x8b, 0x1d, 0x50, 0xdd, 0x05, 0x3b, 0x75, 0x4d, 0xe7, 0x94, 0x8c, - 0x67, 0x86, 0x43, 0xcc, 0x49, 0x2d, 0xd7, 0x50, 0x5a, 0x99, 0xbd, 0x4c, 0x4d, 0x79, 0xac, 0xe0, - 0x72, 0x28, 0xed, 0xcc, 0x0c, 0x47, 0x9f, 0xa0, 0x87, 0xb0, 0x71, 0x91, 0xef, 0xd7, 0xaa, 0x8d, - 0x74, 0x2b, 0x83, 0x2b, 0x49, 0xaa, 0x8f, 0xde, 0x87, 0x8a, 0x65, 0xf8, 0x8c, 0xcc, 0xdc, 0x39, - 0x99, 0x2f, 0x4e, 0xce, 0xe8, 0x79, 0xad, 0x2c, 0xbc, 0x53, 0xe2, 0xf0, 0x81, 0x3b, 0x3f, 0x12, - 0x20, 0xba, 0x0b, 0x20, 0xdc, 0x2c, 0x4c, 0xad, 0xe5, 0xc5, 0x8a, 0xf3, 0x1c, 0x11, 0x66, 0xa2, - 0x8f, 0xa0, 0x20, 0xc2, 0x83, 0xcc, 0x4c, 0x87, 0xf9, 0x35, 0x68, 0xa4, 0x5b, 0x85, 0x5d, 0x75, - 0xc7, 0x72, 0x78, 0xa4, 0x60, 0x2e, 0x39, 0x30, 0x1d, 0x86, 0xc1, 0x0b, 0x7f, 0xfa, 0x68, 0x02, - 0x55, 0x1e, 0x16, 0x64, 0xbc, 0xf0, 0x99, 0x6b, 0x13, 0x8f, 0x8e, 0x5d, 0x6f, 0xe2, 0xd7, 0x0a, - 0x62, 0xe8, 0xcf, 0x76, 0xa2, 0x68, 0xdb, 0xb9, 0x1c, 0x5e, 0x3b, 0x5d, 0xea, 0xb3, 0x8e, 0x18, - 0x87, 0x83, 0x61, 0x9a, 0xc3, 0xbc, 0x73, 0xbc, 0x31, 0xb9, 0x88, 0xa3, 0x0f, 0x00, 0x19, 0x96, - 0xe5, 0xbe, 0x22, 0x3e, 0xb5, 0xa6, 0x44, 0xee, 0x65, 0xad, 0xd2, 0x50, 0x5a, 0x39, 0xac, 0x0a, - 0xc9, 0x90, 0x5a, 0x53, 0xa9, 0x1e, 0x7d, 0x0c, 0x25, 0x61, 0xd3, 0x94, 0x1a, 0x6c, 0xe1, 0x51, - 0xbf, 0xa6, 0x36, 0xd2, 0xad, 0xf2, 0xee, 0x86, 0x5c, 0xc8, 0x7e, 0x00, 0xef, 0x99, 0x0c, 0x17, - 0x39, 0x4f, 0x7e, 0xfb, 0x68, 0x1b, 0xf2, 0xb6, 0xf1, 0x9a, 0xcc, 0x0d, 0x8f, 0xf9, 0xb5, 0x8d, - 0x86, 0xd2, 0x2a, 0xe1, 0x9c, 0x6d, 0xbc, 0x3e, 0xe2, 0xdf, 0x68, 0x07, 0xaa, 0x8e, 0x4b, 0x4c, - 0x67, 0x6a, 0x99, 0xa7, 0x33, 0x46, 0x16, 0xf3, 0x89, 0xc1, 0xa8, 0x5f, 0x43, 0xc2, 0x86, 0x0d, - 0xc7, 0xd5, 0xa5, 0xe4, 0x38, 0x10, 0xa0, 0x0f, 0xa1, 0xca, 0x95, 0xf9, 0x33, 0xc3, 0x9b, 0x10, - 0xdf, 0xfc, 0x81, 0x06, 0x91, 0x71, 0x8b, 0xef, 0x38, 0x56, 0x6d, 0xe3, 0xf5, 0x90, 0x4b, 0x86, - 0xe6, 0x0f, 0x94, 0x47, 0x47, 0xbd, 0x0b, 0xb7, 0x57, 0xbb, 0x83, 0x27, 0x1c, 0xdf, 0x4f, 0x45, - 0x0c, 0xe4, 0x3f, 0xd1, 0x26, 0xac, 0xbd, 0x34, 0xac, 0x05, 0x15, 0x49, 0x58, 0xc4, 0xc1, 0xc7, - 0x2f, 0x52, 0x9f, 0x2a, 0xcd, 0x19, 0x54, 0x47, 0x9e, 0x31, 0x3e, 0xbb, 0x90, 0xc7, 0x17, 0xd3, - 0x50, 0xb9, 0x9c, 0x86, 0x57, 0x2c, 0x2f, 0x75, 0xc5, 0xf2, 0x9a, 0x5f, 0x42, 0x45, 0x04, 0xc4, - 0x3e, 0xa5, 0xd7, 0x55, 0x8b, 0x2d, 0xe0, 0xb5, 0x40, 0x24, 0x4e, 0x50, 0x31, 0xb2, 0x86, 0xcd, - 0x73, 0xa6, 0x39, 0x01, 0x75, 0x39, 0xde, 0x9f, 0xbb, 0x8e, 0x4f, 0x79, 0x29, 0xe0, 0xf1, 0xc2, - 0x03, 0x9e, 0xe7, 0x93, 0xf0, 0x97, 0x22, 0x46, 0x95, 0x25, 0xbe, 0x4f, 0x85, 0xb7, 0x78, 0xbc, - 0xf3, 0x3c, 0x25, 0x96, 0x3b, 0x3e, 0xe3, 0x35, 0xc3, 0x38, 0x97, 0xea, 0x4b, 0x1c, 0xee, 0xb9, - 0xe3, 0xb3, 0x2e, 0x07, 0x9b, 0xdf, 0x07, 0x65, 0x6d, 0xe4, 0x8a, 0xb9, 0xfe, 0x03, 0x77, 0x34, - 0x61, 0x4d, 0x84, 0xae, 0x50, 0x5b, 0xd8, 0x2d, 0xc6, 0x73, 0x00, 0x07, 0xa2, 0xe6, 0xf7, 0x50, - 0x4d, 0x28, 0x97, 0xab, 0xa8, 0x43, 0x6e, 0xee, 0x51, 0xd3, 0x36, 0x4e, 0xa9, 0xd4, 0x1c, 0x7d, - 0xa3, 0x16, 0xac, 0x4f, 0x0d, 0xd3, 0x5a, 0x78, 0xa1, 0xe2, 0x72, 0x18, 0x93, 0x01, 0x8a, 0x43, - 0x71, 0xf3, 0x0e, 0xd4, 0x31, 0xf5, 0x29, 0x3b, 0x34, 0x7d, 0xdf, 0x74, 0x9d, 0x8e, 0xeb, 0x30, - 0xcf, 0xb5, 0xe4, 0x0a, 0x9a, 0x77, 0x61, 0x7b, 0xa5, 0x34, 0x30, 0x81, 0x0f, 0xfe, 0x7a, 0x41, - 0xbd, 0xf3, 0xd5, 0x83, 0xbf, 0x86, 0xed, 0x95, 0x52, 0x69, 0xff, 0x07, 0xb0, 0x36, 0x37, 0x4c, - 0x8f, 0xef, 0x3d, 0xcf, 0xe1, 0xdb, 0xb1, 0x1c, 0x3e, 0x32, 0x4c, 0xef, 0xc0, 0xf4, 0x99, 0xeb, - 0x9d, 0xe3, 0x80, 0xf4, 0x34, 0x93, 0x53, 0xd4, 0x54, 0xf3, 0x0f, 0x0a, 0x14, 0x62, 0x42, 0x9e, - 0x49, 0x8e, 0x3b, 0xa1, 0x64, 0xea, 0xb9, 0x76, 0xe8, 0x04, 0x0e, 0xec, 0x7b, 0xae, 0xcd, 0x63, - 0x42, 0x08, 0x99, 0x2b, 0x03, 0x38, 0xcb, 0x3f, 0x47, 0x2e, 0xfa, 0x10, 0xd6, 0x67, 0x81, 0x02, - 0x51, 0x65, 0x0b, 0xbb, 0xd5, 0x0b, 0x73, 0x77, 0x0d, 0x66, 0xe0, 0x90, 0xf3, 0x34, 0x93, 0x4b, - 0xab, 0x99, 0xa7, 0x99, 0x5c, 0x46, 0x5d, 0x7b, 0x9a, 0xc9, 0xad, 0xa9, 0xd9, 0xa7, 0x99, 0x5c, - 0x56, 0x5d, 0x6f, 0xfe, 0x53, 0x81, 0x5c, 0xc8, 0xe6, 0x96, 0x70, 0x97, 0x12, 0x1e, 0x17, 0x32, - 0x98, 0x72, 0x1c, 0x18, 0x99, 0x36, 0x45, 0x0d, 0x28, 0x0a, 0x61, 0x32, 0x44, 0x81, 0x63, 0x6d, - 0x11, 0xa6, 0xa2, 0xfc, 0x87, 0x0c, 0x11, 0x8f, 0x19, 0x59, 0xfe, 0x03, 0x4a, 0x78, 0xc8, 0xf9, - 0x8b, 0xf1, 0x98, 0xfa, 0x7e, 0x30, 0xcb, 0x5a, 0x40, 0x91, 0x98, 0x98, 0xe8, 0x7d, 0xa8, 0x84, - 0x94, 0x70, 0xae, 0x6c, 0x10, 0xaf, 0x12, 0x96, 0xd3, 0xb5, 0x40, 0x8d, 0xf3, 0xec, 0xe5, 0x81, - 0x53, 0x5e, 0x12, 0xf9, 0xa4, 0xc1, 0xe2, 0x9b, 0x0d, 0xb8, 0xf7, 0xe4, 0x62, 0x14, 0x74, 0x5c, - 0x67, 0x6a, 0x9e, 0x86, 0x9b, 0xfd, 0x1d, 0xdc, 0xbf, 0x92, 0x21, 0x37, 0xfc, 0x13, 0xc8, 0x8e, - 0x05, 0x22, 0xfc, 0x53, 0xd8, 0xbd, 0x1f, 0xf3, 0xfa, 0xca, 0x81, 0x92, 0xde, 0x7c, 0x01, 0xf7, - 0x86, 0xd7, 0xce, 0xfe, 0xdf, 0xab, 0x7e, 0x1b, 0xee, 0x0f, 0xaf, 0x37, 0xbb, 0xf9, 0xdb, 0x14, - 0x6c, 0xae, 0x22, 0xf0, 0x83, 0x73, 0x66, 0x58, 0x53, 0x62, 0x99, 0x53, 0x1a, 0x9d, 0xee, 0x41, - 0xf9, 0xac, 0x70, 0x41, 0xcf, 0x9c, 0xd2, 0xf0, 0x78, 0x7f, 0x00, 0x15, 0x71, 0x66, 0x7a, 0xee, - 0x89, 0x71, 0x62, 0x5a, 0x26, 0x0b, 0x0a, 0x49, 0x0a, 0x97, 0x67, 0xee, 0xfc, 0x68, 0x89, 0xa2, - 0xdb, 0x90, 0x7d, 0x45, 0x79, 0x01, 0x14, 0x3d, 0x4c, 0x0a, 0xcb, 0x2f, 0xf4, 0x31, 0x6c, 0xd9, - 0xc6, 0x6b, 0xd3, 0x5e, 0xd8, 0x64, 0xd9, 0x79, 0xf8, 0x0b, 0x8b, 0xf9, 0x22, 0x54, 0x4a, 0xf8, - 0x96, 0x14, 0x47, 0x25, 0x59, 0x08, 0x51, 0x07, 0xee, 0xd9, 0xa6, 0x23, 0xc6, 0xc9, 0x94, 0x27, - 0x1e, 0xb5, 0x8c, 0xd7, 0xc4, 0x74, 0x18, 0xf5, 0x5e, 0x1a, 0x96, 0x08, 0xa3, 0x0c, 0xde, 0x96, - 0xac, 0xb0, 0x40, 0x70, 0x8e, 0x2e, 0x29, 0xcd, 0x5f, 0xc3, 0x96, 0xc8, 0xe4, 0x98, 0xa1, 0xa1, - 0xe7, 0x79, 0xdc, 0x7b, 0xae, 0x4d, 0x78, 0x6a, 0x85, 0x19, 0xc8, 0x81, 0xbe, 0x3b, 0xa1, 0x3c, - 0x03, 0x99, 0x1b, 0x88, 0x64, 0x06, 0x32, 0x57, 0x08, 0xe2, 0xad, 0x5c, 0x3a, 0xd1, 0xca, 0x35, - 0xcf, 0xa0, 0x76, 0x79, 0x2e, 0x19, 0x41, 0x0d, 0x28, 0xc4, 0x3d, 0xc8, 0xa7, 0x53, 0x70, 0x1c, - 0x8a, 0xa7, 0x76, 0xea, 0xe6, 0xd4, 0x6e, 0xfe, 0x5d, 0x81, 0x8d, 0xbd, 0x85, 0x69, 0x4d, 0x12, - 0x75, 0x3b, 0x6e, 0x9d, 0x92, 0x6c, 0x34, 0x57, 0x75, 0x91, 0xa9, 0x95, 0x5d, 0xe4, 0x07, 0x2b, - 0xda, 0xb0, 0xb4, 0x68, 0xc3, 0x52, 0x2b, 0x9a, 0xb0, 0xfb, 0x50, 0x58, 0xf6, 0x54, 0x7c, 0x4b, - 0xd3, 0xad, 0x22, 0x86, 0x59, 0xd8, 0x50, 0xf9, 0x97, 0x9a, 0xd2, 0xb5, 0x4b, 0x4d, 0x69, 0xf3, - 0x53, 0x40, 0xf1, 0xb5, 0x48, 0x9f, 0x45, 0x27, 0x8c, 0x72, 0xf5, 0x09, 0x73, 0x07, 0xea, 0xc3, - 0xc5, 0x89, 0x3f, 0xf6, 0xcc, 0x13, 0x7a, 0xc0, 0xac, 0xb1, 0xf6, 0x92, 0x3a, 0xcc, 0x0f, 0x53, - 0xfb, 0x5f, 0x19, 0xc8, 0x47, 0x28, 0x3f, 0xc0, 0x4d, 0x67, 0xec, 0xda, 0xe1, 0xba, 0x1c, 0x6a, - 0xf1, 0xa5, 0x05, 0x71, 0xbf, 0x11, 0x8a, 0x3a, 0x81, 0x44, 0x9f, 0x70, 0x7e, 0xc2, 0x0f, 0x92, - 0x9f, 0x0a, 0xf8, 0x71, 0x37, 0x04, 0xfc, 0x16, 0xa8, 0x91, 0xfe, 0x19, 0xb3, 0xc6, 0x91, 0xdf, - 0x70, 0x39, 0xc4, 0xb9, 0x31, 0x01, 0x33, 0xd2, 0x1c, 0x32, 0x33, 0x01, 0x33, 0xc4, 0x25, 0xf3, - 0x6d, 0x28, 0xf2, 0x8a, 0xe9, 0x33, 0xc3, 0x9e, 0x13, 0xc7, 0x97, 0x21, 0x5f, 0x88, 0xb0, 0xbe, - 0x8f, 0xbe, 0x00, 0xa0, 0x7c, 0x7d, 0x84, 0x9d, 0xcf, 0xa9, 0x28, 0x9a, 0xe5, 0xdd, 0x7b, 0xb1, - 0xd8, 0x89, 0x1c, 0xb0, 0x23, 0xfe, 0x1d, 0x9d, 0xcf, 0x29, 0xce, 0xd3, 0xf0, 0x27, 0xfa, 0x12, - 0x4a, 0x53, 0xd7, 0x7b, 0xc5, 0x7b, 0x30, 0x01, 0xca, 0x83, 0x65, 0x2b, 0xa6, 0x61, 0x3f, 0x90, - 0x8b, 0xe1, 0x07, 0x6f, 0xe1, 0xe2, 0x34, 0xf6, 0x8d, 0x9e, 0x01, 0x0a, 0xc7, 0x8b, 0x73, 0x20, - 0x50, 0x92, 0x13, 0x4a, 0xb6, 0x2f, 0x2b, 0xe1, 0x59, 0x1a, 0x2a, 0x52, 0xa7, 0x17, 0x30, 0xf4, - 0x19, 0x14, 0x7d, 0xca, 0x98, 0x45, 0xa5, 0x9a, 0xbc, 0x50, 0x73, 0x3b, 0xd1, 0x24, 0x73, 0x71, - 0xa8, 0xa1, 0xe0, 0x2f, 0x3f, 0xd1, 0x1e, 0x54, 0x2c, 0xd3, 0x39, 0x8b, 0x9b, 0x01, 0x62, 0x7c, - 0x2d, 0x36, 0xbe, 0x67, 0x3a, 0x67, 0x71, 0x1b, 0x4a, 0x56, 0x1c, 0x68, 0x7e, 0x0e, 0xf9, 0xc8, - 0x4b, 0xa8, 0x00, 0xeb, 0xc7, 0xfd, 0x67, 0xfd, 0xc1, 0x37, 0x7d, 0xf5, 0x2d, 0x94, 0x83, 0xcc, - 0x50, 0xeb, 0x77, 0x55, 0x85, 0xc3, 0x58, 0xeb, 0x68, 0xfa, 0x73, 0x4d, 0x4d, 0xf1, 0x8f, 0xfd, - 0x01, 0xfe, 0xa6, 0x8d, 0xbb, 0x6a, 0x7a, 0x6f, 0x1d, 0xd6, 0xc4, 0xbc, 0xcd, 0x3f, 0x2b, 0x90, - 0x13, 0x3b, 0xe8, 0x4c, 0x5d, 0xf4, 0x13, 0x88, 0x82, 0x4b, 0x1c, 0x7f, 0xbc, 0x25, 0x13, 0x51, - 0x57, 0xc2, 0x51, 0xc0, 0x8c, 0x24, 0xce, 0xc9, 0x51, 0x68, 0x44, 0xe4, 0x54, 0x40, 0x0e, 0x05, - 0x11, 0xf9, 0x61, 0x4c, 0x73, 0xa2, 0x2a, 0x65, 0x70, 0x25, 0x14, 0x84, 0x67, 0x70, 0xfc, 0xb2, - 0x94, 0x38, 0xab, 0x63, 0x97, 0x25, 0xc9, 0x6d, 0x7e, 0x02, 0xc5, 0xf8, 0x9e, 0xa3, 0x07, 0x90, - 0x31, 0x9d, 0xa9, 0x2b, 0x13, 0xb1, 0x7a, 0x21, 0xb8, 0xf8, 0x22, 0xb1, 0x20, 0x34, 0x11, 0xa8, - 0x17, 0xf7, 0xb9, 0x59, 0x82, 0x42, 0x6c, 0xd3, 0x9a, 0xff, 0x50, 0xa0, 0x94, 0xd8, 0x84, 0x37, - 0xd6, 0x8e, 0xbe, 0x80, 0xe2, 0x2b, 0xd3, 0xa3, 0x24, 0xde, 0x20, 0x96, 0x77, 0xeb, 0xc9, 0x06, - 0x31, 0xfc, 0xbf, 0xe3, 0x4e, 0x28, 0x2e, 0x70, 0xbe, 0x04, 0xd0, 0x2f, 0xa1, 0x1c, 0x1e, 0x24, - 0x13, 0xca, 0x0c, 0xd3, 0x12, 0xae, 0x2a, 0x27, 0xc2, 0x43, 0x72, 0xbb, 0x42, 0x8e, 0x4b, 0xd3, - 0xf8, 0x27, 0x7a, 0x6f, 0xa9, 0xc0, 0x67, 0x9e, 0xe9, 0x9c, 0x0a, 0xff, 0xe5, 0x23, 0xda, 0x50, - 0x80, 0xbc, 0xd5, 0x2b, 0xc9, 0xb3, 0x6c, 0xc8, 0x0c, 0xb6, 0xe0, 0x37, 0x9d, 0x35, 0x9f, 0x19, - 0xb2, 0x92, 0x95, 0x13, 0xb9, 0x15, 0x23, 0x52, 0x1c, 0xb0, 0x12, 0xfd, 0x71, 0xea, 0x52, 0x7f, - 0xbc, 0xc6, 0x2b, 0x46, 0x50, 0x68, 0x0b, 0xbb, 0x48, 0x2e, 0xfe, 0x60, 0xd4, 0xeb, 0xb4, 0x19, - 0xa3, 0xf6, 0x9c, 0xe1, 0x80, 0x20, 0xfb, 0x9f, 0x2f, 0x01, 0x3a, 0xa6, 0x37, 0x5e, 0x98, 0xec, - 0x19, 0x3d, 0xe7, 0xc7, 0x5a, 0x58, 0xd1, 0x83, 0xb2, 0x97, 0x1d, 0x07, 0x55, 0x7c, 0x0b, 0xd6, - 0xc3, 0x42, 0x14, 0xd4, 0xb7, 0xec, 0x4c, 0x14, 0xa0, 0xe6, 0x5f, 0x32, 0xb0, 0x2d, 0xb7, 0x34, - 0xd8, 0x0d, 0x46, 0xbd, 0x31, 0x9d, 0x47, 0x17, 0xa7, 0x27, 0xb0, 0xb9, 0x2c, 0xaa, 0xc1, 0x44, - 0x24, 0xbc, 0x8c, 0x15, 0x76, 0x6f, 0xc5, 0x56, 0xba, 0x34, 0x03, 0xa3, 0xa8, 0xd8, 0x2e, 0x4d, - 0x7b, 0x1c, 0x53, 0x64, 0xd8, 0xee, 0xc2, 0x91, 0x21, 0x1a, 0x54, 0x3c, 0xb4, 0x0c, 0x67, 0x2e, - 0x12, 0x11, 0xfd, 0x00, 0xa2, 0x20, 0x27, 0xf4, 0xf5, 0xdc, 0xf4, 0xce, 0x45, 0xf5, 0x2b, 0x2d, - 0xcb, 0xad, 0x26, 0xd0, 0x4b, 0xb7, 0x99, 0xd4, 0xe5, 0xdb, 0xcc, 0x67, 0x50, 0x8f, 0xb2, 0x43, - 0xbe, 0x8b, 0xd0, 0x49, 0x74, 0xfa, 0xad, 0x0b, 0x1b, 0xb6, 0x42, 0x06, 0x0e, 0x09, 0xf2, 0x08, - 0x7c, 0x0c, 0x9b, 0xb1, 0xd4, 0x5a, 0x9a, 0x1e, 0x64, 0x22, 0x5a, 0x66, 0x57, 0xdc, 0xf4, 0x68, - 0x84, 0x34, 0x3d, 0xe8, 0x85, 0xa2, 0xfa, 0x2f, 0x4d, 0xff, 0x15, 0x94, 0x2f, 0xbc, 0x1b, 0xe4, - 0xc4, 0xbe, 0xff, 0xfc, 0x72, 0x65, 0x5d, 0xb5, 0x3d, 0x3b, 0x2b, 0x1e, 0x0f, 0x4a, 0xe3, 0xc4, - 0xc3, 0xc1, 0x5d, 0x00, 0xd7, 0x31, 0x5d, 0x87, 0x9c, 0x58, 0xee, 0x89, 0x28, 0xb8, 0x45, 0x9c, - 0x17, 0xc8, 0x9e, 0xe5, 0x9e, 0xd4, 0xbf, 0x02, 0xf4, 0x3f, 0xde, 0xb8, 0xff, 0xaa, 0xc0, 0x9d, - 0xd5, 0x26, 0xca, 0x73, 0xfe, 0xff, 0x16, 0x42, 0x9f, 0x41, 0xd6, 0x18, 0x33, 0xd3, 0x75, 0x64, - 0x65, 0x78, 0x27, 0x36, 0x14, 0x53, 0xdf, 0xb5, 0x5e, 0xd2, 0x03, 0xd7, 0x9a, 0x48, 0x63, 0xda, - 0x82, 0x8a, 0xe5, 0x90, 0x44, 0xd2, 0xa5, 0x93, 0x49, 0xf7, 0xf0, 0x77, 0x19, 0x28, 0x25, 0x2a, - 0x43, 0xf2, 0x68, 0x28, 0x41, 0xbe, 0x3f, 0x20, 0x5d, 0x6d, 0xd4, 0xd6, 0x7b, 0xaa, 0x82, 0x54, - 0x28, 0x0e, 0xfa, 0xfa, 0xa0, 0x4f, 0xba, 0x5a, 0x67, 0xd0, 0xe5, 0x87, 0xc4, 0x2d, 0xd8, 0xe8, - 0xe9, 0xfd, 0x67, 0xa4, 0x3f, 0x18, 0x11, 0xad, 0xa7, 0x3f, 0xd1, 0xf7, 0x7a, 0x9a, 0x9a, 0x46, - 0x9b, 0xa0, 0x0e, 0xfa, 0xa4, 0x73, 0xd0, 0xd6, 0xfb, 0x64, 0xa4, 0x1f, 0x6a, 0x83, 0xe3, 0x91, - 0x9a, 0xe1, 0x28, 0xcf, 0x66, 0xa2, 0x7d, 0xdb, 0xd1, 0xb4, 0xee, 0x90, 0x1c, 0xb6, 0xbf, 0x55, - 0xd7, 0x50, 0x0d, 0x36, 0xf5, 0xfe, 0xf0, 0x78, 0x7f, 0x5f, 0xef, 0xe8, 0x5a, 0x7f, 0x44, 0xf6, - 0xda, 0xbd, 0x76, 0xbf, 0xa3, 0xa9, 0x59, 0x74, 0x1b, 0x90, 0xde, 0xef, 0x0c, 0x0e, 0x8f, 0x7a, - 0xda, 0x48, 0x23, 0xe1, 0x61, 0xb4, 0x8e, 0xaa, 0x50, 0x11, 0x7a, 0xda, 0xdd, 0x2e, 0xd9, 0x6f, - 0xeb, 0x3d, 0xad, 0xab, 0xe6, 0xb8, 0x25, 0x92, 0x31, 0x24, 0x5d, 0x7d, 0xd8, 0xde, 0xe3, 0x70, - 0x9e, 0xcf, 0xa9, 0xf7, 0x9f, 0x0f, 0xf4, 0x8e, 0x46, 0x3a, 0x5c, 0x2d, 0x47, 0x81, 0x93, 0x43, - 0xf4, 0xb8, 0xdf, 0xd5, 0xf0, 0x51, 0x5b, 0xef, 0xaa, 0x05, 0xb4, 0x0d, 0x5b, 0x21, 0xac, 0x7d, - 0x7b, 0xa4, 0xe3, 0x17, 0x64, 0x34, 0x18, 0x90, 0xe1, 0x60, 0xd0, 0x57, 0x8b, 0x71, 0x4d, 0x7c, - 0xb5, 0x83, 0x23, 0xad, 0xaf, 0x96, 0xd0, 0x16, 0x54, 0x0f, 0x8f, 0x8e, 0x48, 0x28, 0x09, 0x17, - 0x5b, 0xe6, 0xf4, 0x76, 0xb7, 0x8b, 0xb5, 0xe1, 0x90, 0x1c, 0xea, 0xc3, 0xc3, 0xf6, 0xa8, 0x73, - 0xa0, 0x56, 0xf8, 0x92, 0x86, 0xda, 0x88, 0x8c, 0x06, 0xa3, 0x76, 0x6f, 0x89, 0xab, 0xdc, 0xa0, - 0x25, 0xce, 0x27, 0xed, 0x0d, 0xbe, 0x51, 0x37, 0xb8, 0xc3, 0x39, 0x3c, 0x78, 0x2e, 0x4d, 0x44, - 0x7c, 0xed, 0x72, 0x7b, 0xc2, 0x39, 0xd5, 0x2a, 0x07, 0xf5, 0xfe, 0xf3, 0x76, 0x4f, 0xef, 0x92, - 0x67, 0xda, 0x0b, 0x71, 0x98, 0x6f, 0x72, 0x30, 0xb0, 0x8c, 0x1c, 0xe1, 0xc1, 0x13, 0x6e, 0x88, - 0x7a, 0x0b, 0x21, 0x28, 0x77, 0x74, 0xdc, 0x39, 0xee, 0xb5, 0x31, 0xc1, 0x83, 0xe3, 0x91, 0xa6, - 0xde, 0x7e, 0xf8, 0x27, 0x05, 0x8a, 0xf1, 0x62, 0xcd, 0x77, 0x5d, 0xef, 0x93, 0xfd, 0x9e, 0xfe, - 0xe4, 0x60, 0x14, 0x04, 0xc1, 0xf0, 0xb8, 0xc3, 0xb7, 0x4c, 0xe3, 0x4d, 0x02, 0x82, 0x72, 0xe0, - 0xf4, 0x68, 0xb1, 0x29, 0x3e, 0x97, 0xc4, 0xfa, 0x03, 0xa9, 0x37, 0xcd, 0x8d, 0x97, 0xa0, 0x86, - 0xf1, 0x00, 0xab, 0x19, 0xf4, 0x2e, 0x34, 0x24, 0xc2, 0xf7, 0x15, 0x63, 0xad, 0x33, 0x22, 0x47, - 0xed, 0x17, 0x87, 0x7c, 0xdb, 0x83, 0x20, 0x1b, 0xaa, 0x6b, 0xe8, 0x3e, 0x6c, 0x47, 0xac, 0x55, - 0x71, 0xf1, 0xf0, 0x73, 0xa8, 0x5d, 0x15, 0xf4, 0x08, 0x20, 0x3b, 0xd4, 0x46, 0xa3, 0x9e, 0x16, - 0x34, 0x36, 0xfb, 0x41, 0xe0, 0x02, 0x64, 0xb1, 0x36, 0x3c, 0x3e, 0xd4, 0xd4, 0xd4, 0xee, 0x1f, - 0xf9, 0x87, 0xc8, 0x1e, 0xf4, 0x15, 0x94, 0x62, 0x4f, 0x93, 0xcf, 0x77, 0xd1, 0xdd, 0x6b, 0x1f, - 0x2d, 0xeb, 0xe1, 0x8b, 0x8d, 0x84, 0x1f, 0x2b, 0x68, 0x0f, 0xca, 0xf1, 0x47, 0xb7, 0xe7, 0xbb, - 0x28, 0xde, 0xa0, 0xae, 0x78, 0x8f, 0x5b, 0xa1, 0xe3, 0x19, 0xa8, 0x9a, 0xcf, 0x4c, 0x9b, 0x9f, - 0x93, 0xf2, 0x59, 0x0c, 0xd5, 0xe3, 0x09, 0x9e, 0x7c, 0x6b, 0xab, 0x6f, 0xaf, 0x94, 0xc9, 0x92, - 0xf3, 0x35, 0xef, 0x49, 0xa2, 0x87, 0xa9, 0x4b, 0x0b, 0x4a, 0xbe, 0x86, 0xd5, 0xef, 0x5d, 0x25, - 0x96, 0xf7, 0xec, 0xf4, 0xef, 0x53, 0x7c, 0x8d, 0xa5, 0x98, 0x6c, 0x85, 0x97, 0x2e, 0x28, 0x5d, - 0x71, 0x72, 0xa3, 0x09, 0x54, 0x57, 0x3c, 0x5a, 0xa1, 0xf7, 0x92, 0x75, 0xec, 0x8a, 0x27, 0xaf, - 0xfa, 0xfb, 0x37, 0xd1, 0xe4, 0xe2, 0x27, 0x50, 0x5d, 0xf1, 0xba, 0x95, 0x98, 0xe5, 0xea, 0xb7, - 0xb1, 0xc4, 0x2c, 0xd7, 0x3d, 0x92, 0xcd, 0x61, 0xeb, 0x8a, 0x67, 0x15, 0xf4, 0xe3, 0x98, 0x8a, - 0xeb, 0x1f, 0x67, 0xea, 0x0f, 0xdf, 0x84, 0xba, 0x9c, 0x71, 0xf8, 0x06, 0x33, 0x0e, 0xdf, 0x7c, - 0xc6, 0x1b, 0x1e, 0x58, 0xd0, 0xf7, 0xa0, 0x5e, 0xbc, 0xf1, 0xa3, 0xe6, 0x45, 0xff, 0x5c, 0x7e, - 0x7a, 0xa8, 0xbf, 0x73, 0x2d, 0x47, 0x2a, 0xd7, 0x01, 0x96, 0x97, 0x62, 0x74, 0x27, 0x36, 0xe4, - 0xd2, 0xbd, 0xbf, 0x7e, 0xf7, 0x0a, 0xa9, 0x54, 0x35, 0x82, 0xea, 0x8a, 0x5b, 0x72, 0x62, 0xc7, - 0xaf, 0xbe, 0x45, 0xd7, 0x37, 0x57, 0x5d, 0x26, 0x1f, 0x2b, 0xe8, 0x30, 0x48, 0xa2, 0xf0, 0x6f, - 0x0a, 0x37, 0x54, 0x85, 0xda, 0xea, 0xa6, 0x77, 0xe1, 0x8b, 0xf4, 0x79, 0xac, 0xa0, 0x01, 0x14, - 0xe3, 0x95, 0xe0, 0xc6, 0x12, 0x71, 0xa3, 0xc2, 0x29, 0x54, 0x12, 0x0d, 0x87, 0xeb, 0xa1, 0x07, - 0x37, 0xb6, 0x4d, 0x81, 0xc7, 0x12, 0x51, 0x7e, 0x4d, 0x7f, 0xd5, 0x52, 0x1e, 0x2b, 0x7b, 0x1f, - 0x7d, 0xf7, 0xe8, 0xd4, 0x64, 0xb3, 0xc5, 0xc9, 0xce, 0xd8, 0xb5, 0x1f, 0x89, 0xbf, 0x01, 0x38, - 0xa6, 0x73, 0xea, 0x50, 0xf6, 0xca, 0xf5, 0xce, 0x1e, 0x59, 0xce, 0xe4, 0x91, 0x48, 0xf5, 0x47, - 0x91, 0xca, 0x93, 0xac, 0xf8, 0xa3, 0xe2, 0x4f, 0xff, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xa0, 0xe5, - 0x92, 0xf0, 0x84, 0x1c, 0x00, 0x00, + // 2955 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x59, 0x5b, 0x77, 0xdb, 0xc6, + 0xf1, 0x0f, 0x48, 0x8a, 0x22, 0x87, 0x17, 0x41, 0x2b, 0xd9, 0xe2, 0x9f, 0xf2, 0x45, 0x61, 0x2e, + 0xe6, 0xdf, 0x4d, 0x64, 0x47, 0x69, 0x93, 0xb4, 0xb9, 0x34, 0x14, 0x09, 0x59, 0xb0, 0x29, 0x52, + 0x59, 0x52, 0x4e, 0x1c, 0x3f, 0x6c, 0x21, 0x72, 0x29, 0xa2, 0x02, 0x01, 0x16, 0x58, 0xda, 0x56, + 0x9e, 0xda, 0x9e, 0xd3, 0x73, 0x7a, 0xfa, 0x61, 0xfa, 0x09, 0x7a, 0x4e, 0xfb, 0xd2, 0x97, 0x7e, + 0x89, 0xbe, 0xf6, 0x13, 0xf4, 0xb9, 0x67, 0x2f, 0x00, 0x01, 0x8a, 0x92, 0xdd, 0xcb, 0x8b, 0x4d, + 0xfc, 0xe6, 0xb7, 0xb3, 0xb3, 0xb3, 0x33, 0xb3, 0xb3, 0x2b, 0xb8, 0xe9, 0x7b, 0x33, 0x46, 0x7d, + 0x7f, 0x3a, 0x78, 0x20, 0x7f, 0xed, 0x4e, 0x7d, 0x8f, 0x79, 0x28, 0x1f, 0xe1, 0xd5, 0xbc, 0x3f, + 0x1d, 0x48, 0xb4, 0xf6, 0xd7, 0x55, 0x40, 0x3d, 0xea, 0x0e, 0x8f, 0xad, 0x8b, 0x09, 0x75, 0x19, + 0xa6, 0xbf, 0x9a, 0xd1, 0x80, 0x21, 0x04, 0x99, 0x21, 0x0d, 0x58, 0x45, 0xdb, 0xd1, 0xea, 0x45, + 0x2c, 0x7e, 0x23, 0x1d, 0xd2, 0xd6, 0x84, 0x55, 0x52, 0x3b, 0x5a, 0x3d, 0x8d, 0xf9, 0x4f, 0xf4, + 0x7f, 0x90, 0xb3, 0x26, 0x8c, 0x4c, 0x02, 0x8b, 0x55, 0x8a, 0x02, 0x5e, 0xb5, 0x26, 0xec, 0x28, + 0xb0, 0x18, 0x7a, 0x1b, 0x8a, 0x53, 0xa9, 0x92, 0x8c, 0xad, 0x60, 0x5c, 0x49, 0x0b, 0x45, 0x05, + 0x85, 0x1d, 0x5a, 0xc1, 0x18, 0xd5, 0x41, 0x1f, 0xd9, 0xae, 0xe5, 0x90, 0x81, 0xc3, 0x5e, 0x90, + 0x21, 0x75, 0x98, 0x55, 0xc9, 0xec, 0x68, 0xf5, 0x15, 0x5c, 0x16, 0x78, 0xd3, 0x61, 0x2f, 0x5a, + 0x1c, 0x8d, 0x2b, 0xb3, 0x86, 0x43, 0xbf, 0xb2, 0x99, 0x50, 0xd6, 0x18, 0x0e, 0x7d, 0x74, 0x0f, + 0xd6, 0x42, 0x8a, 0x2f, 0xd7, 0x50, 0x59, 0xd9, 0xd1, 0xea, 0x79, 0x5c, 0x9e, 0x26, 0x57, 0x76, + 0x0f, 0xd6, 0x98, 0x3d, 0xa1, 0xde, 0x8c, 0x91, 0x80, 0x0e, 0x3c, 0x77, 0x18, 0x54, 0xb2, 0x72, + 0x52, 0x05, 0xf7, 0x24, 0x8a, 0x6a, 0x50, 0x1a, 0x51, 0x4a, 0x1c, 0x7b, 0x62, 0x33, 0xc2, 0x57, + 0xb8, 0x2a, 0x56, 0x58, 0x18, 0x51, 0xda, 0xe6, 0x58, 0xcf, 0x62, 0xe8, 0x5d, 0x28, 0xcf, 0x39, + 0xc2, 0x0d, 0x25, 0x41, 0x2a, 0x86, 0x24, 0xe1, 0x8b, 0x5d, 0xd0, 0xbd, 0x19, 0x3b, 0xf3, 0x6c, + 0xf7, 0x8c, 0x0c, 0xc6, 0x96, 0x4b, 0xec, 0x61, 0x25, 0xb7, 0xa3, 0xd5, 0x33, 0xfb, 0x99, 0x8a, + 0xf6, 0x50, 0xc3, 0xe5, 0x50, 0xda, 0x1c, 0x5b, 0xae, 0x39, 0x44, 0xf7, 0x61, 0x7d, 0x91, 0x1f, + 0x54, 0x36, 0x76, 0xd2, 0xf5, 0x0c, 0x5e, 0x4b, 0x52, 0x03, 0xf4, 0x3e, 0xac, 0x39, 0x56, 0xc0, + 0xc8, 0xd8, 0x9b, 0x92, 0xe9, 0xec, 0xf4, 0x9c, 0x5e, 0x54, 0xca, 0xc2, 0x3b, 0x25, 0x0e, 0x1f, + 0x7a, 0xd3, 0x63, 0x01, 0xa2, 0xdb, 0x00, 0xc2, 0xcd, 0xc2, 0xd4, 0x4a, 0x5e, 0xac, 0x38, 0xcf, + 0x11, 0x61, 0x26, 0xfa, 0x08, 0x0a, 0x22, 0x3c, 0xc8, 0xd8, 0x76, 0x59, 0x50, 0x81, 0x9d, 0x74, + 0xbd, 0xb0, 0xa7, 0xef, 0x3a, 0x2e, 0x8f, 0x14, 0xcc, 0x25, 0x87, 0xb6, 0xcb, 0x30, 0xf8, 0xe1, + 0xcf, 0x00, 0x0d, 0x61, 0x83, 0x87, 0x05, 0x19, 0xcc, 0x02, 0xe6, 0x4d, 0x88, 0x4f, 0x07, 0x9e, + 0x3f, 0x0c, 0x2a, 0x05, 0x31, 0xf4, 0xc7, 0xbb, 0x51, 0xb4, 0xed, 0x5e, 0x0e, 0xaf, 0xdd, 0x16, + 0x0d, 0x58, 0x53, 0x8c, 0xc3, 0x72, 0x98, 0xe1, 0x32, 0xff, 0x02, 0xaf, 0x0f, 0x17, 0x71, 0xf4, + 0x01, 0x20, 0xcb, 0x71, 0xbc, 0x97, 0x24, 0xa0, 0xce, 0x88, 0xa8, 0xbd, 0xac, 0xac, 0xed, 0x68, + 0xf5, 0x1c, 0xd6, 0x85, 0xa4, 0x47, 0x9d, 0x91, 0x52, 0x8f, 0x3e, 0x81, 0x92, 0xb0, 0x69, 0x44, + 0x2d, 0x36, 0xf3, 0x69, 0x50, 0xd1, 0x77, 0xd2, 0xf5, 0xf2, 0xde, 0xba, 0x5a, 0xc8, 0x81, 0x84, + 0xf7, 0x6d, 0x86, 0x8b, 0x9c, 0xa7, 0xbe, 0x03, 0xb4, 0x0d, 0xf9, 0x89, 0xf5, 0x8a, 0x4c, 0x2d, + 0x9f, 0x05, 0x95, 0xf5, 0x1d, 0xad, 0x5e, 0xc2, 0xb9, 0x89, 0xf5, 0xea, 0x98, 0x7f, 0xa3, 0x5d, + 0xd8, 0x70, 0x3d, 0x62, 0xbb, 0x23, 0xc7, 0x3e, 0x1b, 0x33, 0x32, 0x9b, 0x0e, 0x2d, 0x46, 0x83, + 0x0a, 0x12, 0x36, 0xac, 0xbb, 0x9e, 0xa9, 0x24, 0x27, 0x52, 0x80, 0x3e, 0x84, 0x0d, 0xae, 0x2c, + 0x18, 0x5b, 0xfe, 0x90, 0x04, 0xf6, 0x0f, 0x54, 0x46, 0xc6, 0x0d, 0xbe, 0xe3, 0x58, 0x9f, 0x58, + 0xaf, 0x7a, 0x5c, 0xd2, 0xb3, 0x7f, 0xa0, 0x3c, 0x3a, 0xaa, 0x2d, 0xb8, 0xb9, 0xdc, 0x1d, 0x3c, + 0xe1, 0xf8, 0x7e, 0x6a, 0x62, 0x20, 0xff, 0x89, 0x36, 0x61, 0xe5, 0x85, 0xe5, 0xcc, 0xa8, 0x48, + 0xc2, 0x22, 0x96, 0x1f, 0x3f, 0x4b, 0x7d, 0xa6, 0xd5, 0xc6, 0xb0, 0xd1, 0xf7, 0xad, 0xc1, 0xf9, + 0x42, 0x1e, 0x2f, 0xa6, 0xa1, 0x76, 0x39, 0x0d, 0xaf, 0x58, 0x5e, 0xea, 0x8a, 0xe5, 0xd5, 0xbe, + 0x82, 0x35, 0x11, 0x10, 0x07, 0x94, 0x5e, 0x57, 0x2d, 0xb6, 0x80, 0xd7, 0x02, 0x91, 0x38, 0xb2, + 0x62, 0x64, 0xad, 0x09, 0xcf, 0x99, 0xda, 0x10, 0xf4, 0xf9, 0xf8, 0x60, 0xea, 0xb9, 0x01, 0xe5, + 0xa5, 0x80, 0xc7, 0x0b, 0x0f, 0x78, 0x9e, 0x4f, 0xc2, 0x5f, 0x9a, 0x18, 0x55, 0x56, 0xf8, 0x01, + 0x15, 0xde, 0xe2, 0xf1, 0xce, 0xf3, 0x94, 0x38, 0xde, 0xe0, 0x9c, 0xd7, 0x0c, 0xeb, 0x42, 0xa9, + 0x2f, 0x71, 0xb8, 0xed, 0x0d, 0xce, 0x5b, 0x1c, 0xac, 0x3d, 0x97, 0x65, 0xad, 0xef, 0x89, 0xb9, + 0xfe, 0x0d, 0x77, 0xd4, 0x60, 0x45, 0x84, 0xae, 0x50, 0x5b, 0xd8, 0x2b, 0xc6, 0x73, 0x00, 0x4b, + 0x51, 0xed, 0x39, 0x6c, 0x24, 0x94, 0xab, 0x55, 0x54, 0x21, 0x37, 0xf5, 0xa9, 0x3d, 0xb1, 0xce, + 0xa8, 0xd2, 0x1c, 0x7d, 0xa3, 0x3a, 0xac, 0x8e, 0x2c, 0xdb, 0x99, 0xf9, 0xa1, 0xe2, 0x72, 0x18, + 0x93, 0x12, 0xc5, 0xa1, 0xb8, 0x76, 0x0b, 0xaa, 0x98, 0x06, 0x94, 0x1d, 0xd9, 0x41, 0x60, 0x7b, + 0x6e, 0xd3, 0x73, 0x99, 0xef, 0x39, 0x6a, 0x05, 0xb5, 0xdb, 0xb0, 0xbd, 0x54, 0x2a, 0x4d, 0xe0, + 0x83, 0xbf, 0x99, 0x51, 0xff, 0x62, 0xf9, 0xe0, 0x6f, 0x60, 0x7b, 0xa9, 0x54, 0xd9, 0xff, 0x01, + 0xac, 0x4c, 0x2d, 0xdb, 0xe7, 0x7b, 0xcf, 0x73, 0xf8, 0x66, 0x2c, 0x87, 0x8f, 0x2d, 0xdb, 0x3f, + 0xb4, 0x03, 0xe6, 0xf9, 0x17, 0x58, 0x92, 0x1e, 0x67, 0x72, 0x9a, 0x9e, 0xaa, 0xfd, 0x41, 0x83, + 0x42, 0x4c, 0xc8, 0x33, 0xc9, 0xf5, 0x86, 0x94, 0x8c, 0x7c, 0x6f, 0x12, 0x3a, 0x81, 0x03, 0x07, + 0xbe, 0x37, 0xe1, 0x31, 0x21, 0x84, 0xcc, 0x53, 0x01, 0x9c, 0xe5, 0x9f, 0x7d, 0x0f, 0x7d, 0x08, + 0xab, 0x63, 0xa9, 0x40, 0x54, 0xd9, 0xc2, 0xde, 0xc6, 0xc2, 0xdc, 0x2d, 0x8b, 0x59, 0x38, 0xe4, + 0x3c, 0xce, 0xe4, 0xd2, 0x7a, 0xe6, 0x71, 0x26, 0x97, 0xd1, 0x57, 0x1e, 0x67, 0x72, 0x2b, 0x7a, + 0xf6, 0x71, 0x26, 0x97, 0xd5, 0x57, 0x6b, 0xff, 0xd0, 0x20, 0x17, 0xb2, 0xb9, 0x25, 0xdc, 0xa5, + 0x84, 0xc7, 0x85, 0x0a, 0xa6, 0x1c, 0x07, 0xfa, 0xf6, 0x84, 0xa2, 0x1d, 0x28, 0x0a, 0x61, 0x32, + 0x44, 0x81, 0x63, 0x0d, 0x11, 0xa6, 0xa2, 0xfc, 0x87, 0x0c, 0x11, 0x8f, 0x19, 0x55, 0xfe, 0x25, + 0x25, 0x3c, 0xe4, 0x82, 0xd9, 0x60, 0x40, 0x83, 0x40, 0xce, 0xb2, 0x22, 0x29, 0x0a, 0x13, 0x13, + 0xbd, 0x0f, 0x6b, 0x21, 0x25, 0x9c, 0x2b, 0x2b, 0xe3, 0x55, 0xc1, 0x6a, 0xba, 0x3a, 0xe8, 0x71, + 0xde, 0x64, 0x7e, 0xe0, 0x94, 0xe7, 0x44, 0x3e, 0xa9, 0x5c, 0x7c, 0x6d, 0x07, 0xee, 0x3c, 0x5a, + 0x8c, 0x82, 0xa6, 0xe7, 0x8e, 0xec, 0xb3, 0x70, 0xb3, 0xbf, 0x87, 0xbb, 0x57, 0x32, 0xd4, 0x86, + 0x7f, 0x0a, 0xd9, 0x81, 0x40, 0x84, 0x7f, 0x0a, 0x7b, 0x77, 0x63, 0x5e, 0x5f, 0x3a, 0x50, 0xd1, + 0x6b, 0xcf, 0xe0, 0x4e, 0xef, 0xda, 0xd9, 0xff, 0x73, 0xd5, 0x6f, 0xc3, 0xdd, 0xde, 0xf5, 0x66, + 0xd7, 0x7e, 0x9d, 0x82, 0xcd, 0x65, 0x04, 0x7e, 0x70, 0x8e, 0x2d, 0x67, 0x44, 0x1c, 0x7b, 0x44, + 0xa3, 0xd3, 0x5d, 0x96, 0xcf, 0x35, 0x2e, 0x68, 0xdb, 0x23, 0x1a, 0x1e, 0xef, 0xf7, 0x60, 0x4d, + 0x9c, 0x99, 0xbe, 0x77, 0x6a, 0x9d, 0xda, 0x8e, 0xcd, 0x64, 0x21, 0x49, 0xe1, 0xf2, 0xd8, 0x9b, + 0x1e, 0xcf, 0x51, 0x74, 0x13, 0xb2, 0x2f, 0x29, 0x2f, 0x80, 0xa2, 0x87, 0x49, 0x61, 0xf5, 0x85, + 0x3e, 0x81, 0xad, 0x89, 0xf5, 0xca, 0x9e, 0xcc, 0x26, 0x64, 0xde, 0x79, 0x04, 0x33, 0x87, 0x05, + 0x22, 0x54, 0x4a, 0xf8, 0x86, 0x12, 0x47, 0x25, 0x59, 0x08, 0x51, 0x13, 0xee, 0x4c, 0x6c, 0x57, + 0x8c, 0x53, 0x29, 0x4f, 0x7c, 0xea, 0x58, 0xaf, 0x88, 0xed, 0x32, 0xea, 0xbf, 0xb0, 0x1c, 0x11, + 0x46, 0x19, 0xbc, 0xad, 0x58, 0x61, 0x81, 0xe0, 0x1c, 0x53, 0x51, 0x6a, 0xbf, 0x84, 0x2d, 0x91, + 0xc9, 0x31, 0x43, 0x43, 0xcf, 0xf3, 0xb8, 0xf7, 0xbd, 0x09, 0xe1, 0xa9, 0x15, 0x66, 0x20, 0x07, + 0x3a, 0xde, 0x90, 0xf2, 0x0c, 0x64, 0x9e, 0x14, 0xa9, 0x0c, 0x64, 0x9e, 0x10, 0xc4, 0x5b, 0xb9, + 0x74, 0xa2, 0x95, 0xab, 0x9d, 0x43, 0xe5, 0xf2, 0x5c, 0x2a, 0x82, 0x76, 0xa0, 0x10, 0xf7, 0x20, + 0x9f, 0x4e, 0xc3, 0x71, 0x28, 0x9e, 0xda, 0xa9, 0xd7, 0xa7, 0x76, 0xed, 0x6f, 0x1a, 0xac, 0xef, + 0xcf, 0x6c, 0x67, 0x98, 0xa8, 0xdb, 0x71, 0xeb, 0xb4, 0x64, 0xa3, 0xb9, 0xac, 0x8b, 0x4c, 0x2d, + 0xed, 0x22, 0x3f, 0x58, 0xd2, 0x86, 0xa5, 0x45, 0x1b, 0x96, 0x5a, 0xd2, 0x84, 0xdd, 0x85, 0xc2, + 0xbc, 0xa7, 0xe2, 0x5b, 0x9a, 0xae, 0x17, 0x31, 0x8c, 0xc3, 0x86, 0x2a, 0xb8, 0xd4, 0x94, 0xae, + 0x5c, 0x6a, 0x4a, 0x6b, 0x9f, 0x01, 0x8a, 0xaf, 0x45, 0xf9, 0x2c, 0x3a, 0x61, 0xb4, 0xab, 0x4f, + 0x98, 0x5b, 0x50, 0xed, 0xcd, 0x4e, 0x83, 0x81, 0x6f, 0x9f, 0xd2, 0x43, 0xe6, 0x0c, 0x8c, 0x17, + 0xd4, 0x65, 0x41, 0x98, 0xda, 0xff, 0xcc, 0x40, 0x3e, 0x42, 0xf9, 0x01, 0x6e, 0xbb, 0x03, 0x6f, + 0x12, 0xae, 0xcb, 0xa5, 0x0e, 0x5f, 0x9a, 0x8c, 0xfb, 0xf5, 0x50, 0xd4, 0x94, 0x12, 0x73, 0xc8, + 0xf9, 0x09, 0x3f, 0x28, 0x7e, 0x4a, 0xf2, 0xe3, 0x6e, 0x90, 0xfc, 0x3a, 0xe8, 0x91, 0xfe, 0x31, + 0x73, 0x06, 0x91, 0xdf, 0x70, 0x39, 0xc4, 0xb9, 0x31, 0x92, 0x19, 0x69, 0x0e, 0x99, 0x19, 0xc9, + 0x0c, 0x71, 0xc5, 0x7c, 0x1b, 0x8a, 0xbc, 0x62, 0x06, 0xcc, 0x9a, 0x4c, 0x89, 0x1b, 0xa8, 0x90, + 0x2f, 0x44, 0x58, 0x27, 0x40, 0x5f, 0x02, 0x50, 0xbe, 0x3e, 0xc2, 0x2e, 0xa6, 0x54, 0x14, 0xcd, + 0xf2, 0xde, 0x9d, 0x58, 0xec, 0x44, 0x0e, 0xd8, 0x15, 0xff, 0xf6, 0x2f, 0xa6, 0x14, 0xe7, 0x69, + 0xf8, 0x13, 0x7d, 0x05, 0xa5, 0x91, 0xe7, 0xbf, 0xe4, 0x3d, 0x98, 0x00, 0xd5, 0xc1, 0xb2, 0x15, + 0xd3, 0x70, 0x20, 0xe5, 0x62, 0xf8, 0xe1, 0x5b, 0xb8, 0x38, 0x8a, 0x7d, 0xa3, 0x27, 0x80, 0xc2, + 0xf1, 0xe2, 0x1c, 0x90, 0x4a, 0x72, 0x42, 0xc9, 0xf6, 0x65, 0x25, 0x3c, 0x4b, 0x43, 0x45, 0xfa, + 0x68, 0x01, 0x43, 0x9f, 0x43, 0x31, 0xa0, 0x8c, 0x39, 0x54, 0xa9, 0xc9, 0x0b, 0x35, 0x37, 0x13, + 0x4d, 0x32, 0x17, 0x87, 0x1a, 0x0a, 0xc1, 0xfc, 0x13, 0xed, 0xc3, 0x9a, 0x63, 0xbb, 0xe7, 0x71, + 0x33, 0x40, 0x8c, 0xaf, 0xc4, 0xc6, 0xb7, 0x6d, 0xf7, 0x3c, 0x6e, 0x43, 0xc9, 0x89, 0x03, 0xb5, + 0x2f, 0x20, 0x1f, 0x79, 0x09, 0x15, 0x60, 0xf5, 0xa4, 0xf3, 0xa4, 0xd3, 0xfd, 0xb6, 0xa3, 0xbf, + 0x85, 0x72, 0x90, 0xe9, 0x19, 0x9d, 0x96, 0xae, 0x71, 0x18, 0x1b, 0x4d, 0xc3, 0x7c, 0x6a, 0xe8, + 0x29, 0xfe, 0x71, 0xd0, 0xc5, 0xdf, 0x36, 0x70, 0x4b, 0x4f, 0xef, 0xaf, 0xc2, 0x8a, 0x98, 0xb7, + 0xf6, 0x27, 0x0d, 0x72, 0x62, 0x07, 0xdd, 0x91, 0x87, 0x7e, 0x04, 0x51, 0x70, 0x89, 0xe3, 0x8f, + 0xb7, 0x64, 0x22, 0xea, 0x4a, 0x38, 0x0a, 0x98, 0xbe, 0xc2, 0x39, 0x39, 0x0a, 0x8d, 0x88, 0x9c, + 0x92, 0xe4, 0x50, 0x10, 0x91, 0xef, 0xc7, 0x34, 0x27, 0xaa, 0x52, 0x06, 0xaf, 0x85, 0x82, 0xf0, + 0x0c, 0x8e, 0x5f, 0x96, 0x12, 0x67, 0x75, 0xec, 0xb2, 0xa4, 0xb8, 0xb5, 0x4f, 0xa1, 0x18, 0xdf, + 0x73, 0x74, 0x0f, 0x32, 0xb6, 0x3b, 0xf2, 0x54, 0x22, 0x6e, 0x2c, 0x04, 0x17, 0x5f, 0x24, 0x16, + 0x84, 0x1a, 0x02, 0x7d, 0x71, 0x9f, 0x6b, 0x25, 0x28, 0xc4, 0x36, 0xad, 0xf6, 0x77, 0x0d, 0x4a, + 0x89, 0x4d, 0x78, 0x63, 0xed, 0xe8, 0x4b, 0x28, 0xbe, 0xb4, 0x7d, 0x4a, 0xe2, 0x0d, 0x62, 0x79, + 0xaf, 0x9a, 0x6c, 0x10, 0xc3, 0xff, 0x9b, 0xde, 0x90, 0xe2, 0x02, 0xe7, 0x2b, 0x00, 0xfd, 0x1c, + 0xca, 0xe1, 0x41, 0x32, 0xa4, 0xcc, 0xb2, 0x1d, 0xe1, 0xaa, 0x72, 0x22, 0x3c, 0x14, 0xb7, 0x25, + 0xe4, 0xb8, 0x34, 0x8a, 0x7f, 0xa2, 0xf7, 0xe6, 0x0a, 0x02, 0xe6, 0xdb, 0xee, 0x99, 0xf0, 0x5f, + 0x3e, 0xa2, 0xf5, 0x04, 0xc8, 0x5b, 0xbd, 0x92, 0x3a, 0xcb, 0x7a, 0xcc, 0x62, 0x33, 0x7e, 0xd3, + 0x59, 0x09, 0x98, 0xa5, 0x2a, 0x59, 0x39, 0x91, 0x5b, 0x31, 0x22, 0xc5, 0x92, 0x95, 0xe8, 0x8f, + 0x53, 0x97, 0xfa, 0xe3, 0x15, 0x5e, 0x31, 0x64, 0xa1, 0x2d, 0xec, 0x21, 0xb5, 0xf8, 0xc3, 0x7e, + 0xbb, 0xd9, 0x60, 0x8c, 0x4e, 0xa6, 0x0c, 0x4b, 0x82, 0xea, 0x7f, 0xbe, 0x02, 0x68, 0xda, 0xfe, + 0x60, 0x66, 0xb3, 0x27, 0xf4, 0x82, 0x1f, 0x6b, 0x61, 0x45, 0x97, 0x65, 0x2f, 0x3b, 0x90, 0x55, + 0x7c, 0x0b, 0x56, 0xc3, 0x42, 0x24, 0xeb, 0x5b, 0x76, 0x2c, 0x0a, 0x50, 0xed, 0xcf, 0x19, 0xd8, + 0x56, 0x5b, 0x2a, 0x77, 0x83, 0x51, 0x7f, 0x40, 0xa7, 0xd1, 0xc5, 0xe9, 0x11, 0x6c, 0xce, 0x8b, + 0xaa, 0x9c, 0x88, 0x84, 0x97, 0xb1, 0xc2, 0xde, 0x8d, 0xd8, 0x4a, 0xe7, 0x66, 0x60, 0x14, 0x15, + 0xdb, 0xb9, 0x69, 0x0f, 0x63, 0x8a, 0xac, 0x89, 0x37, 0x73, 0x55, 0x88, 0xca, 0x8a, 0x87, 0xe6, + 0xe1, 0xcc, 0x45, 0x22, 0xa2, 0xef, 0x41, 0x14, 0xe4, 0x84, 0xbe, 0x9a, 0xda, 0xfe, 0x85, 0xa8, + 0x7e, 0xa5, 0x79, 0xb9, 0x35, 0x04, 0x7a, 0xe9, 0x36, 0x93, 0xba, 0x7c, 0x9b, 0xf9, 0x1c, 0xaa, + 0x51, 0x76, 0xa8, 0x77, 0x11, 0x3a, 0x8c, 0x4e, 0xbf, 0x55, 0x61, 0xc3, 0x56, 0xc8, 0xc0, 0x21, + 0x41, 0x1d, 0x81, 0x0f, 0x61, 0x33, 0x96, 0x5a, 0x73, 0xd3, 0x65, 0x26, 0xa2, 0x79, 0x76, 0xc5, + 0x4d, 0x8f, 0x46, 0x28, 0xd3, 0x65, 0x2f, 0x14, 0xd5, 0x7f, 0x65, 0xfa, 0x2f, 0xa0, 0xbc, 0xf0, + 0x6e, 0x90, 0x13, 0xfb, 0xfe, 0xd3, 0xcb, 0x95, 0x75, 0xd9, 0xf6, 0xec, 0x2e, 0x79, 0x3c, 0x28, + 0x0d, 0x12, 0x0f, 0x07, 0xb7, 0x01, 0x3c, 0xd7, 0xf6, 0x5c, 0x72, 0xea, 0x78, 0xa7, 0xa2, 0xe0, + 0x16, 0x71, 0x5e, 0x20, 0xfb, 0x8e, 0x77, 0x5a, 0xfd, 0x1a, 0xd0, 0x7f, 0x79, 0xe3, 0xfe, 0x8b, + 0x06, 0xb7, 0x96, 0x9b, 0xa8, 0xce, 0xf9, 0xff, 0x59, 0x08, 0x7d, 0x0e, 0x59, 0x6b, 0xc0, 0x6c, + 0xcf, 0x55, 0x95, 0xe1, 0x9d, 0xd8, 0x50, 0x4c, 0x03, 0xcf, 0x79, 0x41, 0x0f, 0x3d, 0x67, 0xa8, + 0x8c, 0x69, 0x08, 0x2a, 0x56, 0x43, 0x12, 0x49, 0x97, 0x4e, 0x26, 0x5d, 0xed, 0xb7, 0x1a, 0x6c, + 0xc9, 0x6b, 0x3d, 0xdf, 0x71, 0x99, 0xd4, 0x61, 0x02, 0xec, 0x01, 0x88, 0x30, 0x99, 0x7a, 0xb6, + 0xcb, 0xa2, 0x1a, 0x26, 0xb3, 0x52, 0xf5, 0x06, 0xc7, 0x5c, 0x84, 0xf3, 0x9c, 0x26, 0x7e, 0xa2, + 0x8f, 0x17, 0x0c, 0x8d, 0x9f, 0x93, 0xf3, 0x19, 0x92, 0x06, 0xd6, 0xaa, 0x50, 0xb9, 0x6c, 0x83, + 0x74, 0xe1, 0xfd, 0xdf, 0x64, 0xa0, 0x94, 0x28, 0x5d, 0xc9, 0xb3, 0xab, 0x04, 0xf9, 0x4e, 0x97, + 0xb4, 0x8c, 0x7e, 0xc3, 0x6c, 0xeb, 0x1a, 0xd2, 0xa1, 0xd8, 0xed, 0x98, 0xdd, 0x0e, 0x69, 0x19, + 0xcd, 0x6e, 0x8b, 0x9f, 0x62, 0x37, 0x60, 0xbd, 0x6d, 0x76, 0x9e, 0x90, 0x4e, 0xb7, 0x4f, 0x8c, + 0xb6, 0xf9, 0xc8, 0xdc, 0x6f, 0x1b, 0x7a, 0x1a, 0x6d, 0x82, 0xde, 0xed, 0x90, 0xe6, 0x61, 0xc3, + 0xec, 0x90, 0xbe, 0x79, 0x64, 0x74, 0x4f, 0xfa, 0x7a, 0x86, 0xa3, 0xbc, 0xdc, 0x10, 0xe3, 0xbb, + 0xa6, 0x61, 0xb4, 0x7a, 0xe4, 0xa8, 0xf1, 0x9d, 0xbe, 0x82, 0x2a, 0xb0, 0x69, 0x76, 0x7a, 0x27, + 0x07, 0x07, 0x66, 0xd3, 0x34, 0x3a, 0x7d, 0xb2, 0xdf, 0x68, 0x37, 0x3a, 0x4d, 0x43, 0xcf, 0xa2, + 0x9b, 0x80, 0xcc, 0x4e, 0xb3, 0x7b, 0x74, 0xdc, 0x36, 0xfa, 0x06, 0x09, 0x4f, 0xcb, 0x55, 0xb4, + 0x01, 0x6b, 0x42, 0x4f, 0xa3, 0xd5, 0x22, 0x07, 0x0d, 0xb3, 0x6d, 0xb4, 0xf4, 0x1c, 0xb7, 0x44, + 0x31, 0x7a, 0xa4, 0x65, 0xf6, 0x1a, 0xfb, 0x1c, 0xce, 0xf3, 0x39, 0xcd, 0xce, 0xd3, 0xae, 0xd9, + 0x34, 0x48, 0x93, 0xab, 0xe5, 0x28, 0x70, 0x72, 0x88, 0x9e, 0x74, 0x5a, 0x06, 0x3e, 0x6e, 0x98, + 0x2d, 0xbd, 0x80, 0xb6, 0x61, 0x2b, 0x84, 0x8d, 0xef, 0x8e, 0x4d, 0xfc, 0x8c, 0xf4, 0xbb, 0x5d, + 0xd2, 0xeb, 0x76, 0x3b, 0x7a, 0x31, 0xae, 0x89, 0xaf, 0xb6, 0x7b, 0x6c, 0x74, 0xf4, 0x12, 0xda, + 0x82, 0x8d, 0xa3, 0xe3, 0x63, 0x12, 0x4a, 0xc2, 0xc5, 0x96, 0x39, 0xbd, 0xd1, 0x6a, 0x61, 0xa3, + 0xd7, 0x23, 0x47, 0x66, 0xef, 0xa8, 0xd1, 0x6f, 0x1e, 0xea, 0x6b, 0x7c, 0x49, 0x3d, 0xa3, 0x4f, + 0xfa, 0xdd, 0x7e, 0xa3, 0x3d, 0xc7, 0x75, 0x6e, 0xd0, 0x1c, 0xe7, 0x93, 0xb6, 0xbb, 0xdf, 0xea, + 0xeb, 0xdc, 0xe1, 0x1c, 0xee, 0x3e, 0x55, 0x26, 0x22, 0xbe, 0x76, 0xb5, 0x3d, 0xe1, 0x9c, 0xfa, + 0x06, 0x07, 0xcd, 0xce, 0xd3, 0x46, 0xdb, 0x6c, 0x91, 0x27, 0xc6, 0x33, 0xd1, 0x6d, 0x6c, 0x72, + 0x50, 0x5a, 0x46, 0x8e, 0x71, 0xf7, 0x11, 0x37, 0x44, 0xbf, 0x81, 0x10, 0x94, 0x9b, 0x26, 0x6e, + 0x9e, 0xb4, 0x1b, 0x98, 0xe0, 0xee, 0x49, 0xdf, 0xd0, 0x6f, 0xde, 0xff, 0xa3, 0x06, 0xc5, 0xf8, + 0x69, 0xc2, 0x77, 0xdd, 0xec, 0x90, 0x83, 0xb6, 0xf9, 0xe8, 0xb0, 0x2f, 0x83, 0xa0, 0x77, 0xd2, + 0xe4, 0x5b, 0x66, 0xf0, 0x2e, 0x06, 0x41, 0x59, 0x3a, 0x3d, 0x5a, 0x6c, 0x8a, 0xcf, 0xa5, 0xb0, + 0x4e, 0x57, 0xe9, 0x4d, 0x73, 0xe3, 0x15, 0x68, 0x60, 0xdc, 0xc5, 0x7a, 0x06, 0xbd, 0x0b, 0x3b, + 0x0a, 0xe1, 0xfb, 0x8a, 0xb1, 0xd1, 0xec, 0x93, 0xe3, 0xc6, 0xb3, 0x23, 0xbe, 0xed, 0x32, 0xc8, + 0x7a, 0xfa, 0x0a, 0xba, 0x0b, 0xdb, 0x11, 0x6b, 0x59, 0x5c, 0xdc, 0xff, 0x02, 0x2a, 0x57, 0x65, + 0x25, 0x02, 0xc8, 0xf6, 0x8c, 0x7e, 0xbf, 0x6d, 0xc8, 0xce, 0xeb, 0x40, 0x06, 0x2e, 0x40, 0x16, + 0x1b, 0xbd, 0x93, 0x23, 0x43, 0x4f, 0xdd, 0xff, 0x09, 0xe8, 0x8b, 0xa9, 0xc2, 0xe5, 0x46, 0x87, + 0x87, 0x8c, 0xfe, 0x16, 0x4f, 0x00, 0x15, 0x3f, 0xba, 0xc6, 0x55, 0x34, 0x4e, 0xfa, 0x5d, 0x3d, + 0xb5, 0xf7, 0xbb, 0x02, 0x64, 0xc5, 0x0d, 0xc2, 0x47, 0x5f, 0x43, 0x29, 0xf6, 0xe4, 0xfa, 0x74, + 0x0f, 0xdd, 0xbe, 0xf6, 0x31, 0xb6, 0x1a, 0xbe, 0x44, 0x29, 0xf8, 0xa1, 0x86, 0xf6, 0xa1, 0x1c, + 0x7f, 0x4c, 0x7c, 0xba, 0x87, 0xe2, 0x8d, 0xf7, 0x92, 0x77, 0xc6, 0x25, 0x3a, 0x9e, 0x80, 0x6e, + 0x04, 0xcc, 0x9e, 0xf0, 0xf3, 0x5f, 0x3d, 0xf7, 0xa1, 0x6a, 0xbc, 0x70, 0x25, 0xdf, 0x10, 0xab, + 0xdb, 0x4b, 0x65, 0xaa, 0x94, 0x7e, 0xc3, 0x7b, 0xad, 0xe8, 0xc1, 0xed, 0xd2, 0x82, 0x92, 0xaf, + 0x7c, 0xd5, 0x3b, 0x57, 0x89, 0xd5, 0xfb, 0x41, 0xfa, 0xf7, 0x29, 0xbe, 0xc6, 0x52, 0x4c, 0xb6, + 0xc4, 0x4b, 0x0b, 0x4a, 0x97, 0x74, 0x24, 0x68, 0x08, 0x1b, 0x4b, 0x1e, 0xe3, 0xd0, 0x7b, 0xc9, + 0xfa, 0x7c, 0xc5, 0x53, 0x5e, 0xf5, 0xfd, 0xd7, 0xd1, 0xd4, 0xe2, 0x87, 0xb0, 0xb1, 0xe4, 0xd5, + 0x2e, 0x31, 0xcb, 0xd5, 0x6f, 0x7e, 0x89, 0x59, 0xae, 0x7b, 0xfc, 0x9b, 0xc2, 0xd6, 0x15, 0xcf, + 0x45, 0xe8, 0xff, 0x63, 0x2a, 0xae, 0x7f, 0x74, 0xaa, 0xde, 0x7f, 0x13, 0xea, 0x7c, 0xc6, 0xde, + 0x1b, 0xcc, 0xd8, 0x7b, 0xf3, 0x19, 0x5f, 0xf3, 0x70, 0x84, 0x9e, 0x83, 0xbe, 0xf8, 0x92, 0x81, + 0x6a, 0x8b, 0xfe, 0xb9, 0xfc, 0xa4, 0x52, 0x7d, 0xe7, 0x5a, 0x8e, 0x52, 0x6e, 0x02, 0xcc, 0x2f, + 0xfb, 0xe8, 0x56, 0x6c, 0xc8, 0xa5, 0xf7, 0x8c, 0xea, 0xed, 0x2b, 0xa4, 0x4a, 0x55, 0x1f, 0x36, + 0x96, 0xdc, 0xfe, 0x13, 0x3b, 0x7e, 0xf5, 0xeb, 0x40, 0x75, 0x73, 0xd9, 0x25, 0xf9, 0xa1, 0x86, + 0x8e, 0x64, 0x12, 0x85, 0x7f, 0x2b, 0x79, 0x4d, 0x55, 0xa8, 0x2c, 0x6f, 0xe6, 0x67, 0x81, 0x48, + 0x9f, 0x87, 0x1a, 0xea, 0x42, 0x31, 0x5e, 0x09, 0x5e, 0x5b, 0x22, 0x5e, 0xab, 0x70, 0x04, 0x6b, + 0x89, 0x46, 0xca, 0xf3, 0xd1, 0xbd, 0xd7, 0xb6, 0x83, 0xd2, 0x63, 0x89, 0x28, 0xbf, 0xa6, 0x6f, + 0xac, 0xf3, 0x79, 0x9e, 0x83, 0xbe, 0xd8, 0x70, 0x24, 0xa2, 0xe0, 0x8a, 0x8e, 0x28, 0x11, 0x05, + 0x57, 0x75, 0x2c, 0xfb, 0x1f, 0x7d, 0xff, 0xe0, 0xcc, 0x66, 0xe3, 0xd9, 0xe9, 0xee, 0xc0, 0x9b, + 0x3c, 0x10, 0x7f, 0x38, 0x71, 0x6d, 0xf7, 0xcc, 0xa5, 0xec, 0xa5, 0xe7, 0x9f, 0x3f, 0x70, 0xdc, + 0xe1, 0x03, 0x51, 0x47, 0x1e, 0x44, 0xba, 0x4e, 0xb3, 0xe2, 0x2f, 0xb1, 0x1f, 0xff, 0x2b, 0x00, + 0x00, 0xff, 0xff, 0xe2, 0xcb, 0x48, 0x96, 0xb9, 0x1d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2567,6 +2682,12 @@ type RouterClient interface { //In case of interception, the htlc can be either settled, cancelled or //resumed later by using the ResolveHoldForward endpoint. HtlcInterceptor(ctx context.Context, opts ...grpc.CallOption) (Router_HtlcInterceptorClient, error) + // + //UpdateChanStatus attempts to manually set the state of a channel + //(enabled, disabled, or auto). A manual "disable" request will cause the + //channel to stay disabled until a subsequent manual request of either + //"enable" or "auto". + UpdateChanStatus(ctx context.Context, in *UpdateChanStatusRequest, opts ...grpc.CallOption) (*UpdateChanStatusResponse, error) } type routerClient struct { @@ -2852,6 +2973,15 @@ func (x *routerHtlcInterceptorClient) Recv() (*ForwardHtlcInterceptRequest, erro return m, nil } +func (c *routerClient) UpdateChanStatus(ctx context.Context, in *UpdateChanStatusRequest, opts ...grpc.CallOption) (*UpdateChanStatusResponse, error) { + out := new(UpdateChanStatusResponse) + err := c.cc.Invoke(ctx, "/routerrpc.Router/UpdateChanStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RouterServer is the server API for Router service. type RouterServer interface { // @@ -2924,6 +3054,12 @@ type RouterServer interface { //In case of interception, the htlc can be either settled, cancelled or //resumed later by using the ResolveHoldForward endpoint. HtlcInterceptor(Router_HtlcInterceptorServer) error + // + //UpdateChanStatus attempts to manually set the state of a channel + //(enabled, disabled, or auto). A manual "disable" request will cause the + //channel to stay disabled until a subsequent manual request of either + //"enable" or "auto". + UpdateChanStatus(context.Context, *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) } // UnimplementedRouterServer can be embedded to have forward compatible implementations. @@ -2975,6 +3111,9 @@ func (*UnimplementedRouterServer) TrackPayment(req *TrackPaymentRequest, srv Rou func (*UnimplementedRouterServer) HtlcInterceptor(srv Router_HtlcInterceptorServer) error { return status.Errorf(codes.Unimplemented, "method HtlcInterceptor not implemented") } +func (*UnimplementedRouterServer) UpdateChanStatus(ctx context.Context, req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateChanStatus not implemented") +} func RegisterRouterServer(s *grpc.Server, srv RouterServer) { s.RegisterService(&_Router_serviceDesc, srv) @@ -3273,6 +3412,24 @@ func (x *routerHtlcInterceptorServer) Recv() (*ForwardHtlcInterceptResponse, err return m, nil } +func _Router_UpdateChanStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateChanStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).UpdateChanStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/routerrpc.Router/UpdateChanStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).UpdateChanStatus(ctx, req.(*UpdateChanStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Router_serviceDesc = grpc.ServiceDesc{ ServiceName: "routerrpc.Router", HandlerType: (*RouterServer)(nil), @@ -3313,6 +3470,10 @@ var _Router_serviceDesc = grpc.ServiceDesc{ MethodName: "BuildRoute", Handler: _Router_BuildRoute_Handler, }, + { + MethodName: "UpdateChanStatus", + Handler: _Router_UpdateChanStatus_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index ccf8a993d..eec976f2c 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -121,6 +121,15 @@ service Router { */ rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) returns (stream ForwardHtlcInterceptRequest); + + /* + UpdateChanStatus attempts to manually set the state of a channel + (enabled, disabled, or auto). A manual "disable" request will cause the + channel to stay disabled until a subsequent manual request of either + "enable" or "auto". + */ + rpc UpdateChanStatus (UpdateChanStatusRequest) + returns (UpdateChanStatusResponse); } message SendPaymentRequest { @@ -750,3 +759,18 @@ enum ResolveHoldForwardAction { FAIL = 1; RESUME = 2; } + +message UpdateChanStatusRequest { + lnrpc.ChannelPoint chan_point = 1; + + ChanStatusAction action = 2; +} + +enum ChanStatusAction { + ENABLE = 0; + DISABLE = 1; + AUTO = 2; +} + +message UpdateChanStatusResponse { +} diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index 265046c28..ba9c07237 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -407,6 +407,25 @@ ], "default": "IN_FLIGHT" }, + "lnrpcChannelPoint": { + "type": "object", + "properties": { + "funding_txid_bytes": { + "type": "string", + "format": "byte", + "description": "Txid of the funding transaction. When using REST, this field must be\nencoded as base64." + }, + "funding_txid_str": { + "type": "string", + "description": "Hex-encoded string representing the byte-reversed hash of the funding\ntransaction." + }, + "output_index": { + "type": "integer", + "format": "int64", + "title": "The index of the output of the funding transaction" + } + } + }, "lnrpcChannelUpdate": { "type": "object", "properties": { @@ -883,6 +902,15 @@ } } }, + "routerrpcChanStatusAction": { + "type": "string", + "enum": [ + "ENABLE", + "DISABLE", + "AUTO" + ], + "default": "ENABLE" + }, "routerrpcCircuitKey": { "type": "object", "properties": { @@ -1447,6 +1475,9 @@ "routerrpcSettleEvent": { "type": "object" }, + "routerrpcUpdateChanStatusResponse": { + "type": "object" + }, "runtimeError": { "type": "object", "properties": { diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index e3c0a2ae0..c7d1496df 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -697,3 +697,10 @@ func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error { // run the forward interceptor. return newForwardInterceptor(s, stream).run() } + +// UpdateChanStatus allows channel state to be set manually. +func (s *Server) UpdateChanStatus(ctx context.Context, + req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) { + + return nil, fmt.Errorf("unimplemented") +} From db76b970aca247257fb193f5442600cd8b0b4ba1 Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Fri, 12 Feb 2021 23:29:13 -0800 Subject: [PATCH 09/14] routerrpc: expose SetChannel* methods from Router backend Allow router RPC requests to call into the ChanStatusManager to manually update channel state. --- lnrpc/routerrpc/router_backend.go | 12 +++++++++++- rpcserver.go | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index 7fab73548..6f06890f2 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -9,8 +9,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" @@ -83,6 +83,16 @@ type RouterBackend struct { // InterceptableForwarder exposes the ability to intercept forward events // by letting the router register a ForwardInterceptor. InterceptableForwarder htlcswitch.InterceptableHtlcForwarder + + // SetChannelEnabled exposes the ability to manually enable a channel. + SetChannelEnabled func(wire.OutPoint) error + + // SetChannelDisabled exposes the ability to manually disable a channel + SetChannelDisabled func(wire.OutPoint) error + + // SetChannelAuto exposes the ability to restore automatic channel state + // management after manually setting channel status. + SetChannelAuto func(wire.OutPoint) error } // MissionControl defines the mission control dependencies of routerrpc. diff --git a/rpcserver.go b/rpcserver.go index d8883351a..ea3fc8a0b 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -602,6 +602,13 @@ func newRPCServer(cfg *Config, s *server, macService *macaroons.Service, DefaultFinalCltvDelta: uint16(cfg.Bitcoin.TimeLockDelta), SubscribeHtlcEvents: s.htlcNotifier.SubscribeHtlcEvents, InterceptableForwarder: s.interceptableSwitch, + SetChannelEnabled: func(outpoint wire.OutPoint) error { + return s.chanStatusMgr.RequestEnable(outpoint, true) + }, + SetChannelDisabled: func(outpoint wire.OutPoint) error { + return s.chanStatusMgr.RequestDisable(outpoint, true) + }, + SetChannelAuto: s.chanStatusMgr.RequestAuto, } genInvoiceFeatures := func() *lnwire.FeatureVector { From ce2796257e543c142a3b30e8cdfddae18b6417ec Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Sat, 13 Feb 2021 00:05:33 -0800 Subject: [PATCH 10/14] multi: move GetChanPointFundingTxid from lnd to lnrpc This refactor-only change makes the GetChanPointFundingTxid helper function available from sub-systems outside of the root lnd package. --- lnrpc/rpc_utils.go | 24 ++++++ lntest/itest/lnd_forward_interceptor_test.go | 3 +- lntest/itest/lnd_mpp_test.go | 3 +- lntest/itest/lnd_multi-hop-payments_test.go | 9 +- .../lnd_multi-hop_htlc_aggregation_test.go | 3 +- ...d_multi-hop_htlc_local_chain_claim_test.go | 3 +- .../lnd_multi-hop_htlc_local_timeout_test.go | 3 +- ...ulti-hop_htlc_receiver_chain_claim_test.go | 3 +- ..._multi-hop_htlc_remote_chain_claim_test.go | 3 +- lntest/itest/lnd_test.go | 82 +++++++++---------- rpcserver.go | 35 ++------ 11 files changed, 82 insertions(+), 89 deletions(-) diff --git a/lnrpc/rpc_utils.go b/lnrpc/rpc_utils.go index 5dfb87d7b..db3126d65 100644 --- a/lnrpc/rpc_utils.go +++ b/lnrpc/rpc_utils.go @@ -5,6 +5,7 @@ import ( "errors" "sort" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -88,3 +89,26 @@ func ExtractMinConfs(minConfs int32, spendUnconfirmed bool) (int32, error) { return minConfs, nil } } + +// GetChanPointFundingTxid returns the given channel point's funding txid in +// raw bytes. +func GetChanPointFundingTxid(chanPoint *ChannelPoint) (*chainhash.Hash, error) { + var txid []byte + + // A channel point's funding txid can be get/set as a byte slice or a + // string. In the case it is a string, decode it. + switch chanPoint.GetFundingTxid().(type) { + case *ChannelPoint_FundingTxidBytes: + txid = chanPoint.GetFundingTxidBytes() + case *ChannelPoint_FundingTxidStr: + s := chanPoint.GetFundingTxidStr() + h, err := chainhash.NewHashFromStr(s) + if err != nil { + return nil, err + } + + txid = h[:] + } + + return chainhash.NewHash(txid) +} diff --git a/lntest/itest/lnd_forward_interceptor_test.go b/lntest/itest/lnd_forward_interceptor_test.go index ba18eb2fd..4dbc29afa 100644 --- a/lntest/itest/lnd_forward_interceptor_test.go +++ b/lntest/itest/lnd_forward_interceptor_test.go @@ -9,7 +9,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -341,7 +340,7 @@ func (c *interceptorTestContext) waitForChannels() { // Wait for all nodes to have seen all channels. for _, chanPoint := range c.networkChans { for _, node := range c.nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) require.NoError(c.t.t, err, "unable to get txid") point := wire.OutPoint{ diff --git a/lntest/itest/lnd_mpp_test.go b/lntest/itest/lnd_mpp_test.go index 5a5e8ece2..7ccca9e4f 100644 --- a/lntest/itest/lnd_mpp_test.go +++ b/lntest/itest/lnd_mpp_test.go @@ -8,7 +8,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -376,7 +375,7 @@ func (c *mppTestContext) waitForChannels() { // Wait for all nodes to have seen all channels. for _, chanPoint := range c.networkChans { for _, node := range c.nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { c.t.Fatalf("unable to get txid: %v", err) } diff --git a/lntest/itest/lnd_multi-hop-payments_test.go b/lntest/itest/lnd_multi-hop-payments_test.go index afacc4812..decd8cc2c 100644 --- a/lntest/itest/lnd_multi-hop-payments_test.go +++ b/lntest/itest/lnd_multi-hop-payments_test.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -30,7 +29,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -71,7 +70,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { }, ) networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnd.GetChanPointFundingTxid(chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -106,7 +105,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -120,7 +119,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } diff --git a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go index fa6a46b5e..1e01b505a 100644 --- a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go @@ -7,7 +7,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -187,7 +186,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, expectedTxes = 2 } - bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) + bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) require.NoError(t.t, err) _, err = waitForNTxsInMempool( net.Miner.Node, expectedTxes, minerMempoolTimeout, diff --git a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go index 44a07456a..2e50337e3 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -132,7 +131,7 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, net.Miner.Node, expectedTxes, minerMempoolTimeout, ) require.NoError(t.t, err) - bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) + bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) require.NoError(t.t, err) carolFundingPoint := wire.OutPoint{ Hash: *bobFundingTxid, diff --git a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go index e1230b950..858d969ac 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go @@ -7,7 +7,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -109,7 +108,7 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, expectedTxes = 2 } - bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) + bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) require.NoError(t.t, err) _, err = waitForNTxsInMempool( net.Miner.Node, expectedTxes, minerMempoolTimeout, diff --git a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go index 1aace16d0..57856c828 100644 --- a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go @@ -5,7 +5,6 @@ import ( "time" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -122,7 +121,7 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, ) require.NoError(t.t, err) - bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) + bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) require.NoError(t.t, err) carolFundingPoint := wire.OutPoint{ diff --git a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go index 72a3c63cf..36b98e38f 100644 --- a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go @@ -4,7 +4,6 @@ import ( "context" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -160,7 +159,7 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest net.Miner.Node, expectedTxes, minerMempoolTimeout, ) require.NoError(t.t, err) - bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) + bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) require.NoError(t.t, err) carolFundingPoint := wire.OutPoint{ Hash: *bobFundingTxid, diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 721ea4d78..3e53acf38 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -120,7 +120,7 @@ func getTestCaseSplitTranche() ([]*testCase, uint, uint) { } func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.OutPoint { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -179,7 +179,7 @@ func openChannelAndAssert(ctx context.Context, t *harnessTest, if err != nil { t.Fatalf("error while waiting for channel open: %v", err) } - fundingTxID, err := lnd.GetChanPointFundingTxid(fundingChanPoint) + fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -291,7 +291,7 @@ func assertChannelClosed(ctx context.Context, t *harnessTest, fundingChanPoint *lnrpc.ChannelPoint, anchors bool, closeUpdates lnrpc.Lightning_CloseChannelClient) *chainhash.Hash { - txid, err := lnd.GetChanPointFundingTxid(fundingChanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -387,7 +387,7 @@ func assertChannelClosed(ctx context.Context, t *harnessTest, func waitForChannelPendingForceClose(ctx context.Context, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint) error { - txid, err := lnd.GetChanPointFundingTxid(fundingChanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) if err != nil { return err } @@ -1722,7 +1722,7 @@ func testPaymentFollowingChannelOpen(net *lntest.NetworkHarness, t *harnessTest) // txStr returns the string representation of the channel's funding transaction. func txStr(chanPoint *lnrpc.ChannelPoint) string { - fundingTxID, err := lnd.GetChanPointFundingTxid(chanPoint) + fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { return "" } @@ -3692,7 +3692,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // Compute the outpoint of the channel, which we will use repeatedly to // locate the pending channel information in the rpc responses. - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -5534,7 +5534,7 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -5547,7 +5547,7 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, nodes := []*lntest.HarnessNode{carol, dave} for _, chanPoint := range networkChans { for _, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -5870,7 +5870,7 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -5905,7 +5905,7 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { }, ) networkChans = append(networkChans, chanPointBob) - bobChanTXID, err := lnd.GetChanPointFundingTxid(chanPointBob) + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -5919,7 +5919,7 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { nodeNames := []string{"Alice", "Bob", "Carol"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6302,7 +6302,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6335,7 +6335,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { }, ) networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnd.GetChanPointFundingTxid(chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6370,7 +6370,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6385,7 +6385,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6430,7 +6430,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { if err != nil { t.Fatalf("error while waiting for channel open: %v", err) } - fundingTxID, err := lnd.GetChanPointFundingTxid(chanPointPrivate) + fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPointPrivate) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6876,7 +6876,7 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) } // Retrieve Alice's funding outpoint. - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6925,7 +6925,7 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) } // Retrieve Bob's funding outpoint. - bobChanTXID, err := lnd.GetChanPointFundingTxid(chanPointBob) + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -6980,7 +6980,7 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) } // Retrieve Carol's funding point. - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -7651,7 +7651,7 @@ func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("error while waiting for channel open: %v", err) } - fundingTxID, err := lnd.GetChanPointFundingTxid(fundingChanPoint) + fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -10529,11 +10529,11 @@ out: "expected %v, got %v", blockHeight+1, closedChan.ClosedHeight) } - chanPointTxid, err := lnd.GetChanPointFundingTxid(chanPoint) + chanPointTxid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } - closedChanTxid, err := lnd.GetChanPointFundingTxid( + closedChanTxid, err := lnrpc.GetChanPointFundingTxid( closedChan.ChanPoint, ) if err != nil { @@ -11339,7 +11339,7 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11379,7 +11379,7 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { }, ) networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnd.GetChanPointFundingTxid(chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11416,7 +11416,7 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11430,7 +11430,7 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11659,7 +11659,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11699,7 +11699,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { }, ) networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnd.GetChanPointFundingTxid(chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11736,7 +11736,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11750,7 +11750,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -11980,7 +11980,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12021,7 +12021,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness ) networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnd.GetChanPointFundingTxid(chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12058,7 +12058,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12072,7 +12072,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12313,7 +12313,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( ) networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnd.GetChanPointFundingTxid(chanPointAlice) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12353,7 +12353,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( }, ) networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnd.GetChanPointFundingTxid(chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12388,7 +12388,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( ) networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnd.GetChanPointFundingTxid(chanPointCarol) + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12402,7 +12402,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12643,7 +12643,7 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -12904,7 +12904,7 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { } for _, chanPoint := range networkChans { for i, node := range nodes { - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } @@ -13345,7 +13345,7 @@ func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) { chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, channelParam, ) - txid, err := lnd.GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } diff --git a/rpcserver.go b/rpcserver.go index ea3fc8a0b..21ddd81b7 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1737,7 +1737,7 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool, // First, we'll map the RPC's channel point to one we can actually use. index := chanPointShim.ChanPoint.OutputIndex - txid, err := GetChanPointFundingTxid(chanPointShim.ChanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPointShim.ChanPoint) if err != nil { return nil, err } @@ -2086,7 +2086,7 @@ out: update, ok := fundingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanOpen) if ok { chanPoint := update.ChanOpen.ChannelPoint - txid, err := GetChanPointFundingTxid(chanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { return err } @@ -2154,29 +2154,6 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context, } } -// GetChanPointFundingTxid returns the given channel point's funding txid in -// raw bytes. -func GetChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) (*chainhash.Hash, error) { - var txid []byte - - // A channel point's funding txid can be get/set as a byte slice or a - // string. In the case it is a string, decode it. - switch chanPoint.GetFundingTxid().(type) { - case *lnrpc.ChannelPoint_FundingTxidBytes: - txid = chanPoint.GetFundingTxidBytes() - case *lnrpc.ChannelPoint_FundingTxidStr: - s := chanPoint.GetFundingTxidStr() - h, err := chainhash.NewHashFromStr(s) - if err != nil { - return nil, err - } - - txid = h[:] - } - - return chainhash.NewHash(txid) -} - // CloseChannel attempts to close an active channel identified by its channel // point. The actions of this method can additionally be augmented to attempt // a force close after a timeout period in the case of an inactive peer. @@ -2201,7 +2178,7 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, force := in.Force index := in.ChannelPoint.OutputIndex - txid, err := GetChanPointFundingTxid(in.GetChannelPoint()) + txid, err := lnrpc.GetChanPointFundingTxid(in.GetChannelPoint()) if err != nil { rpcsLog.Errorf("[closechannel] unable to get funding txid: %v", err) return err @@ -2484,7 +2461,7 @@ func (r *rpcServer) AbandonChannel(_ context.Context, // We'll parse out the arguments to we can obtain the chanPoint of the // target channel. - txid, err := GetChanPointFundingTxid(in.GetChannelPoint()) + txid, err := lnrpc.GetChanPointFundingTxid(in.GetChannelPoint()) if err != nil { return nil, err } @@ -5997,7 +5974,7 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context, // Otherwise, we're targeting an individual channel by its channel // point. case *lnrpc.PolicyUpdateRequest_ChanPoint: - txid, err := GetChanPointFundingTxid(scope.ChanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(scope.ChanPoint) if err != nil { return nil, err } @@ -6172,7 +6149,7 @@ func (r *rpcServer) ExportChannelBackup(ctx context.Context, // First, we'll convert the lnrpc channel point into a wire.OutPoint // that we can manipulate. - txid, err := GetChanPointFundingTxid(in.ChanPoint) + txid, err := lnrpc.GetChanPointFundingTxid(in.ChanPoint) if err != nil { return nil, err } From 6d01f140d93a12506077909f0d2fbbe032201edb Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Sat, 13 Feb 2021 00:14:54 -0800 Subject: [PATCH 11/14] routerrpc: implement UpdateChanStatus RPC in router server Update UpdateChanStatus stub implementation to call into exposed ChanStatusManager methods in RouterBackend. --- lnrpc/routerrpc/router_server.go | 41 +++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index c7d1496df..e9233716e 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/lightningnetwork/lnd/channeldb" @@ -115,6 +116,10 @@ var ( Entity: "offchain", Action: "write", }}, + "/routerrpc.Router/UpdateChanStatus": {{ + Entity: "offchain", + Action: "write", + }}, } // DefaultRouterMacFilename is the default name of the router macaroon @@ -698,9 +703,43 @@ func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error { return newForwardInterceptor(s, stream).run() } +func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) { + chanPoint := req.GetChanPoint() + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + return nil, err + } + index := chanPoint.OutputIndex + return wire.NewOutPoint(txid, index), nil +} + // UpdateChanStatus allows channel state to be set manually. func (s *Server) UpdateChanStatus(ctx context.Context, req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) { - return nil, fmt.Errorf("unimplemented") + outPoint, err := extractOutPoint(req) + if err != nil { + return nil, err + } + + action := req.GetAction() + + log.Debugf("UpdateChanStatus called for channel(%v) with "+ + "action %v", outPoint, action) + + switch action { + case ChanStatusAction_ENABLE: + err = s.cfg.RouterBackend.SetChannelEnabled(*outPoint) + case ChanStatusAction_DISABLE: + err = s.cfg.RouterBackend.SetChannelDisabled(*outPoint) + case ChanStatusAction_AUTO: + err = s.cfg.RouterBackend.SetChannelAuto(*outPoint) + default: + err = fmt.Errorf("unrecognized ChannelStatusAction %v", action) + } + + if err != nil { + return nil, err + } + return &UpdateChanStatusResponse{}, nil } From 19e9ed5cdf96f6afdf0a17a9e80ae1917e1aae93 Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Sat, 13 Feb 2021 00:35:07 -0800 Subject: [PATCH 12/14] lncli: add updatechanstatus command The updatechanstatus command calls into the UpdateChanStatus RPC in the router server. --- cmd/lncli/cmd_update_chan_status.go | 94 +++++++++++++++++++++++++++++ cmd/lncli/routerrpc.go | 1 + 2 files changed, 95 insertions(+) create mode 100644 cmd/lncli/cmd_update_chan_status.go diff --git a/cmd/lncli/cmd_update_chan_status.go b/cmd/lncli/cmd_update_chan_status.go new file mode 100644 index 000000000..e1d08f073 --- /dev/null +++ b/cmd/lncli/cmd_update_chan_status.go @@ -0,0 +1,94 @@ +package main + +import ( + "context" + "errors" + + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/urfave/cli" +) + +var updateChanStatusCommand = cli.Command{ + Name: "updatechanstatus", + Category: "Channels", + Usage: "Set the status of an existing channel on the network.", + Description: ` + Set the status of an existing channel on the network. The actions can + be "enable", "disable", or "auto". If the action changes the status, a + message will be broadcast over the network. + + Note that enabling / disabling a channel using this command ONLY affects + what's advertised over the network. For example, disabling a channel + using this command does not close it. + + If a channel is manually disabled, automatic / background requests to + re-enable the channel will be ignored. However, if a channel is + manually enabled, automatic / background requests to disable the + channel will succeed (such requests are usually made on channel close + or when the peer is down). + + The "auto" action restores automatic channel state management. Per + the behavior described above, it's only needed to undo the effect of + a prior "disable" action, and will be a no-op otherwise.`, + ArgsUsage: "funding_txid [output_index] action", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "funding_txid", + Usage: "the txid of the channel's funding transaction", + }, + cli.IntFlag{ + Name: "output_index", + Usage: "the output index for the funding output of the funding " + + "transaction", + }, + cli.StringFlag{ + Name: "action", + Usage: `the action to take: must be one of "enable", "disable", ` + + `or "auto"`, + }, + }, + Action: actionDecorator(updateChanStatus), +} + +func updateChanStatus(ctx *cli.Context) error { + conn := getClientConn(ctx, false) + defer conn.Close() + + if ctx.NArg() == 0 && ctx.NumFlags() == 0 { + _ = cli.ShowCommandHelp(ctx, "updatechanstatus") + return nil + } + + channelPoint, err := parseChannelPoint(ctx) + if err != nil { + return err + } + + var action routerrpc.ChanStatusAction + switch ctx.String("action") { + case "enable": + action = routerrpc.ChanStatusAction_ENABLE + case "disable": + action = routerrpc.ChanStatusAction_DISABLE + case "auto": + action = routerrpc.ChanStatusAction_AUTO + default: + return errors.New(`action must be one of "enable", "disable", ` + + `or "auto"`) + } + req := &routerrpc.UpdateChanStatusRequest{ + ChanPoint: channelPoint, + Action: action, + } + + client := routerrpc.NewRouterClient(conn) + ctxb := context.Background() + resp, err := client.UpdateChanStatus(ctxb, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} diff --git a/cmd/lncli/routerrpc.go b/cmd/lncli/routerrpc.go index 04c9676ec..4cea75be4 100644 --- a/cmd/lncli/routerrpc.go +++ b/cmd/lncli/routerrpc.go @@ -11,5 +11,6 @@ func routerCommands() []cli.Command { buildRouteCommand, getCfgCommand, setCfgCommand, + updateChanStatusCommand, } } From 5a54d787e106661da308bc07a206fee9edf1b54b Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 14:23:32 -0800 Subject: [PATCH 13/14] brontide: log at debug, not error, when ignoring enable request --- peer/brontide.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/peer/brontide.go b/peer/brontide.go index 3377a754f..784a65d85 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -2286,7 +2286,10 @@ func (p *Brontide) reenableActiveChannels() { // channel is already active, the update won't be sent. for _, chanPoint := range activePublicChans { err := p.cfg.ChanStatusMgr.RequestEnable(chanPoint, false) - if err != nil { + if err == netann.ErrEnableManuallyDisabledChan { + peerLog.Debugf("Channel(%v) was manually disabled, ignoring "+ + "automatic enable request", chanPoint) + } else if err != nil { peerLog.Errorf("Unable to enable channel %v: %v", chanPoint, err) } From 215bf637ea36a29dd6f012ff3b348b72a7841b8e Mon Sep 17 00:00:00 2001 From: Elliott Jin Date: Tue, 16 Feb 2021 14:06:54 -0800 Subject: [PATCH 14/14] itest: add test for new UpdateChanStatus RPC --- lntest/itest/lnd_test.go | 302 ++++++++++++++++++++++++++ lntest/itest/lnd_test_list_on_test.go | 4 + 2 files changed, 306 insertions(+) diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 3e53acf38..16c8abf18 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -5171,6 +5171,308 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { } +// testUpdateChanStatus checks that calls to the UpdateChanStatus RPC update +// the channel graph as expected, and that channel state is properly updated +// in the presence of interleaved node disconnects / reconnects. +func testUpdateChanStatus(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // Create two fresh nodes and open a channel between them. + alice, err := net.NewNode("Alice", []string{ + "--minbackoff=10s", + "--chan-enable-timeout=1.5s", + "--chan-disable-timeout=3s", + "--chan-status-sample-interval=.5s", + }) + if err != nil { + t.Fatalf("unable to create new node: %v", err) + } + defer shutdownAndAssert(net, t, alice) + + bob, err := net.NewNode("Bob", []string{ + "--minbackoff=10s", + "--chan-enable-timeout=1.5s", + "--chan-disable-timeout=3s", + "--chan-status-sample-interval=.5s", + }) + if err != nil { + t.Fatalf("unable to create new node: %v", err) + } + defer shutdownAndAssert(net, t, bob) + + // Connect Alice to Bob. + if err := net.ConnectNodes(ctxb, alice, bob); err != nil { + t.Fatalf("unable to connect alice to bob: %v", err) + } + + // Give Alice some coins so she can fund a channel. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, alice) + if err != nil { + t.Fatalf("unable to send coins to alice: %v", err) + } + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. + chanAmt := btcutil.Amount(100000) + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPoint := openChannelAndAssert( + ctxt, t, net, alice, bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Wait for Alice and Bob to receive the channel edge from the + // funding manager. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("alice didn't see the alice->bob channel before "+ + "timeout: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("bob didn't see the bob->alice channel before "+ + "timeout: %v", err) + } + + // Launch a node for Carol which will connect to Alice and Bob in + // order to receive graph updates. This will ensure that the + // channel updates are propagated throughout the network. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create Carol's node: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, alice, carol); err != nil { + t.Fatalf("unable to connect alice to carol: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, bob, carol); err != nil { + t.Fatalf("unable to connect bob to carol: %v", err) + } + + carolSub := subscribeGraphNotifications(t, ctxb, carol) + defer close(carolSub.quit) + + // sendReq sends an UpdateChanStatus request to the given node. + sendReq := func(node *lntest.HarnessNode, chanPoint *lnrpc.ChannelPoint, + action routerrpc.ChanStatusAction) { + + req := &routerrpc.UpdateChanStatusRequest{ + ChanPoint: chanPoint, + Action: action, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = node.RouterClient.UpdateChanStatus(ctxt, req) + if err != nil { + t.Fatalf("unable to call UpdateChanStatus for %s's node: %v", + node.Name(), err) + } + } + + // assertEdgeDisabled ensures that a given node has the correct + // Disabled state for a channel. + assertEdgeDisabled := func(node *lntest.HarnessNode, + chanPoint *lnrpc.ChannelPoint, disabled bool) { + + var predErr error + err = wait.Predicate(func() bool { + req := &lnrpc.ChannelGraphRequest{ + IncludeUnannounced: true, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err := node.DescribeGraph(ctxt, req) + if err != nil { + predErr = fmt.Errorf("unable to query node %v's graph: %v", node, err) + return false + } + numEdges := len(chanGraph.Edges) + if numEdges != 1 { + predErr = fmt.Errorf("expected to find 1 edge in the graph, found %d", numEdges) + return false + } + edge := chanGraph.Edges[0] + if edge.ChanPoint != chanPoint.GetFundingTxidStr() { + predErr = fmt.Errorf("expected chan_point %v, got %v", + chanPoint.GetFundingTxidStr(), edge.ChanPoint) + } + var policy *lnrpc.RoutingPolicy + if node.PubKeyStr == edge.Node1Pub { + policy = edge.Node1Policy + } else { + policy = edge.Node2Policy + } + if disabled != policy.Disabled { + predErr = fmt.Errorf("expected policy.Disabled to be %v, "+ + "but policy was %v", disabled, policy) + return false + } + return true + }, defaultTimeout) + if err != nil { + t.Fatalf("%v", predErr) + } + } + + // When updating the state of the channel between Alice and Bob, we + // should expect to see channel updates with the default routing + // policy. The value of "Disabled" will depend on the specific + // scenario being tested. + expectedPolicy := &lnrpc.RoutingPolicy{ + FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat), + FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate), + TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta, + MinHtlc: 1000, // default value + MaxHtlcMsat: calculateMaxHtlc(chanAmt), + } + + // Initially, the channel between Alice and Bob should not be + // disabled. + assertEdgeDisabled(alice, chanPoint, false) + + // Manually disable the channel and ensure that a "Disabled = true" + // update is propagated. + sendReq(alice, chanPoint, routerrpc.ChanStatusAction_DISABLE) + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {alice.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // Re-enable the channel and ensure that a "Disabled = false" update + // is propagated. + sendReq(alice, chanPoint, routerrpc.ChanStatusAction_ENABLE) + expectedPolicy.Disabled = false + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {alice.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // Manually enabling a channel should NOT prevent subsequent + // disconnections from automatically disabling the channel again + // (we don't want to clutter the network with channels that are + // falsely advertised as enabled when they don't work). + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { + t.Fatalf("unable to disconnect Alice from Bob: %v", err) + } + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {alice.PubKeyStr, expectedPolicy, chanPoint}, + {bob.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // Reconnecting the nodes should propagate a "Disabled = false" update. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.EnsureConnected(ctxt, alice, bob); err != nil { + t.Fatalf("unable to reconnect Alice to Bob: %v", err) + } + expectedPolicy.Disabled = false + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {alice.PubKeyStr, expectedPolicy, chanPoint}, + {bob.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // Manually disabling the channel should prevent a subsequent + // disconnect / reconnect from re-enabling the channel on + // Alice's end. Note the asymmetry between manual enable and + // manual disable! + sendReq(alice, chanPoint, routerrpc.ChanStatusAction_DISABLE) + + // Alice sends out the "Disabled = true" update in response to + // the ChanStatusAction_DISABLE request. + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {alice.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { + t.Fatalf("unable to disconnect Alice from Bob: %v", err) + } + + // Bob sends a "Disabled = true" update upon detecting the + // disconnect. + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {bob.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // Bob sends a "Disabled = false" update upon detecting the + // reconnect. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.EnsureConnected(ctxt, alice, bob); err != nil { + t.Fatalf("unable to reconnect Alice to Bob: %v", err) + } + expectedPolicy.Disabled = false + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {bob.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // However, since we manually disabled the channel on Alice's end, + // the policy on Alice's end should still be "Disabled = true". Again, + // note the asymmetry between manual enable and manual disable! + assertEdgeDisabled(alice, chanPoint, true) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { + t.Fatalf("unable to disconnect Alice from Bob: %v", err) + } + + // Bob sends a "Disabled = true" update upon detecting the + // disconnect. + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {bob.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + + // After restoring automatic channel state management on Alice's end, + // BOTH Alice and Bob should set the channel state back to "enabled" + // on reconnect. + sendReq(alice, chanPoint, routerrpc.ChanStatusAction_AUTO) + if err := net.EnsureConnected(ctxt, alice, bob); err != nil { + t.Fatalf("unable to reconnect Alice to Bob: %v", err) + } + expectedPolicy.Disabled = false + waitForChannelUpdate( + t, carolSub, + []expectedChanUpdate{ + {alice.PubKeyStr, expectedPolicy, chanPoint}, + {bob.PubKeyStr, expectedPolicy, chanPoint}, + }, + ) + assertEdgeDisabled(alice, chanPoint, false) +} + func testListPayments(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 7f8404f74..17008187a 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -71,6 +71,10 @@ var allTestCases = []*testCase{ name: "list channels", test: testListChannels, }, + { + name: "update channel status", + test: testUpdateChanStatus, + }, { name: "list outgoing payments", test: testListPayments,