Skip to content

Commit 785790a

Browse files
committed
peer/lnwallet: persist shutdown info on send
In this commit, we start persisting shutdown info when we send the Shutdown message. When starting up a link, we also first check if we have previously persisted Shutdown info and if we have, we start the link in shutdown mode meaning that it will not accept any new outgoing HTLC additions and it will queue the shutdown message after any pending CommitSig has been sent.
1 parent 5de7792 commit 785790a

File tree

3 files changed

+114
-38
lines changed

3 files changed

+114
-38
lines changed

itest/lnd_coop_close_with_htlcs_test.go

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
186186
DeliveryAddress: newAddr.Address,
187187
})
188188

189-
// Assert that both nodes now see this channel as inactive.
189+
// Assert that both nodes see the channel as waiting for close.
190190
ht.AssertChannelInactive(bob, chanPoint)
191191
ht.AssertChannelInactive(alice, chanPoint)
192192

@@ -196,42 +196,35 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
196196

197197
ht.AssertConnected(alice, bob)
198198

199-
// Show that the channel is seen as active again by Alice and Bob.
200-
//
201-
// NOTE: This is a bug and will be fixed in an upcoming commit.
202-
ht.AssertChannelActive(alice, chanPoint)
203-
ht.AssertChannelActive(bob, chanPoint)
199+
// Show that both nodes still see the channel as waiting for close after
200+
// the restart.
201+
ht.AssertChannelInactive(bob, chanPoint)
202+
ht.AssertChannelInactive(alice, chanPoint)
204203

205-
// Let's settle the invoice.
204+
// Settle the invoice.
206205
alice.RPC.SettleInvoice(preimage[:])
207206

208207
// Wait for the channel to appear in the waiting closed list.
209-
//
210-
// NOTE: this will time out at the moment since there is a bug that
211-
// results in shutdown not properly being re-started after a reconnect.
212208
err := wait.Predicate(func() bool {
213209
pendingChansResp := alice.RPC.PendingChannels()
214210
waitingClosed := pendingChansResp.WaitingCloseChannels
215211

216212
return len(waitingClosed) == 1
217213
}, defaultTimeout)
214+
require.NoError(ht, err)
218215

219-
// We assert here that there is a timeout error. This will be fixed in
220-
// an upcoming commit.
221-
require.Error(ht, err)
222-
223-
// Since the channel closure did not continue, we need to re-init the
224-
// close.
225-
closingTXID := ht.CloseChannel(alice, chanPoint)
216+
// Wait for the close tx to be in the Mempool and then mine 6 blocks
217+
// to confirm the close.
218+
closingTx := ht.AssertClosingTxInMempool(
219+
chanPoint, lnrpc.CommitmentType_LEGACY,
220+
)
221+
ht.MineBlocksAndAssertNumTxes(6, 1)
226222

227-
// To further demonstrate the extent of the bug, we inspect the closing
228-
// transaction here to show that the delivery address that Alice
229-
// specified in her original close request is not the one that ended up
230-
// being used.
231-
//
232-
// NOTE: this is a bug that will be fixed in an upcoming commit.
223+
// Finally, we inspect the closing transaction here to show that the
224+
// delivery address that Alice specified in her original close request
225+
// is the one that ended up being used in the final closing transaction.
233226
tx := alice.RPC.GetTransaction(&walletrpc.GetTransactionRequest{
234-
Txid: closingTXID.String(),
227+
Txid: closingTx.TxHash().String(),
235228
})
236229
require.Len(ht, tx.OutputDetails, 2)
237230

@@ -245,6 +238,6 @@ func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
245238
}
246239
require.NotNil(ht, outputDetail)
247240

248-
// Show that the address used is not the one she requested.
249-
require.NotEqual(ht, outputDetail.Address, newAddr.Address)
241+
// Show that the address used is the one she requested.
242+
require.Equal(ht, outputDetail.Address, newAddr.Address)
250243
}

lnwallet/chancloser/chancloser.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,17 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
356356
chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
357357
c.chanPoint)
358358

359+
// At this point, we persist any relevant info regarding the Shutdown
360+
// message we are about to send in order to ensure that if a
361+
// re-establish occurs then we will re-send the same Shutdown message.
362+
shutdownInfo := channeldb.NewShutdownInfo(
363+
c.localDeliveryScript, c.locallyInitiated,
364+
)
365+
err := c.cfg.Channel.MarkShutdownSent(shutdownInfo)
366+
if err != nil {
367+
return nil, err
368+
}
369+
359370
return shutdown, nil
360371
}
361372

peer/brontide.go

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/lightningnetwork/lnd/contractcourt"
2828
"github.com/lightningnetwork/lnd/discovery"
2929
"github.com/lightningnetwork/lnd/feature"
30+
"github.com/lightningnetwork/lnd/fn"
3031
"github.com/lightningnetwork/lnd/funding"
3132
"github.com/lightningnetwork/lnd/htlcswitch"
3233
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
@@ -986,6 +987,59 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
986987
continue
987988
}
988989

990+
shutdownInfo, err := lnChan.State().ShutdownInfo()
991+
if err != nil && !errors.Is(err, channeldb.ErrNoShutdownInfo) {
992+
return nil, err
993+
}
994+
995+
var (
996+
shutdownMsg fn.Option[lnwire.Shutdown]
997+
shutdownInfoErr error
998+
)
999+
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
1000+
// Compute an ideal fee.
1001+
feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW(
1002+
p.cfg.CoopCloseTargetConfs,
1003+
)
1004+
if err != nil {
1005+
shutdownInfoErr = fmt.Errorf("unable to "+
1006+
"estimate fee: %w", err)
1007+
1008+
return
1009+
}
1010+
1011+
chanCloser, err := p.createChanCloser(
1012+
lnChan, info.DeliveryScript.Val, feePerKw, nil,
1013+
info.LocalInitiator.Val,
1014+
)
1015+
if err != nil {
1016+
shutdownInfoErr = fmt.Errorf("unable to "+
1017+
"create chan closer: %w", err)
1018+
1019+
return
1020+
}
1021+
1022+
chanID := lnwire.NewChanIDFromOutPoint(
1023+
&lnChan.State().FundingOutpoint,
1024+
)
1025+
1026+
p.activeChanCloses[chanID] = chanCloser
1027+
1028+
// Create the Shutdown message.
1029+
shutdown, err := chanCloser.ShutdownChan()
1030+
if err != nil {
1031+
delete(p.activeChanCloses, chanID)
1032+
shutdownInfoErr = err
1033+
1034+
return
1035+
}
1036+
1037+
shutdownMsg = fn.Some[lnwire.Shutdown](*shutdown)
1038+
})
1039+
if shutdownInfoErr != nil {
1040+
return nil, shutdownInfoErr
1041+
}
1042+
9891043
// Subscribe to the set of on-chain events for this channel.
9901044
chainEvents, err := p.cfg.ChainArb.SubscribeChannelEvents(
9911045
*chanPoint,
@@ -996,7 +1050,7 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
9961050

9971051
err = p.addLink(
9981052
chanPoint, lnChan, forwardingPolicy, chainEvents,
999-
true,
1053+
true, shutdownMsg,
10001054
)
10011055
if err != nil {
10021056
return nil, fmt.Errorf("unable to add link %v to "+
@@ -1014,7 +1068,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
10141068
lnChan *lnwallet.LightningChannel,
10151069
forwardingPolicy *models.ForwardingPolicy,
10161070
chainEvents *contractcourt.ChainEventSubscription,
1017-
syncStates bool) error {
1071+
syncStates bool, shutdownMsg fn.Option[lnwire.Shutdown]) error {
10181072

10191073
// onChannelFailure will be called by the link in case the channel
10201074
// fails for some reason.
@@ -1083,6 +1137,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
10831137
NotifyInactiveLinkEvent: p.cfg.ChannelNotifier.NotifyInactiveLinkEvent,
10841138
HtlcNotifier: p.cfg.HtlcNotifier,
10851139
GetAliases: p.cfg.GetAliases,
1140+
PreviouslySentShutdown: shutdownMsg,
10861141
}
10871142

10881143
// Before adding our new link, purge the switch of any pending or live
@@ -2802,15 +2857,32 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
28022857
return nil, nil
28032858
}
28042859

2805-
// As mentioned above, we don't re-create the delivery script.
2806-
deliveryScript := c.LocalShutdownScript
2807-
if len(deliveryScript) == 0 {
2808-
var err error
2809-
deliveryScript, err = p.genDeliveryScript()
2810-
if err != nil {
2811-
p.log.Errorf("unable to gen delivery script: %v",
2812-
err)
2813-
return nil, fmt.Errorf("close addr unavailable")
2860+
var deliveryScript []byte
2861+
2862+
shutdownInfo, err := c.ShutdownInfo()
2863+
switch {
2864+
// We have previously stored the delivery script that we need to use
2865+
// in the shutdown message. Re-use this script.
2866+
case err == nil:
2867+
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
2868+
deliveryScript = info.DeliveryScript.Val
2869+
})
2870+
2871+
// An error other than ErrNoShutdownInfo was returned
2872+
case err != nil && !errors.Is(err, channeldb.ErrNoShutdownInfo):
2873+
return nil, err
2874+
2875+
case errors.Is(err, channeldb.ErrNoShutdownInfo):
2876+
deliveryScript = c.LocalShutdownScript
2877+
if len(deliveryScript) == 0 {
2878+
var err error
2879+
deliveryScript, err = p.genDeliveryScript()
2880+
if err != nil {
2881+
p.log.Errorf("unable to gen delivery script: "+
2882+
"%v", err)
2883+
2884+
return nil, fmt.Errorf("close addr unavailable")
2885+
}
28142886
}
28152887
}
28162888

@@ -3905,7 +3977,7 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error {
39053977
// Create the link and add it to the switch.
39063978
err = p.addLink(
39073979
chanPoint, lnChan, initialPolicy, chainEvents,
3908-
shouldReestablish,
3980+
shouldReestablish, fn.None[lnwire.Shutdown](),
39093981
)
39103982
if err != nil {
39113983
return fmt.Errorf("can't register new channel link(%v) with "+

0 commit comments

Comments
 (0)