Skip to content

Commit 4b563e6

Browse files
authored
Merge pull request #9253 from ziggie1984/fix-chanArb-deadlock
fix chanArb deadlock
2 parents a101950 + 879041b commit 4b563e6

File tree

10 files changed

+205
-69
lines changed

10 files changed

+205
-69
lines changed

contractcourt/chain_arbitrator.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,10 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
335335
// ForceCloseChan should force close the contract that this attendant is
336336
// watching over. We'll use this when we decide that we need to go to chain. It
337337
// should in addition tell the switch to remove the corresponding link, such
338-
// that we won't accept any new updates. The returned summary contains all items
339-
// needed to eventually resolve all outputs on chain.
338+
// that we won't accept any new updates.
340339
//
341340
// NOTE: Part of the ArbChannel interface.
342-
func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) {
341+
func (a *arbChannel) ForceCloseChan() (*wire.MsgTx, error) {
343342
// First, we mark the channel as borked, this ensure
344343
// that no new state transitions can happen, and also
345344
// that the link won't be loaded into the switch.
@@ -386,7 +385,15 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
386385
if err != nil {
387386
return nil, err
388387
}
389-
return chanMachine.ForceClose()
388+
389+
closeSummary, err := chanMachine.ForceClose(
390+
lnwallet.WithSkipContractResolutions(),
391+
)
392+
if err != nil {
393+
return nil, err
394+
}
395+
396+
return closeSummary.CloseTx, nil
390397
}
391398

392399
// newActiveChannelArbitrator creates a new instance of an active channel

contractcourt/chain_watcher.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,16 +1175,29 @@ func (c *chainWatcher) dispatchLocalForceClose(
11751175
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
11761176
}
11771177

1178+
resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
1179+
fmt.Errorf("resolutions not found"),
1180+
)
1181+
if err != nil {
1182+
return err
1183+
}
1184+
11781185
// If our commitment output isn't dust or we have active HTLC's on the
11791186
// commitment transaction, then we'll populate the balances on the
11801187
// close channel summary.
1181-
if forceClose.CommitResolution != nil {
1182-
closeSummary.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis()
1183-
closeSummary.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis()
1188+
if resolutions.CommitResolution != nil {
1189+
localBalance := chanSnapshot.LocalBalance.ToSatoshis()
1190+
closeSummary.SettledBalance = localBalance
1191+
closeSummary.TimeLockedBalance = localBalance
11841192
}
1185-
for _, htlc := range forceClose.HtlcResolutions.OutgoingHTLCs {
1186-
htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value)
1187-
closeSummary.TimeLockedBalance += htlcValue
1193+
1194+
if resolutions.HtlcResolutions != nil {
1195+
for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
1196+
htlcValue := btcutil.Amount(
1197+
htlc.SweepSignDesc.Output.Value,
1198+
)
1199+
closeSummary.TimeLockedBalance += htlcValue
1200+
}
11881201
}
11891202

11901203
// Attempt to add a channel sync message to the close summary.

contractcourt/chain_watcher_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,14 +504,24 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) {
504504
// outputs.
505505
select {
506506
case summary := <-chanEvents.LocalUnilateralClosure:
507+
resOpt := summary.LocalForceCloseSummary.
508+
ContractResolutions
509+
510+
resolutions, err := resOpt.UnwrapOrErr(
511+
fmt.Errorf("resolutions not found"),
512+
)
513+
if err != nil {
514+
t.Fatalf("unable to get resolutions: %v", err)
515+
}
516+
507517
// Make sure we correctly extracted the commit
508518
// resolution if we had a local output.
509519
if remoteOutputOnly {
510-
if summary.CommitResolution != nil {
520+
if resolutions.CommitResolution != nil {
511521
t.Fatalf("expected no commit resolution")
512522
}
513523
} else {
514-
if summary.CommitResolution == nil {
524+
if resolutions.CommitResolution == nil {
515525
t.Fatalf("expected commit resolution")
516526
}
517527
}

contractcourt/channel_arbitrator.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ type ArbChannel interface {
9898
// corresponding link, such that we won't accept any new updates. The
9999
// returned summary contains all items needed to eventually resolve all
100100
// outputs on chain.
101-
ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
101+
ForceCloseChan() (*wire.MsgTx, error)
102102

103103
// NewAnchorResolutions returns the anchor resolutions for currently
104104
// valid commitment transactions.
@@ -1098,7 +1098,7 @@ func (c *ChannelArbitrator) stateStep(
10981098
// We'll tell the switch that it should remove the link for
10991099
// this channel, in addition to fetching the force close
11001100
// summary needed to close this channel on chain.
1101-
closeSummary, err := c.cfg.Channel.ForceCloseChan()
1101+
forceCloseTx, err := c.cfg.Channel.ForceCloseChan()
11021102
if err != nil {
11031103
log.Errorf("ChannelArbitrator(%v): unable to "+
11041104
"force close: %v", c.cfg.ChanPoint, err)
@@ -1118,7 +1118,7 @@ func (c *ChannelArbitrator) stateStep(
11181118

11191119
return StateError, closeTx, err
11201120
}
1121-
closeTx = closeSummary.CloseTx
1121+
closeTx = forceCloseTx
11221122

11231123
// Before publishing the transaction, we store it to the
11241124
// database, such that we can re-publish later in case it
@@ -2812,11 +2812,36 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
28122812
}
28132813
closeTx := closeInfo.CloseTx
28142814

2815+
resolutions, err := closeInfo.ContractResolutions.
2816+
UnwrapOrErr(
2817+
fmt.Errorf("resolutions not found"),
2818+
)
2819+
if err != nil {
2820+
log.Errorf("ChannelArbitrator(%v): unable to "+
2821+
"get resolutions: %v", c.cfg.ChanPoint,
2822+
err)
2823+
2824+
return
2825+
}
2826+
2827+
// We make sure that the htlc resolutions are present
2828+
// otherwise we would panic dereferencing the pointer.
2829+
//
2830+
// TODO(ziggie): Refactor ContractResolutions to use
2831+
// options.
2832+
if resolutions.HtlcResolutions == nil {
2833+
log.Errorf("ChannelArbitrator(%v): htlc "+
2834+
"resolutions not found",
2835+
c.cfg.ChanPoint)
2836+
2837+
return
2838+
}
2839+
28152840
contractRes := &ContractResolutions{
28162841
CommitHash: closeTx.TxHash(),
2817-
CommitResolution: closeInfo.CommitResolution,
2818-
HtlcResolutions: *closeInfo.HtlcResolutions,
2819-
AnchorResolution: closeInfo.AnchorResolution,
2842+
CommitResolution: resolutions.CommitResolution,
2843+
HtlcResolutions: *resolutions.HtlcResolutions,
2844+
AnchorResolution: resolutions.AnchorResolution,
28202845
}
28212846

28222847
// When processing a unilateral close event, we'll
@@ -2825,7 +2850,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
28252850
// available to fetch in that state, we'll also write
28262851
// the commit set so we can reconstruct our chain
28272852
// actions on restart.
2828-
err := c.log.LogContractResolutions(contractRes)
2853+
err = c.log.LogContractResolutions(contractRes)
28292854
if err != nil {
28302855
log.Errorf("Unable to write resolutions: %v",
28312856
err)

contractcourt/channel_arbitrator_test.go

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -693,11 +693,15 @@ func TestChannelArbitratorLocalForceClose(t *testing.T) {
693693
chanArbCtx.AssertState(StateCommitmentBroadcasted)
694694

695695
// Now notify about the local force close getting confirmed.
696+
//
697+
//nolint:lll
696698
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
697699
SpendDetail: &chainntnfs.SpendDetail{},
698700
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
699-
CloseTx: &wire.MsgTx{},
700-
HtlcResolutions: &lnwallet.HtlcResolutions{},
701+
CloseTx: &wire.MsgTx{},
702+
ContractResolutions: fn.Some(lnwallet.ContractResolutions{
703+
HtlcResolutions: &lnwallet.HtlcResolutions{},
704+
}),
701705
},
702706
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
703707
}
@@ -987,15 +991,18 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
987991
},
988992
}
989993

994+
//nolint:lll
990995
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
991996
SpendDetail: &chainntnfs.SpendDetail{},
992997
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
993998
CloseTx: closeTx,
994-
HtlcResolutions: &lnwallet.HtlcResolutions{
995-
OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{
996-
outgoingRes,
999+
ContractResolutions: fn.Some(lnwallet.ContractResolutions{
1000+
HtlcResolutions: &lnwallet.HtlcResolutions{
1001+
OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{
1002+
outgoingRes,
1003+
},
9971004
},
998-
},
1005+
}),
9991006
},
10001007
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
10011008
CommitSet: CommitSet{
@@ -1613,12 +1620,15 @@ func TestChannelArbitratorCommitFailure(t *testing.T) {
16131620
},
16141621
{
16151622
closeType: channeldb.LocalForceClose,
1623+
//nolint:lll
16161624
sendEvent: func(chanArb *ChannelArbitrator) {
16171625
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
16181626
SpendDetail: &chainntnfs.SpendDetail{},
16191627
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
1620-
CloseTx: &wire.MsgTx{},
1621-
HtlcResolutions: &lnwallet.HtlcResolutions{},
1628+
CloseTx: &wire.MsgTx{},
1629+
ContractResolutions: fn.Some(lnwallet.ContractResolutions{
1630+
HtlcResolutions: &lnwallet.HtlcResolutions{},
1631+
}),
16221632
},
16231633
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
16241634
}
@@ -1946,11 +1956,15 @@ func TestChannelArbitratorDanglingCommitForceClose(t *testing.T) {
19461956
// being canalled back. Also note that there're no HTLC
19471957
// resolutions sent since we have none on our
19481958
// commitment transaction.
1959+
//
1960+
//nolint:lll
19491961
uniCloseInfo := &LocalUnilateralCloseInfo{
19501962
SpendDetail: &chainntnfs.SpendDetail{},
19511963
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
1952-
CloseTx: closeTx,
1953-
HtlcResolutions: &lnwallet.HtlcResolutions{},
1964+
CloseTx: closeTx,
1965+
ContractResolutions: fn.Some(lnwallet.ContractResolutions{
1966+
HtlcResolutions: &lnwallet.HtlcResolutions{},
1967+
}),
19541968
},
19551969
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
19561970
CommitSet: CommitSet{
@@ -2870,12 +2884,15 @@ func TestChannelArbitratorAnchors(t *testing.T) {
28702884
},
28712885
}
28722886

2887+
//nolint:lll
28732888
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
28742889
SpendDetail: &chainntnfs.SpendDetail{},
28752890
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
2876-
CloseTx: closeTx,
2877-
HtlcResolutions: &lnwallet.HtlcResolutions{},
2878-
AnchorResolution: anchorResolution,
2891+
CloseTx: closeTx,
2892+
ContractResolutions: fn.Some(lnwallet.ContractResolutions{
2893+
HtlcResolutions: &lnwallet.HtlcResolutions{},
2894+
AnchorResolution: anchorResolution,
2895+
}),
28792896
},
28802897
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
28812898
CommitSet: CommitSet{
@@ -3109,14 +3126,10 @@ func (m *mockChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
31093126
return &lnwallet.AnchorResolutions{}, nil
31103127
}
31113128

3112-
func (m *mockChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) {
3129+
func (m *mockChannel) ForceCloseChan() (*wire.MsgTx, error) {
31133130
if m.forceCloseErr != nil {
31143131
return nil, m.forceCloseErr
31153132
}
31163133

3117-
summary := &lnwallet.LocalForceCloseSummary{
3118-
CloseTx: &wire.MsgTx{},
3119-
HtlcResolutions: &lnwallet.HtlcResolutions{},
3120-
}
3121-
return summary, nil
3134+
return &wire.MsgTx{}, nil
31223135
}

docs/release-notes/release-notes-0.19.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656

5757
* [Fixed a case](https://github.com/lightningnetwork/lnd/pull/9258) where the
5858
confirmation notification may be missed.
59+
60+
* [Make the contract resolutions for the channel arbitrator optional](
61+
https://github.com/lightningnetwork/lnd/pull/9253)
5962

6063
# New Features
6164
## Functional Enhancements

0 commit comments

Comments
 (0)