2019-01-16 22:03:59 +03:00
|
|
|
package contractcourt
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2019-01-17 15:11:04 +03:00
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2019-01-16 22:03:59 +03:00
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
|
|
)
|
|
|
|
|
|
|
|
// commitSweepResolver is a resolver that will attempt to sweep the commitment
|
|
|
|
// output paying to us, in the case that the remote party broadcasts their
|
|
|
|
// version of the commitment transaction. We can sweep this output immediately,
|
|
|
|
// as it doesn't have a time-lock delay.
|
|
|
|
type commitSweepResolver struct {
|
|
|
|
// commitResolution contains all data required to successfully sweep
|
|
|
|
// this HTLC on-chain.
|
|
|
|
commitResolution lnwallet.CommitOutputResolution
|
|
|
|
|
|
|
|
// resolved reflects if the contract has been fully resolved or not.
|
|
|
|
resolved bool
|
|
|
|
|
|
|
|
// broadcastHeight is the height that the original contract was
|
|
|
|
// broadcast to the main-chain at. We'll use this value to bound any
|
|
|
|
// historical queries to the chain for spends/confirmations.
|
|
|
|
broadcastHeight uint32
|
|
|
|
|
|
|
|
// chanPoint is the channel point of the original contract.
|
|
|
|
chanPoint wire.OutPoint
|
|
|
|
|
|
|
|
ResolverKit
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResolverKey returns an identifier which should be globally unique for this
|
|
|
|
// particular resolver within the chain the original contract resides within.
|
|
|
|
func (c *commitSweepResolver) ResolverKey() []byte {
|
|
|
|
key := newResolverID(c.commitResolution.SelfOutPoint)
|
|
|
|
return key[:]
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// that the contract requires further resolution, then another resolve is
|
|
|
|
// returned.
|
|
|
|
//
|
|
|
|
// NOTE: This function MUST be run as a goroutine.
|
|
|
|
func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
|
|
|
|
// If we're already resolved, then we can exit early.
|
|
|
|
if c.resolved {
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("%T(%v): waiting for commit tx to confirm", c, c.chanPoint)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case _, ok := <-confNtfn.Confirmed:
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("quitting")
|
|
|
|
}
|
|
|
|
|
|
|
|
case <-c.Quit:
|
|
|
|
return nil, fmt.Errorf("quitting")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're dealing with our commitment transaction if the delay on the
|
|
|
|
// resolution isn't zero.
|
|
|
|
isLocalCommitTx := c.commitResolution.MaturityDelay != 0
|
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
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.
|
2019-01-16 17:47:43 +03:00
|
|
|
inp := input.MakeBaseInput(
|
2019-01-16 22:03:59 +03:00
|
|
|
&c.commitResolution.SelfOutPoint,
|
2019-01-16 17:47:43 +03:00
|
|
|
input.CommitmentNoDelay,
|
2019-01-16 22:03:59 +03:00
|
|
|
&c.commitResolution.SelfOutputSignDesc,
|
|
|
|
c.broadcastHeight,
|
|
|
|
)
|
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
// 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)
|
2019-01-16 22:03:59 +03:00
|
|
|
if err != nil {
|
2019-01-17 15:11:04 +03:00
|
|
|
log.Errorf("%T(%v): unable to sweep input: %v",
|
|
|
|
c, c.chanPoint, err)
|
|
|
|
|
2019-01-16 22:03:59 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
// 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)
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
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")
|
2019-01-16 22:03:59 +03:00
|
|
|
}
|
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
c.resolved = true
|
|
|
|
return nil, c.Checkpoint(c)
|
|
|
|
}
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
// 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
|
|
|
|
}
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
log.Infof("%T(%v): waiting for commit output to be swept", c,
|
|
|
|
c.chanPoint)
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
var sweepTx *wire.MsgTx
|
|
|
|
select {
|
|
|
|
case commitSpend, ok := <-spendNtfn.Spend:
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("quitting")
|
|
|
|
}
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
// 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
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
log.Infof("%T(%v): commit output swept by txid=%v",
|
|
|
|
c, c.chanPoint, sweepTx.TxHash())
|
2019-01-16 22:03:59 +03:00
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
if err := c.Checkpoint(c); err != nil {
|
|
|
|
log.Errorf("unable to Checkpoint: %v", err)
|
|
|
|
return nil, err
|
2019-01-16 22:03:59 +03:00
|
|
|
}
|
2019-01-17 15:11:04 +03:00
|
|
|
case <-c.Quit:
|
|
|
|
return nil, fmt.Errorf("quitting")
|
2019-01-16 22:03:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("%T(%v): waiting for commit sweep txid=%v conf", c, c.chanPoint,
|
2019-01-17 15:11:04 +03:00
|
|
|
sweepTx.TxHash())
|
2019-01-16 22:03:59 +03:00
|
|
|
|
|
|
|
// Now we'll wait until the sweeping transaction has been fully
|
|
|
|
// confirmed. Once it's confirmed, we can mark this contract resolved.
|
2019-01-17 15:11:04 +03:00
|
|
|
sweepTXID := sweepTx.TxHash()
|
|
|
|
sweepingScript := sweepTx.TxOut[0].PkScript
|
2019-01-16 22:03:59 +03:00
|
|
|
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, fmt.Errorf("quitting")
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("ChannelPoint(%v) commit tx is fully resolved, at height: %v",
|
|
|
|
c.chanPoint, confInfo.BlockHeight)
|
|
|
|
|
|
|
|
case <-c.Quit:
|
|
|
|
return nil, fmt.Errorf("quitting")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
|
|
// suspend.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
|
|
|
func (c *commitSweepResolver) Stop() {
|
|
|
|
close(c.Quit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsResolved returns true if the stored state in the resolve is fully
|
|
|
|
// resolved. In this case the target output can be forgotten.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
|
|
|
func (c *commitSweepResolver) IsResolved() bool {
|
|
|
|
return c.resolved
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the passed
|
|
|
|
// Writer.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
|
|
|
func (c *commitSweepResolver) Encode(w io.Writer) error {
|
|
|
|
if err := encodeCommitResolution(w, &c.commitResolution); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := binary.Write(w, endian, c.resolved); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := binary.Write(w, endian, c.broadcastHeight); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := w.Write(c.chanPoint.Hash[:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err := binary.Write(w, endian, c.chanPoint.Index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
// 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.
|
2019-01-16 22:03:59 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode attempts to decode an encoded ContractResolver from the passed Reader
|
|
|
|
// instance, returning an active ContractResolver instance.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
|
|
|
func (c *commitSweepResolver) Decode(r io.Reader) error {
|
|
|
|
if err := decodeCommitResolution(r, &c.commitResolution); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := binary.Read(r, endian, &c.resolved); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := binary.Read(r, endian, &c.broadcastHeight); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err := io.ReadFull(r, c.chanPoint.Hash[:])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = binary.Read(r, endian, &c.chanPoint.Index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-17 15:11:04 +03:00
|
|
|
// 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.
|
2019-01-16 22:03:59 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AttachResolverKit should be called once a resolved is successfully decoded
|
|
|
|
// from its stored format. This struct delivers a generic tool kit that
|
|
|
|
// resolvers need to complete their duty.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
|
|
|
func (c *commitSweepResolver) AttachResolverKit(r ResolverKit) {
|
|
|
|
c.ResolverKit = r
|
|
|
|
}
|
|
|
|
|
|
|
|
// A compile time assertion to ensure commitSweepResolver meets the
|
|
|
|
// ContractResolver interface.
|
|
|
|
var _ ContractResolver = (*commitSweepResolver)(nil)
|