Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions contractcourt/channel_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,20 @@ func (c *ChannelArbitrator) Start(state *chanArbStartState) error {
return err
}

c.wg.Add(1)
go c.channelAttendant(bestHeight, state.commitSet)

return nil
}

// progressStateMachineAfterRestart attempts to progress the state machine
// after a restart. This makes sure that if the state transition failed, we
// will try to progress the state machine again. Moreover it will relaunch
// resolvers if the channel is still in the pending close state and has not
// been fully resolved yet.
func (c *ChannelArbitrator) progressStateMachineAfterRestart(bestHeight int32,
Comment thread
ziggie1984 marked this conversation as resolved.
commitSet *CommitSet) error {

// If the channel has been marked pending close in the database, and we
// haven't transitioned the state machine to StateContractClosed (or a
// succeeding state), then a state transition most likely failed. We'll
Expand Down Expand Up @@ -527,7 +541,7 @@ func (c *ChannelArbitrator) Start(state *chanArbStartState) error {
// on-chain state, and our set of active contracts.
startingState := c.state
nextState, _, err := c.advanceState(
triggerHeight, trigger, state.commitSet,
triggerHeight, trigger, commitSet,
)
if err != nil {
switch err {
Expand Down Expand Up @@ -564,14 +578,12 @@ func (c *ChannelArbitrator) Start(state *chanArbStartState) error {
// receive a chain event from the chain watcher that the
// commitment has been confirmed on chain, and before we
// advance our state step, we call InsertConfirmedCommitSet.
err := c.relaunchResolvers(state.commitSet, triggerHeight)
err := c.relaunchResolvers(commitSet, triggerHeight)
if err != nil {
return err
}
}

c.wg.Add(1)
go c.channelAttendant(bestHeight)
return nil
}

Expand Down Expand Up @@ -2716,13 +2728,28 @@ func (c *ChannelArbitrator) updateActiveHTLCs() {
// Nursery for incubation, and ultimate sweeping.
//
// NOTE: This MUST be run as a goroutine.
func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
//
//nolint:funlen
func (c *ChannelArbitrator) channelAttendant(bestHeight int32,
commitSet *CommitSet) {

// TODO(roasbeef): tell top chain arb we're done
defer func() {
c.wg.Done()
}()

err := c.progressStateMachineAfterRestart(bestHeight, commitSet)
if err != nil {
// In case of an error, we return early but we do not shutdown
// LND, because there might be other channels that still can be
// resolved and we don't want to interfere with that.
Comment thread
ziggie1984 marked this conversation as resolved.
// We continue to run the channel attendant in case the channel
// closes via other means for example the remote pary force
// closes the channel. So we log the error and continue.
log.Errorf("Unable to progress state machine after "+
"restart: %v", err)
}

for {
select {

Expand Down
27 changes: 20 additions & 7 deletions contractcourt/channel_arbitrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
Expand Down Expand Up @@ -1045,10 +1046,19 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {

// Post restart, it should be the case that our resolver was properly
// supplemented, and we only have a single resolver in the final set.
if len(chanArb.activeResolvers) != 1 {
t.Fatalf("expected single resolver, instead got: %v",
len(chanArb.activeResolvers))
}
// The resolvers are added concurrently so we need to wait here.
err = wait.NoError(func() error {
chanArb.activeResolversLock.Lock()
defer chanArb.activeResolversLock.Unlock()

if len(chanArb.activeResolvers) != 1 {
return fmt.Errorf("expected single resolver, instead "+
"got: %v", len(chanArb.activeResolvers))
}

return nil
}, defaultTimeout)
require.NoError(t, err)

// We'll now examine the in-memory state of the active resolvers to
// ensure t hey were populated properly.
Expand Down Expand Up @@ -3000,9 +3010,12 @@ func TestChannelArbitratorStartForceCloseFail(t *testing.T) {
{
name: "Commitment is rejected with an " +
"unmatched error",
broadcastErr: fmt.Errorf("Reject Commitment Tx"),
expectedState: StateBroadcastCommit,
expectedStartup: false,
broadcastErr: fmt.Errorf("Reject Commitment Tx"),
expectedState: StateBroadcastCommit,
// We should still be able to start up since we other
// channels might be closing as well and we should
// resolve the contracts.
expectedStartup: true,
},

// We started after the DLP was triggered, and try to force
Expand Down
4 changes: 4 additions & 0 deletions docs/release-notes/release-notes-0.18.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
cause a nil pointer dereference during the probing of a payment request that
does not contain a payment address.

* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/9324) to prevent
potential deadlocks when LND depends on external components (e.g. aux
components, hooks).

# New Features

The main channel state machine and database now allow for processing and storing
Expand Down