diff --git a/channeldb/db.go b/channeldb/db.go index a67ccd2f..e8a8e5af 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -1094,6 +1094,54 @@ func (d *DB) AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr, error) { return dedupedAddrs, nil } +// AbandonChannel attempts to remove the target channel from the open channel +// database. If the channel was already removed (has a closed channel entry), +// then we'll return a nil error. Otherwise, we'll insert a new close summary +// into the database. +func (d *DB) AbandonChannel(chanPoint *wire.OutPoint, bestHeight uint32) error { + // With the chanPoint constructed, we'll attempt to find the target + // channel in the database. If we can't find the channel, then we'll + // return the error back to the caller. + dbChan, err := d.FetchChannel(*chanPoint) + switch { + // If the channel wasn't found, then it's possible that it was already + // abandoned from the database. + case err == ErrChannelNotFound: + _, closedErr := d.FetchClosedChannel(chanPoint) + if closedErr != nil { + return closedErr + } + + // If the channel was already closed, then we don't return an + // error as we'd like fro this step to be repeatable. + return nil + case err != nil: + return err + } + + // Now that we've found the channel, we'll populate a close summary for + // the channel, so we can store as much information for this abounded + // channel as possible. We also ensure that we set Pending to false, to + // indicate that this channel has been "fully" closed. + summary := &ChannelCloseSummary{ + CloseType: Abandoned, + ChanPoint: *chanPoint, + ChainHash: dbChan.ChainHash, + CloseHeight: bestHeight, + RemotePub: dbChan.IdentityPub, + Capacity: dbChan.Capacity, + SettledBalance: dbChan.LocalCommitment.LocalBalance.ToSatoshis(), + ShortChanID: dbChan.ShortChanID(), + RemoteCurrentRevocation: dbChan.RemoteCurrentRevocation, + RemoteNextRevocation: dbChan.RemoteNextRevocation, + LocalChanConfig: dbChan.LocalChanCfg, + } + + // Finally, we'll close the channel in the DB, and return back to the + // caller. + return dbChan.CloseChannel(summary) +} + // syncVersions function is used for safe db version synchronization. It // applies migration functions to the current database and recovers the // previous state of db if at least one error/panic appeared during migration. diff --git a/channeldb/db_test.go b/channeldb/db_test.go index 198f6e2b..4deffe98 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -469,3 +469,67 @@ func TestRestoreChannelShells(t *testing.T) { t.Fatalf("only a single edge should be inserted: %v", err) } } + +// TestAbandonChannel tests that the AbandonChannel method is able to properly +// remove a channel from the database and add a close channel summary. If +// called after a channel has already been removed, the method shouldn't return +// an error. +func TestAbandonChannel(t *testing.T) { + t.Parallel() + + cdb, cleanUp, err := makeTestDB() + if err != nil { + t.Fatalf("unable to make test database: %v", err) + } + defer cleanUp() + + // If we attempt to abandon the state of a channel that doesn't exist + // in the open or closed channel bucket, then we should receive an + // error. + err = cdb.AbandonChannel(&wire.OutPoint{}, 0) + if err == nil { + t.Fatalf("removing non-existent channel should have failed") + } + + // We'll now create a new channel to abandon shortly. + chanState, err := createTestChannelState(cdb) + if err != nil { + t.Fatalf("unable to create channel state: %v", err) + } + addr := &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 18555, + } + err = chanState.SyncPending(addr, 10) + if err != nil { + t.Fatalf("unable to sync pending channel: %v", err) + } + + // We should now be able to abandon the channel without any errors. + closeHeight := uint32(11) + err = cdb.AbandonChannel(&chanState.FundingOutpoint, closeHeight) + if err != nil { + t.Fatalf("unable to abandon channel: %v", err) + } + + // At this point, the channel should no longer be found in the set of + // open channels. + _, err = cdb.FetchChannel(chanState.FundingOutpoint) + if err != ErrChannelNotFound { + t.Fatalf("channel should not have been found: %v", err) + } + + // However we should be able to retrieve a close channel summary for + // the channel. + _, err = cdb.FetchClosedChannel(&chanState.FundingOutpoint) + if err != nil { + t.Fatalf("unable to fetch closed channel: %v", err) + } + + // Finally, if we attempt to abandon the channel again, we should get a + // nil error as the channel has already been abandoned. + err = cdb.AbandonChannel(&chanState.FundingOutpoint, closeHeight) + if err != nil { + t.Fatalf("unable to abandon channel: %v", err) + } +}