Skip to content

Commit 8cb2add

Browse files
committed
tapchannel: update sweepContracts for HTLC sweeps
In this commit, we add some intermediate functions and types needed to properly handle HTLC sweeps. We now use the preimage info added in a prior commit to make sure that once we learn of the preimage (after vPkt creation, but before publish), we'll properly insert it in the correct location. In sweepContracts, when we go to create the new anchor change output, we'll make sure to omit any second level transactions, as they already have a destination location specified. Direct spends are spends directly from the commitment transaction. If we don't have any direct spends, then we don't need to make a change addr, as second level spends are already bound to an anchor output.
1 parent f616ae1 commit 8cb2add

File tree

2 files changed

+239
-37
lines changed

2 files changed

+239
-37
lines changed

tapchannel/aux_sweeper.go

Lines changed: 234 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,91 @@ func (a *AuxSweeper) signSweepVpackets(vPackets []*tappsbt.VPacket,
430430
return nil
431431
}
432432

433+
// vPktsWithInput couples a vPkt along with the input that contained it.
434+
type vPktsWithInput struct {
435+
// btcInput is the Bitcoin that the vPkt will the spending from (on the
436+
// TAP layer).
437+
btcInput input.Input
438+
439+
// vPkts is the set of vPacket that will be used to spend the input.
440+
vPkts []*tappsbt.VPacket
441+
442+
// tapSigDesc houses the information we'll need to re-sign the vPackets
443+
// above. Note that this is only set if this is a second level packet.
444+
tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
445+
}
446+
447+
// isPresigned returns true if the vPktsWithInput is presigned. This will be the
448+
// for an HTLC spent directly from our local commitment transaction.
449+
func (v vPktsWithInput) isPresigned() bool {
450+
witType := v.btcInput.WitnessType()
451+
switch witType {
452+
case input.TaprootHtlcAcceptedLocalSuccess:
453+
return true
454+
case input.TaprootHtlcLocalOfferedTimeout:
455+
return true
456+
default:
457+
return false
458+
}
459+
}
460+
461+
// sweepVpkts contains the set of vPkts needed for sweeping an output. Most
462+
// outputs will only have the first level specified. The second level is needed
463+
// for HTLC outputs on our local commitment transaction.
464+
type sweepVpkts struct {
465+
// firstLevel houses vPackets that are used to sweep outputs directly
466+
// from the commitment transaction.
467+
firstLevel []vPktsWithInput
468+
469+
// secondLevel is used to sweep outputs that are created by second level
470+
// HTLC transactions.
471+
secondLevel []vPktsWithInput
472+
}
473+
474+
// firstLevelPkts returns a slice of the first level pkts.
475+
func (s sweepVpkts) firstLevelPkts() []*tappsbt.VPacket {
476+
return fn.FlatMap(
477+
s.firstLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
478+
return v.vPkts
479+
},
480+
)
481+
}
482+
483+
// secondLevelPkts returns a slice of the second level pkts.
484+
func (s sweepVpkts) secondLevelPkts() []*tappsbt.VPacket {
485+
return fn.FlatMap(
486+
s.secondLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
487+
return v.vPkts
488+
},
489+
)
490+
}
491+
492+
// allPkts returns a slice of both the first and second level pkts.
493+
func (s sweepVpkts) allPkts() []*tappsbt.VPacket {
494+
return append(s.firstLevelPkts(), s.secondLevelPkts()...)
495+
}
496+
497+
// allVpktsWithInput returns a slice of all vPktsWithInput.
498+
func (s sweepVpkts) allVpktsWithInput() []vPktsWithInput {
499+
return append(s.firstLevel, s.secondLevel...)
500+
}
501+
502+
// directSpendPkts returns the slice of all vPkts that are a direct spend from
503+
// the commitment transaction. This excludes vPkts that are the pre-signed 2nd
504+
// level transaction variant.
505+
func (s sweepVpkts) directSpendPkts() []*tappsbt.VPacket {
506+
directSpends := lfn.Filter(func(vi vPktsWithInput) bool {
507+
return !vi.isPresigned()
508+
}, s.allVpktsWithInput())
509+
directPkts := fn.FlatMap(
510+
directSpends, func(v vPktsWithInput) []*tappsbt.VPacket {
511+
return v.vPkts
512+
},
513+
)
514+
515+
return directPkts
516+
}
517+
433518
// createAndSignSweepVpackets creates vPackets that sweep the funds from the
434519
// channel to the wallet, and then signs them as well.
435520
func (a *AuxSweeper) createAndSignSweepVpackets(
@@ -641,6 +726,8 @@ func remoteHtlcTimeoutSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
641726
return lfn.Err[tapscriptSweepDescs](err)
642727
}
643728

729+
// TODO(roasbeef): use GenTaprootHtlcScript instead?
730+
644731
// Now that we have the script tree, we'll make the control block needed
645732
// to spend it, but taking the revoked path.
646733
ctrlBlock, err := htlcScriptTree.CtrlBlockForPath(
@@ -1223,8 +1310,8 @@ func importOutputProofs(scid lnwire.ShortChannelID,
12231310
spew.Sdump(inputProofLocator))
12241311

12251312
// Before we combine the proofs below, we'll be sure to update
1226-
// the transition proof to include the proper block+merkle
1227-
// proof information.
1313+
// the transition proof to include the proper block+merkle proof
1314+
// information.
12281315
blockHash, err := chainBridge.GetBlockHash(
12291316
ctxb, int64(scid.BlockHeight),
12301317
)
@@ -1815,8 +1902,8 @@ func newBlobWithWitnessInfo(i input.Input) lfn.Result[blobWithWitnessInfo] {
18151902
secondLevel bool
18161903
)
18171904
switch i.WitnessType() {
1818-
18191905
// This is the case when we're sweeping the HTLC output on our local
1906+
18201907
// commitment transaction via a second level HTLC.
18211908
//
18221909
// The final witness stack is:
@@ -1873,40 +1960,121 @@ func newBlobWithWitnessInfo(i input.Input) lfn.Result[blobWithWitnessInfo] {
18731960
})
18741961
}
18751962

1963+
// prepVpkts decodes the set of vPkts, supplementing them as needed to ensure
1964+
// all inputs can be swept properly.
1965+
func prepVpkts(bRes lfn.Result[blobWithWitnessInfo],
1966+
secondLevel bool) (*vPktsWithInput, error) {
1967+
1968+
b, err := bRes.Unpack()
1969+
if err != nil {
1970+
return nil, err
1971+
}
1972+
1973+
var res cmsg.ContractResolution
1974+
err = res.Decode(bytes.NewReader(b.resolutionBlob))
1975+
if err != nil {
1976+
return nil, err
1977+
}
1978+
1979+
// For each vPacket, if we have a preimage to insert, then we'll we'll
1980+
// update the witness to insert the preimage at the correct index.
1981+
var tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
1982+
pkts := res.Vpkts1()
1983+
if secondLevel {
1984+
pkts = res.Vpkts2()
1985+
tapSigDesc = res.SigDescs()
1986+
}
1987+
1988+
err = lfn.MapOptionZ(b.preimageInfo, func(p preimageDesc) error {
1989+
for _, pkt := range pkts {
1990+
newAsset := pkt.Outputs[0].Asset
1991+
1992+
prevWitness := newAsset.PrevWitnesses[0].TxWitness
1993+
prevWitness = slices.Insert(
1994+
prevWitness, p.witnessIndex,
1995+
p.preimage[:],
1996+
)
1997+
err := newAsset.UpdateTxWitness(0, prevWitness)
1998+
if err != nil {
1999+
return err
2000+
}
2001+
}
2002+
2003+
return nil
2004+
})
2005+
if err != nil {
2006+
return nil, err
2007+
}
2008+
2009+
return &vPktsWithInput{
2010+
vPkts: pkts,
2011+
btcInput: b.input,
2012+
tapSigDesc: tapSigDesc,
2013+
}, nil
2014+
}
2015+
18762016
// extractInputVPackets extracts the vPackets from the inputs passed in. If
18772017
// none of the inputs have any resolution blobs. Then an empty slice will be
18782018
// returned.
1879-
func extractInputVPackets(inputs []input.Input) lfn.Result[[]*tappsbt.VPacket] {
1880-
type returnType = []*tappsbt.VPacket
1881-
1882-
// Otherwise, we'll extract the set of resolution blobs from the inputs
2019+
func extractInputVPackets(inputs []input.Input) lfn.Result[sweepVpkts] {
2020+
// First, we'll extract the set of resolution blobs from the inputs
18832021
// passed in.
18842022
relevantInputs := fn.Filter(inputs, func(i input.Input) bool {
18852023
return i.ResolutionBlob().IsSome()
18862024
})
1887-
resolutionBlobs := fn.Map(relevantInputs, func(i input.Input) tlv.Blob {
1888-
// We already know this has a blob from the filter above.
1889-
return i.ResolutionBlob().UnwrapOr(nil)
1890-
})
2025+
resolutionInfo := fn.Map(
2026+
relevantInputs, newBlobWithWitnessInfo,
2027+
)
18912028

1892-
// With our set of resolution inputs extracted, we'll now decode them
1893-
// in the vPackets we'll use to generate the output to addr.
1894-
vPkts, err := fn.FlatMapErr(
1895-
resolutionBlobs,
1896-
func(b tlv.Blob) ([]*tappsbt.VPacket, error) {
1897-
var res cmsg.ContractResolution
1898-
if err := res.Decode(bytes.NewReader(b)); err != nil {
1899-
return nil, err
1900-
}
2029+
firstLevelSweeps := lfn.Filter(
2030+
func(info lfn.Result[blobWithWitnessInfo]) bool {
2031+
var secondLevel bool
2032+
info.WhenResult(func(i blobWithWitnessInfo) {
2033+
secondLevel = i.secondLevel
2034+
})
19012035

1902-
return res.Vpkts1(), nil
2036+
return !secondLevel
19032037
},
2038+
resolutionInfo,
19042039
)
1905-
if err != nil {
1906-
return lfn.Err[returnType](err)
2040+
secondLevelSweeps := lfn.Filter(
2041+
func(info lfn.Result[blobWithWitnessInfo]) bool {
2042+
var secondLevel bool
2043+
info.WhenResult(func(i blobWithWitnessInfo) {
2044+
secondLevel = i.secondLevel
2045+
})
2046+
2047+
return secondLevel
2048+
},
2049+
resolutionInfo,
2050+
)
2051+
2052+
// With our set of resolution inputs extracted, we'll now decode them in
2053+
// the vPackets we'll use to generate the output to addr.
2054+
var vPkts1 []vPktsWithInput
2055+
for _, bRes := range firstLevelSweeps {
2056+
vpkt, err := prepVpkts(bRes, false)
2057+
if err != nil {
2058+
return lfn.Err[sweepVpkts](err)
2059+
}
2060+
2061+
vPkts1 = append(vPkts1, *vpkt)
19072062
}
19082063

1909-
return lfn.Ok(vPkts)
2064+
var vPkts2 []vPktsWithInput
2065+
for _, bRes := range secondLevelSweeps {
2066+
vpkt, err := prepVpkts(bRes, true)
2067+
if err != nil {
2068+
return lfn.Err[sweepVpkts](err)
2069+
}
2070+
2071+
vPkts2 = append(vPkts2, *vpkt)
2072+
}
2073+
2074+
return lfn.Ok(sweepVpkts{
2075+
firstLevel: vPkts1,
2076+
secondLevel: vPkts2,
2077+
})
19102078
}
19112079

19122080
// sweepContracts takes a set of inputs, and the change address we'd use to
@@ -1930,13 +2098,25 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19302098

19312099
// Now that we know we have a relevant input set, extract all the
19322100
// vPackets from the inputs.
1933-
vPkts, err := extractInputVPackets(inputs).Unpack()
2101+
sPkts, err := extractInputVPackets(inputs).Unpack()
19342102
if err != nil {
19352103
return lfn.Err[returnType](err)
19362104
}
19372105

19382106
log.Infof("Generating anchor output for vpkts=%v",
1939-
limitSpewer.Sdump(vPkts))
2107+
limitSpewer.Sdump(sPkts))
2108+
2109+
// If this is a sweep from the local commitment transaction. Then we'll
2110+
// have both the first and second level sweeps. However for the first
2111+
// sweep, it's a broadcast of a pre-signed transaction, so we don't need
2112+
// an anchor output for those.
2113+
directPkts := sPkts.directSpendPkts()
2114+
2115+
// If there're no direct level vPkts, then we can just return a nil
2116+
// error as we don't have a real sweep output to create.
2117+
if len(directPkts) == 0 {
2118+
return lfn.Err[sweep.SweepOutput](nil)
2119+
}
19402120

19412121
// At this point, now that we're about to generate a new output, we'll
19422122
// need an internal key, so we can update all the vPkts.
@@ -1949,17 +2129,33 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19492129
if err != nil {
19502130
return lfn.Err[returnType](err)
19512131
}
1952-
for idx := range vPkts {
1953-
for _, vOut := range vPkts[idx].Outputs {
2132+
for idx := range directPkts {
2133+
for _, vOut := range directPkts[idx].Outputs {
19542134
vOut.SetAnchorInternalKey(
19552135
internalKey, a.cfg.ChainParams.HDCoinType,
19562136
)
19572137
}
19582138
}
19592139

2140+
// For any second level outputs we're sweeping, we'll need to sign for
2141+
// it, as now we know the txid of the sweeping transaction. We'll do
2142+
// this again when we register for the final broadcast, we we need to
2143+
// sign the right prevIDs.
2144+
for _, sweepSet := range sPkts.secondLevel {
2145+
for _, vPkt := range sweepSet.vPkts {
2146+
prevOut := sweepSet.btcInput.OutPoint()
2147+
for _, vIn := range vPkt.Inputs {
2148+
vIn.PrevID.OutPoint = prevOut
2149+
}
2150+
for _, vOut := range vPkt.Outputs {
2151+
vOut.Asset.PrevWitnesses[0].PrevID.OutPoint = prevOut //nolint:lll
2152+
}
2153+
}
2154+
}
2155+
19602156
// Now that we have our set of resolutions, we'll make a new commitment
19612157
// out of all the vPackets contained.
1962-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2158+
outCommitments, err := tapsend.CreateOutputCommitments(directPkts)
19632159
if err != nil {
19642160
return lfn.Errf[returnType]("unable to create "+
19652161
"output commitments: %w", err)
@@ -2039,7 +2235,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20392235

20402236
// If we don't have any vPackets that had our resolution data in them,
20412237
// then we can exit early.
2042-
if len(vPkts) == 0 {
2238+
if len(vPkts.firstLevel) == 0 && len(vPkts.secondLevel) == 0 {
20432239
log.Infof("Sweep request had no vPkts, exiting")
20442240
return nil
20452241
}
@@ -2062,16 +2258,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20622258

20632259
// We'll also use the passed in context to set the anchor key again for
20642260
// all the vOuts.
2065-
for idx := range vPkts {
2066-
for _, vOut := range vPkts[idx].Outputs {
2261+
for idx := range vPkts.firstLevelPkts() {
2262+
for _, vOut := range vPkts.firstLevelPkts()[idx].Outputs {
20672263
vOut.SetAnchorInternalKey(
20682264
internalKey, a.cfg.ChainParams.HDCoinType,
20692265
)
20702266
}
20712267
}
20722268

20732269
// Now that we have our vPkts, we'll re-create the output commitments.
2074-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2270+
outCommitments, err := tapsend.CreateOutputCommitments(vPkts.allPkts())
20752271
if err != nil {
20762272
return fmt.Errorf("unable to create output "+
20772273
"commitments: %w", err)
@@ -2093,15 +2289,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20932289
//
20942290
// TODO(roasbeef): base off allocations? then can serialize, then
20952291
// re-use the logic
2096-
for idx := range vPkts {
2097-
vPkt := vPkts[idx]
2292+
allVpkts := vPkts.allPkts()
2293+
for idx := range allVpkts {
2294+
vPkt := allVpkts[idx]
20982295
for outIdx := range vPkt.Outputs {
20992296
exclusionCreator := sweepExclusionProofGen(
21002297
changeInternalKey,
21012298
)
21022299

21032300
proofSuffix, err := tapsend.CreateProofSuffixCustom(
2104-
sweepTx, vPkt, outCommitments, outIdx, vPkts,
2301+
sweepTx, vPkt, outCommitments, outIdx, allVpkts,
21052302
exclusionCreator,
21062303
)
21072304
if err != nil {
@@ -2121,7 +2318,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
21212318
// We pass false for the last arg as we already updated our suffix
21222319
// proofs here.
21232320
return shipChannelTxn(
2124-
a.cfg.TxSender, sweepTx, outCommitments, vPkts, int64(fee),
2321+
a.cfg.TxSender, sweepTx, outCommitments, allVpkts, int64(fee),
21252322
)
21262323
}
21272324

tapchannelmsg/records.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,11 @@ func (c *ContractResolution) Decode(r io.Reader) error {
22012201
return nil
22022202
}
22032203

2204+
// SigDescs returns the list of tapscriptSigDescs.
2205+
func (c *ContractResolution) SigDescs() lfn.Option[TapscriptSigDesc] {
2206+
return c.secondLevelSigDescs.ValOpt()
2207+
}
2208+
22042209
// Vpkts1 returns the set of first level Vpkts.
22052210
func (c *ContractResolution) Vpkts1() []*tappsbt.VPacket {
22062211
return c.firstLevelSweepVpkts.Val.Pkts

0 commit comments

Comments
 (0)