cnct: remove nursery dependency in commit sweep resolver
The channel arbitrator no longer passes the direct commitment output to the nursery for incubation. Instead the resolver itself will await the csv lock if any. The reason to change this now is to prevent having to deal with the (legacy) nursery code for a planned anchor outputs related change to the commit sweep resolver (also csv lock to_remote). It is no problem if there are any lingering incubating outputs at the time of upgrade. This just means that the output will be offered twice to the sweeper and this doesn't hurt.
This commit is contained in:
parent
1597a92160
commit
9acb236665
@ -141,6 +141,10 @@ const (
|
||||
// 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.
|
||||
@ -873,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.
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
@ -52,6 +62,7 @@ func newCommitSweepResolver(res lnwallet.CommitOutputResolution,
|
||||
}
|
||||
|
||||
r.initLogger(r)
|
||||
r.initReport()
|
||||
|
||||
return r
|
||||
}
|
||||
@ -63,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
|
||||
@ -76,155 +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
|
||||
}
|
||||
|
||||
c.log.Debugf("waiting for commit tx to confirm")
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.log.Errorf("unable to sweep input: %v",
|
||||
sweepResult.Err)
|
||||
|
||||
return nil, sweepResult.Err
|
||||
}
|
||||
|
||||
c.log.Infof("commit tx fully resolved by sweep tx: %v",
|
||||
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
|
||||
}
|
||||
|
||||
c.log.Infof("waiting for commit output to be swept")
|
||||
|
||||
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
|
||||
|
||||
c.log.Infof("commit output swept by txid=%v", sweepTx.TxHash())
|
||||
|
||||
if err := c.Checkpoint(c); err != nil {
|
||||
c.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
|
||||
}
|
||||
|
||||
c.log.Infof("waiting for commit sweep txid=%v conf", 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
|
||||
}
|
||||
|
||||
c.log.Infof("commit tx is fully resolved, at height: %v",
|
||||
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)
|
||||
}
|
||||
@ -309,10 +322,42 @@ func newCommitSweepResolverFromReader(r io.Reader, resCfg ResolverConfig) (
|
||||
// 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)
|
||||
|
59
rpcserver.go
59
rpcserver.go
@ -2452,25 +2452,51 @@ func (r *rpcServer) arbitratorPopulateForceCloseResp(chanPoint *wire.OutPoint,
|
||||
reports := arbitrator.Report()
|
||||
|
||||
for _, report := range reports {
|
||||
incoming := report.Type == contractcourt.ReportOutputIncomingHtlc
|
||||
switch report.Type {
|
||||
|
||||
htlc := &lnrpc.PendingHTLC{
|
||||
Incoming: incoming,
|
||||
Amount: int64(report.Amount),
|
||||
Outpoint: report.Outpoint.String(),
|
||||
MaturityHeight: report.MaturityHeight,
|
||||
Stage: report.Stage,
|
||||
}
|
||||
// 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 htlc.MaturityHeight != 0 {
|
||||
htlc.BlocksTilMaturity =
|
||||
int32(htlc.MaturityHeight) - currentHeight
|
||||
// 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
|
||||
@ -2501,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
|
||||
|
Loading…
Reference in New Issue
Block a user