diff --git a/channeldb/reports.go b/channeldb/reports.go index 7e15220b..f71a1c4c 100644 --- a/channeldb/reports.go +++ b/channeldb/reports.go @@ -55,6 +55,10 @@ const ( // ResolverTypeOutgoingHtlc represents resolution of an outgoing htlc. ResolverTypeOutgoingHtlc ResolverType = 2 + + // ResolverTypeCommit represents resolution of our time locked commit + // when we force close. + ResolverTypeCommit ResolverType = 3 ) // ResolverOutcome indicates the outcome for the resolver that that the contract diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index b7d297da..06bdfd53 100644 --- a/contractcourt/commit_sweep_resolver.go +++ b/contractcourt/commit_sweep_resolver.go @@ -6,9 +6,11 @@ import ( "io" "sync" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/sweep" @@ -235,11 +237,13 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { return nil, err } + var sweepTxID chainhash.Hash + // Sweeper is going to join this input with other inputs if // possible and publish the sweep tx. When the sweep tx // confirms, it signals us through the result channel with the // outcome. Wait for this to happen. - recovered := true + outcome := channeldb.ResolverOutcomeClaimed select { case sweepResult := <-resultChan: switch sweepResult.Err { @@ -250,7 +254,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // the contract. c.log.Warnf("local commitment output was swept by "+ "remote party via %v", sweepResult.Tx.TxHash()) - recovered = false + outcome = channeldb.ResolverOutcomeUnclaimed case nil: // No errors, therefore continue processing. c.log.Infof("local commitment output fully resolved by "+ @@ -262,22 +266,30 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { return nil, sweepResult.Err } + + sweepTxID = sweepResult.Tx.TxHash() + case <-c.quit: return nil, errResolverShuttingDown } // Funds have been swept and balance is no longer in limbo. c.reportLock.Lock() - if recovered { + if outcome == channeldb.ResolverOutcomeClaimed { // We only record the balance as recovered if it actually came // back to us. c.currentReport.RecoveredBalance = c.currentReport.LimboBalance } c.currentReport.LimboBalance = 0 c.reportLock.Unlock() - + report := c.currentReport.resolverReport( + &sweepTxID, channeldb.ResolverTypeCommit, outcome, + ) c.resolved = true - return nil, c.Checkpoint(c, nil) + + // Checkpoint the resolver with a closure that will write the outcome + // of the resolver and its sweep transaction to disk. + return nil, c.Checkpoint(c, report) } // Stop signals the resolver to cancel any current resolution processes, and diff --git a/contractcourt/commit_sweep_resolver_test.go b/contractcourt/commit_sweep_resolver_test.go index 89c173f7..578b5c45 100644 --- a/contractcourt/commit_sweep_resolver_test.go +++ b/contractcourt/commit_sweep_resolver_test.go @@ -169,13 +169,44 @@ func TestCommitSweepResolverNoDelay(t *testing.T) { } ctx := newCommitSweepResolverTestContext(t, &res) + + // Replace our checkpoint with one which will push reports into a + // channel for us to consume. We replace this function on the resolver + // itself because it is created by the test context. + reportChan := make(chan *channeldb.ResolverReport) + ctx.resolver.Checkpoint = func(_ ContractResolver, + reports ...*channeldb.ResolverReport) error { + + // Send all of our reports into the channel. + for _, report := range reports { + reportChan <- report + } + + return nil + } + ctx.resolve() - ctx.notifier.confChan <- &chainntnfs.TxConfirmation{} + spendTx := &wire.MsgTx{} + spendHash := spendTx.TxHash() + ctx.notifier.confChan <- &chainntnfs.TxConfirmation{ + Tx: spendTx, + } // No csv delay, so the input should be swept immediately. <-ctx.sweeper.sweptInputs + amt := btcutil.Amount(res.SelfOutputSignDesc.Output.Value) + expectedReport := &channeldb.ResolverReport{ + OutPoint: wire.OutPoint{}, + Amount: amt, + ResolverType: channeldb.ResolverTypeCommit, + ResolverOutcome: channeldb.ResolverOutcomeClaimed, + SpendTxID: &spendHash, + } + + assertResolverReport(t, reportChan, expectedReport) + ctx.waitForResult() } @@ -203,6 +234,21 @@ func testCommitSweepResolverDelay(t *testing.T, sweepErr error) { ctx := newCommitSweepResolverTestContext(t, &res) + // Replace our checkpoint with one which will push reports into a + // channel for us to consume. We replace this function on the resolver + // itself because it is created by the test context. + reportChan := make(chan *channeldb.ResolverReport) + ctx.resolver.Checkpoint = func(_ ContractResolver, + reports ...*channeldb.ResolverReport) error { + + // Send all of our reports into the channel. + for _, report := range reports { + reportChan <- report + } + + return nil + } + // Setup whether we expect the sweeper to receive a sweep error in this // test case. ctx.sweeper.sweepErr = sweepErr @@ -250,6 +296,22 @@ func testCommitSweepResolverDelay(t *testing.T, sweepErr error) { <-ctx.sweeper.sweptInputs + // Set the resolution report outcome based on whether our sweep + // succeeded. + outcome := channeldb.ResolverOutcomeClaimed + if sweepErr != nil { + outcome = channeldb.ResolverOutcomeUnclaimed + } + sweepTx := ctx.sweeper.sweepTx.TxHash() + + assertResolverReport(t, reportChan, &channeldb.ResolverReport{ + OutPoint: outpoint, + ResolverType: channeldb.ResolverTypeCommit, + ResolverOutcome: outcome, + Amount: btcutil.Amount(amt), + SpendTxID: &sweepTx, + }) + ctx.waitForResult() // If this test case generates a sweep error, we don't expect to be