mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
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:
commit
95b248a1ef
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -436,6 +436,7 @@ func (c *chainWatcher) handleUnknownLocalState(
|
||||
return s.FetchLeavesFromCommit(
|
||||
lnwallet.NewAuxChanState(c.cfg.chanState),
|
||||
c.cfg.chanState.LocalCommitment, *commitKeyRing,
|
||||
lntypes.Local,
|
||||
)
|
||||
},
|
||||
).Unpack()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -22,10 +22,6 @@
|
||||
* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/8857) to correctly
|
||||
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
|
||||
@ -161,11 +157,7 @@ The underlying functionality between those two options remain the same.
|
||||
## Breaking Changes
|
||||
## Performance Improvements
|
||||
|
||||
* 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.
|
||||
* Log rotation can now use ZSTD
|
||||
|
||||
# Technical and Architectural Updates
|
||||
## BOLT Spec Updates
|
||||
|
1
go.mod
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
@ -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[:],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
Expiry: htlc.RefundTimeout,
|
||||
ClaimOutpoint: op,
|
||||
SweepSignDesc: signDesc,
|
||||
CsvDelay: csvDelay,
|
||||
ResolutionBlob: resolutionBlob,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -7253,31 +7305,78 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
|
||||
)
|
||||
|
||||
// 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,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcSweepScript.PkScript(),
|
||||
Value: int64(secondLevelOutputAmt),
|
||||
},
|
||||
HashType: sweepSigHash(chanType),
|
||||
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
|
||||
htlcSweepScript.PkScript(),
|
||||
int64(secondLevelOutputAmt),
|
||||
),
|
||||
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: input.SignDescriptor{
|
||||
KeyDesc: localChanCfg.DelayBasePoint,
|
||||
SingleTweak: localDelayTweak,
|
||||
WitnessScript: htlcSweepWitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcSweepScript.PkScript(),
|
||||
Value: int64(secondLevelOutputAmt),
|
||||
},
|
||||
HashType: sweepSigHash(chanType),
|
||||
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
|
||||
htlcSweepScript.PkScript(),
|
||||
int64(secondLevelOutputAmt),
|
||||
),
|
||||
SignMethod: signMethod,
|
||||
ControlBlock: ctrlBlock,
|
||||
},
|
||||
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),
|
||||
ClaimOutpoint: op,
|
||||
SweepSignDesc: signDesc,
|
||||
CsvDelay: htlcCsvDelay,
|
||||
ResolutionBlob: resolutionBlob,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -7510,30 +7647,76 @@ func newIncomingHtlcResolution(signer input.Signer,
|
||||
localDelayTweak := input.SingleTweakBytes(
|
||||
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
|
||||
)
|
||||
|
||||
// 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,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcSweepScript.PkScript(),
|
||||
Value: int64(secondLevelOutputAmt),
|
||||
},
|
||||
HashType: sweepSigHash(chanType),
|
||||
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
|
||||
htlcSweepScript.PkScript(),
|
||||
int64(secondLevelOutputAmt),
|
||||
),
|
||||
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: input.SignDescriptor{
|
||||
KeyDesc: localChanCfg.DelayBasePoint,
|
||||
SingleTweak: localDelayTweak,
|
||||
WitnessScript: htlcSweepWitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcSweepScript.PkScript(),
|
||||
Value: int64(secondLevelOutputAmt),
|
||||
},
|
||||
HashType: sweepSigHash(chanType),
|
||||
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
|
||||
htlcSweepScript.PkScript(),
|
||||
int64(secondLevelOutputAmt),
|
||||
),
|
||||
SignMethod: signMethod,
|
||||
ControlBlock: ctrlBlock,
|
||||
},
|
||||
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)
|
||||
|
@ -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()
|
||||
|
@ -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
51
macaroons/fuzz_test.go
Normal 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,
|
||||
)
|
||||
})
|
||||
}
|
@ -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
|
||||
@ -1042,10 +1047,11 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64,
|
||||
// The tx has been created without any errors, we now register a new
|
||||
// record by overwriting the same requestID.
|
||||
t.records.Store(requestID, &monitorRecord{
|
||||
tx: sweepCtx.tx,
|
||||
req: r.req,
|
||||
feeFunction: r.feeFunction,
|
||||
fee: sweepCtx.fee,
|
||||
tx: sweepCtx.tx,
|
||||
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
|
||||
@ -1331,9 +1344,10 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
|
||||
)(changeOutputsOpt)
|
||||
|
||||
return &sweepTxCtx{
|
||||
tx: sweepTx,
|
||||
fee: txFee,
|
||||
extraTxOut: fn.FlattenOption(extraTxOut),
|
||||
tx: sweepTx,
|
||||
fee: txFee,
|
||||
extraTxOut: fn.FlattenOption(extraTxOut),
|
||||
outpointToTxIndex: outpointToTxIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user