contractcourt: store htlc timeout sweeps

This commit is contained in:
carla 2020-07-07 19:49:57 +02:00
parent a38dc256fd
commit d0ec872ef3
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
2 changed files with 97 additions and 7 deletions

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@ -275,6 +276,8 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
} }
} }
var spendTxID *chainhash.Hash
// waitForOutputResolution waits for the HTLC output to be fully // waitForOutputResolution waits for the HTLC output to be fully
// resolved. The output is considered fully resolved once it has been // resolved. The output is considered fully resolved once it has been
// spent, and the spending transaction has been fully confirmed. // spent, and the spending transaction has been fully confirmed.
@ -291,10 +294,11 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
} }
select { select {
case _, ok := <-spendNtfn.Spend: case spendDetail, ok := <-spendNtfn.Spend:
if !ok { if !ok {
return errResolverShuttingDown return errResolverShuttingDown
} }
spendTxID = spendDetail.SpenderTxHash
case <-h.quit: case <-h.quit:
return errResolverShuttingDown return errResolverShuttingDown
@ -333,6 +337,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
if !ok { if !ok {
return nil, errResolverShuttingDown return nil, errResolverShuttingDown
} }
spendTxID = spend.SpenderTxHash
case <-h.quit: case <-h.quit:
return nil, errResolverShuttingDown return nil, errResolverShuttingDown
@ -365,6 +370,8 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
return nil, err return nil, err
} }
var reports []*channeldb.ResolverReport
// Finally, if this was an output on our commitment transaction, we'll // Finally, if this was an output on our commitment transaction, we'll
// wait for the second-level HTLC output to be spent, and for that // wait for the second-level HTLC output to be spent, and for that
// transaction itself to confirm. // transaction itself to confirm.
@ -374,12 +381,35 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
if err := waitForOutputResolution(); err != nil { if err := waitForOutputResolution(); err != nil {
return nil, err return nil, err
} }
// Once our timeout tx has confirmed, we add a resolution for
// our timeoutTx tx first stage transaction.
timeoutTx := h.htlcResolution.SignedTimeoutTx
spendHash := timeoutTx.TxHash()
reports = append(reports, &channeldb.ResolverReport{
OutPoint: timeoutTx.TxIn[0].PreviousOutPoint,
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendHash,
})
} }
// With the clean up message sent, we'll now mark the contract // With the clean up message sent, we'll now mark the contract
// resolved, and wait. // resolved, record the timeout and the sweep txid on disk, and wait.
h.resolved = true h.resolved = true
return nil, h.Checkpoint(h)
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports = append(reports, &channeldb.ResolverReport{
OutPoint: h.htlcResolution.ClaimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
SpendTxID: spendTxID,
})
return nil, h.Checkpoint(h, reports...)
} }
// Stop signals the resolver to cancel any current resolution processes, and // Stop signals the resolver to cancel any current resolution processes, and

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/kvdb" "github.com/lightningnetwork/lnd/channeldb/kvdb"
@ -129,6 +130,10 @@ func TestHtlcTimeoutResolver(t *testing.T) {
// can use this to customize the witness used when spending to // can use this to customize the witness used when spending to
// trigger various redemption cases. // trigger various redemption cases.
txToBroadcast func() (*wire.MsgTx, error) txToBroadcast func() (*wire.MsgTx, error)
// outcome is the resolver outcome that we expect to be reported
// once the contract is fully resolved.
outcome channeldb.ResolverOutcome
}{ }{
// Remote commitment is broadcast, we time out the HTLC on // Remote commitment is broadcast, we time out the HTLC on
// chain, and should expect a fail HTLC resolution. // chain, and should expect a fail HTLC resolution.
@ -148,6 +153,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
return templateTx, nil return templateTx, nil
}, },
outcome: channeldb.ResolverOutcomeTimeout,
}, },
// Our local commitment is broadcast, we timeout the HTLC and // Our local commitment is broadcast, we timeout the HTLC and
@ -166,8 +172,13 @@ func TestHtlcTimeoutResolver(t *testing.T) {
} }
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
// Set the outpoint to be on our commitment, since
// we need to claim in two stages.
templateTx.TxIn[0].PreviousOutPoint = testChanPoint1
return templateTx, nil return templateTx, nil
}, },
outcome: channeldb.ResolverOutcomeTimeout,
}, },
// The remote commitment is broadcast, they sweep with the // The remote commitment is broadcast, they sweep with the
@ -189,6 +200,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
return templateTx, nil return templateTx, nil
}, },
outcome: channeldb.ResolverOutcomeClaimed,
}, },
// The local commitment is broadcast, they sweep it with a // The local commitment is broadcast, they sweep it with a
@ -210,6 +222,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
return templateTx, nil return templateTx, nil
}, },
outcome: channeldb.ResolverOutcomeClaimed,
}, },
} }
@ -226,6 +239,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
checkPointChan := make(chan struct{}, 1) checkPointChan := make(chan struct{}, 1)
incubateChan := make(chan struct{}, 1) incubateChan := make(chan struct{}, 1)
resolutionChan := make(chan ResolutionMsg, 1) resolutionChan := make(chan ResolutionMsg, 1)
reportChan := make(chan *channeldb.ResolverReport)
chainCfg := ChannelArbitratorConfig{ chainCfg := ChannelArbitratorConfig{
ChainArbitratorConfig: ChainArbitratorConfig{ ChainArbitratorConfig: ChainArbitratorConfig{
@ -260,24 +274,49 @@ func TestHtlcTimeoutResolver(t *testing.T) {
cfg := ResolverConfig{ cfg := ResolverConfig{
ChannelArbitratorConfig: chainCfg, ChannelArbitratorConfig: chainCfg,
Checkpoint: func(_ ContractResolver, Checkpoint: func(_ ContractResolver,
_ ...*channeldb.ResolverReport) error { reports ...*channeldb.ResolverReport) error {
checkPointChan <- struct{}{} checkPointChan <- struct{}{}
// Send all of our reports into the channel.
for _, report := range reports {
reportChan <- report
}
return nil return nil
}, },
} }
resolver := &htlcTimeoutResolver{ resolver := &htlcTimeoutResolver{
htlcResolution: lnwallet.OutgoingHtlcResolution{
ClaimOutpoint: testChanPoint2,
SweepSignDesc: *fakeSignDesc,
},
contractResolverKit: *newContractResolverKit( contractResolverKit: *newContractResolverKit(
cfg, cfg,
), ),
htlc: channeldb.HTLC{
Amt: testHtlcAmt,
},
} }
resolver.htlcResolution.SweepSignDesc = *fakeSignDesc
var reports []*channeldb.ResolverReport
// If the test case needs the remote commitment to be // If the test case needs the remote commitment to be
// broadcast, then we'll set the timeout commit to a fake // broadcast, then we'll set the timeout commit to a fake
// transaction to force the code path. // transaction to force the code path.
if !testCase.remoteCommit { if !testCase.remoteCommit {
resolver.htlcResolution.SignedTimeoutTx = sweepTx resolver.htlcResolution.SignedTimeoutTx = sweepTx
if testCase.timeout {
success := sweepTx.TxHash()
reports = append(reports, &channeldb.ResolverReport{
OutPoint: sweepTx.TxIn[0].PreviousOutPoint,
Amount: testHtlcAmt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &success,
})
}
} }
// With all the setup above complete, we can initiate the // With all the setup above complete, we can initiate the
@ -312,9 +351,12 @@ func TestHtlcTimeoutResolver(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to generate tx: %v", err) t.Fatalf("unable to generate tx: %v", err)
} }
spendTxHash := spendingTx.TxHash()
select { select {
case notifier.spendChan <- &chainntnfs.SpendDetail{ case notifier.spendChan <- &chainntnfs.SpendDetail{
SpendingTx: spendingTx, SpendingTx: spendingTx,
SpenderTxHash: &spendTxHash,
}: }:
case <-time.After(time.Second * 5): case <-time.After(time.Second * 5):
t.Fatalf("failed to request spend ntfn") t.Fatalf("failed to request spend ntfn")
@ -370,7 +412,8 @@ func TestHtlcTimeoutResolver(t *testing.T) {
if !testCase.remoteCommit { if !testCase.remoteCommit {
select { select {
case notifier.spendChan <- &chainntnfs.SpendDetail{ case notifier.spendChan <- &chainntnfs.SpendDetail{
SpendingTx: spendingTx, SpendingTx: spendingTx,
SpenderTxHash: &spendTxHash,
}: }:
case <-time.After(time.Second * 5): case <-time.After(time.Second * 5):
t.Fatalf("failed to request spend ntfn") t.Fatalf("failed to request spend ntfn")
@ -388,6 +431,23 @@ func TestHtlcTimeoutResolver(t *testing.T) {
t.Fatalf("check point not received") t.Fatalf("check point not received")
} }
// Add a report to our set of expected reports with the outcome
// that the test specifies (either success or timeout).
spendTxID := spendingTx.TxHash()
amt := btcutil.Amount(fakeSignDesc.Output.Value)
reports = append(reports, &channeldb.ResolverReport{
OutPoint: testChanPoint2,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: testCase.outcome,
SpendTxID: &spendTxID,
})
for _, report := range reports {
assertResolverReport(t, reportChan, report)
}
wg.Wait() wg.Wait()
// Finally, the resolver should be marked as resolved. // Finally, the resolver should be marked as resolved.