cnct: split resolvers in separate files
This commit is contained in:
6 files changed
+1459
-1414
No files matched your search
@@ -0,0 +1,281 @@
|
||||
package contractcourt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// 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
|
||||
scriptToWatch []byte
|
||||
err error
|
||||
)
|
||||
if h.htlcResolution.SignedTimeoutTx == nil {
|
||||
outPointToWatch = h.htlcResolution.ClaimOutpoint
|
||||
scriptToWatch = h.htlcResolution.SweepSignDesc.Output.PkScript
|
||||
} else {
|
||||
// If this is the remote party's commitment, then we'll need to
|
||||
// grab watch the output that our timeout transaction points
|
||||
// to. We can directly grab the outpoint, then also extract the
|
||||
// witness script (the last element of the witness stack) to
|
||||
// re-construct the pkScipt we need to watch.
|
||||
outPointToWatch = h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
|
||||
witness := h.htlcResolution.SignedTimeoutTx.TxIn[0].Witness
|
||||
scriptToWatch, err = lnwallet.WitnessScriptHash(
|
||||
witness[len(witness)-1],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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, scriptToWatch, 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
|
||||
// sweep the pre-image from the output.
|
||||
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 the current height is >= expiry-1, then a spend will be valid to
|
||||
// be included in the next block, and we can immediately return the
|
||||
// resolver.
|
||||
//
|
||||
// TODO(joostjager): Statement above may not be valid. For CLTV locks,
|
||||
// the expiry value is the last _invalid_ block. The likely reason that
|
||||
// this does not create a problem, is that utxonursery is checking the
|
||||
// expiry again (in the proper way). Same holds for minus one operation
|
||||
// below.
|
||||
//
|
||||
// Source:
|
||||
// https://github.com/btcsuite/btcd/blob/991d32e72fe84d5fbf9c47cd604d793a0cd3a072/blockchain/validate.go#L154
|
||||
|
||||
if uint32(currentHeight) >= h.htlcResolution.Expiry-1 {
|
||||
log.Infof("%T(%v): HTLC has expired (height=%v, expiry=%v), "+
|
||||
"transforming into timeout resolver", h,
|
||||
h.htlcResolution.ClaimOutpoint, currentHeight,
|
||||
h.htlcResolution.Expiry)
|
||||
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(nil)
|
||||
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)
|
||||
Reference in new issue
Block a user