50abb41e94
This change prepares for accessing the onion blob from a resolver.
219 lines
6.9 KiB
Go
219 lines
6.9 KiB
Go
package contractcourt
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"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
|
|
}
|
|
|
|
// newOutgoingContestResolver instantiates a new outgoing contested htlc
|
|
// resolver.
|
|
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
|
|
broadcastHeight uint32, htlc channeldb.HTLC,
|
|
resCfg ResolverConfig) *htlcOutgoingContestResolver {
|
|
|
|
timeout := newTimeoutResolver(
|
|
res, broadcastHeight, htlc, resCfg,
|
|
)
|
|
|
|
return &htlcOutgoingContestResolver{
|
|
htlcTimeoutResolver: *timeout,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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.
|
|
outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
|
|
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, errResolverShuttingDown
|
|
}
|
|
|
|
// TODO(roasbeef): Checkpoint?
|
|
return h.claimCleanUp(commitSpend)
|
|
|
|
// If it hasn't, then we'll watch for both the expiration, and the
|
|
// sweeping out this output.
|
|
default:
|
|
}
|
|
|
|
// 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, errResolverShuttingDown
|
|
}
|
|
|
|
// If the current height is >= expiry-1, then a timeout
|
|
// path 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).
|
|
//
|
|
// Source:
|
|
// https://github.com/btcsuite/btcd/blob/991d32e72fe84d5fbf9c47cd604d793a0cd3a072/blockchain/validate.go#L154
|
|
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, errResolverShuttingDown
|
|
}
|
|
|
|
// 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 h.claimCleanUp(commitSpend)
|
|
|
|
case <-h.quit:
|
|
return nil, fmt.Errorf("resolver canceled")
|
|
}
|
|
}
|
|
}
|
|
|
|
// report returns a report on the resolution state of the contract.
|
|
func (h *htlcOutgoingContestResolver) report() *ContractReport {
|
|
// No locking needed as these values are read-only.
|
|
|
|
finalAmt := h.htlc.Amt.ToSatoshis()
|
|
if h.htlcResolution.SignedTimeoutTx != nil {
|
|
finalAmt = btcutil.Amount(
|
|
h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
|
|
)
|
|
}
|
|
|
|
return &ContractReport{
|
|
Outpoint: h.htlcResolution.ClaimOutpoint,
|
|
Incoming: false,
|
|
Amount: finalAmt,
|
|
MaturityHeight: h.htlcResolution.Expiry,
|
|
LimboBalance: finalAmt,
|
|
Stage: 1,
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
|
|
// from the passed Reader instance, returning an active ContractResolver
|
|
// instance.
|
|
func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
|
|
*htlcOutgoingContestResolver, error) {
|
|
|
|
h := &htlcOutgoingContestResolver{}
|
|
timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h.htlcTimeoutResolver = *timeoutResolver
|
|
return h, nil
|
|
}
|
|
|
|
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
|
|
// ContractResolver interface.
|
|
var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)
|