Merge pull request #3644 from joostjager/commit-sweep-no-nursery
cnct: commit sweep without nursery
This commit is contained in:
commit
840051cc3d
@ -15,7 +15,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
)
|
||||
|
||||
// ErrChainArbExiting signals that the chain arbitrator is shutting down.
|
||||
@ -113,8 +112,7 @@ type ChainArbitratorConfig struct {
|
||||
// the process of incubation. This is used when a resolver wishes to
|
||||
// pass off the output to the nursery as we're only waiting on an
|
||||
// absolute/relative item block.
|
||||
IncubateOutputs func(wire.OutPoint, *lnwallet.CommitOutputResolution,
|
||||
*lnwallet.OutgoingHtlcResolution,
|
||||
IncubateOutputs func(wire.OutPoint, *lnwallet.OutgoingHtlcResolution,
|
||||
*lnwallet.IncomingHtlcResolution, uint32) error
|
||||
|
||||
// PreimageDB is a global store of all known pre-images. We'll use this
|
||||
@ -142,7 +140,7 @@ type ChainArbitratorConfig struct {
|
||||
DisableChannel func(wire.OutPoint) error
|
||||
|
||||
// Sweeper allows resolvers to sweep their final outputs.
|
||||
Sweeper *sweep.UtxoSweeper
|
||||
Sweeper UtxoSweeper
|
||||
|
||||
// Registry is the invoice database that is used by resolvers to lookup
|
||||
// preimages and settle invoices.
|
||||
|
@ -26,6 +26,7 @@ func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, _ []byte,
|
||||
heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||
return &chainntnfs.ConfirmationEvent{
|
||||
Confirmed: m.confChan,
|
||||
Cancel: func() {},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -129,13 +129,31 @@ type ChannelArbitratorConfig struct {
|
||||
ChainArbitratorConfig
|
||||
}
|
||||
|
||||
// ReportOutputType describes the type of output that is being reported
|
||||
// on.
|
||||
type ReportOutputType uint8
|
||||
|
||||
const (
|
||||
// ReportOutputIncomingHtlc is an incoming hash time locked contract on
|
||||
// the commitment tx.
|
||||
ReportOutputIncomingHtlc ReportOutputType = iota
|
||||
|
||||
// ReportOutputOutgoingHtlc is an outgoing hash time locked contract on
|
||||
// the commitment tx.
|
||||
ReportOutputOutgoingHtlc
|
||||
|
||||
// ReportOutputUnencumbered is an uncontested output on the commitment
|
||||
// transaction paying to us directly.
|
||||
ReportOutputUnencumbered
|
||||
)
|
||||
|
||||
// ContractReport provides a summary of a commitment tx output.
|
||||
type ContractReport struct {
|
||||
// Outpoint is the final output that will be swept back to the wallet.
|
||||
Outpoint wire.OutPoint
|
||||
|
||||
// Incoming indicates whether the htlc was incoming to this channel.
|
||||
Incoming bool
|
||||
// Type indicates the type of the reported output.
|
||||
Type ReportOutputType
|
||||
|
||||
// Amount is the final value that will be swept in back to the wallet.
|
||||
Amount btcutil.Amount
|
||||
@ -859,27 +877,6 @@ func (c *ChannelArbitrator) stateStep(
|
||||
break
|
||||
}
|
||||
|
||||
// If we've have broadcast the commitment transaction, we send
|
||||
// our commitment output for incubation, but only if it wasn't
|
||||
// trimmed. We'll need to wait for a CSV timeout before we can
|
||||
// reclaim the funds.
|
||||
commitRes := contractResolutions.CommitResolution
|
||||
if commitRes != nil && commitRes.MaturityDelay > 0 {
|
||||
log.Infof("ChannelArbitrator(%v): sending commit "+
|
||||
"output for incubation", c.cfg.ChanPoint)
|
||||
|
||||
err = c.cfg.IncubateOutputs(
|
||||
c.cfg.ChanPoint, commitRes,
|
||||
nil, nil, triggerHeight,
|
||||
)
|
||||
if err != nil {
|
||||
// TODO(roasbeef): check for AlreadyExists errors
|
||||
log.Errorf("unable to incubate commitment "+
|
||||
"output: %v", err)
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know we'll need to act, we'll process the htlc
|
||||
// actions, wen create the structures we need to resolve all
|
||||
// outstanding contracts.
|
||||
|
@ -301,7 +301,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
|
||||
spendChan: make(chan *chainntnfs.SpendDetail),
|
||||
confChan: make(chan *chainntnfs.TxConfirmation),
|
||||
},
|
||||
IncubateOutputs: func(wire.OutPoint, *lnwallet.CommitOutputResolution,
|
||||
IncubateOutputs: func(wire.OutPoint,
|
||||
*lnwallet.OutgoingHtlcResolution,
|
||||
*lnwallet.IncomingHtlcResolution, uint32) error {
|
||||
|
||||
|
@ -2,9 +2,12 @@ package contractcourt
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
@ -36,6 +39,13 @@ type commitSweepResolver struct {
|
||||
// chanPoint is the channel point of the original contract.
|
||||
chanPoint wire.OutPoint
|
||||
|
||||
// currentReport stores the current state of the resolver for reporting
|
||||
// over the rpc interface.
|
||||
currentReport ContractReport
|
||||
|
||||
// reportLock prevents concurrent access to the resolver report.
|
||||
reportLock sync.Mutex
|
||||
|
||||
contractResolverKit
|
||||
}
|
||||
|
||||
@ -44,12 +54,17 @@ func newCommitSweepResolver(res lnwallet.CommitOutputResolution,
|
||||
broadcastHeight uint32,
|
||||
chanPoint wire.OutPoint, resCfg ResolverConfig) *commitSweepResolver {
|
||||
|
||||
return &commitSweepResolver{
|
||||
r := &commitSweepResolver{
|
||||
contractResolverKit: *newContractResolverKit(resCfg),
|
||||
commitResolution: res,
|
||||
broadcastHeight: broadcastHeight,
|
||||
chanPoint: chanPoint,
|
||||
}
|
||||
|
||||
r.initLogger(r)
|
||||
r.initReport()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ResolverKey returns an identifier which should be globally unique for this
|
||||
@ -59,6 +74,63 @@ func (c *commitSweepResolver) ResolverKey() []byte {
|
||||
return key[:]
|
||||
}
|
||||
|
||||
// waitForHeight registers for block notifications and waits for the provided
|
||||
// block height to be reached.
|
||||
func (c *commitSweepResolver) waitForHeight(waitHeight uint32) error {
|
||||
// Register for block epochs. After registration, the current height
|
||||
// will be sent on the channel immediately.
|
||||
blockEpochs, err := c.Notifier.RegisterBlockEpochNtfn(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer blockEpochs.Cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case newBlock, ok := <-blockEpochs.Epochs:
|
||||
if !ok {
|
||||
return errResolverShuttingDown
|
||||
}
|
||||
height := newBlock.Height
|
||||
if height >= int32(waitHeight) {
|
||||
return nil
|
||||
}
|
||||
|
||||
case <-c.quit:
|
||||
return errResolverShuttingDown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getCommitTxConfHeight waits for confirmation of the commitment tx and returns
|
||||
// the confirmation height.
|
||||
func (c *commitSweepResolver) getCommitTxConfHeight() (uint32, error) {
|
||||
txID := c.commitResolution.SelfOutPoint.Hash
|
||||
signDesc := c.commitResolution.SelfOutputSignDesc
|
||||
pkScript := signDesc.Output.PkScript
|
||||
const confDepth = 1
|
||||
confChan, err := c.Notifier.RegisterConfirmationsNtfn(
|
||||
&txID, pkScript, confDepth, c.broadcastHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer confChan.Cancel()
|
||||
|
||||
select {
|
||||
case txConfirmation, ok := <-confChan.Confirmed:
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("cannot get confirmation "+
|
||||
"for commit tx %v", txID)
|
||||
}
|
||||
|
||||
return txConfirmation.BlockHeight, nil
|
||||
|
||||
case <-c.quit:
|
||||
return 0, errResolverShuttingDown
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve instructs the contract resolver to resolve the output on-chain. Once
|
||||
// the output has been *fully* resolved, the function should return immediately
|
||||
// with a nil ContractResolver value for the first return value. In the case
|
||||
@ -72,159 +144,100 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// First, we'll register for a notification once the commitment output
|
||||
// itself has been confirmed.
|
||||
//
|
||||
// TODO(roasbeef): instead sweep asap if remote commit? yeh
|
||||
commitTXID := c.commitResolution.SelfOutPoint.Hash
|
||||
sweepScript := c.commitResolution.SelfOutputSignDesc.Output.PkScript
|
||||
confNtfn, err := c.Notifier.RegisterConfirmationsNtfn(
|
||||
&commitTXID, sweepScript, 1, c.broadcastHeight,
|
||||
)
|
||||
confHeight, err := c.getCommitTxConfHeight()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("%T(%v): waiting for commit tx to confirm", c, c.chanPoint)
|
||||
unlockHeight := confHeight + c.commitResolution.MaturityDelay
|
||||
|
||||
select {
|
||||
case _, ok := <-confNtfn.Confirmed:
|
||||
if !ok {
|
||||
return nil, errResolverShuttingDown
|
||||
c.log.Debugf("commit conf_height=%v, unlock_height=%v",
|
||||
confHeight, unlockHeight)
|
||||
|
||||
// Update report now that we learned the confirmation height.
|
||||
c.reportLock.Lock()
|
||||
c.currentReport.MaturityHeight = unlockHeight
|
||||
c.reportLock.Unlock()
|
||||
|
||||
// If there is a csv delay, we'll wait for that.
|
||||
if c.commitResolution.MaturityDelay > 0 {
|
||||
c.log.Debugf("waiting for csv lock to expire at height %v",
|
||||
unlockHeight)
|
||||
|
||||
// We only need to wait for the block before the block that
|
||||
// unlocks the spend path.
|
||||
err := c.waitForHeight(unlockHeight - 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case <-c.quit:
|
||||
return nil, errResolverShuttingDown
|
||||
}
|
||||
|
||||
// We're dealing with our commitment transaction if the delay on the
|
||||
// resolution isn't zero.
|
||||
isLocalCommitTx := c.commitResolution.MaturityDelay != 0
|
||||
|
||||
if !isLocalCommitTx {
|
||||
// There're two types of commitments, those that have tweaks
|
||||
// for the remote key (us in this case), and those that don't.
|
||||
// We'll rely on the presence of the commitment tweak to to
|
||||
// discern which type of commitment this is.
|
||||
var witnessType input.WitnessType
|
||||
if c.commitResolution.SelfOutputSignDesc.SingleTweak == nil {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
} else {
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
// We'll craft an input with all the information required for
|
||||
// the sweeper to create a fully valid sweeping transaction to
|
||||
// recover these coins.
|
||||
inp := input.MakeBaseInput(
|
||||
&c.commitResolution.SelfOutPoint,
|
||||
witnessType,
|
||||
&c.commitResolution.SelfOutputSignDesc,
|
||||
c.broadcastHeight,
|
||||
)
|
||||
|
||||
// With our input constructed, we'll now offer it to the
|
||||
// sweeper.
|
||||
log.Infof("%T(%v): sweeping commit output", c, c.chanPoint)
|
||||
|
||||
feePref := sweep.FeePreference{ConfTarget: commitOutputConfTarget}
|
||||
resultChan, err := c.Sweeper.SweepInput(&inp, feePref)
|
||||
if err != nil {
|
||||
log.Errorf("%T(%v): unable to sweep input: %v",
|
||||
c, c.chanPoint, err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sweeper is going to join this input with other inputs if
|
||||
// possible and publish the sweep tx. When the sweep tx
|
||||
// confirms, it signals us through the result channel with the
|
||||
// outcome. Wait for this to happen.
|
||||
select {
|
||||
case sweepResult := <-resultChan:
|
||||
if sweepResult.Err != nil {
|
||||
log.Errorf("%T(%v): unable to sweep input: %v",
|
||||
c, c.chanPoint, sweepResult.Err)
|
||||
|
||||
return nil, sweepResult.Err
|
||||
}
|
||||
|
||||
log.Infof("ChannelPoint(%v) commit tx is fully resolved by "+
|
||||
"sweep tx: %v", c.chanPoint, sweepResult.Tx.TxHash())
|
||||
case <-c.quit:
|
||||
return nil, errResolverShuttingDown
|
||||
}
|
||||
|
||||
c.resolved = true
|
||||
return nil, c.Checkpoint(c)
|
||||
// There're two types of commitments, those that have tweaks
|
||||
// for the remote key (us in this case), and those that don't.
|
||||
// We'll rely on the presence of the commitment tweak to to
|
||||
// discern which type of commitment this is.
|
||||
var witnessType input.WitnessType
|
||||
switch {
|
||||
case isLocalCommitTx:
|
||||
witnessType = input.CommitmentTimeLock
|
||||
case c.commitResolution.SelfOutputSignDesc.SingleTweak == nil:
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
default:
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
// Otherwise we are dealing with a local commitment transaction and the
|
||||
// output we need to sweep has been sent to the nursery for incubation.
|
||||
// In this case, we'll wait until the commitment output has been spent.
|
||||
spendNtfn, err := c.Notifier.RegisterSpendNtfn(
|
||||
// We'll craft an input with all the information required for
|
||||
// the sweeper to create a fully valid sweeping transaction to
|
||||
// recover these coins.
|
||||
inp := input.NewCsvInput(
|
||||
&c.commitResolution.SelfOutPoint,
|
||||
c.commitResolution.SelfOutputSignDesc.Output.PkScript,
|
||||
witnessType,
|
||||
&c.commitResolution.SelfOutputSignDesc,
|
||||
c.broadcastHeight,
|
||||
c.commitResolution.MaturityDelay,
|
||||
)
|
||||
|
||||
// With our input constructed, we'll now offer it to the
|
||||
// sweeper.
|
||||
c.log.Infof("sweeping commit output")
|
||||
|
||||
feePref := sweep.FeePreference{ConfTarget: commitOutputConfTarget}
|
||||
resultChan, err := c.Sweeper.SweepInput(inp, feePref)
|
||||
if err != nil {
|
||||
c.log.Errorf("unable to sweep input: %v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("%T(%v): waiting for commit output to be swept", c,
|
||||
c.chanPoint)
|
||||
|
||||
var sweepTx *wire.MsgTx
|
||||
// Sweeper is going to join this input with other inputs if
|
||||
// possible and publish the sweep tx. When the sweep tx
|
||||
// confirms, it signals us through the result channel with the
|
||||
// outcome. Wait for this to happen.
|
||||
select {
|
||||
case commitSpend, ok := <-spendNtfn.Spend:
|
||||
if !ok {
|
||||
return nil, errResolverShuttingDown
|
||||
case sweepResult := <-resultChan:
|
||||
if sweepResult.Err != nil {
|
||||
c.log.Errorf("unable to sweep input: %v",
|
||||
sweepResult.Err)
|
||||
|
||||
return nil, sweepResult.Err
|
||||
}
|
||||
|
||||
// Once we detect the commitment output has been spent,
|
||||
// we'll extract the spending transaction itself, as we
|
||||
// now consider this to be our sweep transaction.
|
||||
sweepTx = commitSpend.SpendingTx
|
||||
|
||||
log.Infof("%T(%v): commit output swept by txid=%v",
|
||||
c, c.chanPoint, sweepTx.TxHash())
|
||||
|
||||
if err := c.Checkpoint(c); err != nil {
|
||||
log.Errorf("unable to Checkpoint: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
c.log.Infof("commit tx fully resolved by sweep tx: %v",
|
||||
sweepResult.Tx.TxHash())
|
||||
case <-c.quit:
|
||||
return nil, errResolverShuttingDown
|
||||
}
|
||||
|
||||
log.Infof("%T(%v): waiting for commit sweep txid=%v conf", c, c.chanPoint,
|
||||
sweepTx.TxHash())
|
||||
// Funds have been swept and balance is no longer in limbo.
|
||||
c.reportLock.Lock()
|
||||
c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
|
||||
c.currentReport.LimboBalance = 0
|
||||
c.reportLock.Unlock()
|
||||
|
||||
// Now we'll wait until the sweeping transaction has been fully
|
||||
// confirmed. Once it's confirmed, we can mark this contract resolved.
|
||||
sweepTXID := sweepTx.TxHash()
|
||||
sweepingScript := sweepTx.TxOut[0].PkScript
|
||||
confNtfn, err = c.Notifier.RegisterConfirmationsNtfn(
|
||||
&sweepTXID, sweepingScript, 1, c.broadcastHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
select {
|
||||
case confInfo, ok := <-confNtfn.Confirmed:
|
||||
if !ok {
|
||||
return nil, errResolverShuttingDown
|
||||
}
|
||||
|
||||
log.Infof("ChannelPoint(%v) commit tx is fully resolved, at height: %v",
|
||||
c.chanPoint, confInfo.BlockHeight)
|
||||
|
||||
case <-c.quit:
|
||||
return nil, errResolverShuttingDown
|
||||
}
|
||||
|
||||
// Once the transaction has received a sufficient number of
|
||||
// confirmations, we'll mark ourselves as fully resolved and exit.
|
||||
c.resolved = true
|
||||
return nil, c.Checkpoint(c)
|
||||
}
|
||||
@ -308,9 +321,43 @@ func newCommitSweepResolverFromReader(r io.Reader, resCfg ResolverConfig) (
|
||||
// removed this, but keep in mind that this data may still be present in
|
||||
// the database.
|
||||
|
||||
c.initLogger(c)
|
||||
c.initReport()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// report returns a report on the resolution state of the contract.
|
||||
func (c *commitSweepResolver) report() *ContractReport {
|
||||
c.reportLock.Lock()
|
||||
defer c.reportLock.Unlock()
|
||||
|
||||
copy := c.currentReport
|
||||
return ©
|
||||
}
|
||||
|
||||
// initReport initializes the pending channels report for this resolver.
|
||||
func (c *commitSweepResolver) initReport() {
|
||||
amt := btcutil.Amount(
|
||||
c.commitResolution.SelfOutputSignDesc.Output.Value,
|
||||
)
|
||||
|
||||
// Set the initial report. All fields are filled in, except for the
|
||||
// maturity height which remains 0 until Resolve() is executed.
|
||||
//
|
||||
// TODO(joostjager): Resolvers only activate after the commit tx
|
||||
// confirms. With more refactoring in channel arbitrator, it would be
|
||||
// possible to make the confirmation height part of ResolverConfig and
|
||||
// populate MaturityHeight here.
|
||||
c.currentReport = ContractReport{
|
||||
Outpoint: c.commitResolution.SelfOutPoint,
|
||||
Type: ReportOutputUnencumbered,
|
||||
Amount: amt,
|
||||
LimboBalance: amt,
|
||||
RecoveredBalance: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// A compile time assertion to ensure commitSweepResolver meets the
|
||||
// ContractResolver interface.
|
||||
var _ ContractResolver = (*commitSweepResolver)(nil)
|
||||
var _ reportingContractResolver = (*commitSweepResolver)(nil)
|
||||
|
225
contractcourt/commit_sweep_resolver_test.go
Normal file
225
contractcourt/commit_sweep_resolver_test.go
Normal file
@ -0,0 +1,225 @@
|
||||
package contractcourt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
)
|
||||
|
||||
type commitSweepResolverTestContext struct {
|
||||
resolver *commitSweepResolver
|
||||
notifier *mockNotifier
|
||||
sweeper *mockSweeper
|
||||
resolverResultChan chan resolveResult
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func newCommitSweepResolverTestContext(t *testing.T,
|
||||
resolution *lnwallet.CommitOutputResolution) *commitSweepResolverTestContext {
|
||||
|
||||
notifier := &mockNotifier{
|
||||
epochChan: make(chan *chainntnfs.BlockEpoch),
|
||||
spendChan: make(chan *chainntnfs.SpendDetail),
|
||||
confChan: make(chan *chainntnfs.TxConfirmation),
|
||||
}
|
||||
|
||||
sweeper := newMockSweeper()
|
||||
|
||||
checkPointChan := make(chan struct{}, 1)
|
||||
|
||||
chainCfg := ChannelArbitratorConfig{
|
||||
ChainArbitratorConfig: ChainArbitratorConfig{
|
||||
Notifier: notifier,
|
||||
Sweeper: sweeper,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := ResolverConfig{
|
||||
ChannelArbitratorConfig: chainCfg,
|
||||
Checkpoint: func(_ ContractResolver) error {
|
||||
checkPointChan <- struct{}{}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
resolver := newCommitSweepResolver(
|
||||
*resolution, 0, wire.OutPoint{}, cfg,
|
||||
)
|
||||
|
||||
return &commitSweepResolverTestContext{
|
||||
resolver: resolver,
|
||||
notifier: notifier,
|
||||
sweeper: sweeper,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *commitSweepResolverTestContext) resolve() {
|
||||
// Start resolver.
|
||||
i.resolverResultChan = make(chan resolveResult, 1)
|
||||
go func() {
|
||||
nextResolver, err := i.resolver.Resolve()
|
||||
i.resolverResultChan <- resolveResult{
|
||||
nextResolver: nextResolver,
|
||||
err: err,
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (i *commitSweepResolverTestContext) notifyEpoch(height int32) {
|
||||
i.notifier.epochChan <- &chainntnfs.BlockEpoch{
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *commitSweepResolverTestContext) waitForResult() {
|
||||
i.t.Helper()
|
||||
|
||||
result := <-i.resolverResultChan
|
||||
if result.err != nil {
|
||||
i.t.Fatal(result.err)
|
||||
}
|
||||
|
||||
if result.nextResolver != nil {
|
||||
i.t.Fatal("expected no next resolver")
|
||||
}
|
||||
}
|
||||
|
||||
type mockSweeper struct {
|
||||
sweptInputs chan input.Input
|
||||
}
|
||||
|
||||
func newMockSweeper() *mockSweeper {
|
||||
return &mockSweeper{
|
||||
sweptInputs: make(chan input.Input),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mockSweeper) SweepInput(input input.Input,
|
||||
feePreference sweep.FeePreference) (chan sweep.Result, error) {
|
||||
|
||||
s.sweptInputs <- input
|
||||
|
||||
result := make(chan sweep.Result, 1)
|
||||
result <- sweep.Result{
|
||||
Tx: &wire.MsgTx{},
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *mockSweeper) CreateSweepTx(inputs []input.Input, feePref sweep.FeePreference,
|
||||
currentBlockHeight uint32) (*wire.MsgTx, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var _ UtxoSweeper = &mockSweeper{}
|
||||
|
||||
// TestCommitSweepResolverNoDelay tests resolution of a direct commitment output
|
||||
// unencumbered by a time lock.
|
||||
func TestCommitSweepResolverNoDelay(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
res := lnwallet.CommitOutputResolution{
|
||||
SelfOutputSignDesc: input.SignDescriptor{
|
||||
Output: &wire.TxOut{
|
||||
Value: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := newCommitSweepResolverTestContext(t, &res)
|
||||
ctx.resolve()
|
||||
|
||||
ctx.notifier.confChan <- &chainntnfs.TxConfirmation{}
|
||||
|
||||
// No csv delay, so the input should be swept immediately.
|
||||
<-ctx.sweeper.sweptInputs
|
||||
|
||||
ctx.waitForResult()
|
||||
}
|
||||
|
||||
// TestCommitSweepResolverDelay tests resolution of a direct commitment output
|
||||
// that is encumbered by a time lock.
|
||||
func TestCommitSweepResolverDelay(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
amt := int64(100)
|
||||
outpoint := wire.OutPoint{
|
||||
Index: 5,
|
||||
}
|
||||
res := lnwallet.CommitOutputResolution{
|
||||
SelfOutputSignDesc: input.SignDescriptor{
|
||||
Output: &wire.TxOut{
|
||||
Value: amt,
|
||||
},
|
||||
},
|
||||
MaturityDelay: 3,
|
||||
SelfOutPoint: outpoint,
|
||||
}
|
||||
|
||||
ctx := newCommitSweepResolverTestContext(t, &res)
|
||||
|
||||
report := ctx.resolver.report()
|
||||
if !reflect.DeepEqual(report, &ContractReport{
|
||||
Outpoint: outpoint,
|
||||
Type: ReportOutputUnencumbered,
|
||||
Amount: btcutil.Amount(amt),
|
||||
LimboBalance: btcutil.Amount(amt),
|
||||
}) {
|
||||
t.Fatal("unexpected resolver report")
|
||||
}
|
||||
|
||||
ctx.resolve()
|
||||
|
||||
ctx.notifier.confChan <- &chainntnfs.TxConfirmation{
|
||||
BlockHeight: testInitialBlockHeight - 1,
|
||||
}
|
||||
|
||||
// Allow resolver to process confirmation.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Expect report to be updated.
|
||||
report = ctx.resolver.report()
|
||||
if report.MaturityHeight != testInitialBlockHeight+2 {
|
||||
t.Fatal("report maturity height incorrect")
|
||||
}
|
||||
|
||||
// Notify initial block height. The csv lock is still in effect, so we
|
||||
// don't expect any sweep to happen yet.
|
||||
ctx.notifyEpoch(testInitialBlockHeight)
|
||||
|
||||
select {
|
||||
case <-ctx.sweeper.sweptInputs:
|
||||
t.Fatal("no sweep expected")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
// A new block arrives. The commit tx confirmed at height -1 and the csv
|
||||
// is 3, so a spend will be valid in the first block after height +1.
|
||||
ctx.notifyEpoch(testInitialBlockHeight + 1)
|
||||
|
||||
<-ctx.sweeper.sweptInputs
|
||||
|
||||
ctx.waitForResult()
|
||||
|
||||
report = ctx.resolver.report()
|
||||
if !reflect.DeepEqual(report, &ContractReport{
|
||||
Outpoint: outpoint,
|
||||
Type: ReportOutputUnencumbered,
|
||||
Amount: btcutil.Amount(amt),
|
||||
RecoveredBalance: btcutil.Amount(amt),
|
||||
MaturityHeight: testInitialBlockHeight + 2,
|
||||
}) {
|
||||
t.Fatal("unexpected resolver report")
|
||||
}
|
||||
}
|
@ -3,9 +3,12 @@ package contractcourt
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
)
|
||||
|
||||
@ -93,6 +96,8 @@ type ResolverConfig struct {
|
||||
type contractResolverKit struct {
|
||||
ResolverConfig
|
||||
|
||||
log btclog.Logger
|
||||
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
@ -104,6 +109,12 @@ func newContractResolverKit(cfg ResolverConfig) *contractResolverKit {
|
||||
}
|
||||
}
|
||||
|
||||
// initLogger initializes the resolver-specific logger.
|
||||
func (r *contractResolverKit) initLogger(resolver ContractResolver) {
|
||||
logPrefix := fmt.Sprintf("%T(%v):", resolver, r.ChanPoint)
|
||||
r.log = build.NewPrefixLog(logPrefix, log)
|
||||
}
|
||||
|
||||
var (
|
||||
// errResolverShuttingDown is returned when the resolver stops
|
||||
// progressing because it received the quit signal.
|
||||
|
@ -293,7 +293,7 @@ func (h *htlcIncomingContestResolver) report() *ContractReport {
|
||||
|
||||
return &ContractReport{
|
||||
Outpoint: h.htlcResolution.ClaimOutpoint,
|
||||
Incoming: true,
|
||||
Type: ReportOutputIncomingHtlc,
|
||||
Amount: finalAmt,
|
||||
MaturityHeight: h.htlcExpiry,
|
||||
LimboBalance: finalAmt,
|
||||
|
@ -294,7 +294,7 @@ func (i *incomingResolverTestContext) waitForResult(expectSuccessRes bool) {
|
||||
}
|
||||
|
||||
if !expectSuccessRes {
|
||||
if err != nil {
|
||||
if i.nextResolver != nil {
|
||||
i.t.Fatal("expected no next resolver")
|
||||
}
|
||||
return
|
||||
|
@ -166,7 +166,7 @@ func (h *htlcOutgoingContestResolver) report() *ContractReport {
|
||||
|
||||
return &ContractReport{
|
||||
Outpoint: h.htlcResolution.ClaimOutpoint,
|
||||
Incoming: false,
|
||||
Type: ReportOutputOutgoingHtlc,
|
||||
Amount: finalAmt,
|
||||
MaturityHeight: h.htlcResolution.Expiry,
|
||||
LimboBalance: finalAmt,
|
||||
|
@ -211,7 +211,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
||||
h, h.htlc.RHash[:])
|
||||
|
||||
err := h.IncubateOutputs(
|
||||
h.ChanPoint, nil, nil, &h.htlcResolution,
|
||||
h.ChanPoint, nil, &h.htlcResolution,
|
||||
h.broadcastHeight,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -247,7 +247,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
h.htlcResolution.ClaimOutpoint)
|
||||
|
||||
err := h.IncubateOutputs(
|
||||
h.ChanPoint, nil, &h.htlcResolution, nil,
|
||||
h.ChanPoint, &h.htlcResolution, nil,
|
||||
h.broadcastHeight,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -216,7 +216,6 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
||||
Notifier: notifier,
|
||||
PreimageDB: witnessBeacon,
|
||||
IncubateOutputs: func(wire.OutPoint,
|
||||
*lnwallet.CommitOutputResolution,
|
||||
*lnwallet.OutgoingHtlcResolution,
|
||||
*lnwallet.IncomingHtlcResolution,
|
||||
uint32) error {
|
||||
|
@ -3,11 +3,14 @@ package contractcourt
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
)
|
||||
|
||||
// Registry is an interface which represents the invoice registry.
|
||||
@ -36,3 +39,16 @@ type OnionProcessor interface {
|
||||
// the passed io.Reader instance.
|
||||
ReconstructHopIterator(r io.Reader, rHash []byte) (hop.Iterator, error)
|
||||
}
|
||||
|
||||
// UtxoSweeper defines the sweep functions that contract court requires.
|
||||
type UtxoSweeper interface {
|
||||
// SweepInput sweeps inputs back into the wallet.
|
||||
SweepInput(input input.Input,
|
||||
feePreference sweep.FeePreference) (chan sweep.Result, error)
|
||||
|
||||
// CreateSweepTx accepts a list of inputs and signs and generates a txn
|
||||
// that spends from them. This method also makes an accurate fee
|
||||
// estimate before generating the required witnesses.
|
||||
CreateSweepTx(inputs []input.Input, feePref sweep.FeePreference,
|
||||
currentBlockHeight uint32) (*wire.MsgTx, error)
|
||||
}
|
||||
|
@ -44,10 +44,11 @@ type Input interface {
|
||||
}
|
||||
|
||||
type inputKit struct {
|
||||
outpoint wire.OutPoint
|
||||
witnessType WitnessType
|
||||
signDesc SignDescriptor
|
||||
heightHint uint32
|
||||
outpoint wire.OutPoint
|
||||
witnessType WitnessType
|
||||
signDesc SignDescriptor
|
||||
heightHint uint32
|
||||
blockToMaturity uint32
|
||||
}
|
||||
|
||||
// OutPoint returns the breached output's identifier that is to be included as
|
||||
@ -74,6 +75,13 @@ func (i *inputKit) HeightHint() uint32 {
|
||||
return i.heightHint
|
||||
}
|
||||
|
||||
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
|
||||
// must be built on top of the confirmation height before the output can be
|
||||
// spent. For non-CSV locked inputs this is always zero.
|
||||
func (i *inputKit) BlocksToMaturity() uint32 {
|
||||
return i.blockToMaturity
|
||||
}
|
||||
|
||||
// BaseInput contains all the information needed to sweep a basic output
|
||||
// (CSV/CLTV/no time lock)
|
||||
type BaseInput struct {
|
||||
@ -107,6 +115,23 @@ func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
|
||||
return &input
|
||||
}
|
||||
|
||||
// NewCsvInput assembles a new csv-locked input that can be used to
|
||||
// construct a sweep transaction.
|
||||
func NewCsvInput(outpoint *wire.OutPoint, witnessType WitnessType,
|
||||
signDescriptor *SignDescriptor, heightHint uint32,
|
||||
blockToMaturity uint32) *BaseInput {
|
||||
|
||||
return &BaseInput{
|
||||
inputKit{
|
||||
outpoint: *outpoint,
|
||||
witnessType: witnessType,
|
||||
signDesc: *signDescriptor,
|
||||
heightHint: heightHint,
|
||||
blockToMaturity: blockToMaturity,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CraftInputScript returns a valid set of input scripts allowing this output
|
||||
// to be spent. The returned input scripts should target the input at location
|
||||
// txIndex within the passed transaction. The input scripts generated by this
|
||||
@ -119,13 +144,6 @@ func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
|
||||
return witnessFunc(txn, hashCache, txinIdx)
|
||||
}
|
||||
|
||||
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
|
||||
// must be built on top of the confirmation height before the output can be
|
||||
// spent. For non-CSV locked inputs this is always zero.
|
||||
func (bi *BaseInput) BlocksToMaturity() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
|
||||
// is expected to reside on the commitment tx of the remote party and should
|
||||
// not be a second level tx output.
|
||||
@ -175,13 +193,6 @@ func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
|
||||
// must be built on top of the confirmation height before the output can be
|
||||
// spent.
|
||||
func (h *HtlcSucceedInput) BlocksToMaturity() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compile-time constraints to ensure each input struct implement the Input
|
||||
// interface.
|
||||
var _ Input = (*BaseInput)(nil)
|
||||
|
60
rpcserver.go
60
rpcserver.go
@ -34,6 +34,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/chanbackup"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||
"github.com/lightningnetwork/lnd/contractcourt"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
@ -2451,23 +2452,51 @@ func (r *rpcServer) arbitratorPopulateForceCloseResp(chanPoint *wire.OutPoint,
|
||||
reports := arbitrator.Report()
|
||||
|
||||
for _, report := range reports {
|
||||
htlc := &lnrpc.PendingHTLC{
|
||||
Incoming: report.Incoming,
|
||||
Amount: int64(report.Amount),
|
||||
Outpoint: report.Outpoint.String(),
|
||||
MaturityHeight: report.MaturityHeight,
|
||||
Stage: report.Stage,
|
||||
}
|
||||
switch report.Type {
|
||||
|
||||
if htlc.MaturityHeight != 0 {
|
||||
htlc.BlocksTilMaturity =
|
||||
int32(htlc.MaturityHeight) - currentHeight
|
||||
// For a direct output, populate/update the top level
|
||||
// response properties.
|
||||
case contractcourt.ReportOutputUnencumbered:
|
||||
// Populate the maturity height fields for the direct
|
||||
// commitment output to us.
|
||||
forceClose.MaturityHeight = report.MaturityHeight
|
||||
|
||||
// If the transaction has been confirmed, then we can
|
||||
// compute how many blocks it has left.
|
||||
if forceClose.MaturityHeight != 0 {
|
||||
forceClose.BlocksTilMaturity =
|
||||
int32(forceClose.MaturityHeight) -
|
||||
currentHeight
|
||||
}
|
||||
|
||||
// Add htlcs to the PendingHtlcs response property.
|
||||
case contractcourt.ReportOutputIncomingHtlc,
|
||||
contractcourt.ReportOutputOutgoingHtlc:
|
||||
|
||||
incoming := report.Type == contractcourt.ReportOutputIncomingHtlc
|
||||
htlc := &lnrpc.PendingHTLC{
|
||||
Incoming: incoming,
|
||||
Amount: int64(report.Amount),
|
||||
Outpoint: report.Outpoint.String(),
|
||||
MaturityHeight: report.MaturityHeight,
|
||||
Stage: report.Stage,
|
||||
}
|
||||
|
||||
if htlc.MaturityHeight != 0 {
|
||||
htlc.BlocksTilMaturity =
|
||||
int32(htlc.MaturityHeight) - currentHeight
|
||||
}
|
||||
|
||||
forceClose.PendingHtlcs = append(forceClose.PendingHtlcs, htlc)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown report output type: %v",
|
||||
report.Type)
|
||||
}
|
||||
|
||||
forceClose.LimboBalance += int64(report.LimboBalance)
|
||||
forceClose.RecoveredBalance += int64(report.RecoveredBalance)
|
||||
|
||||
forceClose.PendingHtlcs = append(forceClose.PendingHtlcs, htlc)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -2498,15 +2527,6 @@ func (r *rpcServer) nurseryPopulateForceCloseResp(chanPoint *wire.OutPoint,
|
||||
// wallet.
|
||||
forceClose.LimboBalance = int64(nurseryInfo.limboBalance)
|
||||
forceClose.RecoveredBalance = int64(nurseryInfo.recoveredBalance)
|
||||
forceClose.MaturityHeight = nurseryInfo.maturityHeight
|
||||
|
||||
// If the transaction has been confirmed, then we can compute how many
|
||||
// blocks it has left.
|
||||
if forceClose.MaturityHeight != 0 {
|
||||
forceClose.BlocksTilMaturity =
|
||||
int32(forceClose.MaturityHeight) -
|
||||
currentHeight
|
||||
}
|
||||
|
||||
for _, htlcReport := range nurseryInfo.htlcs {
|
||||
// TODO(conner) set incoming flag appropriately after handling
|
||||
|
@ -847,7 +847,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
return nil
|
||||
},
|
||||
IncubateOutputs: func(chanPoint wire.OutPoint,
|
||||
commitRes *lnwallet.CommitOutputResolution,
|
||||
outHtlcRes *lnwallet.OutgoingHtlcResolution,
|
||||
inHtlcRes *lnwallet.IncomingHtlcResolution,
|
||||
broadcastHeight uint32) error {
|
||||
@ -864,7 +863,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
}
|
||||
|
||||
return s.utxoNursery.IncubateOutputs(
|
||||
chanPoint, commitRes, outRes, inRes,
|
||||
chanPoint, outRes, inRes,
|
||||
broadcastHeight,
|
||||
)
|
||||
},
|
||||
|
@ -330,7 +330,6 @@ func (u *utxoNursery) Stop() error {
|
||||
// they're CLTV absolute time locked, or if they're CSV relative time locked.
|
||||
// Once all outputs reach maturity, they'll be swept back into the wallet.
|
||||
func (u *utxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
|
||||
commitResolution *lnwallet.CommitOutputResolution,
|
||||
outgoingHtlcs []lnwallet.OutgoingHtlcResolution,
|
||||
incomingHtlcs []lnwallet.IncomingHtlcResolution,
|
||||
broadcastHeight uint32) error {
|
||||
@ -352,8 +351,6 @@ func (u *utxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
|
||||
|
||||
numHtlcs := len(incomingHtlcs) + len(outgoingHtlcs)
|
||||
var (
|
||||
hasCommit bool
|
||||
|
||||
// Kid outputs can be swept after an initial confirmation
|
||||
// followed by a maturity period.Baby outputs are two stage and
|
||||
// will need to wait for an absolute time out to reach a
|
||||
@ -364,28 +361,6 @@ func (u *utxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
|
||||
|
||||
// 1. Build all the spendable outputs that we will try to incubate.
|
||||
|
||||
// It could be that our to-self output was below the dust limit. In
|
||||
// that case the commit resolution would be nil and we would not have
|
||||
// that output to incubate.
|
||||
if commitResolution != nil {
|
||||
hasCommit = true
|
||||
selfOutput := makeKidOutput(
|
||||
&commitResolution.SelfOutPoint,
|
||||
&chanPoint,
|
||||
commitResolution.MaturityDelay,
|
||||
input.CommitmentTimeLock,
|
||||
&commitResolution.SelfOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
|
||||
// We'll skip any zero valued outputs as this indicates we
|
||||
// don't have a settled balance within the commitment
|
||||
// transaction.
|
||||
if selfOutput.Amount() > 0 {
|
||||
kidOutputs = append(kidOutputs, selfOutput)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(roasbeef): query and see if we already have, if so don't add?
|
||||
|
||||
// For each incoming HTLC, we'll register a kid output marked as a
|
||||
@ -436,8 +411,8 @@ func (u *utxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
|
||||
// * need ability to cancel in the case that we learn of pre-image or
|
||||
// remote party pulls
|
||||
|
||||
utxnLog.Infof("Incubating Channel(%s) has-commit=%v, num-htlcs=%d",
|
||||
chanPoint, hasCommit, numHtlcs)
|
||||
utxnLog.Infof("Incubating Channel(%s) num-htlcs=%d",
|
||||
chanPoint, numHtlcs)
|
||||
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
@ -538,8 +513,6 @@ func (u *utxoNursery) NurseryReport(
|
||||
// Preschool outputs are awaiting the
|
||||
// confirmation of the commitment transaction.
|
||||
switch kid.WitnessType() {
|
||||
case input.CommitmentTimeLock:
|
||||
report.AddLimboCommitment(&kid)
|
||||
|
||||
case input.HtlcAcceptedSuccessSecondLevel:
|
||||
// An HTLC output on our commitment transaction
|
||||
@ -561,11 +534,6 @@ func (u *utxoNursery) NurseryReport(
|
||||
// We can distinguish them via their witness
|
||||
// types.
|
||||
switch kid.WitnessType() {
|
||||
case input.CommitmentTimeLock:
|
||||
// The commitment transaction has been
|
||||
// confirmed, and we are waiting the CSV
|
||||
// delay to expire.
|
||||
report.AddLimboCommitment(&kid)
|
||||
|
||||
case input.HtlcOfferedRemoteTimeout:
|
||||
// This is an HTLC output on the
|
||||
@ -590,11 +558,6 @@ func (u *utxoNursery) NurseryReport(
|
||||
// will contribute towards the recovered
|
||||
// balance.
|
||||
switch kid.WitnessType() {
|
||||
case input.CommitmentTimeLock:
|
||||
// The commitment output was
|
||||
// successfully swept back into a
|
||||
// regular p2wkh output.
|
||||
report.AddRecoveredCommitment(&kid)
|
||||
|
||||
case input.HtlcAcceptedSuccessSecondLevel:
|
||||
fallthrough
|
||||
@ -1071,11 +1034,6 @@ type contractMaturityReport struct {
|
||||
// recoveredBalance is the total value that has been successfully swept
|
||||
// back to the user's wallet.
|
||||
recoveredBalance btcutil.Amount
|
||||
|
||||
// maturityHeight is the absolute block height that this output will
|
||||
// mature at.
|
||||
maturityHeight uint32
|
||||
|
||||
// htlcs records a maturity report for each htlc output in this channel.
|
||||
htlcs []htlcMaturityReport
|
||||
}
|
||||
@ -1100,26 +1058,6 @@ type htlcMaturityReport struct {
|
||||
stage uint32
|
||||
}
|
||||
|
||||
// AddLimboCommitment adds an incubating commitment output to maturity
|
||||
// report's htlcs, and contributes its amount to the limbo balance.
|
||||
func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) {
|
||||
c.limboBalance += kid.Amount()
|
||||
|
||||
// If the confirmation height is set, then this means the contract has
|
||||
// been confirmed, and we know the final maturity height.
|
||||
if kid.ConfHeight() != 0 {
|
||||
c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight()
|
||||
}
|
||||
}
|
||||
|
||||
// AddRecoveredCommitment adds a graduated commitment output to maturity
|
||||
// report's htlcs, and contributes its amount to the recovered balance.
|
||||
func (c *contractMaturityReport) AddRecoveredCommitment(kid *kidOutput) {
|
||||
c.recoveredBalance += kid.Amount()
|
||||
|
||||
c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight()
|
||||
}
|
||||
|
||||
// AddLimboStage1TimeoutHtlc adds an htlc crib output to the maturity report's
|
||||
// htlcs, and contributes its amount to the limbo balance.
|
||||
func (c *contractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
|
||||
|
@ -650,7 +650,6 @@ func incubateTestOutput(t *testing.T, nursery *utxoNursery,
|
||||
// Hand off to nursery.
|
||||
err := nursery.IncubateOutputs(
|
||||
testChanPoint,
|
||||
nil,
|
||||
[]lnwallet.OutgoingHtlcResolution{*outgoingRes},
|
||||
nil, 0,
|
||||
)
|
||||
@ -839,59 +838,6 @@ func testNurseryOutgoingHtlcSuccessOnRemote(t *testing.T,
|
||||
ctx.finish()
|
||||
}
|
||||
|
||||
func TestNurseryCommitSuccessOnLocal(t *testing.T) {
|
||||
testRestartLoop(t, testNurseryCommitSuccessOnLocal)
|
||||
}
|
||||
|
||||
func testNurseryCommitSuccessOnLocal(t *testing.T,
|
||||
checkStartStop func(func()) bool) {
|
||||
|
||||
ctx := createNurseryTestContext(t, checkStartStop)
|
||||
|
||||
commitRes := createCommitmentRes()
|
||||
|
||||
// Hand off to nursery.
|
||||
err := ctx.nursery.IncubateOutputs(
|
||||
testChanPoint,
|
||||
commitRes, nil, nil, 0,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify that commitment output is showing up in nursery report as
|
||||
// limbo balance.
|
||||
assertNurseryReport(t, ctx.nursery, 0, 0, 10000)
|
||||
|
||||
ctx.restart()
|
||||
|
||||
// Notify confirmation of the commitment tx.
|
||||
err = ctx.notifier.ConfirmTx(&commitRes.SelfOutPoint.Hash, 124)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for output to be promoted from PSCL to KNDR.
|
||||
select {
|
||||
case <-ctx.store.preschoolToKinderChan:
|
||||
case <-time.After(defaultTestTimeout):
|
||||
t.Fatalf("output not promoted to KNDR")
|
||||
}
|
||||
|
||||
ctx.restart()
|
||||
|
||||
// Notify arrival of block where commit output CSV expires.
|
||||
ctx.notifyEpoch(126)
|
||||
|
||||
// Check final sweep into wallet.
|
||||
testSweep(t, ctx, func() {
|
||||
// Check limbo balance after sweep publication
|
||||
assertNurseryReport(t, ctx.nursery, 0, 0, 10000)
|
||||
})
|
||||
|
||||
ctx.finish()
|
||||
}
|
||||
|
||||
func testSweepHtlc(t *testing.T, ctx *nurseryTestContext) {
|
||||
testSweep(t, ctx, func() {
|
||||
// Verify stage in nursery report. HTLCs should now both still
|
||||
|
Loading…
Reference in New Issue
Block a user