contractcourt: add Launch method to htlc success resolver

This commit breaks the `Resolve` into two parts - the first part is
moved into a `Launch` method that handles sending sweep requests, and
the second part remains in `Resolve` which handles waiting for the
spend. Since we are using both utxo nursery and sweeper at the same
time, to make sure this change doesn't break the existing behavior, we
implement the `Launch` as following,
- zero-fee htlc - handled by the sweeper
- direct output from the remote commit - handled by the sweeper
- legacy htlc - handled by the utxo nursery
This commit is contained in:
yyforyongyu
2024-07-16 07:24:45 +08:00
parent 913f5d4657
commit cf105e67f4
2 changed files with 153 additions and 122 deletions

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -20,6 +21,7 @@ import (
"github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
var testHtlcAmt = lnwire.MilliSatoshi(200000)
@@ -39,6 +41,15 @@ type htlcResolverTestContext struct {
t *testing.T
}
func newHtlcResolverTestContextFromReader(t *testing.T,
newResolver func(htlc channeldb.HTLC,
cfg ResolverConfig) ContractResolver) *htlcResolverTestContext {
ctx := newHtlcResolverTestContext(t, newResolver)
return ctx
}
func newHtlcResolverTestContext(t *testing.T,
newResolver func(htlc channeldb.HTLC,
cfg ResolverConfig) ContractResolver) *htlcResolverTestContext {
@@ -133,6 +144,7 @@ func newHtlcResolverTestContext(t *testing.T,
func (i *htlcResolverTestContext) resolve() {
// Start resolver.
i.resolverResultChan = make(chan resolveResult, 1)
go func() {
nextResolver, err := i.resolver.Resolve()
i.resolverResultChan <- resolveResult{
@@ -192,6 +204,7 @@ func TestHtlcSuccessSingleStage(t *testing.T) {
// sweeper.
details := &chainntnfs.SpendDetail{
SpendingTx: sweepTx,
SpentOutPoint: &htlcOutpoint,
SpenderTxHash: &sweepTxid,
}
ctx.notifier.SpendChan <- details
@@ -215,8 +228,8 @@ func TestHtlcSuccessSingleStage(t *testing.T) {
)
}
// TestSecondStageResolution tests successful sweep of a second stage htlc
// claim, going through the Nursery.
// TestHtlcSuccessSecondStageResolution tests successful sweep of a second
// stage htlc claim, going through the Nursery.
func TestHtlcSuccessSecondStageResolution(t *testing.T) {
commitOutpoint := wire.OutPoint{Index: 2}
htlcOutpoint := wire.OutPoint{Index: 3}
@@ -279,6 +292,7 @@ func TestHtlcSuccessSecondStageResolution(t *testing.T) {
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
SpendingTx: sweepTx,
SpentOutPoint: &htlcOutpoint,
SpenderTxHash: &sweepHash,
}
@@ -302,6 +316,8 @@ func TestHtlcSuccessSecondStageResolution(t *testing.T) {
// TestHtlcSuccessSecondStageResolutionSweeper test that a resolver with
// non-nil SignDetails will offer the second-level transaction to the sweeper
// for re-signing.
//
//nolint:ll
func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
commitOutpoint := wire.OutPoint{Index: 2}
htlcOutpoint := wire.OutPoint{Index: 3}
@@ -399,7 +415,20 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
_ bool) error {
resolver := ctx.resolver.(*htlcSuccessResolver)
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
var (
inp input.Input
ok bool
)
select {
case inp, ok = <-resolver.Sweeper.(*mockSweeper).sweptInputs:
require.True(t, ok)
case <-time.After(1 * time.Second):
t.Fatal("expected input to be swept")
}
op := inp.OutPoint()
if op != commitOutpoint {
return fmt.Errorf("outpoint %v swept, "+
@@ -412,6 +441,7 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
SpenderTxHash: &reSignedHash,
SpenderInputIndex: 1,
SpendingHeight: 10,
SpentOutPoint: &commitOutpoint,
}
return nil
},
@@ -434,13 +464,37 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
SpenderTxHash: &reSignedHash,
SpenderInputIndex: 1,
SpendingHeight: 10,
SpentOutPoint: &commitOutpoint,
}
}
// We expect it to sweep the second-level
// transaction we notfied about above.
resolver := ctx.resolver.(*htlcSuccessResolver)
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
// Mock `waitForSpend` to return the commit
// spend.
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
SpendingTx: reSignedSuccessTx,
SpenderTxHash: &reSignedHash,
SpenderInputIndex: 1,
SpendingHeight: 10,
SpentOutPoint: &commitOutpoint,
}
var (
inp input.Input
ok bool
)
select {
case inp, ok = <-resolver.Sweeper.(*mockSweeper).sweptInputs:
require.True(t, ok)
case <-time.After(1 * time.Second):
t.Fatal("expected input to be swept")
}
op := inp.OutPoint()
exp := wire.OutPoint{
Hash: reSignedHash,
@@ -457,6 +511,7 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
SpendingTx: sweepTx,
SpenderTxHash: &sweepHash,
SpendingHeight: 14,
SpentOutPoint: &op,
}
return nil
@@ -504,11 +559,14 @@ func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
// for the next portion of the test.
ctx := newHtlcResolverTestContext(t,
func(htlc channeldb.HTLC, cfg ResolverConfig) ContractResolver {
return &htlcSuccessResolver{
r := &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(cfg),
htlc: htlc,
htlcResolution: resolution,
}
r.initLogger("htlcSuccessResolver")
return r
},
)
@@ -606,7 +664,12 @@ func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext,
checkpointedState = append(checkpointedState, b.Bytes())
nextCheckpoint++
checkpointChan <- struct{}{}
select {
case checkpointChan <- struct{}{}:
case <-time.After(1 * time.Second):
t.Fatal("checkpoint timeout")
}
return nil
}
@@ -617,6 +680,8 @@ func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext,
// preCheckpoint logic if needed.
resumed := true
for i, cp := range expectedCheckpoints {
t.Logf("Running checkpoint %d", i)
if cp.preCheckpoint != nil {
if err := cp.preCheckpoint(ctx, resumed); err != nil {
t.Fatalf("failure at stage %d: %v", i, err)
@@ -625,15 +690,15 @@ func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext,
resumed = false
// Wait for the resolver to have checkpointed its state.
<-checkpointChan
select {
case <-checkpointChan:
case <-time.After(1 * time.Second):
t.Fatalf("resolver did not checkpoint at stage %d", i)
}
}
// Wait for the resolver to fully complete.
ctx.waitForResult()
if nextCheckpoint < len(expectedCheckpoints) {
t.Fatalf("not all checkpoints hit")
}
return checkpointedState
}