Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contractcourt/briefcase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ func TestContractInsertionRetrieval(t *testing.T) {
resolved: false,
broadcastHeight: 109,
chanPoint: testChanPoint1,
sweepTx: nil,
},
}

Expand Down
188 changes: 71 additions & 117 deletions contractcourt/commit_sweep_resolver.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package contractcourt

import (
"bytes"
"encoding/binary"
"fmt"
"github.com/lightningnetwork/lnd/input"
"io"
"io/ioutil"

"github.com/lightningnetwork/lnd/input"

"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
)

// commitSweepResolver is a resolver that will attempt to sweep the commitment
Expand All @@ -34,11 +31,6 @@ type commitSweepResolver struct {
// chanPoint is the channel point of the original contract.
chanPoint wire.OutPoint

// sweepTx is the fully signed transaction which when broadcast, will
// sweep the commitment output into an output under control by the
// source wallet.
sweepTx *wire.MsgTx

ResolverKit
}

Expand Down Expand Up @@ -87,127 +79,101 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
return nil, fmt.Errorf("quitting")
}

// TODO(roasbeef): checkpoint tx confirmed?

// We're dealing with our commitment transaction if the delay on the
// resolution isn't zero.
isLocalCommitTx := c.commitResolution.MaturityDelay != 0

switch {
// If the sweep transaction isn't already generated, and the remote
// party broadcast the commitment transaction then we'll create it now.
case c.sweepTx == nil && !isLocalCommitTx:
// As we haven't already generated the sweeping transaction,
// we'll now craft an input with all the information required
// to create a fully valid sweeping transaction to recover
// these coins.
if !isLocalCommitTx {
// 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,
input.CommitmentNoDelay,
&c.commitResolution.SelfOutputSignDesc,
c.broadcastHeight,
)

// With out input constructed, we'll now request that the
// sweeper construct a valid sweeping transaction for this
// input.
//
// TODO: Set tx lock time to current block height instead of
// zero. Will be taken care of once sweeper implementation is
// complete.
//
// TODO: Use time-based sweeper and result chan.
c.sweepTx, err = c.Sweeper.CreateSweepTx(
[]input.Input{&inp},
sweep.FeePreference{
ConfTarget: sweepConfTarget,
}, 0,
)
// With our input constructed, we'll now offer it to the
// sweeper.
log.Infof("%T(%v): sweeping commit output", c, c.chanPoint)

resultChan, err := c.Sweeper.SweepInput(&inp)
if err != nil {
log.Errorf("%T(%v): unable to sweep input: %v",
c, c.chanPoint, err)

return nil, err
}

log.Infof("%T(%v): sweeping commit output with tx=%v", c,
c.chanPoint, spew.Sdump(c.sweepTx))
// 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)

// With the sweep transaction constructed, we'll now Checkpoint
// our state.
if err := c.Checkpoint(c); err != nil {
log.Errorf("unable to Checkpoint: %v", err)
return nil, err
}
return nil, err
}

// With the sweep transaction checkpointed, we'll now publish
// the transaction. Upon restart, the resolver will immediately
// take the case below since the sweep tx is checkpointed.
err := c.PublishTx(c.sweepTx)
if err != nil && err != lnwallet.ErrDoubleSpend {
log.Errorf("%T(%v): unable to publish sweep tx: %v",
c, c.chanPoint, err)
return nil, err
log.Infof("ChannelPoint(%v) commit tx is fully resolved by "+
"sweep tx: %v", c.chanPoint, sweepResult.Tx.TxHash())
case <-c.Quit:
return nil, fmt.Errorf("quitting")
}

// If the sweep transaction has been generated, and the remote party
// broadcast the commit transaction, we'll republish it for reliability
// to ensure it confirms. The resolver will enter this case after
// checkpointing in the case above, ensuring we reliably on restarts.
case c.sweepTx != nil && !isLocalCommitTx:
err := c.PublishTx(c.sweepTx)
if err != nil && err != lnwallet.ErrDoubleSpend {
log.Errorf("%T(%v): unable to publish sweep tx: %v",
c, c.chanPoint, err)
return nil, err
}
c.resolved = true
return nil, c.Checkpoint(c)
}

// Otherwise, this is our commitment transaction, So we'll obtain the
// sweep transaction once the commitment output has been spent.
case c.sweepTx == nil && isLocalCommitTx:
// Otherwise, if we're dealing with our local commitment
// transaction, then 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(
&c.commitResolution.SelfOutPoint,
c.commitResolution.SelfOutputSignDesc.Output.PkScript,
c.broadcastHeight,
)
if err != nil {
return nil, err
}
// 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(
&c.commitResolution.SelfOutPoint,
c.commitResolution.SelfOutputSignDesc.Output.PkScript,
c.broadcastHeight,
)
if err != nil {
return nil, err
}

log.Infof("%T(%v): waiting for commit output to be swept", c,
c.chanPoint)
log.Infof("%T(%v): waiting for commit output to be swept", c,
c.chanPoint)

select {
case commitSpend, ok := <-spendNtfn.Spend:
if !ok {
return nil, fmt.Errorf("quitting")
}
var sweepTx *wire.MsgTx
select {
case commitSpend, ok := <-spendNtfn.Spend:
if !ok {
return nil, fmt.Errorf("quitting")
}

// 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.
c.sweepTx = commitSpend.SpendingTx
// 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, c.sweepTx.TxHash())
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
}
case <-c.Quit:
return nil, fmt.Errorf("quitting")
if err := c.Checkpoint(c); err != nil {
log.Errorf("unable to Checkpoint: %v", err)
return nil, err
}
case <-c.Quit:
return nil, fmt.Errorf("quitting")
}

log.Infof("%T(%v): waiting for commit sweep txid=%v conf", c, c.chanPoint,
c.sweepTx.TxHash())
sweepTx.TxHash())

// Now we'll wait until the sweeping transaction has been fully
// confirmed. Once it's confirmed, we can mark this contract resolved.
sweepTXID := c.sweepTx.TxHash()
sweepingScript := c.sweepTx.TxOut[0].PkScript
sweepTXID := sweepTx.TxHash()
sweepingScript := sweepTx.TxOut[0].PkScript
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc, the spend notification will only trigger after it has one conf, so i'm not sure this second stage does anything? obviously this existed prior and independent of this PR, but perhaps we should consider bumping the number of confirmations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I indeed think this second stage does nothing, but not sure if we want to change anything to it in this pr. The greater plan is to remove nursery completely and wait out the csv inside the resolver.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this was left over from when we still did mempool confirmations. IMO there's no harm in leaving it in as we should move away from single conf dispatch in many areas of the codebase.

confNtfn, err = c.Notifier.RegisterConfirmationsNtfn(
&sweepTXID, sweepingScript, 1, c.broadcastHeight,
)
Expand Down Expand Up @@ -272,9 +238,9 @@ func (c *commitSweepResolver) Encode(w io.Writer) error {
return err
}

if c.sweepTx != nil {
Comment thread
joostjager marked this conversation as resolved.
Outdated
return c.sweepTx.Serialize(w)
}
// Previously a sweep tx was serialized at this point. Refactoring
// removed this, but keep in mind that this data may still be present in
// the database.

return nil
}
Expand Down Expand Up @@ -303,22 +269,10 @@ func (c *commitSweepResolver) Decode(r io.Reader) error {
return err
}

txBytes, err := ioutil.ReadAll(r)
if err != nil {
return err
}

if len(txBytes) == 0 {
return nil
}

txReader := bytes.NewReader(txBytes)
tx := &wire.MsgTx{}
if err := tx.Deserialize(txReader); err != nil {
return nil
}
// Previously a sweep tx was deserialized at this point. Refactoring
// removed this, but keep in mind that this data may still be present in
// the database.

c.sweepTx = tx
return nil
}

Expand Down