contractcourt: store htlc timeout sweeps
This commit is contained in:
parent
a38dc256fd
commit
d0ec872ef3
@ -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")
|
||||||
@ -371,6 +413,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
|||||||
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.
|
||||||
|
Loading…
Reference in New Issue
Block a user