contractcourt: save htlc success resolvers, including first stage

Checkpoint our htlc claims with on chain reasolutions, including our
first stage success tx where required.
This commit is contained in:
carla 2020-07-07 19:49:55 +02:00
parent eb07f89090
commit 03b76ad9a4
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
3 changed files with 122 additions and 7 deletions

View File

@ -77,6 +77,11 @@ const (
// ResolverOutcomeTimeout indicates that a contract was timed out on
// chain.
ResolverOutcomeTimeout ResolverOutcome = 3
// ResolverOutcomeFirstStage indicates that a htlc had to be claimed
// over two stages, with this outcome representing the confirmation
// of our success/timeout tx.
ResolverOutcomeFirstStage ResolverOutcome = 4
)
// ResolverReport provides an account of the outcome of a resolver. This differs

View File

@ -4,7 +4,9 @@ import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
@ -189,7 +191,12 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// Once the transaction has received a sufficient number of
// confirmations, we'll mark ourselves as fully resolved and exit.
h.resolved = true
return nil, h.Checkpoint(h)
// Checkpoint the resolver, and write the outcome to disk.
return nil, h.checkpointClaim(
&sweepTXID,
channeldb.ResolverOutcomeClaimed,
)
}
log.Infof("%T(%x): broadcasting second-layer transition tx: %v",
@ -241,18 +248,63 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
"after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
var spendTxid *chainhash.Hash
select {
case _, ok := <-spendNtfn.Spend:
case spend, ok := <-spendNtfn.Spend:
if !ok {
return nil, errResolverShuttingDown
}
spendTxid = spend.SpenderTxHash
case <-h.quit:
return nil, errResolverShuttingDown
}
h.resolved = true
return nil, h.Checkpoint(h)
return nil, h.checkpointClaim(
spendTxid, channeldb.ResolverOutcomeClaimed,
)
}
// checkpointClaim checkpoints the success resolver with the reports it needs.
// If this htlc was claimed two stages, it will write reports for both stages,
// otherwise it will just write for the single htlc claim.
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
outcome channeldb.ResolverOutcome) error {
// Create a resolver report for claiming of the htlc itself.
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports := []*channeldb.ResolverReport{
{
OutPoint: h.htlcResolution.ClaimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: outcome,
SpendTxID: spendTx,
},
}
// If we have a success tx, we append a report to represent our first
// stage claim.
if h.htlcResolution.SignedSuccessTx != nil {
// If the SignedSuccessTx is not nil, we are claiming the htlc
// in two stages, so we need to create a report for the first
// stage transaction as well.
spendTx := h.htlcResolution.SignedSuccessTx
spendTxID := spendTx.TxHash()
report := &channeldb.ResolverReport{
OutPoint: spendTx.TxIn[0].PreviousOutPoint,
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendTxID,
}
reports = append(reports, report)
}
// Finally, we checkpoint the resolver with our report(s).
return h.Checkpoint(h, reports...)
}
// Stop signals the resolver to cancel any current resolution processes, and

View File

@ -4,12 +4,16 @@ import (
"testing"
"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"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
var testHtlcAmt = lnwire.MilliSatoshi(200000)
type htlcSuccessResolverTestContext struct {
resolver *htlcSuccessResolver
notifier *mockNotifier
@ -61,6 +65,7 @@ func newHtlcSuccessResolverTextContext(t *testing.T) *htlcSuccessResolverTestCon
htlc: channeldb.HTLC{
RHash: testResHash,
OnionBlob: testOnionBlob,
Amt: testHtlcAmt,
},
}
@ -117,14 +122,23 @@ func TestSingleStageSuccess(t *testing.T) {
}
}
sweepTxid := sweepTx.TxHash()
claim := &channeldb.ResolverReport{
OutPoint: htlcOutpoint,
Amount: btcutil.Amount(testSignDesc.Output.Value),
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
SpendTxID: &sweepTxid,
}
testHtlcSuccess(
t, singleStageResolution, resolve, sweepTx,
t, singleStageResolution, resolve, sweepTx, claim,
)
}
// TestSecondStageResolution tests successful sweep of a second stage htlc
// claim.
func TestSecondStageResolution(t *testing.T) {
commitOutpoint := wire.OutPoint{Index: 2}
htlcOutpoint := wire.OutPoint{Index: 3}
sweepTx := &wire.MsgTx{
@ -138,7 +152,11 @@ func TestSecondStageResolution(t *testing.T) {
twoStageResolution := lnwallet.IncomingHtlcResolution{
Preimage: [32]byte{},
SignedSuccessTx: &wire.MsgTx{
TxIn: []*wire.TxIn{},
TxIn: []*wire.TxIn{
{
PreviousOutPoint: commitOutpoint,
},
},
TxOut: []*wire.TxOut{},
},
ClaimOutpoint: htlcOutpoint,
@ -153,17 +171,53 @@ func TestSecondStageResolution(t *testing.T) {
}
}
testHtlcSuccess(t, twoStageResolution, resolve, sweepTx)
successTx := twoStageResolution.SignedSuccessTx.TxHash()
firstStage := &channeldb.ResolverReport{
OutPoint: commitOutpoint,
Amount: testHtlcAmt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &successTx,
}
secondStage := &channeldb.ResolverReport{
OutPoint: htlcOutpoint,
Amount: btcutil.Amount(testSignDesc.Output.Value),
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
SpendTxID: &sweepHash,
}
testHtlcSuccess(
t, twoStageResolution, resolve, sweepTx, secondStage, firstStage,
)
}
// testHtlcSuccess tests resolution of a success resolver. It takes a resolve
// function which triggers resolution and the sweeptxid that will resolve it.
func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
resolve func(*htlcSuccessResolverTestContext), sweepTx *wire.MsgTx) {
resolve func(*htlcSuccessResolverTestContext),
sweepTx *wire.MsgTx, reports ...*channeldb.ResolverReport) {
defer timeout(t)()
ctx := newHtlcSuccessResolverTextContext(t)
// 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.resolver.htlcResolution = resolution
// We set the sweepTx to be non-nil and mark the output as already
@ -178,6 +232,10 @@ func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
// Trigger and event that will resolve our test context.
resolve(ctx)
for _, report := range reports {
assertResolverReport(t, reportChan, report)
}
// Wait for the resolver to fully complete.
ctx.waitForResult()
}