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
|
// ResolverOutcomeTimeout indicates that a contract was timed out on
|
||||||
// chain.
|
// chain.
|
||||||
ResolverOutcomeTimeout ResolverOutcome = 3
|
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
|
// ResolverReport provides an account of the outcome of a resolver. This differs
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
@ -189,7 +191,12 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
|||||||
// Once the transaction has received a sufficient number of
|
// Once the transaction has received a sufficient number of
|
||||||
// confirmations, we'll mark ourselves as fully resolved and exit.
|
// confirmations, we'll mark ourselves as fully resolved and exit.
|
||||||
h.resolved = true
|
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",
|
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 "+
|
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
|
||||||
"after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
|
"after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
|
||||||
|
|
||||||
|
var spendTxid *chainhash.Hash
|
||||||
select {
|
select {
|
||||||
case _, ok := <-spendNtfn.Spend:
|
case spend, ok := <-spendNtfn.Spend:
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
h.resolved = true
|
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
|
// Stop signals the resolver to cancel any current resolution processes, and
|
||||||
|
@ -4,12 +4,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testHtlcAmt = lnwire.MilliSatoshi(200000)
|
||||||
|
|
||||||
type htlcSuccessResolverTestContext struct {
|
type htlcSuccessResolverTestContext struct {
|
||||||
resolver *htlcSuccessResolver
|
resolver *htlcSuccessResolver
|
||||||
notifier *mockNotifier
|
notifier *mockNotifier
|
||||||
@ -61,6 +65,7 @@ func newHtlcSuccessResolverTextContext(t *testing.T) *htlcSuccessResolverTestCon
|
|||||||
htlc: channeldb.HTLC{
|
htlc: channeldb.HTLC{
|
||||||
RHash: testResHash,
|
RHash: testResHash,
|
||||||
OnionBlob: testOnionBlob,
|
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(
|
testHtlcSuccess(
|
||||||
t, singleStageResolution, resolve, sweepTx,
|
t, singleStageResolution, resolve, sweepTx, claim,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSecondStageResolution tests successful sweep of a second stage htlc
|
// TestSecondStageResolution tests successful sweep of a second stage htlc
|
||||||
// claim.
|
// claim.
|
||||||
func TestSecondStageResolution(t *testing.T) {
|
func TestSecondStageResolution(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 2}
|
||||||
htlcOutpoint := wire.OutPoint{Index: 3}
|
htlcOutpoint := wire.OutPoint{Index: 3}
|
||||||
|
|
||||||
sweepTx := &wire.MsgTx{
|
sweepTx := &wire.MsgTx{
|
||||||
@ -138,7 +152,11 @@ func TestSecondStageResolution(t *testing.T) {
|
|||||||
twoStageResolution := lnwallet.IncomingHtlcResolution{
|
twoStageResolution := lnwallet.IncomingHtlcResolution{
|
||||||
Preimage: [32]byte{},
|
Preimage: [32]byte{},
|
||||||
SignedSuccessTx: &wire.MsgTx{
|
SignedSuccessTx: &wire.MsgTx{
|
||||||
TxIn: []*wire.TxIn{},
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: commitOutpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
TxOut: []*wire.TxOut{},
|
TxOut: []*wire.TxOut{},
|
||||||
},
|
},
|
||||||
ClaimOutpoint: htlcOutpoint,
|
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
|
// testHtlcSuccess tests resolution of a success resolver. It takes a resolve
|
||||||
// function which triggers resolution and the sweeptxid that will resolve it.
|
// function which triggers resolution and the sweeptxid that will resolve it.
|
||||||
func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
|
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)()
|
defer timeout(t)()
|
||||||
|
|
||||||
ctx := newHtlcSuccessResolverTextContext(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
|
ctx.resolver.htlcResolution = resolution
|
||||||
|
|
||||||
// We set the sweepTx to be non-nil and mark the output as already
|
// 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.
|
// Trigger and event that will resolve our test context.
|
||||||
resolve(ctx)
|
resolve(ctx)
|
||||||
|
|
||||||
|
for _, report := range reports {
|
||||||
|
assertResolverReport(t, reportChan, report)
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the resolver to fully complete.
|
// Wait for the resolver to fully complete.
|
||||||
ctx.waitForResult()
|
ctx.waitForResult()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user