Skip to content

Commit 3d49e1a

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 6949191 commit 3d49e1a

File tree

2 files changed

+201
-33
lines changed

2 files changed

+201
-33
lines changed

tapchannel/aux_sweeper.go

Lines changed: 196 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,61 @@ func (a *AuxSweeper) signSweepVpackets(vPackets []*tappsbt.VPacket,
432432
return nil
433433
}
434434

435+
// vPktsWithInput couples a vPkt along with the input that contained it.
436+
type vPktsWithInput struct {
437+
// btcInput is the Bitcoin that the vPkt will the spending from (on the
438+
// TAP layer).
439+
btcInput input.Input
440+
441+
// vPkts is the set of vPacket that will be used to spend the input.
442+
vPkts []*tappsbt.VPacket
443+
444+
// tapSigDesc houses the information we'll need to re-sign the vPackets
445+
// above. Note that this is only set if this is a second level packet.
446+
tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
447+
}
448+
449+
// sweepVpkts contains the set of vPkts needed for sweeping an output. Most
450+
// outputs will only have the first level specified. The second level is needed
451+
// for HTLC outputs on our local commitment transaction.
452+
type sweepVpkts struct {
453+
// firstLevel houses vPackets that are used to sweep outputs directly
454+
// from the commitment transaction.
455+
firstLevel []vPktsWithInput
456+
457+
// secondLevel is used to sweep outputs that are created by second level
458+
// HTLC transactions.
459+
secondLevel []vPktsWithInput
460+
}
461+
462+
// isEmpty returns true if the sweepVpkts is empty.
463+
func (s sweepVpkts) isEmpty() bool {
464+
return len(s.firstLevel) == 0 && len(s.secondLevel) == 0
465+
}
466+
467+
// firstLevelPkts returns a slice of the first level pkts.
468+
func (s sweepVpkts) firstLevelPkts() []*tappsbt.VPacket {
469+
return fn.FlatMap(
470+
s.firstLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
471+
return v.vPkts
472+
},
473+
)
474+
}
475+
476+
// secondLevelPkts returns a slice of the second level pkts.
477+
func (s sweepVpkts) secondLevelPkts() []*tappsbt.VPacket {
478+
return fn.FlatMap(
479+
s.secondLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
480+
return v.vPkts
481+
},
482+
)
483+
}
484+
485+
// allPkts returns a slice of both the first and second level pkts.
486+
func (s sweepVpkts) allPkts() []*tappsbt.VPacket {
487+
return append(s.firstLevelPkts(), s.secondLevelPkts()...)
488+
}
489+
435490
// createAndSignSweepVpackets creates vPackets that sweep the funds from the
436491
// channel to the wallet, and then signs them as well.
437492
func (a *AuxSweeper) createAndSignSweepVpackets(
@@ -643,6 +698,8 @@ func remoteHtlcTimeoutSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
643698
return lfn.Err[tapscriptSweepDescs](err)
644699
}
645700

701+
// TODO(roasbeef): use GenTaprootHtlcScript instead?
702+
646703
// Now that we have the script tree, we'll make the control block needed
647704
// to spend it, but taking the revoked path.
648705
ctrlBlock, err := htlcScriptTree.CtrlBlockForPath(
@@ -1813,8 +1870,8 @@ func newBlobWithWitnessInfo(i input.Input) lfn.Result[blobWithWitnessInfo] {
18131870
secondLevel bool
18141871
)
18151872
switch i.WitnessType() {
1816-
18171873
// This is the case when we're sweeping the HTLC output on our local
1874+
18181875
// commitment transaction via a second level HTLC.
18191876
//
18201877
// The final witness stack is:
@@ -1874,40 +1931,118 @@ func newBlobWithWitnessInfo(i input.Input) lfn.Result[blobWithWitnessInfo] {
18741931
})
18751932
}
18761933

1934+
// prepVpkts decodes the set of vPkts, supplementing them as needed to ensure
1935+
// all inputs can be swept properly.
1936+
func prepVpkts(bRes lfn.Result[blobWithWitnessInfo],
1937+
secondLevel bool) (*vPktsWithInput, error) {
1938+
1939+
b, err := bRes.Unpack()
1940+
if err != nil {
1941+
return nil, err
1942+
}
1943+
1944+
var res cmsg.ContractResolution
1945+
err = res.Decode(bytes.NewReader(b.resolutionBlob))
1946+
if err != nil {
1947+
return nil, err
1948+
}
1949+
1950+
// For each vPacket, if we have a preimage to insert, then we'll we'll
1951+
// update the witness to insert the preimage at the correct index.
1952+
var tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
1953+
pkts := res.Vpkts1()
1954+
if secondLevel {
1955+
pkts = res.Vpkts2()
1956+
tapSigDesc = res.SigDescs()
1957+
}
1958+
1959+
b.preimageInfo.WhenSome(func(p preimageDesc) {
1960+
for _, pkt := range pkts {
1961+
newAsset := pkt.Outputs[0].Asset
1962+
1963+
prevWitness := newAsset.PrevWitnesses[0].TxWitness
1964+
prevWitness = slices.Insert(
1965+
prevWitness, p.witnessIndex,
1966+
p.preimage[:],
1967+
)
1968+
newAsset.UpdateTxWitness(0, prevWitness)
1969+
}
1970+
})
1971+
1972+
return &vPktsWithInput{
1973+
vPkts: pkts,
1974+
btcInput: b.input,
1975+
tapSigDesc: tapSigDesc,
1976+
}, nil
1977+
}
1978+
18771979
// extractInputVPackets extracts the vPackets from the inputs passed in. If
18781980
// none of the inputs have any resolution blobs. Then an empty slice will be
18791981
// returned.
1880-
func extractInputVPackets(inputs []input.Input) lfn.Result[[]*tappsbt.VPacket] {
1881-
type returnType = []*tappsbt.VPacket
1982+
func extractInputVPackets(inputs []input.Input) lfn.Result[sweepVpkts] {
1983+
type returnType = sweepVpkts
18821984

18831985
// Otherwise, we'll extract the set of resolution blobs from the inputs
18841986
// passed in.
18851987
relevantInputs := fn.Filter(inputs, func(i input.Input) bool {
18861988
return i.ResolutionBlob().IsSome()
18871989
})
1888-
resolutionBlobs := fn.Map(relevantInputs, func(i input.Input) tlv.Blob {
1889-
// We already know this has a blob from the filter above.
1890-
return i.ResolutionBlob().UnwrapOr(nil)
1891-
})
1990+
resolutionInfo := fn.Map(
1991+
relevantInputs,
1992+
func(i input.Input) lfn.Result[blobWithWitnessInfo] {
1993+
return newBlobWithWitnessInfo(i)
1994+
},
1995+
)
18921996

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

1903-
return res.Vpkts1(), nil
2004+
return !secondLevel
19042005
},
2006+
resolutionInfo,
19052007
)
1906-
if err != nil {
1907-
return lfn.Err[returnType](err)
2008+
secondLevelSweeps := lfn.Filter(
2009+
func(info lfn.Result[blobWithWitnessInfo]) bool {
2010+
var secondLevel bool
2011+
info.WhenResult(func(i blobWithWitnessInfo) {
2012+
secondLevel = i.secondLevel
2013+
})
2014+
2015+
return secondLevel
2016+
},
2017+
resolutionInfo,
2018+
)
2019+
2020+
// With our set of resolution inputs extracted, we'll now decode them in
2021+
// the vPackets we'll use to generate the output to addr.
2022+
var vPkts1 []vPktsWithInput
2023+
for _, bRes := range firstLevelSweeps {
2024+
vpkt, err := prepVpkts(bRes, false)
2025+
if err != nil {
2026+
return lfn.Err[sweepVpkts](err)
2027+
}
2028+
2029+
vPkts1 = append(vPkts1, *vpkt)
19082030
}
19092031

1910-
return lfn.Ok(vPkts)
2032+
var vPkts2 []vPktsWithInput
2033+
for _, bRes := range secondLevelSweeps {
2034+
vpkt, err := prepVpkts(bRes, true)
2035+
if err != nil {
2036+
return lfn.Err[sweepVpkts](err)
2037+
}
2038+
2039+
vPkts2 = append(vPkts2, *vpkt)
2040+
}
2041+
2042+
return lfn.Ok(sweepVpkts{
2043+
firstLevel: vPkts1,
2044+
secondLevel: vPkts2,
2045+
})
19112046
}
19122047

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

19322067
// Now that we know we have a relevant input set, extract all the
19332068
// vPackets from the inputs.
1934-
vPkts, err := extractInputVPackets(inputs).Unpack()
2069+
sPkts, err := extractInputVPackets(inputs).Unpack()
19352070
if err != nil {
19362071
return lfn.Err[returnType](err)
19372072
}
19382073

19392074
log.Infof("Generating anchor output for vpkts=%v",
1940-
limitSpewer.Sdump(vPkts))
2075+
limitSpewer.Sdump(sPkts))
2076+
2077+
// If this is a sweep from the local commitment transaction. Then we'll
2078+
// have both the first and second level sweeps. However for the first
2079+
// sweep, it's a broadcast of a pre-signed transaction, so we don't need
2080+
// an anchor output for those.
2081+
directPkts := sPkts.directSpendPkts()
2082+
2083+
// If there're no direct level vPkts, then we can just return a nil
2084+
// error as we don't have a real sweep output to create.
2085+
if len(directPkts) == 0 {
2086+
return lfn.Err[sweep.SweepOutput](nil)
2087+
}
19412088

19422089
// At this point, now that we're about to generate a new output, we'll
19432090
// need an internal key, so we can update all the vPkts.
@@ -1950,17 +2097,32 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19502097
if err != nil {
19512098
return lfn.Err[returnType](err)
19522099
}
1953-
for idx := range vPkts {
1954-
for _, vOut := range vPkts[idx].Outputs {
2100+
for idx := range directPkts {
2101+
for _, vOut := range directPkts[idx].Outputs {
19552102
vOut.SetAnchorInternalKey(
19562103
internalKey, a.cfg.ChainParams.HDCoinType,
19572104
)
19582105
}
19592106
}
19602107

2108+
// For any second level outputs we're sweeping, we'll need to sign for
2109+
// it, as now we know the txid of the sweeping transaction. We'll do
2110+
// this again when we register for the final broadcast, we we need to
2111+
// sign the right prevIDs.
2112+
for _, sweepSet := range sPkts.secondLevel {
2113+
for _, vPkt := range sweepSet.vPkts {
2114+
for _, vIn := range vPkt.Inputs {
2115+
vIn.PrevID.OutPoint = sweepSet.btcInput.OutPoint()
2116+
}
2117+
for _, vOut := range vPkt.Outputs {
2118+
vOut.Asset.PrevWitnesses[0].PrevID.OutPoint = sweepSet.btcInput.OutPoint()
2119+
}
2120+
}
2121+
}
2122+
19612123
// Now that we have our set of resolutions, we'll make a new commitment
19622124
// out of all the vPackets contained.
1963-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2125+
outCommitments, err := tapsend.CreateOutputCommitments(directPkts)
19642126
if err != nil {
19652127
return lfn.Errf[returnType]("unable to create "+
19662128
"output commitments: %w", err)
@@ -2040,7 +2202,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20402202

20412203
// If we don't have any vPackets that had our resolution data in them,
20422204
// then we can exit early.
2043-
if len(vPkts) == 0 {
2205+
if len(vPkts.firstLevel) == 0 && len(vPkts.secondLevel) == 0 {
20442206
log.Infof("Sweep request had no vPkts, exiting")
20452207
return nil
20462208
}
@@ -2063,16 +2225,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20632225

20642226
// We'll also use the passed in context to set the anchor key again for
20652227
// all the vOuts.
2066-
for idx := range vPkts {
2067-
for _, vOut := range vPkts[idx].Outputs {
2228+
for idx := range vPkts.firstLevelPkts() {
2229+
for _, vOut := range vPkts.firstLevelPkts()[idx].Outputs {
20682230
vOut.SetAnchorInternalKey(
20692231
internalKey, a.cfg.ChainParams.HDCoinType,
20702232
)
20712233
}
20722234
}
20732235

20742236
// Now that we have our vPkts, we'll re-create the output commitments.
2075-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2237+
outCommitments, err := tapsend.CreateOutputCommitments(vPkts.allPkts())
20762238
if err != nil {
20772239
return fmt.Errorf("unable to create output "+
20782240
"commitments: %w", err)
@@ -2094,15 +2256,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20942256
//
20952257
// TODO(roasbeef): base off allocations? then can serialize, then
20962258
// re-use the logic
2097-
for idx := range vPkts {
2098-
vPkt := vPkts[idx]
2259+
allVpkts := vPkts.allPkts()
2260+
for idx := range allVpkts {
2261+
vPkt := allVpkts[idx]
20992262
for outIdx := range vPkt.Outputs {
21002263
exclusionCreator := sweepExclusionProofGen(
21012264
changeInternalKey,
21022265
)
21032266

21042267
proofSuffix, err := tapsend.CreateProofSuffixCustom(
2105-
sweepTx, vPkt, outCommitments, outIdx, vPkts,
2268+
sweepTx, vPkt, outCommitments, outIdx, allVpkts,
21062269
exclusionCreator,
21072270
)
21082271
if err != nil {
@@ -2122,7 +2285,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
21222285
// We pass false for the last arg as we already updated our suffix
21232286
// proofs here.
21242287
return shipChannelTxn(
2125-
a.cfg.TxSender, sweepTx, outCommitments, vPkts, int64(fee),
2288+
a.cfg.TxSender, sweepTx, outCommitments, allVpkts, int64(fee),
21262289
)
21272290
}
21282291

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)