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:
parent
eb07f89090
commit
03b76ad9a4
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user