Merge pull request #9194 from lightningnetwork/aux-channel-htlc

multi: generate and pass along HTLC resolution blobs for aux channels
This commit is contained in:
Oliver Gugger 2024-11-15 13:07:35 +01:00 committed by GitHub
commit 95b248a1ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 728 additions and 141 deletions

View File

@ -752,7 +752,7 @@ justiceTxBroadcast:
}
return aux.NotifyBroadcast(
&bumpReq, finalTx.justiceTx, finalTx.fee,
&bumpReq, finalTx.justiceTx, finalTx.fee, nil,
)
})
if err != nil {
@ -1160,6 +1160,11 @@ func (bo *breachedOutput) SignDesc() *input.SignDescriptor {
return &bo.signDesc
}
// Preimage returns the preimage that was used to create the breached output.
func (bo *breachedOutput) Preimage() fn.Option[lntypes.Preimage] {
return fn.None[lntypes.Preimage]()
}
// CraftInputScript computes a valid witness that allows us to spend from the
// breached output. It does so by first generating and memoizing the witness
// generation function, which parameterized primarily by the witness type and

View File

@ -1571,6 +1571,7 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
})
}
htlcBlobs := newAuxHtlcBlobs()
for _, htlc := range c.HtlcResolutions.IncomingHTLCs {
htlc := htlc
@ -1581,8 +1582,9 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
continue
}
var resID resolverID
if htlc.SignedSuccessTx != nil {
resID := newResolverID(
resID = newResolverID(
htlc.SignedSuccessTx.TxIn[0].PreviousOutPoint,
)
//nolint:lll
@ -1598,10 +1600,14 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = bridgeCtrlBlock
}
} else {
resID := newResolverID(htlc.ClaimOutpoint)
resID = newResolverID(htlc.ClaimOutpoint)
//nolint:lll
tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = ctrlBlock
}
htlc.ResolutionBlob.WhenSome(func(b []byte) {
htlcBlobs[resID] = b
})
}
for _, htlc := range c.HtlcResolutions.OutgoingHTLCs {
htlc := htlc
@ -1613,8 +1619,9 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
continue
}
var resID resolverID
if htlc.SignedTimeoutTx != nil {
resID := newResolverID(
resID = newResolverID(
htlc.SignedTimeoutTx.TxIn[0].PreviousOutPoint,
)
//nolint:lll
@ -1632,10 +1639,14 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = bridgeCtrlBlock
}
} else {
resID := newResolverID(htlc.ClaimOutpoint)
resID = newResolverID(htlc.ClaimOutpoint)
//nolint:lll
tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = ctrlBlock
}
htlc.ResolutionBlob.WhenSome(func(b []byte) {
htlcBlobs[resID] = b
})
}
if c.AnchorResolution != nil {
@ -1643,6 +1654,12 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
tapCase.TapTweaks.Val.AnchorTweak = anchorSignDesc.TapTweak
}
if len(htlcBlobs) != 0 {
tapCase.HtlcBlobs = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType4](htlcBlobs),
)
}
return tapCase.Encode(w)
}
@ -1661,6 +1678,8 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error {
})
}
htlcBlobs := tapCase.HtlcBlobs.ValOpt().UnwrapOr(newAuxHtlcBlobs())
for i := range c.HtlcResolutions.IncomingHTLCs {
htlc := c.HtlcResolutions.IncomingHTLCs[i]
@ -1687,7 +1706,12 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error {
htlc.SweepSignDesc.ControlBlock = ctrlBlock
}
if htlcBlob, ok := htlcBlobs[resID]; ok {
htlc.ResolutionBlob = fn.Some(htlcBlob)
}
c.HtlcResolutions.IncomingHTLCs[i] = htlc
}
for i := range c.HtlcResolutions.OutgoingHTLCs {
htlc := c.HtlcResolutions.OutgoingHTLCs[i]
@ -1715,6 +1739,10 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error {
htlc.SweepSignDesc.ControlBlock = ctrlBlock
}
if htlcBlob, ok := htlcBlobs[resID]; ok {
htlc.ResolutionBlob = fn.Some(htlcBlob)
}
c.HtlcResolutions.OutgoingHTLCs[i] = htlc
}

View File

@ -436,6 +436,7 @@ func (c *chainWatcher) handleUnknownLocalState(
return s.FetchLeavesFromCommit(
lnwallet.NewAuxChanState(c.cfg.chanState),
c.cfg.chanState.LocalCommitment, *commitKeyRing,
lntypes.Local,
)
},
).Unpack()

View File

@ -99,17 +99,6 @@ func (h *htlcIncomingContestResolver) Resolve(
return nil, nil
}
// If the HTLC has custom records, then for now we'll pause resolution.
//
// TODO(roasbeef): Implement resolving HTLCs with custom records
// (follow-up PR).
if len(h.htlc.CustomRecords) != 0 {
select { //nolint:gosimple
case <-h.quit:
return nil, errResolverShuttingDown
}
}
// First try to parse the payload. If that fails, we can stop resolution
// now.
payload, nextHopOnionBlob, err := h.decodePayload()

View File

@ -6,7 +6,9 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/tlv"
)
// htlcLeaseResolver is a struct that houses the lease specific HTLC resolution
@ -52,8 +54,8 @@ func (h *htlcLeaseResolver) deriveWaitHeight(csvDelay uint32,
// send to the sweeper so the output can ultimately be swept.
func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint,
wType, cltvWtype input.StandardWitnessType,
signDesc *input.SignDescriptor,
csvDelay, broadcastHeight uint32, payHash [32]byte) *input.BaseInput {
signDesc *input.SignDescriptor, csvDelay, broadcastHeight uint32,
payHash [32]byte, resBlob fn.Option[tlv.Blob]) *input.BaseInput {
if h.hasCLTV() {
log.Infof("%T(%x): CSV and CLTV locks expired, offering "+
@ -63,13 +65,17 @@ func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint,
op, cltvWtype, signDesc,
broadcastHeight, csvDelay,
h.leaseExpiry,
input.WithResolutionBlob(resBlob),
)
}
log.Infof("%T(%x): CSV lock expired, offering second-layer output to "+
"sweeper: %v", h, payHash, op)
return input.NewCsvInput(op, wType, signDesc, broadcastHeight, csvDelay)
return input.NewCsvInput(
op, wType, signDesc, broadcastHeight, csvDelay,
input.WithResolutionBlob(resBlob),
)
}
// SupplementState allows the user of a ContractResolver to supplement it with

View File

@ -58,17 +58,6 @@ func (h *htlcOutgoingContestResolver) Resolve(
return nil, nil
}
// If the HTLC has custom records, then for now we'll pause resolution.
//
// TODO(roasbeef): Implement resolving HTLCs with custom records
// (follow-up PR).
if len(h.htlc.CustomRecords) != 0 {
select { //nolint:gosimple
case <-h.quit:
return nil, errResolverShuttingDown
}
}
// Otherwise, we'll watch for two external signals to decide if we'll
// morph into another resolver, or fully resolve the contract.
//

View File

@ -123,17 +123,6 @@ func (h *htlcSuccessResolver) Resolve(
return nil, nil
}
// If the HTLC has custom records, then for now we'll pause resolution.
//
// TODO(roasbeef): Implement resolving HTLCs with custom records
// (follow-up PR).
if len(h.htlc.CustomRecords) != 0 {
select { //nolint:gosimple
case <-h.quit:
return nil, errResolverShuttingDown
}
}
// 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 {
@ -258,6 +247,9 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) (
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
)
} else {
//nolint:lll
@ -414,7 +406,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) (
input.LeaseHtlcAcceptedSuccessSecondLevel,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
h.htlc.RHash,
h.htlc.RHash, h.htlcResolution.ResolutionBlob,
)
// Calculate the budget for this sweep.
@ -470,6 +462,9 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput(immediate bool) (
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else {
inp = lnutils.Ptr(input.MakeHtlcSucceedInput(

View File

@ -426,17 +426,6 @@ func (h *htlcTimeoutResolver) Resolve(
return nil, nil
}
// If the HTLC has custom records, then for now we'll pause resolution.
//
// TODO(roasbeef): Implement resolving HTLCs with custom records
// (follow-up PR).
if len(h.htlc.CustomRecords) != 0 {
select { //nolint:gosimple
case <-h.quit:
return nil, errResolverShuttingDown
}
}
// Start by spending the HTLC output, either by broadcasting the
// second-level timeout transaction, or directly if this is the remote
// commitment.
@ -499,6 +488,9 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error {
h.htlcResolution.SignedTimeoutTx,
h.htlcResolution.SignDetails,
h.broadcastHeight,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else {
inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutAnchorInput(
@ -592,6 +584,7 @@ func (h *htlcTimeoutResolver) sweepDirectHtlcOutput(immediate bool) error {
&h.htlcResolution.ClaimOutpoint, htlcWitnessType,
&h.htlcResolution.SweepSignDesc, h.broadcastHeight,
h.htlcResolution.CsvDelay, h.htlcResolution.Expiry,
input.WithResolutionBlob(h.htlcResolution.ResolutionBlob),
)
// Calculate the budget.
@ -846,6 +839,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.CsvDelay,
uint32(commitSpend.SpendingHeight), h.htlc.RHash,
h.htlcResolution.ResolutionBlob,
)
// Calculate the budget for this sweep.

View File

@ -39,7 +39,9 @@ type taprootBriefcase struct {
// used to sweep a remote party's breached output.
BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob]
// TODO(roasbeef): htlc blobs
// HtlcBlobs is an optikonal record that contains the opaque blobs for
// the set of active HTLCs on the commitment transaction.
HtlcBlobs tlv.OptionalRecordT[tlv.TlvType4, htlcAuxBlobs]
}
// TODO(roasbeef): morph into new tlv record
@ -70,6 +72,9 @@ func (t *taprootBriefcase) EncodeRecords() []tlv.Record {
records = append(records, r.Record())
},
)
t.HtlcBlobs.WhenSome(func(r tlv.RecordT[tlv.TlvType4, htlcAuxBlobs]) {
records = append(records, r.Record())
})
return records
}
@ -96,10 +101,11 @@ func (t *taprootBriefcase) Encode(w io.Writer) error {
func (t *taprootBriefcase) Decode(r io.Reader) error {
settledCommitBlob := t.SettledCommitBlob.Zero()
breachedCommitBlob := t.BreachedCommitBlob.Zero()
htlcBlobs := t.HtlcBlobs.Zero()
records := append(
t.DecodeRecords(),
settledCommitBlob.Record(),
breachedCommitBlob.Record(),
t.DecodeRecords(), settledCommitBlob.Record(),
breachedCommitBlob.Record(), htlcBlobs.Record(),
)
stream, err := tlv.NewStream(records...)
if err != nil {
@ -117,6 +123,9 @@ func (t *taprootBriefcase) Decode(r io.Reader) error {
if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil {
t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob)
}
if v, ok := typeMap[t.HtlcBlobs.TlvType()]; ok && v == nil {
t.HtlcBlobs = tlv.SomeRecordT(htlcBlobs)
}
return nil
}
@ -686,3 +695,110 @@ func (t *tapTweaks) Decode(r io.Reader) error {
return stream.Decode(r)
}
// htlcAuxBlobs is a map of resolver IDs to their corresponding HTLC blobs.
// This is used to store the resolution blobs for HTLCs that are not yet
// resolved.
type htlcAuxBlobs map[resolverID]tlv.Blob
// newAuxHtlcBlobs returns a new instance of the htlcAuxBlobs struct.
func newAuxHtlcBlobs() htlcAuxBlobs {
return make(htlcAuxBlobs)
}
// Encode encodes the set of HTLC blobs into the target writer.
func (h *htlcAuxBlobs) Encode(w io.Writer) error {
var buf [8]byte
numBlobs := uint64(len(*h))
if err := tlv.WriteVarInt(w, numBlobs, &buf); err != nil {
return err
}
for id, blob := range *h {
if _, err := w.Write(id[:]); err != nil {
return err
}
if err := varBytesEncoder(w, &blob, &buf); err != nil {
return err
}
}
return nil
}
// Decode decodes the set of HTLC blobs from the target reader.
func (h *htlcAuxBlobs) Decode(r io.Reader) error {
var buf [8]byte
numBlobs, err := tlv.ReadVarInt(r, &buf)
if err != nil {
return err
}
for i := uint64(0); i < numBlobs; i++ {
var id resolverID
if _, err := io.ReadFull(r, id[:]); err != nil {
return err
}
var blob tlv.Blob
if err := varBytesDecoder(r, &blob, &buf, 0); err != nil {
return err
}
(*h)[id] = blob
}
return nil
}
// eHtlcAuxBlobsEncoder is a custom TLV encoder for the htlcAuxBlobs struct.
func htlcAuxBlobsEncoder(w io.Writer, val any, _ *[8]byte) error {
if t, ok := val.(*htlcAuxBlobs); ok {
return (*t).Encode(w)
}
return tlv.NewTypeForEncodingErr(val, "htlcAuxBlobs")
}
// dHtlcAuxBlobsDecoder is a custom TLV decoder for the htlcAuxBlobs struct.
func htlcAuxBlobsDecoder(r io.Reader, val any, _ *[8]byte,
l uint64) error {
if typ, ok := val.(*htlcAuxBlobs); ok {
blobReader := io.LimitReader(r, int64(l))
htlcBlobs := newAuxHtlcBlobs()
err := htlcBlobs.Decode(blobReader)
if err != nil {
return err
}
*typ = htlcBlobs
return nil
}
return tlv.NewTypeForDecodingErr(val, "htlcAuxBlobs", l, l)
}
// Record returns a tlv.Record for the htlcAuxBlobs struct.
func (h *htlcAuxBlobs) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := htlcAuxBlobsEncoder(&b, h, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, h, recordSize, htlcAuxBlobsEncoder, htlcAuxBlobsDecoder,
)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
)
func randResolverCtrlBlocks(t *testing.T) resolverCtrlBlocks {
@ -53,6 +54,25 @@ func randHtlcTweaks(t *testing.T) htlcTapTweaks {
return tweaks
}
func randHtlcAuxBlobs(t *testing.T) htlcAuxBlobs {
numBlobs := rand.Int() % 256
blobs := make(htlcAuxBlobs, numBlobs)
for i := 0; i < numBlobs; i++ {
var id resolverID
_, err := rand.Read(id[:])
require.NoError(t, err)
var blob [100]byte
_, err = rand.Read(blob[:])
require.NoError(t, err)
blobs[id] = blob[:]
}
return blobs
}
// TestTaprootBriefcase tests the encode/decode methods of the taproot
// briefcase extension.
func TestTaprootBriefcase(t *testing.T) {
@ -93,6 +113,9 @@ func TestTaprootBriefcase(t *testing.T) {
BreachedCommitBlob: tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]),
),
HtlcBlobs: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType4](randHtlcAuxBlobs(t)),
),
}
var b bytes.Buffer
@ -103,3 +126,21 @@ func TestTaprootBriefcase(t *testing.T) {
require.Equal(t, testCase, &decodedCase)
}
// TestHtlcAuxBlobEncodeDecode tests the encode/decode methods of the HTLC aux
// blobs.
func TestHtlcAuxBlobEncodeDecode(t *testing.T) {
t.Parallel()
rapid.Check(t, func(t *rapid.T) {
htlcBlobs := rapid.Make[htlcAuxBlobs]().Draw(t, "htlcAuxBlobs")
var b bytes.Buffer
require.NoError(t, htlcBlobs.Encode(&b))
decodedBlobs := newAuxHtlcBlobs()
require.NoError(t, decodedBlobs.Decode(&b))
require.Equal(t, htlcBlobs, decodedBlobs)
})
}

View File

@ -0,0 +1,78 @@
# 2024/09/02 14:02:53.354676 [TestHtlcAuxBlobEncodeDecode] [rapid] draw htlcAuxBlobs: contractcourt.htlcAuxBlobs{contractcourt.resolverID{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}:[]uint8{}}
#
v0.4.8#15807814492030881602
0x5555555555555
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0

View File

@ -18,11 +18,23 @@
- [Tooling and Documentation](#tooling-and-documentation)
# Bug Fixes
* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9134) that would
cause a nil pointer dereference during the probing of a payment request that
does not contain a payment address.
# New Features
The main channel state machine and database now allow for processing and storing
custom Taproot script leaves, [allowing the implementation of custom channel
types](https://github.com/lightningnetwork/lnd/pull/8960).
custom Taproot script leaves, allowing the implementation of custom channel
types in a series of changes:
* https://github.com/lightningnetwork/lnd/pull/9025
* https://github.com/lightningnetwork/lnd/pull/9030
* https://github.com/lightningnetwork/lnd/pull/9049
* https://github.com/lightningnetwork/lnd/pull/9072
* https://github.com/lightningnetwork/lnd/pull/9095
* https://github.com/lightningnetwork/lnd/pull/8960
* https://github.com/lightningnetwork/lnd/pull/9194
## Functional Enhancements
@ -82,6 +94,10 @@ types](https://github.com/lightningnetwork/lnd/pull/8960).
## Breaking Changes
## Performance Improvements
* [A new method](https://github.com/lightningnetwork/lnd/pull/9195)
`AssertTxnsNotInMempool` has been added to `lntest` package to allow batch
exclusion check in itest.
# Technical and Architectural Updates
## BOLT Spec Updates

View File

@ -23,10 +23,6 @@
propagate mission control and debug level config values to the main LND config
struct so that the GetDebugInfo response is accurate.
* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9134) that would
cause a nil pointer dereference during the probing of a payment request that
does not contain a payment address.
* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/9033) where we
would not signal an error when trying to bump a non-anchor channel but
instead report a successful cpfp registration although no fee bumping is
@ -163,10 +159,6 @@ The underlying functionality between those two options remain the same.
* Log rotation can now use ZSTD
* [A new method](https://github.com/lightningnetwork/lnd/pull/9195)
`AssertTxnsNotInMempool` has been added to `lntest` package to allow batch
exclusion check in itest.
# Technical and Architectural Updates
## BOLT Spec Updates

1
go.mod
View File

@ -64,6 +64,7 @@ require (
google.golang.org/protobuf v1.33.0
gopkg.in/macaroon-bakery.v2 v2.0.1
gopkg.in/macaroon.v2 v2.0.0
pgregory.net/rapid v1.1.0
)
require (

2
go.sum
View File

@ -1076,6 +1076,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -69,6 +69,9 @@ type Input interface {
// ResolutionBlob returns a special opaque blob to be used to
// sweep/resolve this input.
ResolutionBlob() fn.Option[tlv.Blob]
// Preimage returns the preimage for the input if it is an HTLC input.
Preimage() fn.Option[lntypes.Preimage]
}
// TxInfo describes properties of a parent tx that are relevant for CPFP.
@ -285,6 +288,11 @@ func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
return witnessFunc(txn, hashCache, txinIdx)
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (bi *BaseInput) Preimage() fn.Option[lntypes.Preimage] {
return fn.None[lntypes.Preimage]()
}
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
// is expected to reside on the commitment tx of the remote party and should
// not be a second level tx output.
@ -357,7 +365,6 @@ func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
}
desc.SignMethod = TaprootScriptSpendSignMethod
witness, err = SenderHTLCScriptTaprootRedeem(
signer, &desc, txn, h.preimage, nil, nil,
)
@ -375,6 +382,15 @@ func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
}, nil
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (h *HtlcSucceedInput) Preimage() fn.Option[lntypes.Preimage] {
if len(h.preimage) == 0 {
return fn.None[lntypes.Preimage]()
}
return fn.Some(lntypes.Preimage(h.preimage))
}
// HtlcSecondLevelAnchorInput is an input type used to spend HTLC outputs
// using a re-signed second level transaction, either via the timeout or success
// paths.
@ -391,6 +407,8 @@ type HtlcSecondLevelAnchorInput struct {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error)
preimage []byte
}
// RequiredTxOut returns the tx out needed to be present on the sweep tx for
@ -427,6 +445,15 @@ func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer,
}, nil
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (i *HtlcSecondLevelAnchorInput) Preimage() fn.Option[lntypes.Preimage] {
if len(i.preimage) == 0 {
return fn.None[lntypes.Preimage]()
}
return fn.Some(lntypes.Preimage(i.preimage))
}
// MakeHtlcSecondLevelTimeoutAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level timeout
// transaction.
@ -545,6 +572,7 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx,
SignedTx: signedTx,
inputKit: input.inputKit,
createWitness: createWitness,
preimage: preimage[:],
}
}
@ -588,6 +616,7 @@ func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx,
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
preimage: preimage[:],
}
}

View File

@ -140,6 +140,17 @@ func (m *MockInput) ResolutionBlob() fn.Option[tlv.Blob] {
return info.(fn.Option[tlv.Blob])
}
func (m *MockInput) Preimage() fn.Option[lntypes.Preimage] {
args := m.Called()
info := args.Get(0)
if info == nil {
return fn.None[lntypes.Preimage]()
}
return info.(fn.Option[lntypes.Preimage])
}
// MockWitnessType implements the `WitnessType` interface and is used by other
// packages for mock testing.
type MockWitnessType struct {

View File

@ -178,7 +178,8 @@ type AuxLeafStore interface {
// commitment.
FetchLeavesFromCommit(chanState AuxChanState,
commit channeldb.ChannelCommitment,
keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult]
keyRing CommitmentKeyRing, whoseCommit lntypes.ChannelParty,
) fn.Result[CommitDiffAuxResult]
// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves
// from a channel revocation that stores balance + blob information.

View File

@ -3,6 +3,7 @@ package lnwallet
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
@ -24,6 +25,19 @@ const (
Breach
)
// AuxSigDesc stores optional information related to 2nd level HTLCs for aux
// channels.
type AuxSigDesc struct {
// AuxSig is the second-level signature for the HTLC that we are trying
// to resolve. This is only present if this is a resolution request for
// an HTLC on our commitment transaction.
AuxSig []byte
// SignDetails is the sign details for the second-level HTLC. This may
// be used to generate the second signature needed for broadcast.
SignDetails input.SignDetails
}
// ResolutionReq is used to ask an outside sub-system for additional
// information needed to resolve a contract.
type ResolutionReq struct {
@ -31,6 +45,9 @@ type ResolutionReq struct {
// resolve.
ChanPoint wire.OutPoint
// ChanType is the type of the channel that we are trying to resolve.
ChanType channeldb.ChannelType
// ShortChanID is the short channel ID of the channel that we are
// trying to resolve.
ShortChanID lnwire.ShortChannelID
@ -44,6 +61,13 @@ type ResolutionReq struct {
// FundingBlob is an optional funding blob for the channel.
FundingBlob fn.Option[tlv.Blob]
// HtlcID is the ID of the HTLC that we are trying to resolve. This is
// only set if this is a resolution request for an HTLC.
HtlcID fn.Option[input.HtlcIndex]
// HtlcAmt is the amount of the HTLC that we are trying to resolve.
HtlcAmt btcutil.Amount
// Type is the type of the witness that we are trying to resolve.
Type input.WitnessType
@ -69,14 +93,26 @@ type ResolutionReq struct {
// CsvDelay is the CSV delay for the local output for this commitment.
CsvDelay uint32
// CommitCsvDelay is the CSV delay for the remote output for this
// commitment.
CommitCsvDelay uint32
// BreachCsvDelay is the CSV delay for the remote output. This is only
// set when the CloseType is Breach. This indicates the CSV delay to
// use for the remote party's to_local delayed output, that is now
// rightfully ours in a breach situation.
BreachCsvDelay fn.Option[uint32]
// CltvDelay is the CLTV delay for the outpoint.
// CltvDelay is the CLTV delay for the outpoint/transaction.
CltvDelay fn.Option[uint32]
// PayHash is the payment hash for the HTLC that we are trying to
// resolve. This is optional as it only applies HTLC outputs.
PayHash fn.Option[[32]byte]
// AuxSigDesc is an optional field that contains additional information
// needed to sweep second level HTLCs.
AuxSigDesc fn.Option[AuxSigDesc]
}
// AuxContractResolver is an interface that is used to resolve contracts that

View File

@ -666,6 +666,7 @@ func (lc *LightningChannel) diskCommitToMemCommit(
return s.FetchLeavesFromCommit(
NewAuxChanState(lc.channelState), *diskCommit,
*commitKeys.GetForParty(whoseCommit),
whoseCommit,
)
},
).Unpack()
@ -1834,7 +1835,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates(
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(lc.channelState), pendingCommit,
*pendingRemoteKeys,
*pendingRemoteKeys, lntypes.Remote,
)
},
).Unpack()
@ -2202,6 +2203,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// resolution data for this output.
resolveReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
FundingBlob: chanState.CustomBlob,
@ -2281,6 +2283,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// resolution data for this output.
resolveReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
FundingBlob: chanState.CustomBlob,
@ -3152,7 +3155,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), *diskCommit,
*keyRing,
*keyRing, lntypes.Remote,
)
},
).Unpack()
@ -4453,7 +4456,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context,
// Next, we'll need to send over any updates we sent as part of
// this new proposed commitment state.
for _, logUpdate := range commitDiff.LogUpdates {
commitUpdates = append(commitUpdates, logUpdate.UpdateMsg)
commitUpdates = append(
commitUpdates, logUpdate.UpdateMsg,
)
}
// If this is a taproot channel, then we need to regenerate the
@ -4736,7 +4741,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), *diskCommit,
*keyRing,
*keyRing, lntypes.Local,
)
},
).Unpack()
@ -6704,7 +6709,7 @@ type UnilateralCloseSummary struct {
// happen in case we have lost state) it should be set to an empty struct, in
// which case we will attempt to sweep the non-HTLC output using the passed
// commitPoint.
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen
signer input.Signer, commitSpend *chainntnfs.SpendDetail,
remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey,
leafStore fn.Option[AuxLeafStore],
@ -6723,7 +6728,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), remoteCommit,
*keyRing,
*keyRing, lntypes.Remote,
)
},
).Unpack()
@ -6748,8 +6753,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType,
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitSpend.SpendingTx,
chanState.ChanType, isRemoteInitiator, leaseExpiry,
auxResult.AuxLeaves,
chanState.ChanType, isRemoteInitiator, leaseExpiry, chanState,
auxResult.AuxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to create htlc resolutions: %w",
@ -6839,6 +6844,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
// resolution data for this output.
resolveReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
@ -6956,6 +6962,11 @@ type IncomingHtlcResolution struct {
// necessary items required to spend the sole output of the above
// transaction.
SweepSignDesc input.SignDescriptor
// ResolutionBlob is a blob used for aux channels that permits a
// spender of the output to properly resolve it in the case of a force
// close.
ResolutionBlob fn.Option[tlv.Blob]
}
// OutgoingHtlcResolution houses the information necessary to sweep any
@ -7005,6 +7016,11 @@ type OutgoingHtlcResolution struct {
// necessary items required to spend the sole output of the above
// transaction.
SweepSignDesc input.SignDescriptor
// ResolutionBlob is a blob used for aux channels that permits a
// spender of the output to properly resolve it in the case of a force
// close.
ResolutionBlob fn.Option[tlv.Blob]
}
// HtlcResolutions contains the items necessary to sweep HTLC's on chain
@ -7029,8 +7045,10 @@ func newOutgoingHtlcResolution(signer input.Signer,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType,
auxLeaves fn.Option[CommitAuxLeaves]) (*OutgoingHtlcResolution, error) {
chanType channeldb.ChannelType, chanState *channeldb.OpenChannel,
auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver],
) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
@ -7061,6 +7079,8 @@ func newOutgoingHtlcResolution(signer input.Signer,
return nil, err
}
htlcCsvDelay := HtlcSecondLevelInputSequence(chanType)
// If we're spending this HTLC output from the remote node's
// commitment, then we won't need to go to the second level as our
// outputs don't have a CSV delay.
@ -7098,11 +7118,43 @@ func newOutgoingHtlcResolution(signer input.Signer,
}
}
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootHtlcOfferedRemoteTimeout,
CloseType: RemoteForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: signDesc,
KeyRing: keyRing,
CsvDelay: htlcCsvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.RemoteCommitment.CommitFee,
HtlcID: fn.Some(htlc.HtlcIndex),
PayHash: fn.Some(htlc.RHash),
}
resolveRes := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: HtlcSecondLevelInputSequence(chanType),
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
}, nil
}
@ -7253,16 +7305,9 @@ func newOutgoingHtlcResolution(signer input.Signer,
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
)
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
SignedTimeoutTx: timeoutTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ClaimOutpoint: wire.OutPoint{
Hash: timeoutTx.TxHash(),
Index: 0,
},
SweepSignDesc: input.SignDescriptor{
// In addition to the info in txSignDetails, we also need extra
// information to sweep the second level output after confirmation.
sweepSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
@ -7277,7 +7322,61 @@ func newOutgoingHtlcResolution(signer input.Signer,
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
}
// This might be an aux channel, so we'll go ahead and attempt to
// generate the resolution blob for the channel so we can pass along to
// the sweeping sub-system.
resolveRes := fn.MapOptionZ(
auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] {
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:lll
FundingBlob: chanState.CustomBlob,
Type: input.TaprootHtlcLocalOfferedTimeout, //nolint:lll
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: sweepSignDesc,
KeyRing: keyRing,
CsvDelay: htlcCsvDelay,
HtlcAmt: btcutil.Amount(txOut.Value),
CommitCsvDelay: csvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.LocalCommitment.CommitFee, //nolint:lll
HtlcID: fn.Some(htlc.HtlcIndex),
PayHash: fn.Some(htlc.RHash),
AuxSigDesc: fn.Some(AuxSigDesc{
SignDetails: *txSignDetails,
AuxSig: func() []byte {
tlvType := htlcCustomSigType.TypeVal() //nolint:lll
return htlc.CustomRecords[uint64(tlvType)] //nolint:lll
}(),
}),
}
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
SignedTimeoutTx: timeoutTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
ClaimOutpoint: wire.OutPoint{
Hash: timeoutTx.TxHash(),
Index: 0,
},
SweepSignDesc: sweepSignDesc,
}, nil
}
@ -7293,8 +7392,10 @@ func newIncomingHtlcResolution(signer input.Signer,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType,
auxLeaves fn.Option[CommitAuxLeaves]) (*IncomingHtlcResolution, error) {
chanType channeldb.ChannelType, chanState *channeldb.OpenChannel,
auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver],
) (*IncomingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
@ -7326,6 +7427,8 @@ func newIncomingHtlcResolution(signer input.Signer,
return nil, err
}
htlcCsvDelay := HtlcSecondLevelInputSequence(chanType)
// If we're spending this output from the remote node's commitment,
// then we can skip the second layer and spend the output directly.
if whoseCommit.IsRemote() {
@ -7361,10 +7464,44 @@ func newIncomingHtlcResolution(signer input.Signer,
}
}
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
Type: input.TaprootHtlcAcceptedRemoteSuccess,
FundingBlob: chanState.CustomBlob,
CloseType: RemoteForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: signDesc,
KeyRing: keyRing,
HtlcID: fn.Some(htlc.HtlcIndex),
CsvDelay: htlcCsvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.RemoteCommitment.CommitFee,
PayHash: fn.Some(htlc.RHash),
CommitCsvDelay: csvDelay,
HtlcAmt: htlc.Amt.ToSatoshis(),
}
resolveRes := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &IncomingHtlcResolution{
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: HtlcSecondLevelInputSequence(chanType),
CsvDelay: htlcCsvDelay,
ResolutionBlob: resolutionBlob,
}, nil
}
@ -7510,15 +7647,10 @@ func newIncomingHtlcResolution(signer input.Signer,
localDelayTweak := input.SingleTweakBytes(
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
)
return &IncomingHtlcResolution{
SignedSuccessTx: successTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ClaimOutpoint: wire.OutPoint{
Hash: successTx.TxHash(),
Index: 0,
},
SweepSignDesc: input.SignDescriptor{
// In addition to the info in txSignDetails, we also need extra
// information to sweep the second level output after confirmation.
sweepSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
@ -7533,7 +7665,58 @@ func newIncomingHtlcResolution(signer input.Signer,
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
}
resolveRes := fn.MapOptionZ(
auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] {
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:lll
Type: input.TaprootHtlcAcceptedLocalSuccess, //nolint:lll
FundingBlob: chanState.CustomBlob,
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: sweepSignDesc,
KeyRing: keyRing,
HtlcID: fn.Some(htlc.HtlcIndex),
CsvDelay: htlcCsvDelay,
CommitFee: chanState.LocalCommitment.CommitFee, //nolint:lll
PayHash: fn.Some(htlc.RHash),
AuxSigDesc: fn.Some(AuxSigDesc{
SignDetails: *txSignDetails,
AuxSig: func() []byte {
tlvType := htlcCustomSigType.TypeVal() //nolint:lll
return htlc.CustomRecords[uint64(tlvType)] //nolint:lll
}(),
}),
CommitCsvDelay: csvDelay,
HtlcAmt: btcutil.Amount(txOut.Value),
CltvDelay: fn.Some(htlc.RefundTimeout),
}
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &IncomingHtlcResolution{
SignedSuccessTx: successTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
ClaimOutpoint: wire.OutPoint{
Hash: successTx.TxHash(),
Index: 0,
},
SweepSignDesc: sweepSignDesc,
}, nil
}
@ -7570,7 +7753,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
commitTx *wire.MsgTx, chanType channeldb.ChannelType,
isCommitFromInitiator bool, leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*HtlcResolutions, error) {
chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver]) (*HtlcResolutions, error) {
// TODO(roasbeef): don't need to swap csv delay?
dustLimit := remoteChanCfg.DustLimit
@ -7605,7 +7789,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
signer, localChanCfg, commitTx, &htlc,
keyRing, feePerKw, uint32(csvDelay),
leaseExpiry, whoseCommit, isCommitFromInitiator,
chanType, auxLeaves,
chanType, chanState, auxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("incoming resolution "+
@ -7619,7 +7803,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
ohr, err := newOutgoingHtlcResolution(
signer, localChanCfg, commitTx, &htlc, keyRing,
feePerKw, uint32(csvDelay), leaseExpiry, whoseCommit,
isCommitFromInitiator, chanType, auxLeaves,
isCommitFromInitiator, chanType, chanState, auxLeaves,
auxResolver,
)
if err != nil {
return nil, fmt.Errorf("outgoing resolution "+
@ -7765,6 +7950,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState),
chanState.LocalCommitment, *keyRing,
lntypes.Local,
)
},
).Unpack()
@ -7871,7 +8057,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
//nolint:lll
return a.ResolveContract(ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanPoint: chanState.FundingOutpoint, //nolint:lll
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob,
@ -7904,7 +8091,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local,
signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitTx, chanState.ChanType,
chanState.IsInitiator, leaseExpiry, auxResult.AuxLeaves,
chanState.IsInitiator, leaseExpiry, chanState,
auxResult.AuxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to gen htlc resolution: %w", err)

View File

@ -1307,7 +1307,7 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
leafStore, func(a AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return a.FetchLeavesFromCommit(
NewAuxChanState(chanState), chanCommit,
*keyRing,
*keyRing, lntypes.Remote,
)
},
).Unpack()

View File

@ -19,6 +19,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/mock"
@ -420,8 +421,8 @@ func (*MockAuxLeafStore) FetchLeavesFromView(
// correspond to the passed aux blob, and an existing channel
// commitment.
func (*MockAuxLeafStore) FetchLeavesFromCommit(_ AuxChanState,
_ channeldb.ChannelCommitment,
_ CommitmentKeyRing) fn.Result[CommitDiffAuxResult] {
_ channeldb.ChannelCommitment, _ CommitmentKeyRing,
_ lntypes.ChannelParty) fn.Result[CommitDiffAuxResult] {
return fn.Ok(CommitDiffAuxResult{})
}

51
macaroons/fuzz_test.go Normal file
View File

@ -0,0 +1,51 @@
package macaroons
import (
"context"
"testing"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
func FuzzUnmarshalMacaroon(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
mac := &macaroon.Macaroon{}
_ = mac.UnmarshalBinary(data)
})
}
func FuzzAuthChecker(f *testing.F) {
rootKeyStore := bakery.NewMemRootKeyStore()
ctx := context.Background()
f.Fuzz(func(t *testing.T, location, entity, action, method string,
rootKey, id []byte) {
macService, err := NewService(
rootKeyStore, location, true, IPLockChecker,
)
if err != nil {
return
}
requiredPermissions := []bakery.Op{{
Entity: entity,
Action: action,
}}
mac, err := macaroon.New(rootKey, id, location, macaroon.V2)
if err != nil {
return
}
macBytes, err := mac.MarshalBinary()
if err != nil {
return
}
_ = macService.CheckMacAuth(
ctx, macBytes, requiredPermissions, method,
)
})
}

View File

@ -603,7 +603,9 @@ func (t *TxPublisher) broadcast(requestID uint64) (*BumpResult, error) {
// Before we go to broadcast, we'll notify the aux sweeper, if it's
// present of this new broadcast attempt.
err := fn.MapOptionZ(t.cfg.AuxSweeper, func(aux AuxSweeper) error {
return aux.NotifyBroadcast(record.req, tx, record.fee)
return aux.NotifyBroadcast(
record.req, tx, record.fee, record.outpointToTxIndex,
)
})
if err != nil {
return nil, fmt.Errorf("unable to notify aux sweeper: %w", err)
@ -725,6 +727,9 @@ type monitorRecord struct {
// fee is the fee paid by the tx.
fee btcutil.Amount
// outpointToTxIndex is a map of outpoint to tx index.
outpointToTxIndex map[wire.OutPoint]int
}
// Start starts the publisher by subscribing to block epoch updates and kicking
@ -1046,6 +1051,7 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64,
req: r.req,
feeFunction: r.feeFunction,
fee: sweepCtx.fee,
outpointToTxIndex: sweepCtx.outpointToTxIndex,
})
// Attempt to broadcast this new tx.
@ -1199,6 +1205,10 @@ type sweepTxCtx struct {
fee btcutil.Amount
extraTxOut fn.Option[SweepOutput]
// outpointToTxIndex maps the outpoint of the inputs to their index in
// the sweep transaction.
outpointToTxIndex map[wire.OutPoint]int
}
// createSweepTx creates a sweeping tx based on the given inputs, change
@ -1229,6 +1239,7 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
// We start by adding all inputs that commit to an output. We do this
// since the input and output index must stay the same for the
// signatures to be valid.
outpointToTxIndex := make(map[wire.OutPoint]int)
for _, o := range inputs {
if o.RequiredTxOut() == nil {
continue
@ -1240,6 +1251,8 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
Sequence: o.BlocksToMaturity(),
})
sweepTx.AddTxOut(o.RequiredTxOut())
outpointToTxIndex[o.OutPoint()] = len(sweepTx.TxOut) - 1
}
// Sum up the value contained in the remaining inputs, and add them to
@ -1334,6 +1347,7 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
tx: sweepTx,
fee: txFee,
extraTxOut: fn.FlattenOption(extraTxOut),
outpointToTxIndex: outpointToTxIndex,
}, nil
}

View File

@ -93,5 +93,6 @@ type AuxSweeper interface {
// NotifyBroadcast is used to notify external callers of the broadcast
// of a sweep transaction, generated by the passed BumpRequest.
NotifyBroadcast(req *BumpRequest, tx *wire.MsgTx,
totalFees btcutil.Amount) error
totalFees btcutil.Amount,
outpointToTxIndex map[wire.OutPoint]int) error
}

View File

@ -352,7 +352,7 @@ func (m *MockAuxSweeper) ExtraBudgetForInputs(
// NotifyBroadcast is used to notify external callers of the broadcast
// of a sweep transaction, generated by the passed BumpRequest.
func (*MockAuxSweeper) NotifyBroadcast(_ *BumpRequest, _ *wire.MsgTx,
_ btcutil.Amount) error {
_ btcutil.Amount, _ map[wire.OutPoint]int) error {
return nil
}

View File

@ -373,7 +373,9 @@ func (b *BudgetInputSet) Budget() btcutil.Amount {
budget += input.params.Budget
}
return budget
// We'll also tack on the extra budget which will eventually be
// accounted for by the wallet txns when we're broadcasting.
return budget + b.extraBudget
}
// DeadlineHeight returns the deadline height of the set.