aa6e5bdd2a
This commit fixes a potential race condition within the IncomingContestResolver, that could cause us to miss a preimage that was delivered in time. Currently we query the db for the preimage, and then subscribe for notifications. This permits the following ordering of events: - query for preimage, returns nothing - preimage is added and delivered to subscribers - subscribe to preimages - preimage never comes through!! We fix this by reordering to subscribe for preimages and then query just in case it already exists. The effect is that the query will always return a valid read of the preimages that are currently queued for delivery.
1458 lines
46 KiB
Go
1458 lines
46 KiB
Go
package contractcourt
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
var (
|
|
endian = binary.BigEndian
|
|
)
|
|
|
|
// ContractResolver is an interface which packages a state machine which is
|
|
// able to carry out the necessary steps required to fully resolve a Bitcoin
|
|
// contract on-chain. Resolvers are fully encodable to ensure callers are able
|
|
// to persist them properly. A resolver may produce another resolver in the
|
|
// case that claiming an HTLC is a multi-stage process. In this case, we may
|
|
// partially resolve the contract, then persist, and set up for an additional
|
|
// resolution.
|
|
type ContractResolver interface {
|
|
// ResolverKey returns an identifier which should be globally unique
|
|
// for this particular resolver within the chain the original contract
|
|
// resides within.
|
|
ResolverKey() []byte
|
|
|
|
// 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.
|
|
Resolve() (ContractResolver, error)
|
|
|
|
// IsResolved returns true if the stored state in the resolve is fully
|
|
// resolved. In this case the target output can be forgotten.
|
|
IsResolved() bool
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the
|
|
// passed Writer.
|
|
Encode(w io.Writer) error
|
|
|
|
// Decode attempts to decode an encoded ContractResolver from the
|
|
// passed Reader instance, returning an active ContractResolver
|
|
// instance.
|
|
Decode(r io.Reader) error
|
|
|
|
// 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.
|
|
AttachResolverKit(ResolverKit)
|
|
|
|
// Stop signals the resolver to cancel any current resolution
|
|
// processes, and suspend.
|
|
Stop()
|
|
}
|
|
|
|
// ResolverKit is meant to be used as a mix-in struct to be embedded within a
|
|
// given ContractResolver implementation. It contains all the items that a
|
|
// resolver requires to carry out its duties.
|
|
type ResolverKit struct {
|
|
// ChannelArbitratorConfig contains all the interfaces and closures
|
|
// required for the resolver to interact with outside sub-systems.
|
|
ChannelArbitratorConfig
|
|
|
|
// Checkpoint allows a resolver to check point its state. This function
|
|
// should write the state of the resolver to persistent storage, and
|
|
// return a non-nil error upon success.
|
|
Checkpoint func(ContractResolver) error
|
|
|
|
Quit chan struct{}
|
|
}
|
|
|
|
// htlcTimeoutResolver is a ContractResolver that's capable of resolving an
|
|
// outgoing HTLC. The HTLC may be on our commitment transaction, or on the
|
|
// commitment transaction of the remote party. An output on our commitment
|
|
// transaction is considered fully resolved once the second-level transaction
|
|
// has been confirmed (and reached a sufficient depth). An output on the
|
|
// commitment transaction of the remote party is resolved once we detect a
|
|
// spend of the direct HTLC output using the timeout clause.
|
|
type htlcTimeoutResolver struct {
|
|
// htlcResolution contains all the information required to properly
|
|
// resolve this outgoing HTLC.
|
|
htlcResolution lnwallet.OutgoingHtlcResolution
|
|
|
|
// outputIncubating returns true if we've sent the output to the output
|
|
// incubator (utxo nursery).
|
|
outputIncubating bool
|
|
|
|
// 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.
|
|
//
|
|
// TODO(roasbeef): wrap above into definite resolution embedding?
|
|
broadcastHeight uint32
|
|
|
|
// htlcIndex is the index of this HTLC within the trace of the
|
|
// additional commitment state machine.
|
|
htlcIndex uint64
|
|
|
|
ResolverKit
|
|
}
|
|
|
|
// ResolverKey returns an identifier which should be globally unique for this
|
|
// particular resolver within the chain the original contract resides within.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcTimeoutResolver) ResolverKey() []byte {
|
|
// The primary key for this resolver will be the outpoint of the HTLC
|
|
// on the commitment transaction itself. If this is our commitment,
|
|
// then the output can be found within the signed timeout tx,
|
|
// otherwise, it's just the ClaimOutpoint.
|
|
var op wire.OutPoint
|
|
if h.htlcResolution.SignedTimeoutTx != nil {
|
|
op = h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
|
|
} else {
|
|
op = h.htlcResolution.ClaimOutpoint
|
|
}
|
|
|
|
key := newResolverID(op)
|
|
return key[:]
|
|
}
|
|
|
|
// Resolve kicks off full resolution of an outgoing HTLC output. If it's our
|
|
// commitment, it isn't resolved until we see the second level HTLC txn
|
|
// confirmed. If it's the remote party's commitment, we don't resolve until we
|
|
// see a direct sweep via the timeout clause.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
|
// If we're already resolved, then we can exit early.
|
|
if h.resolved {
|
|
return nil, nil
|
|
}
|
|
|
|
// If we haven't already sent the output to the utxo nursery, then
|
|
// we'll do so now.
|
|
if !h.outputIncubating {
|
|
log.Tracef("%T(%v): incubating htlc output", h,
|
|
h.htlcResolution.ClaimOutpoint)
|
|
|
|
err := h.IncubateOutputs(h.ChanPoint, nil, &h.htlcResolution, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
h.outputIncubating = true
|
|
|
|
if err := h.Checkpoint(h); err != nil {
|
|
log.Errorf("unable to Checkpoint: %v", err)
|
|
}
|
|
}
|
|
|
|
// waitForOutputResolution waits for the HTLC output to be fully
|
|
// resolved. The output is considered fully resolved once it has been
|
|
// spent, and the spending transaction has been fully confirmed.
|
|
waitForOutputResolution := func() error {
|
|
// We first need to register to see when the HTLC output itself
|
|
// has been spent by a confirmed transaction.
|
|
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
|
|
&h.htlcResolution.ClaimOutpoint,
|
|
h.broadcastHeight,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
select {
|
|
case _, ok := <-spendNtfn.Spend:
|
|
if !ok {
|
|
return fmt.Errorf("notifier quit")
|
|
}
|
|
|
|
case <-h.Quit:
|
|
return fmt.Errorf("quitting")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// With the output sent to the nursery, we'll now wait until the output
|
|
// has been fully resolved before sending the clean up message.
|
|
//
|
|
// TODO(roasbeef): need to be able to cancel nursery?
|
|
// * if they pull on-chain while we're waiting
|
|
|
|
// If we don't have a second layer transaction, then this is a remote
|
|
// party's commitment, so we'll watch for a direct spend.
|
|
if h.htlcResolution.SignedTimeoutTx == nil {
|
|
// We'll block until: the HTLC output has been spent, and the
|
|
// transaction spending that output is sufficiently confirmed.
|
|
log.Infof("%T(%v): waiting for nursery to spend CLTV-locked "+
|
|
"output", h, h.htlcResolution.ClaimOutpoint)
|
|
if err := waitForOutputResolution(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// Otherwise, this is our commitment, so we'll watch for the
|
|
// second-level transaction to be sufficiently confirmed.
|
|
secondLevelTXID := h.htlcResolution.SignedTimeoutTx.TxHash()
|
|
confNtfn, err := h.Notifier.RegisterConfirmationsNtfn(
|
|
&secondLevelTXID, 1, h.broadcastHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("%T(%v): waiting second-level tx (txid=%v) to be "+
|
|
"fully confirmed", h, h.htlcResolution.ClaimOutpoint,
|
|
secondLevelTXID)
|
|
|
|
select {
|
|
case _, ok := <-confNtfn.Confirmed:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
case <-h.Quit:
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
}
|
|
|
|
// TODO(roasbeef): need to watch for remote party sweeping with pre-image?
|
|
// * have another waiting on spend above, will check the type, if it's
|
|
// pre-image, then we'll cancel, and send a clean up back with
|
|
// pre-image, also add to preimage cache
|
|
|
|
log.Infof("%T(%v): resolving htlc with incoming fail msg, fully "+
|
|
"confirmed", h, h.htlcResolution.ClaimOutpoint)
|
|
|
|
// At this point, the second-level transaction is sufficiently
|
|
// confirmed, or a transaction directly spending the output is.
|
|
// Therefore, we can now send back our clean up message.
|
|
failureMsg := &lnwire.FailPermanentChannelFailure{}
|
|
if err := h.DeliverResolutionMsg(ResolutionMsg{
|
|
SourceChan: h.ShortChanID,
|
|
HtlcIndex: h.htlcIndex,
|
|
Failure: failureMsg,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, if this was an output on our commitment transaction, we'll
|
|
// for the second-level HTLC output to be spent, and for that
|
|
// transaction itself to confirm.
|
|
if h.htlcResolution.SignedTimeoutTx != nil {
|
|
log.Infof("%T(%v): waiting for nursery to spend CSV delayed "+
|
|
"output", h, h.htlcResolution.ClaimOutpoint)
|
|
if err := waitForOutputResolution(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// With the clean up message sent, we'll now mark the contract
|
|
// resolved, and wait.
|
|
h.resolved = true
|
|
return nil, h.Checkpoint(h)
|
|
}
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
// suspend.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcTimeoutResolver) Stop() {
|
|
close(h.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 (h *htlcTimeoutResolver) IsResolved() bool {
|
|
return h.resolved
|
|
}
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the passed
|
|
// Writer.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
|
|
// First, we'll write out the relevant fields of the
|
|
// OutgoingHtlcResolution to the writer.
|
|
if err := encodeOutgoingResolution(w, &h.htlcResolution); err != nil {
|
|
return err
|
|
}
|
|
|
|
// With that portion written, we can now write out the fields specific
|
|
// to the resolver itself.
|
|
if err := binary.Write(w, endian, h.outputIncubating); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(w, endian, h.resolved); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(w, endian, h.htlcIndex); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 (h *htlcTimeoutResolver) Decode(r io.Reader) error {
|
|
// First, we'll read out all the mandatory fields of the
|
|
// OutgoingHtlcResolution that we store.
|
|
if err := decodeOutgoingResolution(r, &h.htlcResolution); err != nil {
|
|
return err
|
|
}
|
|
|
|
// With those fields read, we can now read back the fields that are
|
|
// specific to the resolver itself.
|
|
if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Read(r, endian, &h.resolved); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Read(r, endian, &h.htlcIndex); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 (h *htlcTimeoutResolver) AttachResolverKit(r ResolverKit) {
|
|
h.ResolverKit = r
|
|
}
|
|
|
|
// A compile time assertion to ensure htlcTimeoutResolver meets the
|
|
// ContractResolver interface.
|
|
var _ ContractResolver = (*htlcTimeoutResolver)(nil)
|
|
|
|
// htlcSuccessResolver is a resolver that's capable of sweeping an incoming
|
|
// HTLC output on-chain. If this is the remote party's commitment, we'll sweep
|
|
// it directly from the commitment output *immediately*. If this is our
|
|
// commitment, we'll first broadcast the success transaction, then send it to
|
|
// the incubator for sweeping. That's it, no need to send any clean up
|
|
// messages.
|
|
//
|
|
// TODO(roasbeef): don't need to broadcast?
|
|
type htlcSuccessResolver struct {
|
|
// htlcResolution is the incoming HTLC resolution for this HTLC. It
|
|
// contains everything we need to properly resolve this HTLC.
|
|
htlcResolution lnwallet.IncomingHtlcResolution
|
|
|
|
// outputIncubating returns true if we've sent the output to the output
|
|
// incubator (utxo nursery).
|
|
outputIncubating bool
|
|
|
|
// 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
|
|
|
|
// payHash is the payment hash of the original HTLC extended to us.
|
|
payHash [32]byte
|
|
|
|
// sweepTx will be non-nil if we've already crafted a transaction to
|
|
// sweep a direct HTLC output. This is only a concern if we're sweeping
|
|
// from the commitment transaction of the remote party.
|
|
//
|
|
// TODO(roasbeef): send off to utxobundler
|
|
sweepTx *wire.MsgTx
|
|
|
|
ResolverKit
|
|
}
|
|
|
|
// ResolverKey returns an identifier which should be globally unique for this
|
|
// particular resolver within the chain the original contract resides within.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) ResolverKey() []byte {
|
|
// The primary key for this resolver will be the outpoint of the HTLC
|
|
// on the commitment transaction itself. If this is our commitment,
|
|
// then the output can be found within the signed success tx,
|
|
// otherwise, it's just the ClaimOutpoint.
|
|
var op wire.OutPoint
|
|
if h.htlcResolution.SignedSuccessTx != nil {
|
|
op = h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
|
|
} else {
|
|
op = h.htlcResolution.ClaimOutpoint
|
|
}
|
|
|
|
key := newResolverID(op)
|
|
return key[:]
|
|
}
|
|
|
|
// Resolve attempts to resolve an unresolved incoming HTLC that we know the
|
|
// preimage to. If the HTLC is on the commitment of the remote party, then
|
|
// we'll simply sweep it directly. Otherwise, we'll hand this off to the utxo
|
|
// nursery to do its duty.
|
|
//
|
|
// TODO(roasbeef): create multi to batch
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
|
// If we're already resolved, then we can exit early.
|
|
if h.resolved {
|
|
return nil, nil
|
|
}
|
|
|
|
// If we don't have a success transaction, then this means that this is
|
|
// an output on the remote party's commitment transaction.
|
|
if h.htlcResolution.SignedSuccessTx == nil {
|
|
// If we don't already have the sweep transaction constructed,
|
|
// we'll do so and broadcast it.
|
|
if h.sweepTx == nil {
|
|
log.Infof("%T(%x): crafting sweep tx for "+
|
|
"incoming+remote htlc confirmed", h,
|
|
h.payHash[:])
|
|
|
|
// In this case, we can sweep it directly from the
|
|
// commitment output. We'll first grab a fresh address
|
|
// from the wallet to sweep the output.
|
|
addr, err := h.NewSweepAddr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// With out address obtained, we'll query for an
|
|
// estimate to be confirmed at ease.
|
|
//
|
|
// TODO(roasbeef): signal up if fee would be too large
|
|
// to sweep singly, need to batch
|
|
feePerVSize, err := h.FeeEstimator.EstimateFeePerVSize(6)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Debugf("%T(%x): using %v sat/vbyte to sweep htlc"+
|
|
"incoming+remote htlc confirmed", h,
|
|
h.payHash[:], int64(feePerVSize))
|
|
|
|
// Using a weight estimator, we'll compute the total
|
|
// fee required, and from that the value we'll end up
|
|
// with.
|
|
totalVSize := (&lnwallet.TxWeightEstimator{}).
|
|
AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize).
|
|
AddP2WKHOutput().VSize()
|
|
totalFees := feePerVSize.FeeForVSize(int64(totalVSize))
|
|
sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value -
|
|
int64(totalFees)
|
|
|
|
// With the fee computation finished, we'll now
|
|
// construct the sweep transaction.
|
|
htlcPoint := h.htlcResolution.ClaimOutpoint
|
|
h.sweepTx = wire.NewMsgTx(2)
|
|
h.sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: htlcPoint,
|
|
})
|
|
h.sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: addr,
|
|
Value: sweepAmt,
|
|
})
|
|
|
|
// With the transaction fully assembled, we can now
|
|
// generate a valid witness for the transaction.
|
|
h.htlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes(
|
|
h.sweepTx,
|
|
)
|
|
h.sweepTx.TxIn[0].Witness, err = lnwallet.SenderHtlcSpendRedeem(
|
|
h.Signer, &h.htlcResolution.SweepSignDesc, h.sweepTx,
|
|
h.htlcResolution.Preimage[:],
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("%T(%x): crafted sweep tx=%v", h,
|
|
h.payHash[:], spew.Sdump(h.sweepTx))
|
|
|
|
// With the sweep transaction confirmed, we'll now
|
|
// Checkpoint our state.
|
|
if err := h.Checkpoint(h); err != nil {
|
|
log.Errorf("unable to Checkpoint: %v", err)
|
|
}
|
|
|
|
// Finally, we'll broadcast the sweep transaction to
|
|
// the network.
|
|
//
|
|
// TODO(roasbeef): validate first?
|
|
if err := h.PublishTx(h.sweepTx); err != nil {
|
|
log.Infof("%T(%x): unable to publish tx: %v",
|
|
h, h.payHash[:], err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// With the sweep transaction broadcast, we'll wait for its
|
|
// confirmation.
|
|
sweepTXID := h.sweepTx.TxHash()
|
|
confNtfn, err := h.Notifier.RegisterConfirmationsNtfn(
|
|
&sweepTXID, 1, h.broadcastHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("%T(%x): waiting for sweep tx (txid=%v) to be "+
|
|
"confirmed", h, h.payHash[:], sweepTXID)
|
|
|
|
select {
|
|
case _, ok := <-confNtfn.Confirmed:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
case <-h.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.
|
|
h.resolved = true
|
|
return nil, h.Checkpoint(h)
|
|
}
|
|
|
|
log.Infof("%T(%x): broadcasting second-layer transition tx: %v",
|
|
h, h.payHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx))
|
|
|
|
// We'll now broadcast the second layer transaction so we can kick off
|
|
// the claiming process.
|
|
//
|
|
// TODO(roasbeef): after changing sighashes send to tx bundler
|
|
if err := h.PublishTx(h.htlcResolution.SignedSuccessTx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Otherwise, this is an output on our commitment transaction. In this
|
|
// case, we'll send it to the incubator, but only if we haven't already
|
|
// done so.
|
|
if !h.outputIncubating {
|
|
log.Infof("%T(%x): incubating incoming htlc output",
|
|
h, h.payHash[:])
|
|
|
|
err := h.IncubateOutputs(h.ChanPoint, nil, nil, &h.htlcResolution)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
h.outputIncubating = true
|
|
|
|
if err := h.Checkpoint(h); err != nil {
|
|
log.Errorf("unable to Checkpoint: %v", err)
|
|
}
|
|
}
|
|
|
|
// To wrap this up, we'll wait until the second-level transaction has
|
|
// been spent, then fully resolve the contract.
|
|
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
|
|
&h.htlcResolution.ClaimOutpoint, h.broadcastHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
|
|
"after csv_delay=%v", h, h.payHash[:], h.htlcResolution.CsvDelay)
|
|
|
|
select {
|
|
case _, ok := <-spendNtfn.Spend:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
case <-h.Quit:
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
h.resolved = true
|
|
return nil, h.Checkpoint(h)
|
|
}
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
// suspend.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) Stop() {
|
|
close(h.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 (h *htlcSuccessResolver) IsResolved() bool {
|
|
return h.resolved
|
|
}
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the passed
|
|
// Writer.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
|
|
// First we'll encode our inner HTLC resolution.
|
|
if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Next, we'll write out the fields that are specified to the contract
|
|
// resolver.
|
|
if err := binary.Write(w, endian, h.outputIncubating); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(w, endian, h.resolved); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
|
|
return err
|
|
}
|
|
if _, err := w.Write(h.payHash[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 (h *htlcSuccessResolver) Decode(r io.Reader) error {
|
|
// First we'll decode our inner HTLC resolution.
|
|
if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Next, we'll read all the fields that are specified to the contract
|
|
// resolver.
|
|
if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Read(r, endian, &h.resolved); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.ReadFull(r, h.payHash[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 (h *htlcSuccessResolver) AttachResolverKit(r ResolverKit) {
|
|
h.ResolverKit = r
|
|
}
|
|
|
|
// A compile time assertion to ensure htlcSuccessResolver meets the
|
|
// ContractResolver interface.
|
|
var _ ContractResolver = (*htlcSuccessResolver)(nil)
|
|
|
|
// htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
|
|
// outgoing HTLC that is still contested. An HTLC is still contested, if at the
|
|
// time that we broadcast the commitment transaction, it isn't able to be fully
|
|
// resolved. In this case, we'll either wait for the HTLC to timeout, or for
|
|
// us to learn of the preimage.
|
|
type htlcOutgoingContestResolver struct {
|
|
// htlcTimeoutResolver is the inner solver that this resolver may turn
|
|
// into. This only happens if the HTLC expires on-chain.
|
|
htlcTimeoutResolver
|
|
}
|
|
|
|
// Resolve commences the resolution of this contract. As this contract hasn't
|
|
// yet timed out, we'll wait for one of two things to happen
|
|
//
|
|
// 1. The HTLC expires. In this case, we'll sweep the funds and send a clean
|
|
// up cancel message to outside sub-systems.
|
|
//
|
|
// 2. The remote party sweeps this HTLC on-chain, in which case we'll add the
|
|
// pre-image to our global cache, then send a clean up settle message
|
|
// backwards.
|
|
//
|
|
// When either of these two things happens, we'll create a new resolver which
|
|
// is able to handle the final resolution of the contract. We're only the pivot
|
|
// point.
|
|
func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
|
|
// If we're already full resolved, then we don't have anything further
|
|
// to do.
|
|
if h.resolved {
|
|
return nil, nil
|
|
}
|
|
|
|
// claimCleanUp is a helper function that's called once the HTLC output
|
|
// is spent by the remote party. It'll extract the preimage, add it to
|
|
// the global cache, and finally send the appropriate clean up message.
|
|
claimCleanUp := func(commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
|
|
// Depending on if this is our commitment or not, then we'll be
|
|
// looking for a different witness pattern.
|
|
spenderIndex := commitSpend.SpenderInputIndex
|
|
spendingInput := commitSpend.SpendingTx.TxIn[spenderIndex]
|
|
|
|
log.Infof("%T(%v): extracting preimage! remote party spent "+
|
|
"HTLC with tx=%v", h, h.htlcResolution.ClaimOutpoint,
|
|
spew.Sdump(commitSpend.SpendingTx))
|
|
|
|
// If this is the remote party's commitment, then we'll be
|
|
// looking for them to spend using the second-level success
|
|
// transaction.
|
|
var preimage [32]byte
|
|
if h.htlcResolution.SignedTimeoutTx == nil {
|
|
// The witness stack when the remote party sweeps the
|
|
// output to them looks like:
|
|
//
|
|
// * <sender sig> <recvr sig> <preimage> <witness script>
|
|
copy(preimage[:], spendingInput.Witness[3])
|
|
} else {
|
|
// Otherwise, they'll be spending directly from our
|
|
// commitment output. In which case the witness stack
|
|
// looks like:
|
|
//
|
|
// * <sig> <preimage> <witness script>
|
|
copy(preimage[:], spendingInput.Witness[1])
|
|
}
|
|
|
|
log.Infof("%T(%v): extracting preimage=%x from on-chain "+
|
|
"spend!", h, h.htlcResolution.ClaimOutpoint, preimage[:])
|
|
|
|
// With the preimage obtained, we can now add it to the global
|
|
// cache.
|
|
if err := h.PreimageDB.AddPreimage(preimage[:]); err != nil {
|
|
log.Errorf("%T(%v): unable to add witness to cache",
|
|
h, h.htlcResolution.ClaimOutpoint)
|
|
}
|
|
|
|
// Finally, we'll send the clean up message, mark ourselves as
|
|
// resolved, then exit.
|
|
if err := h.DeliverResolutionMsg(ResolutionMsg{
|
|
SourceChan: h.ShortChanID,
|
|
HtlcIndex: h.htlcIndex,
|
|
PreImage: &preimage,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
h.resolved = true
|
|
return nil, h.Checkpoint(h)
|
|
}
|
|
|
|
// Otherwise, we'll watch for two external signals to decide if we'll
|
|
// morph into another resolver, or fully resolve the contract.
|
|
|
|
// The output we'll be watching for is the *direct* spend from the HTLC
|
|
// output. If this isn't our commitment transaction, it'll be right on
|
|
// the resolution. Otherwise, we fetch this pointer from the input of
|
|
// the time out transaction.
|
|
var outPointToWatch wire.OutPoint
|
|
if h.htlcResolution.SignedTimeoutTx == nil {
|
|
outPointToWatch = h.htlcResolution.ClaimOutpoint
|
|
} else {
|
|
outPointToWatch = h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
|
|
}
|
|
|
|
// First, we'll register for a spend notification for this output. If
|
|
// the remote party sweeps with the pre-image, we'll be notified.
|
|
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
|
|
&outPointToWatch, h.broadcastHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We'll quickly check to see if the output has already been spent.
|
|
select {
|
|
// If the output has already been spent, then we can stop early and
|
|
case commitSpend, ok := <-spendNtfn.Spend:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
// TODO(roasbeef): Checkpoint?
|
|
return claimCleanUp(commitSpend)
|
|
|
|
// If it hasn't, then we'll watch for both the expiration, and the
|
|
// sweeping out this output.
|
|
default:
|
|
}
|
|
|
|
// We'll check the current height, if the HTLC has already expired,
|
|
// then we'll morph immediately into a resolver that can sweep the
|
|
// HTLC.
|
|
//
|
|
// TODO(roasbeef): use grace period instead?
|
|
_, currentHeight, err := h.ChainIO.GetBestBlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if uint32(currentHeight) >= h.htlcResolution.Expiry {
|
|
log.Infof("%T(%v): HTLC has expired (height=%v, expiry=%v), "+
|
|
"transforming into timeout resolver", h,
|
|
h.htlcResolution.ClaimOutpoint)
|
|
return &h.htlcTimeoutResolver, nil
|
|
}
|
|
|
|
// If we reach this point, then we can't fully act yet, so we'll await
|
|
// either of our signals triggering: the HTLC expires, or we learn of
|
|
// the preimage.
|
|
blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer blockEpochs.Cancel()
|
|
|
|
for {
|
|
select {
|
|
|
|
// A new block has arrived, we'll check to see if this leads to
|
|
// HTLC expiration.
|
|
case newBlock, ok := <-blockEpochs.Epochs:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
// If this new height expires the HTLC, then we can
|
|
// exit early and create a resolver that's capable of
|
|
// handling the time locked output.
|
|
newHeight := uint32(newBlock.Height)
|
|
if newHeight >= h.htlcResolution.Expiry-1 {
|
|
log.Infof("%T(%v): HTLC has expired "+
|
|
"(height=%v, expiry=%v), transforming "+
|
|
"into timeout resolver", h,
|
|
h.htlcResolution.ClaimOutpoint,
|
|
newHeight, h.htlcResolution.Expiry)
|
|
return &h.htlcTimeoutResolver, nil
|
|
}
|
|
|
|
// The output has been spent! This means the preimage has been
|
|
// revealed on-chain.
|
|
case commitSpend, ok := <-spendNtfn.Spend:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
// The only way this output can be spent by the remote
|
|
// party is by revealing the preimage. So we'll perform
|
|
// our duties to clean up the contract once it has been
|
|
// claimed.
|
|
return claimCleanUp(commitSpend)
|
|
|
|
case <-h.Quit:
|
|
return nil, fmt.Errorf("resolver cancelled")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
// suspend.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcOutgoingContestResolver) Stop() {
|
|
close(h.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 (h *htlcOutgoingContestResolver) IsResolved() bool {
|
|
return h.resolved
|
|
}
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the passed
|
|
// Writer.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
|
|
return h.htlcTimeoutResolver.Encode(w)
|
|
}
|
|
|
|
// Decode attempts to decode an encoded ContractResolver from the passed Reader
|
|
// instance, returning an active ContractResolver instance.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcOutgoingContestResolver) Decode(r io.Reader) error {
|
|
return h.htlcTimeoutResolver.Decode(r)
|
|
}
|
|
|
|
// 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 (h *htlcOutgoingContestResolver) AttachResolverKit(r ResolverKit) {
|
|
h.ResolverKit = r
|
|
}
|
|
|
|
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
|
|
// ContractResolver interface.
|
|
var _ ContractResolver = (*htlcOutgoingContestResolver)(nil)
|
|
|
|
// htlcIncomingContestResolver is a ContractResolver that's able to resolve an
|
|
// incoming HTLC that is still contested. An HTLC is still contested, if at the
|
|
// time of commitment broadcast, we don't know of the preimage for it yet, and
|
|
// it hasn't expired. In this case, we can resolve the HTLC if we learn of the
|
|
// preimage, otherwise the remote party will sweep it after it expires.
|
|
//
|
|
// TODO(roasbeef): just embed the other resolver?
|
|
type htlcIncomingContestResolver struct {
|
|
// htlcExpiry is the absolute expiry of this incoming HTLC. We use this
|
|
// value to determine if we can exit early as if the HTLC times out,
|
|
// before we learn of the preimage then we can't claim it on chain
|
|
// successfully.
|
|
htlcExpiry uint32
|
|
|
|
// htlcSuccessResolver is the inner resolver that may be utilized if we
|
|
// learn of the preimage.
|
|
htlcSuccessResolver
|
|
}
|
|
|
|
// Resolve attempts to resolve this contract. As we don't yet know of the
|
|
// preimage for the contract, we'll wait for one of two things to happen:
|
|
//
|
|
// 1. We learn of the preimage! In this case, we can sweep the HTLC incoming
|
|
// and ensure that if this was a multi-hop HTLC we are made whole. In this
|
|
// case, an additional ContractResolver will be returned to finish the
|
|
// job.
|
|
//
|
|
// 2. The HTLC expires. If this happens, then the contract is fully resolved
|
|
// as we have no remaining actions left at our disposal.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|
// If we're already full resolved, then we don't have anything further
|
|
// to do.
|
|
if h.resolved {
|
|
return nil, nil
|
|
}
|
|
|
|
// We'll first check if this HTLC has been timed out, if so, we can
|
|
// return now and mark ourselves as resolved.
|
|
_, currentHeight, err := h.ChainIO.GetBestBlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If we're past the point of expiry of the HTLC, then at this point
|
|
// the sender can sweep it, so we'll end our lifetime.
|
|
if uint32(currentHeight) >= h.htlcExpiry {
|
|
// TODO(roasbeef): should also somehow check if outgoing is
|
|
// resolved or not
|
|
// * may need to hook into the circuit map
|
|
// * can't timeout before the outgoing has been
|
|
|
|
log.Infof("%T(%v): HTLC has timed out (expiry=%v, height=%v), "+
|
|
"abandoning", h, h.htlcResolution.ClaimOutpoint,
|
|
h.htlcExpiry, currentHeight)
|
|
h.resolved = true
|
|
return nil, h.Checkpoint(h)
|
|
}
|
|
|
|
// applyPreimage is a helper function that will populate our internal
|
|
// resolver with the preimage we learn of. This should be called once
|
|
// the preimage is revealed so the inner resolver can properly complete
|
|
// its duties.
|
|
applyPreimage := func(preimage []byte) {
|
|
copy(h.htlcResolution.Preimage[:], preimage)
|
|
|
|
log.Infof("%T(%v): extracted preimage=%x from beacon!", h,
|
|
h.htlcResolution.ClaimOutpoint, preimage[:])
|
|
|
|
// If this our commitment transaction, then we'll need to
|
|
// populate the witness for the second-level HTLC transaction.
|
|
if h.htlcResolution.SignedSuccessTx != nil {
|
|
// Within the witness for the success transaction, the
|
|
// preimage is the 4th element as it looks like:
|
|
//
|
|
// * <sender sig> <recvr sig> <preimage> <witness script>
|
|
//
|
|
// We'll populate it within the witness, as since this
|
|
// was a "contest" resolver, we didn't yet know of the
|
|
// preimage.
|
|
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
|
|
}
|
|
|
|
copy(h.htlcResolution.Preimage[:], preimage[:])
|
|
}
|
|
|
|
// If the HTLC hasn't expired yet, then we may still be able to claim
|
|
// it if we learn of the pre-image, so we'll subscribe to the preimage
|
|
// database to see if it turns up, or the HTLC times out.
|
|
//
|
|
// NOTE: This is done BEFORE opportunistically querying the db, to
|
|
// ensure the preimage can't be delivered between querying and
|
|
// registering for the preimage subscription.
|
|
preimageSubscription := h.PreimageDB.SubscribeUpdates()
|
|
blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
preimageSubscription.CancelSubscription()
|
|
blockEpochs.Cancel()
|
|
}()
|
|
|
|
// With the epochs and preimage subscriptions initialized, we'll query
|
|
// to see if we already know the preimage.
|
|
preimage, ok := h.PreimageDB.LookupPreimage(h.payHash[:])
|
|
if ok {
|
|
// If we do, then this means we can claim the HTLC! However,
|
|
// we don't know how to ourselves, so we'll return our inner
|
|
// resolver which has the knowledge to do so.
|
|
applyPreimage(preimage[:])
|
|
return &h.htlcSuccessResolver, nil
|
|
}
|
|
|
|
for {
|
|
|
|
select {
|
|
case preimage := <-preimageSubscription.WitnessUpdates:
|
|
// If this isn't our preimage, then we'll continue
|
|
// onwards.
|
|
newHash := sha256.Sum256(preimage)
|
|
preimageMatches := bytes.Equal(newHash[:], h.payHash[:])
|
|
if !preimageMatches {
|
|
continue
|
|
}
|
|
|
|
// Otherwise, we've learned of the preimage! We'll add
|
|
// this information to our inner resolver, then return
|
|
// it so it can continue contract resolution.
|
|
applyPreimage(preimage)
|
|
return &h.htlcSuccessResolver, nil
|
|
|
|
case newBlock, ok := <-blockEpochs.Epochs:
|
|
if !ok {
|
|
return nil, fmt.Errorf("quitting")
|
|
}
|
|
|
|
// If this new height expires the HTLC, then this means
|
|
// we never found out the preimage, so we can mark
|
|
// resolved and
|
|
// exit.
|
|
newHeight := uint32(newBlock.Height)
|
|
if newHeight >= h.htlcExpiry {
|
|
log.Infof("%T(%v): HTLC has timed out "+
|
|
"(expiry=%v, height=%v), abandoning", h,
|
|
h.htlcResolution.ClaimOutpoint,
|
|
h.htlcExpiry, currentHeight)
|
|
h.resolved = true
|
|
return nil, h.Checkpoint(h)
|
|
}
|
|
|
|
case <-h.Quit:
|
|
return nil, fmt.Errorf("resolver stopped")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
// suspend.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcIncomingContestResolver) Stop() {
|
|
close(h.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 (h *htlcIncomingContestResolver) IsResolved() bool {
|
|
return h.resolved
|
|
}
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the passed
|
|
// Writer.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcIncomingContestResolver) Encode(w io.Writer) error {
|
|
// We'll first write out the one field unique to this resolver.
|
|
if err := binary.Write(w, endian, h.htlcExpiry); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Then we'll write out our internal resolver.
|
|
return h.htlcSuccessResolver.Encode(w)
|
|
}
|
|
|
|
// Decode attempts to decode an encoded ContractResolver from the passed Reader
|
|
// instance, returning an active ContractResolver instance.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcIncomingContestResolver) Decode(r io.Reader) error {
|
|
// We'll first read the one field unique to this resolver.
|
|
if err := binary.Read(r, endian, &h.htlcExpiry); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Then we'll decode our internal resolver.
|
|
return h.htlcSuccessResolver.Decode(r)
|
|
}
|
|
|
|
// 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 (h *htlcIncomingContestResolver) AttachResolverKit(r ResolverKit) {
|
|
h.ResolverKit = r
|
|
}
|
|
|
|
// A compile time assertion to ensure htlcIncomingContestResolver meets the
|
|
// ContractResolver interface.
|
|
var _ ContractResolver = (*htlcIncomingContestResolver)(nil)
|
|
|
|
// 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
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
confNtfn, err := c.Notifier.RegisterConfirmationsNtfn(
|
|
&commitTXID, 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")
|
|
}
|
|
|
|
// 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:
|
|
// Now that the commitment transaction has confirmed, we'll
|
|
// craft a transaction to sweep this output into the wallet.
|
|
signDesc := c.commitResolution.SelfOutputSignDesc
|
|
|
|
// First, we'll estimate the total weight so we can compute
|
|
// fees properly. We'll use a lax estimate, as this output is
|
|
// in no immediate danger.
|
|
feePerVSize, err := c.FeeEstimator.EstimateFeePerVSize(6)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Debugf("%T(%v): using %v sat/vsize for sweep tx", c,
|
|
c.chanPoint, int64(feePerVSize))
|
|
|
|
totalVSize := (&lnwallet.TxWeightEstimator{}).
|
|
AddP2PKHInput().
|
|
AddP2WKHOutput().VSize()
|
|
totalFees := feePerVSize.FeeForVSize(int64(totalVSize))
|
|
sweepAmt := signDesc.Output.Value - int64(totalFees)
|
|
|
|
c.sweepTx = wire.NewMsgTx(2)
|
|
c.sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: c.commitResolution.SelfOutPoint,
|
|
})
|
|
sweepAddr, err := c.NewSweepAddr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: sweepAddr,
|
|
Value: sweepAmt,
|
|
})
|
|
|
|
// With the transaction fully assembled, we can now generate a
|
|
// valid witness for the transaction.
|
|
signDesc.SigHashes = txscript.NewTxSigHashes(c.sweepTx)
|
|
c.sweepTx.TxIn[0].Witness, err = lnwallet.CommitSpendNoDelay(
|
|
c.Signer, &signDesc, c.sweepTx,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("%T(%v): sweeping commit output with tx=%v", c,
|
|
c.chanPoint, spew.Sdump(c.sweepTx))
|
|
|
|
// Finally, we'll broadcast the sweep transaction to the
|
|
// network.
|
|
if err := c.PublishTx(c.sweepTx); err != nil {
|
|
log.Errorf("%T(%v): unable to publish sweep tx: %v",
|
|
c, c.chanPoint, err)
|
|
return nil, err
|
|
}
|
|
|
|
// With the sweep transaction confirmed, we'll now Checkpoint
|
|
// our state.
|
|
if err := c.Checkpoint(c); err != nil {
|
|
log.Errorf("unable to Checkpoint: %v", err)
|
|
}
|
|
|
|
// 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.broadcastHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
// 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
|
|
|
|
log.Infof("%T(%v): commit output swept by txid=%v",
|
|
c, c.chanPoint, c.sweepTx.TxHash())
|
|
|
|
if err := c.Checkpoint(c); err != nil {
|
|
log.Errorf("unable to Checkpoint: %v", 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())
|
|
|
|
// 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()
|
|
confNtfn, err = c.Notifier.RegisterConfirmationsNtfn(
|
|
&sweepTXID, 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
|
|
}
|
|
|
|
if c.sweepTx != nil {
|
|
return c.sweepTx.Serialize(w)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
c.sweepTx = tx
|
|
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)
|