Skip to content

Commit c7ca387

Browse files
authored
Merge pull request #2744 from cfromknecht/disable-before-close
cnct+chancloser: disable channel before closing
2 parents e58fa33 + 714e42f commit c7ca387

File tree

5 files changed

+123
-38
lines changed

5 files changed

+123
-38
lines changed

chancloser.go

+8-10
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,14 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
427427
}
428428
c.closingTx = closeTx
429429

430+
// Before closing, we'll attempt to send a disable update for
431+
// the channel. We do so before closing the channel as otherwise
432+
// the current edge policy won't be retrievable from the graph.
433+
if err := c.cfg.disableChannel(c.chanPoint); err != nil {
434+
peerLog.Warnf("Unable to disable channel %v on "+
435+
"close: %v", c.chanPoint, err)
436+
}
437+
430438
// With the closing transaction crafted, we'll now broadcast it
431439
// to the network.
432440
peerLog.Infof("Broadcasting cooperative close tx: %v",
@@ -440,16 +448,6 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
440448
return nil, false, err
441449
}
442450

443-
// We'll attempt to disable the channel in the background to
444-
// avoid blocking due to sending the update message to all
445-
// active peers.
446-
go func() {
447-
if err := c.cfg.disableChannel(c.chanPoint); err != nil {
448-
peerLog.Errorf("Unable to disable channel %v on "+
449-
"close: %v", c.chanPoint, err)
450-
}
451-
}()
452-
453451
// Finally, we'll transition to the closeFinished state, and
454452
// also return the final close signed message we sent.
455453
// Additionally, we return true for the second argument to

contractcourt/chain_arbitrator.go

+8-10
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,14 @@ func (c *ChainArbitrator) ForceCloseContract(chanPoint wire.OutPoint) (*wire.Msg
635635

636636
log.Infof("Attempting to force close ChannelPoint(%v)", chanPoint)
637637

638+
// Before closing, we'll attempt to send a disable update for the
639+
// channel. We do so before closing the channel as otherwise the current
640+
// edge policy won't be retrievable from the graph.
641+
if err := c.cfg.DisableChannel(chanPoint); err != nil {
642+
log.Warnf("Unable to disable channel %v on "+
643+
"close: %v", chanPoint, err)
644+
}
645+
638646
errChan := make(chan error, 1)
639647
respChan := make(chan *wire.MsgTx, 1)
640648

@@ -667,16 +675,6 @@ func (c *ChainArbitrator) ForceCloseContract(chanPoint wire.OutPoint) (*wire.Msg
667675
return nil, ErrChainArbExiting
668676
}
669677

670-
// We'll attempt to disable the channel in the background to
671-
// avoid blocking due to sending the update message to all
672-
// active peers.
673-
go func() {
674-
if err := c.cfg.DisableChannel(chanPoint); err != nil {
675-
log.Errorf("Unable to disable channel %v on "+
676-
"close: %v", chanPoint, err)
677-
}
678-
}()
679-
680678
return closeTx, nil
681679
}
682680

lnd_test.go

+92-18
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,80 @@ func openChannelAndAssert(ctx context.Context, t *harnessTest,
223223
// closure is attempted, therefore the passed context should be a child derived
224224
// via timeout from a base parent. Additionally, once the channel has been
225225
// detected as closed, an assertion checks that the transaction is found within
226-
// a block.
226+
// a block. Finally, this assertion verifies that the node always sends out a
227+
// disable update when closing the channel if the channel was previously enabled.
228+
//
229+
// NOTE: This method assumes that the provided funding point is confirmed
230+
// on-chain AND that the edge exists in the node's channel graph. If the funding
231+
// transactions was reorged out at some point, use closeReorgedChannelAndAssert.
227232
func closeChannelAndAssert(ctx context.Context, t *harnessTest,
228233
net *lntest.NetworkHarness, node *lntest.HarnessNode,
229234
fundingChanPoint *lnrpc.ChannelPoint, force bool) *chainhash.Hash {
230235

236+
// Fetch the current channel policy. If the channel is currently
237+
// enabled, we will register for graph notifications before closing to
238+
// assert that the node sends out a disabling update as a result of the
239+
// channel being closed.
240+
curPolicy := getChannelPolicies(t, node, node.PubKeyStr, fundingChanPoint)[0]
241+
expectDisable := !curPolicy.Disabled
242+
243+
// If the current channel policy is enabled, begin subscribing the graph
244+
// updates before initiating the channel closure.
245+
var graphSub *graphSubscription
246+
if expectDisable {
247+
sub := subscribeGraphNotifications(t, ctx, node)
248+
graphSub = &sub
249+
defer close(graphSub.quit)
250+
}
251+
252+
closeUpdates, _, err := net.CloseChannel(ctx, node, fundingChanPoint, force)
253+
if err != nil {
254+
t.Fatalf("unable to close channel: %v", err)
255+
}
256+
257+
// If the channel policy was enabled prior to the closure, wait until we
258+
// received the disabled update.
259+
if expectDisable {
260+
curPolicy.Disabled = true
261+
waitForChannelUpdate(
262+
t, *graphSub,
263+
[]expectedChanUpdate{
264+
{node.PubKeyStr, curPolicy, fundingChanPoint},
265+
},
266+
)
267+
}
268+
269+
return assertChannelClosed(ctx, t, net, node, fundingChanPoint, closeUpdates)
270+
}
271+
272+
// closeReorgedChannelAndAssert attempts to close a channel identified by the
273+
// passed channel point owned by the passed Lightning node. A fully blocking
274+
// channel closure is attempted, therefore the passed context should be a child
275+
// derived via timeout from a base parent. Additionally, once the channel has
276+
// been detected as closed, an assertion checks that the transaction is found
277+
// within a block.
278+
//
279+
// NOTE: This method does not verify that the node sends a disable update for
280+
// the closed channel.
281+
func closeReorgedChannelAndAssert(ctx context.Context, t *harnessTest,
282+
net *lntest.NetworkHarness, node *lntest.HarnessNode,
283+
fundingChanPoint *lnrpc.ChannelPoint, force bool) *chainhash.Hash {
284+
231285
closeUpdates, _, err := net.CloseChannel(ctx, node, fundingChanPoint, force)
232286
if err != nil {
233287
t.Fatalf("unable to close channel: %v", err)
234288
}
235289

290+
return assertChannelClosed(ctx, t, net, node, fundingChanPoint, closeUpdates)
291+
}
292+
293+
// assertChannelClosed asserts that the channel is properly cleaned up after
294+
// initiating a cooperative or local close.
295+
func assertChannelClosed(ctx context.Context, t *harnessTest,
296+
net *lntest.NetworkHarness, node *lntest.HarnessNode,
297+
fundingChanPoint *lnrpc.ChannelPoint,
298+
closeUpdates lnrpc.Lightning_CloseChannelClient) *chainhash.Hash {
299+
236300
txidHash, err := getChanPointFundingTxid(fundingChanPoint)
237301
if err != nil {
238302
t.Fatalf("unable to get txid: %v", err)
@@ -989,7 +1053,6 @@ out:
9891053
select {
9901054
case graphUpdate := <-subscription.updateChan:
9911055
for _, update := range graphUpdate.ChannelUpdates {
992-
9931056
// For each expected update, check if it matches
9941057
// the update we just received.
9951058
for i, exp := range expUpdates {
@@ -1070,11 +1133,12 @@ func assertNoChannelUpdates(t *harnessTest, subscription graphSubscription,
10701133
}
10711134
}
10721135

1073-
// assertChannelPolicy asserts that the passed node's known channel policy for
1074-
// the passed chanPoint is consistent with the expected policy values.
1075-
func assertChannelPolicy(t *harnessTest, node *lntest.HarnessNode,
1076-
advertisingNode string, expectedPolicy *lnrpc.RoutingPolicy,
1077-
chanPoints ...*lnrpc.ChannelPoint) {
1136+
// getChannelPolicies queries the channel graph and retrieves the current edge
1137+
// policies for the provided channel points.
1138+
func getChannelPolicies(t *harnessTest, node *lntest.HarnessNode,
1139+
advertisingNode string,
1140+
chanPoints ...*lnrpc.ChannelPoint) []*lnrpc.RoutingPolicy {
1141+
10781142
ctxb := context.Background()
10791143

10801144
descReq := &lnrpc.ChannelGraphRequest{
@@ -1086,25 +1150,18 @@ func assertChannelPolicy(t *harnessTest, node *lntest.HarnessNode,
10861150
t.Fatalf("unable to query for alice's graph: %v", err)
10871151
}
10881152

1153+
var policies []*lnrpc.RoutingPolicy
10891154
out:
10901155
for _, chanPoint := range chanPoints {
10911156
for _, e := range chanGraph.Edges {
10921157
if e.ChanPoint != txStr(chanPoint) {
10931158
continue
10941159
}
10951160

1096-
var err error
10971161
if e.Node1Pub == advertisingNode {
1098-
err = checkChannelPolicy(
1099-
e.Node1Policy, expectedPolicy,
1100-
)
1162+
policies = append(policies, e.Node1Policy)
11011163
} else {
1102-
err = checkChannelPolicy(
1103-
e.Node2Policy, expectedPolicy,
1104-
)
1105-
}
1106-
if err != nil {
1107-
t.Fatalf(err.Error())
1164+
policies = append(policies, e.Node2Policy)
11081165
}
11091166

11101167
continue out
@@ -1114,6 +1171,23 @@ out:
11141171
// able to find this specific one, then we'll fail.
11151172
t.Fatalf("did not find edge %v", txStr(chanPoint))
11161173
}
1174+
1175+
return policies
1176+
}
1177+
1178+
// assertChannelPolicy asserts that the passed node's known channel policy for
1179+
// the passed chanPoint is consistent with the expected policy values.
1180+
func assertChannelPolicy(t *harnessTest, node *lntest.HarnessNode,
1181+
advertisingNode string, expectedPolicy *lnrpc.RoutingPolicy,
1182+
chanPoints ...*lnrpc.ChannelPoint) {
1183+
1184+
policies := getChannelPolicies(t, node, advertisingNode, chanPoints...)
1185+
for _, policy := range policies {
1186+
err := checkChannelPolicy(policy, expectedPolicy)
1187+
if err != nil {
1188+
t.Fatalf(err.Error())
1189+
}
1190+
}
11171191
}
11181192

11191193
// checkChannelPolicy checks that the policy matches the expected one.
@@ -1872,7 +1946,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) {
18721946
assertTxInBlock(t, block, fundingTxID)
18731947

18741948
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
1875-
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
1949+
closeReorgedChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
18761950
}
18771951

18781952
// testDisconnectingTargetPeer performs a test which

netann/chan_status_manager.go

+12
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,18 @@ func (m *ChanStatusManager) disableInactiveChannels() {
466466
if err != nil {
467467
log.Errorf("Unable to sign update disabling "+
468468
"channel(%v): %v", outpoint, err)
469+
470+
// If the edge was not found, this is a likely indicator
471+
// that the channel has been closed. Thus we remove the
472+
// outpoint from the set of tracked outpoints to prevent
473+
// further attempts.
474+
if err == channeldb.ErrEdgeNotFound {
475+
log.Debugf("Removing channel(%v) from "+
476+
"consideration for passive disabling",
477+
outpoint)
478+
delete(m.chanStates, outpoint)
479+
}
480+
469481
continue
470482
}
471483

test_utils.go

+3
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
392392
if err != nil {
393393
return nil, nil, nil, nil, err
394394
}
395+
if err = chanStatusMgr.Start(); err != nil {
396+
return nil, nil, nil, nil, err
397+
}
395398
s.chanStatusMgr = chanStatusMgr
396399

397400
alicePeer := &peer{

0 commit comments

Comments
 (0)