mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
contractcourt: store htlc timeout sweeps
This commit is contained in:
parent
a38dc256fd
commit
d0ec872ef3
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"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
|
||||
// resolved. The output is considered fully resolved once it has been
|
||||
// spent, and the spending transaction has been fully confirmed.
|
||||
@ -291,10 +294,11 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
}
|
||||
|
||||
select {
|
||||
case _, ok := <-spendNtfn.Spend:
|
||||
case spendDetail, ok := <-spendNtfn.Spend:
|
||||
if !ok {
|
||||
return errResolverShuttingDown
|
||||
}
|
||||
spendTxID = spendDetail.SpenderTxHash
|
||||
|
||||
case <-h.quit:
|
||||
return errResolverShuttingDown
|
||||
@ -333,6 +337,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
if !ok {
|
||||
return nil, errResolverShuttingDown
|
||||
}
|
||||
spendTxID = spend.SpenderTxHash
|
||||
|
||||
case <-h.quit:
|
||||
return nil, errResolverShuttingDown
|
||||
@ -365,6 +370,8 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reports []*channeldb.ResolverReport
|
||||
|
||||
// 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
|
||||
// transaction itself to confirm.
|
||||
@ -374,12 +381,35 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
if err := waitForOutputResolution(); err != nil {
|
||||
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
|
||||
// resolved, and wait.
|
||||
// resolved, record the timeout and the sweep txid on disk, and wait.
|
||||
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
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"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
|
||||
// trigger various redemption cases.
|
||||
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
|
||||
// chain, and should expect a fail HTLC resolution.
|
||||
@ -148,6 +153,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
templateTx.TxIn[0].Witness = witness
|
||||
return templateTx, nil
|
||||
},
|
||||
outcome: channeldb.ResolverOutcomeTimeout,
|
||||
},
|
||||
|
||||
// Our local commitment is broadcast, we timeout the HTLC and
|
||||
@ -166,8 +172,13 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
outcome: channeldb.ResolverOutcomeTimeout,
|
||||
},
|
||||
|
||||
// The remote commitment is broadcast, they sweep with the
|
||||
@ -189,6 +200,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
templateTx.TxIn[0].Witness = witness
|
||||
return templateTx, nil
|
||||
},
|
||||
outcome: channeldb.ResolverOutcomeClaimed,
|
||||
},
|
||||
|
||||
// The local commitment is broadcast, they sweep it with a
|
||||
@ -210,6 +222,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
templateTx.TxIn[0].Witness = witness
|
||||
return templateTx, nil
|
||||
},
|
||||
outcome: channeldb.ResolverOutcomeClaimed,
|
||||
},
|
||||
}
|
||||
|
||||
@ -226,6 +239,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
checkPointChan := make(chan struct{}, 1)
|
||||
incubateChan := make(chan struct{}, 1)
|
||||
resolutionChan := make(chan ResolutionMsg, 1)
|
||||
reportChan := make(chan *channeldb.ResolverReport)
|
||||
|
||||
chainCfg := ChannelArbitratorConfig{
|
||||
ChainArbitratorConfig: ChainArbitratorConfig{
|
||||
@ -260,24 +274,49 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
cfg := ResolverConfig{
|
||||
ChannelArbitratorConfig: chainCfg,
|
||||
Checkpoint: func(_ ContractResolver,
|
||||
_ ...*channeldb.ResolverReport) error {
|
||||
reports ...*channeldb.ResolverReport) error {
|
||||
|
||||
checkPointChan <- struct{}{}
|
||||
|
||||
// Send all of our reports into the channel.
|
||||
for _, report := range reports {
|
||||
reportChan <- report
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
resolver := &htlcTimeoutResolver{
|
||||
htlcResolution: lnwallet.OutgoingHtlcResolution{
|
||||
ClaimOutpoint: testChanPoint2,
|
||||
SweepSignDesc: *fakeSignDesc,
|
||||
},
|
||||
contractResolverKit: *newContractResolverKit(
|
||||
cfg,
|
||||
),
|
||||
htlc: channeldb.HTLC{
|
||||
Amt: testHtlcAmt,
|
||||
},
|
||||
}
|
||||
resolver.htlcResolution.SweepSignDesc = *fakeSignDesc
|
||||
|
||||
var reports []*channeldb.ResolverReport
|
||||
|
||||
// If the test case needs the remote commitment to be
|
||||
// broadcast, then we'll set the timeout commit to a fake
|
||||
// transaction to force the code path.
|
||||
if !testCase.remoteCommit {
|
||||
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
|
||||
@ -312,9 +351,12 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx: %v", err)
|
||||
}
|
||||
spendTxHash := spendingTx.TxHash()
|
||||
|
||||
select {
|
||||
case notifier.spendChan <- &chainntnfs.SpendDetail{
|
||||
SpendingTx: spendingTx,
|
||||
SpendingTx: spendingTx,
|
||||
SpenderTxHash: &spendTxHash,
|
||||
}:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("failed to request spend ntfn")
|
||||
@ -370,7 +412,8 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
if !testCase.remoteCommit {
|
||||
select {
|
||||
case notifier.spendChan <- &chainntnfs.SpendDetail{
|
||||
SpendingTx: spendingTx,
|
||||
SpendingTx: spendingTx,
|
||||
SpenderTxHash: &spendTxHash,
|
||||
}:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("failed to request spend ntfn")
|
||||
@ -388,6 +431,23 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
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()
|
||||
|
||||
// Finally, the resolver should be marked as resolved.
|
||||
|
Loading…
Reference in New Issue
Block a user