Skip to content

[bug]: Restoring a node with SCB file after counterparty force closed channel long ago generates a pending_force_close channel that remains forever #8366

@matheusd

Description

@matheusd

On the following scenario:

  • Alice <-> Bob channel
  • Bob goes offline
  • Some time passes
  • Alice force-closes the channel
  • Some time passes
  • Bob restores its node and applies SCB file during wallet init

Bob ends up with a pending_force_close channel with negative blocks_til_maturity that never resolves itself automatically.

How should such channels be handled?

Some options:

  • Leave channel there forever
    • Gonna have a channel on pending channels query forever
    • Node operator needs to always remember to ignore this channel
  • Restore again, but skip SCB
    • Could further interfere with other ongoing operations
    • More work for the node operator
  • (Optionally) filter when listing pending channels
    • Node operator will have to remember to specify the filter condition
    • If the filtering isn't optional, node operator may want to actually know about the channel
  • Add an RPC to remove this pending channel
    • Risky if user doesn't know what it's doing
    • Requires node operator intervention
  • Detect and remove such pending channels automatically
    • How to detect (which conditions apply here that don't conflict with other issues)?
    • Less work for node operator
    • May remove data before operator actually wants it gone

NOTE: Bob finds the on-chain funds correctly in this case, the issue is the extraneous entry in the pending_foce_close list.

Example itest that demonstrates issue
package itest

import (
	"github.com/btcsuite/btcd/btcutil"
	"github.com/davecgh/go-spew/spew"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lntest"
)

func testRestoreWithSCBAfterLongForceClosed(ht *lntest.HarnessTest) {
	chanAmt := btcutil.Amount(100000)
	alice := ht.Alice

	// Create bob and track seed.
	password := []byte("El Psy Kongroo")
	bob, mnemonic, _ := ht.NewNodeWithSeed(
		"dave", nil, password, false,
	)
	ht.EnsureConnected(alice, bob)

	// Open alice <> bob channel.
	cp := ht.OpenChannel(
		alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
	)

	// Advance channel state.
	const paymentAmt = 1000
	payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
	ht.CompletePaymentRequests(alice, payReqs)

	// Save bob's backup.
	chanBackup := bob.RPC.ExportAllChanBackups()

	// Bob goes offline.
	ht.Shutdown(bob)

	// Alice force-closes the channel while Bob is offline.
	ht.ForceCloseChannel(alice, cp)

	// Mine a few blocks for good measure.
	ht.MineBlocks(64)

	// Bob restores its node from seed, applying the SCB.
	backupSnapshot := &lnrpc.ChanBackupSnapshot{
		MultiChanBackup: chanBackup.MultiChanBackup,
	}
	bob2 := ht.RestoreNodeWithSeed("bob2", nil, password, mnemonic, "",
		revocationWindow, backupSnapshot)

	// Mine a few blocks for good measure.
	ht.MineBlocks(64)

	// Channel was closed long ago, how to handle the pending force close
	// channel?
	resp := bob2.RPC.PendingChannels()
	ht.Logf(spew.Sdump(resp))
	ht.AssertNumPendingForceClose(bob, 0) // This fails.
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    SCBRelated to static channel backupbugUnintended code behaviour

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions