contractcourt: add Launch method to incoming contest resolver

A minor refactor is done to support implementing `Launch`.
This commit is contained in:
yyforyongyu 2024-06-20 22:10:26 +08:00
parent 6c786e5637
commit da4ce49c49
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
3 changed files with 205 additions and 65 deletions

View File

@ -78,6 +78,37 @@ func (h *htlcIncomingContestResolver) processFinalHtlcFail() error {
return nil
}
// Launch will call the inner resolver's launch method if the preimage can be
// found, otherwise it's a no-op.
func (h *htlcIncomingContestResolver) Launch() error {
// NOTE: we don't mark this resolver as launched as the inner resolver
// will set it when it's launched.
if h.launched {
h.log.Tracef("already launched")
return nil
}
h.log.Debugf("launching contest resolver...")
// Query the preimage and apply it if we already know it.
applied, err := h.findAndapplyPreimage()
if err != nil {
return err
}
// No preimage found, leave it to be handled by the resolver.
if !applied {
return nil
}
h.log.Debugf("found preimage for htlc=%x, launching success resolver",
h.htlc.RHash)
// Once we've applied the preimage, we'll launch the inner resolver to
// attempt to claim the HTLC.
return h.htlcSuccessResolver.Launch()
}
// 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:
//
@ -94,6 +125,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// If we're already full resolved, then we don't have anything further
// to do.
if h.resolved {
h.log.Errorf("already resolved")
return nil, nil
}
@ -112,8 +144,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// now.
payload, nextHopOnionBlob, err := h.decodePayload()
if err != nil {
log.Debugf("ChannelArbitrator(%v): cannot decode payload of "+
"htlc %v", h.ChanPoint, h.HtlcPoint())
h.log.Debugf("cannot decode payload of htlc %v", h.HtlcPoint())
// If we've locked in an htlc with an invalid payload on our
// commitment tx, we don't need to resolve it. The other party
@ -188,65 +219,6 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
return nil, h.Checkpoint(h, report)
}
// 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. The error return value indicates whether the preimage
// was properly applied.
applyPreimage := func(preimage lntypes.Preimage) error {
// Sanity check to see if this preimage matches our htlc. At
// this point it should never happen that it does not match.
if !preimage.Matches(h.htlc.RHash) {
return errors.New("preimage does not match hash")
}
// Update htlcResolution with the matching preimage.
h.htlcResolution.Preimage = preimage
log.Infof("%T(%v): applied preimage=%v", h,
h.htlcResolution.ClaimOutpoint, preimage)
isSecondLevel := h.htlcResolution.SignedSuccessTx != nil
// If we didn't have to go to the second level to claim (this
// is the remote commitment transaction), then we don't need to
// modify our canned witness.
if !isSecondLevel {
return nil
}
isTaproot := txscript.IsPayToTaproot(
h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
)
// If this is our commitment transaction, then we'll need to
// populate the witness for the second-level HTLC transaction.
switch {
// For taproot channels, the witness for sweeping with success
// looks like:
// - <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
//
// So we'll insert it at the 3rd index of the witness.
case isTaproot:
//nolint:lll
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:]
// Within the witness for the success transaction, the
// preimage is the 4th element as it looks like:
//
// * <0> <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.
case !isTaproot:
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
}
return nil
}
// Define a closure to process htlc resolutions either directly or
// triggered by future notifications.
processHtlcResolution := func(e invoices.HtlcResolution) (
@ -258,7 +230,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// If the htlc resolution was a settle, apply the
// preimage and return a success resolver.
case *invoices.HtlcSettleResolution:
err := applyPreimage(resolution.Preimage)
err := h.applyPreimage(resolution.Preimage)
if err != nil {
return nil, err
}
@ -380,7 +352,9 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// However, we don't know how to ourselves, so we'll
// return our inner resolver which has the knowledge to
// do so.
if err := applyPreimage(preimage); err != nil {
h.log.Debugf("Found preimage for htlc=%x", h.htlc.RHash)
if err := h.applyPreimage(preimage); err != nil {
return nil, err
}
@ -399,7 +373,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
continue
}
if err := applyPreimage(preimage); err != nil {
h.log.Debugf("Received preimage for htlc=%x",
h.htlc.RHash)
if err := h.applyPreimage(preimage); err != nil {
return nil, err
}
@ -446,6 +423,76 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
}
}
// 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. The error
// return value indicates whether the preimage was properly applied.
func (h *htlcIncomingContestResolver) applyPreimage(
preimage lntypes.Preimage) error {
// Sanity check to see if this preimage matches our htlc. At this point
// it should never happen that it does not match.
if !preimage.Matches(h.htlc.RHash) {
return errors.New("preimage does not match hash")
}
// We may already have the preimage since both the `Launch` and
// `Resolve` methods will look for it.
if h.htlcResolution.Preimage != lntypes.ZeroHash {
h.log.Debugf("already applied preimage for htlc=%x",
h.htlc.RHash)
return nil
}
// Update htlcResolution with the matching preimage.
h.htlcResolution.Preimage = preimage
log.Infof("%T(%v): applied preimage=%v", h,
h.htlcResolution.ClaimOutpoint, preimage)
isSecondLevel := h.htlcResolution.SignedSuccessTx != nil
// If we didn't have to go to the second level to claim (this
// is the remote commitment transaction), then we don't need to
// modify our canned witness.
if !isSecondLevel {
return nil
}
isTaproot := txscript.IsPayToTaproot(
h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
)
// If this is our commitment transaction, then we'll need to
// populate the witness for the second-level HTLC transaction.
switch {
// For taproot channels, the witness for sweeping with success
// looks like:
// - <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
//
// So we'll insert it at the 3rd index of the witness.
case isTaproot:
//nolint:lll
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:]
// Within the witness for the success transaction, the
// preimage is the 4th element as it looks like:
//
// * <0> <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.
case !isTaproot:
//nolint:lll
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
}
return nil
}
// report returns a report on the resolution state of the contract.
func (h *htlcIncomingContestResolver) report() *ContractReport {
// No locking needed as these values are read-only.
@ -472,6 +519,7 @@ func (h *htlcIncomingContestResolver) report() *ContractReport {
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcIncomingContestResolver) Stop() {
h.log.Debugf("stopping...")
close(h.quit)
}
@ -571,3 +619,77 @@ func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload,
// A compile time assertion to ensure htlcIncomingContestResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
// findAndapplyPreimage attempts to find the preimage for the incoming HTLC. If
// found, it will be applied.
func (h *htlcIncomingContestResolver) findAndapplyPreimage() (bool, error) {
// Query to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
// If the preimage is known, we'll apply it.
if ok {
if err := h.applyPreimage(preimage); err != nil {
return false, err
}
// Successfully applied the preimage, we can now return.
return true, nil
}
// First try to parse the payload.
payload, _, err := h.decodePayload()
if err != nil {
h.log.Errorf("Cannot decode payload of htlc %v", h.HtlcPoint())
// If we cannot decode the payload, we will return a nil error
// and let it to be handled in `Resolve`.
return false, nil
}
// Exit early if this is not the exit hop, which means we are not the
// payment receiver and don't have preimage.
if payload.FwdInfo.NextHop != hop.Exit {
return false, nil
}
// Notify registry that we are potentially resolving as an exit hop
// on-chain. If this HTLC indeed pays to an existing invoice, the
// invoice registry will tell us what to do with the HTLC. This is
// identical to HTLC resolution in the link.
circuitKey := models.CircuitKey{
ChanID: h.ShortChanID,
HtlcID: h.htlc.HtlcIndex,
}
// Try get the resolution - if it doesn't give us a resolution
// immediately, we'll assume we don't know it yet and let the `Resolve`
// handle the waiting.
//
// NOTE: we use a nil subscriber here and a zero current height as we
// are only interested in the settle resolution.
//
// TODO(yy): move this logic to link and let the preimage be accessed
// via the preimage beacon.
resolution, err := h.Registry.NotifyExitHopHtlc(
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, 0,
circuitKey, nil, nil, payload,
)
if err != nil {
return false, err
}
res, ok := resolution.(*invoices.HtlcSettleResolution)
// Exit early if it's not a settle resolution.
if !ok {
return false, nil
}
// Otherwise we have a settle resolution, apply the preimage.
err = h.applyPreimage(res.Preimage)
if err != nil {
return false, err
}
return true, nil
}

View File

@ -5,11 +5,13 @@ import (
"io"
"testing"
"github.com/btcsuite/btcd/wire"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnmock"
@ -356,6 +358,7 @@ func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolver
return nil
},
Sweeper: newMockSweeper(),
},
PutResolverReport: func(_ kvdb.RwTx,
_ *channeldb.ResolverReport) error {
@ -374,10 +377,16 @@ func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolver
},
}
res := lnwallet.IncomingHtlcResolution{
SweepSignDesc: input.SignDescriptor{
Output: &wire.TxOut{},
},
}
c.resolver = &htlcIncomingContestResolver{
htlcSuccessResolver: &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(cfg),
htlcResolution: lnwallet.IncomingHtlcResolution{},
htlcResolution: res,
htlc: channeldb.HTLC{
Amt: lnwire.MilliSatoshi(testHtlcAmount),
RHash: testResHash,
@ -386,6 +395,7 @@ func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolver
},
htlcExpiry: testHtlcExpiry,
}
c.resolver.initLogger("htlcIncomingContestResolver")
return c
}
@ -395,6 +405,10 @@ func (i *incomingResolverTestContext) resolve() {
i.resolveErr = make(chan error, 1)
go func() {
var err error
err = i.resolver.Launch()
require.NoError(i.t, err)
i.nextResolver, err = i.resolver.Resolve()
i.resolveErr <- err
}()

View File

@ -1806,6 +1806,10 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
func (i *InvoiceRegistry) hodlSubscribe(subscriber chan<- interface{},
circuitKey CircuitKey) {
if subscriber == nil {
return
}
i.hodlSubscriptionsMux.Lock()
defer i.hodlSubscriptionsMux.Unlock()