Skip to content

Commit

Permalink
Merge pull request #1295 from joostjager/forget
Browse files Browse the repository at this point in the history
rpc: add support for a `forgetchannel` RPC only enabled w/ the debug flag
  • Loading branch information
Roasbeef authored Sep 22, 2018
2 parents b96745e + a75b0c8 commit f430509
Show file tree
Hide file tree
Showing 12 changed files with 919 additions and 448 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ profile.tmp
.DS_Store

main*

.vscode
5 changes: 5 additions & 0 deletions channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,11 @@ const (
// we or the remote fail at some point during the opening workflow, or
// we timeout waiting for the funding transaction to be confirmed.
FundingCanceled ClosureType = 3

// Abandoned indicates that the channel state was removed without
// any further actions. This is intended to clean up unusable
// channels during development.
Abandoned ClosureType = 5
)

// ChannelCloseSummary contains the final state of a channel at the point it
Expand Down
140 changes: 109 additions & 31 deletions cmd/lncli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,43 +709,19 @@ func closeChannel(ctx *cli.Context) error {
return nil
}

channelPoint, err := parseChannelPoint(ctx)
if err != nil {
return err
}

// TODO(roasbeef): implement time deadline within server
req := &lnrpc.CloseChannelRequest{
ChannelPoint: &lnrpc.ChannelPoint{},
ChannelPoint: channelPoint,
Force: ctx.Bool("force"),
TargetConf: int32(ctx.Int64("conf_target")),
SatPerByte: ctx.Int64("sat_per_byte"),
}

args := ctx.Args()

switch {
case ctx.IsSet("funding_txid"):
req.ChannelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: ctx.String("funding_txid"),
}
case args.Present():
req.ChannelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: args.First(),
}
args = args.Tail()
default:
return fmt.Errorf("funding txid argument missing")
}

switch {
case ctx.IsSet("output_index"):
req.ChannelPoint.OutputIndex = uint32(ctx.Int("output_index"))
case args.Present():
index, err := strconv.ParseUint(args.First(), 10, 32)
if err != nil {
return fmt.Errorf("unable to decode output index: %v", err)
}
req.ChannelPoint.OutputIndex = uint32(index)
default:
req.ChannelPoint.OutputIndex = 0
}

// After parsing the request, we'll spin up a goroutine that will
// retrieve the closing transaction ID when attempting to close the
// channel. We do this to because `executeChannelClose` can block, so we
Expand All @@ -765,7 +741,7 @@ func closeChannel(ctx *cli.Context) error {
})
}()

err := executeChannelClose(client, req, txidChan, ctx.Bool("block"))
err = executeChannelClose(client, req, txidChan, ctx.Bool("block"))
if err != nil {
return err
}
Expand Down Expand Up @@ -1029,6 +1005,102 @@ func promptForConfirmation(msg string) bool {
}
}

var abandonChannelCommand = cli.Command{
Name: "abandonchannel",
Category: "Channels",
Usage: "Abandons an existing channel.",
Description: `
Removes all channel state from the database except for a close
summary. This method can be used to get rid of permanently unusable
channels due to bugs fixed in newer versions of lnd.
Only available when lnd is built in debug mode.
To view which funding_txids/output_indexes can be used for this command,
see the channel_point values within the listchannels command output.
The format for a channel_point is 'funding_txid:output_index'.`,
ArgsUsage: "funding_txid [output_index]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "funding_txid",
Usage: "the txid of the channel's funding transaction",
},
cli.IntFlag{
Name: "output_index",
Usage: "the output index for the funding output of the funding " +
"transaction",
},
},
Action: actionDecorator(abandonChannel),
}

func abandonChannel(ctx *cli.Context) error {
ctxb := context.Background()

client, cleanUp := getClient(ctx)
defer cleanUp()

// Show command help if no arguments and flags were provided.
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "abandonchannel")
return nil
}

channelPoint, err := parseChannelPoint(ctx)
if err != nil {
return err
}

req := &lnrpc.AbandonChannelRequest{
ChannelPoint: channelPoint,
}

resp, err := client.AbandonChannel(ctxb, req)
if err != nil {
return err
}

printRespJSON(resp)
return nil
}

// parseChannelPoint parses a funding txid and output index from the command
// line. Both named options as well as unnamed parameters are supported.
func parseChannelPoint(ctx *cli.Context) (*lnrpc.ChannelPoint, error) {
channelPoint := &lnrpc.ChannelPoint{}

args := ctx.Args()

switch {
case ctx.IsSet("funding_txid"):
channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: ctx.String("funding_txid"),
}
case args.Present():
channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: args.First(),
}
args = args.Tail()
default:
return nil, fmt.Errorf("funding txid argument missing")
}

switch {
case ctx.IsSet("output_index"):
channelPoint.OutputIndex = uint32(ctx.Int("output_index"))
case args.Present():
index, err := strconv.ParseUint(args.First(), 10, 32)
if err != nil {
return nil, fmt.Errorf("unable to decode output index: %v", err)
}
channelPoint.OutputIndex = uint32(index)
default:
channelPoint.OutputIndex = 0
}

return channelPoint, nil
}

var listPeersCommand = cli.Command{
Name: "listpeers",
Category: "Peers",
Expand Down Expand Up @@ -1618,6 +1690,11 @@ var closedChannelsCommand = cli.Command{
Name: "funding_canceled",
Usage: "list channels that were never fully opened",
},
cli.BoolFlag{
Name: "abandoned",
Usage: "list channels that were abandoned by " +
"the local node",
},
},
Action: actionDecorator(closedChannels),
}
Expand All @@ -1633,6 +1710,7 @@ func closedChannels(ctx *cli.Context) error {
RemoteForce: ctx.Bool("remote_force"),
Breach: ctx.Bool("breach"),
FundingCanceled: ctx.Bool("funding_cancelled"),
Abandoned: ctx.Bool("abandoned"),
}

resp, err := client.ClosedChannels(ctxb, req)
Expand Down
1 change: 1 addition & 0 deletions cmd/lncli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ func main() {
openChannelCommand,
closeChannelCommand,
closeAllChannelsCommand,
abandonChannelCommand,
listPeersCommand,
walletBalanceCommand,
channelBalanceCommand,
Expand Down
6 changes: 6 additions & 0 deletions config_debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// +build debug

package main

// DebugBuild signals that this is a debug build.
const DebugBuild = true
6 changes: 6 additions & 0 deletions config_production.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// +build !debug

package main

// DebugBuild signals that this is a debug build.
const DebugBuild = false
101 changes: 101 additions & 0 deletions lnd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12009,6 +12009,103 @@ func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) {
mineBlocks(t, net, 1)
}

// testAbandonChannel abandones a channel and asserts that it is no
// longer open and not in one of the pending closure states. It also
// verifies that the abandoned channel is reported as closed with close
// type 'abandoned'.
func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) {
timeout := time.Duration(time.Second * 5)
ctxb := context.Background()

// First establish a channel between Alice and Bob.
ctxt, _ := context.WithTimeout(ctxb, timeout)

channelParam := lntest.OpenChannelParams{
Amt: maxBtcFundingAmount,
PushAmt: btcutil.Amount(100000),
}

chanPoint := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob, channelParam)

// Wait for channel to be confirmed open.
ctxt, _ = context.WithTimeout(ctxb, time.Second*15)
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("alice didn't report channel: %v", err)
}
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("bob didn't report channel: %v", err)
}

// Send request to abandon channel.
abandonChannelRequest := &lnrpc.AbandonChannelRequest{
ChannelPoint: chanPoint,
}

ctxt, _ = context.WithTimeout(ctxb, timeout)
_, err = net.Alice.AbandonChannel(ctxt, abandonChannelRequest)

if err != nil {
t.Fatalf("unable to abandon channel: %v", err)
}

// Assert that channel in no longer open.
listReq := &lnrpc.ListChannelsRequest{}
aliceChannelList, err := net.Alice.ListChannels(ctxb, listReq)
if err != nil {
t.Fatalf("unable to list channels: %v", err)
}
if len(aliceChannelList.Channels) != 0 {
t.Fatalf("alice should only have no channels open, "+
"instead she has %v",
len(aliceChannelList.Channels))
}

// Assert that channel is not pending closure.
pendingReq := &lnrpc.PendingChannelsRequest{}
alicePendingList, err := net.Alice.PendingChannels(ctxb, pendingReq)
if err != nil {
t.Fatalf("unable to list pending channels: %v", err)
}
if len(alicePendingList.PendingClosingChannels) != 0 {
t.Fatalf("alice should only have no pending closing channels, "+
"instead she has %v",
len(alicePendingList.PendingClosingChannels))
}
if len(alicePendingList.PendingForceClosingChannels) != 0 {
t.Fatalf("alice should only have no pending force closing "+
"channels instead she has %v",
len(alicePendingList.PendingForceClosingChannels))
}
if len(alicePendingList.WaitingCloseChannels) != 0 {
t.Fatalf("alice should only have no waiting close "+
"channels instead she has %v",
len(alicePendingList.WaitingCloseChannels))
}

// Assert that channel is listed as abandoned.
closedReq := &lnrpc.ClosedChannelsRequest{
Abandoned: true,
}
aliceClosedList, err := net.Alice.ClosedChannels(ctxb, closedReq)
if err != nil {
t.Fatalf("unable to list closed channels: %v", err)
}
if len(aliceClosedList.Channels) != 1 {
t.Fatalf("alice should only have a single abandoned channel, "+
"instead she has %v",
len(aliceClosedList.Channels))
}

// Now that we're done with the test, the channel can be closed. This is
// necessary to avoid unexpected outcomes of other tests that use Bob's
// lnd instance.
ctxt, _ = context.WithTimeout(ctxb, timeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPoint, true)
}

type testCase struct {
name string
test func(net *lntest.NetworkHarness, t *harnessTest)
Expand Down Expand Up @@ -12196,6 +12293,10 @@ var testsCases = []*testCase{
name: "garbage collect link nodes",
test: testGarbageCollectLinkNodes,
},
{
name: "abandonchannel",
test: testAbandonChannel,
},
{
name: "revoked uncooperative close retribution zero value remote output",
test: testRevokedCloseRetributionZeroValueRemoteOutput,
Expand Down
Loading

0 comments on commit f430509

Please sign in to comment.