Skip to content

Commit 94bdfa5

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 bf3960c commit 94bdfa5

File tree

2 files changed

+226
-37
lines changed

2 files changed

+226
-37
lines changed

tapchannel/aux_sweeper.go

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

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

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

12241311
// Before we combine the proofs below, we'll be sure to update
1225-
// the transition proof to include the proper block+merkle
1226-
// proof information.
1312+
// the transition proof to include the proper block+merkle proof
1313+
// information.
12271314
blockHash, err := chainBridge.GetBlockHash(
12281315
ctxb, int64(scid.BlockHeight),
12291316
)
@@ -1813,6 +1900,7 @@ func newBlobWithWitnessInfo(i input.Input) blobWithWitnessInfo {
18131900
)
18141901
switch i.WitnessType() {
18151902
// This is the case when we're sweeping the HTLC output on our local
1903+
18161904
// commitment transaction via a second level HTLC.
18171905
//
18181906
// The final witness stack is:
@@ -1868,40 +1956,106 @@ func newBlobWithWitnessInfo(i input.Input) blobWithWitnessInfo {
18681956
}
18691957
}
18701958

1959+
// prepVpkts decodes the set of vPkts, supplementing them as needed to ensure
1960+
// all inputs can be swept properly.
1961+
func prepVpkts(bRes blobWithWitnessInfo,
1962+
secondLevel bool) (*vPktsWithInput, error) {
1963+
1964+
var res cmsg.ContractResolution
1965+
err := res.Decode(bytes.NewReader(bRes.resolutionBlob))
1966+
if err != nil {
1967+
return nil, err
1968+
}
1969+
1970+
// For each vPacket, if we have a preimage to insert, then we'll we'll
1971+
// update the witness to insert the preimage at the correct index.
1972+
var tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
1973+
pkts := res.Vpkts1()
1974+
if secondLevel {
1975+
pkts = res.Vpkts2()
1976+
tapSigDesc = res.SigDescs()
1977+
}
1978+
1979+
err = lfn.MapOptionZ(bRes.preimageInfo, func(p preimageDesc) error {
1980+
for _, pkt := range pkts {
1981+
newAsset := pkt.Outputs[0].Asset
1982+
1983+
prevWitness := newAsset.PrevWitnesses[0].TxWitness
1984+
prevWitness = slices.Insert(
1985+
prevWitness, p.witnessIndex,
1986+
p.preimage[:],
1987+
)
1988+
err := newAsset.UpdateTxWitness(0, prevWitness)
1989+
if err != nil {
1990+
return err
1991+
}
1992+
}
1993+
1994+
return nil
1995+
})
1996+
if err != nil {
1997+
return nil, err
1998+
}
1999+
2000+
return &vPktsWithInput{
2001+
vPkts: pkts,
2002+
btcInput: bRes.input,
2003+
tapSigDesc: tapSigDesc,
2004+
}, nil
2005+
}
2006+
18712007
// extractInputVPackets extracts the vPackets from the inputs passed in. If
18722008
// none of the inputs have any resolution blobs. Then an empty slice will be
18732009
// returned.
1874-
func extractInputVPackets(inputs []input.Input) lfn.Result[[]*tappsbt.VPacket] {
1875-
type returnType = []*tappsbt.VPacket
1876-
1877-
// Otherwise, we'll extract the set of resolution blobs from the inputs
2010+
func extractInputVPackets(inputs []input.Input) lfn.Result[sweepVpkts] {
2011+
// First, we'll extract the set of resolution blobs from the inputs
18782012
// passed in.
18792013
relevantInputs := fn.Filter(inputs, func(i input.Input) bool {
18802014
return i.ResolutionBlob().IsSome()
18812015
})
1882-
resolutionBlobs := fn.Map(relevantInputs, func(i input.Input) tlv.Blob {
1883-
// We already know this has a blob from the filter above.
1884-
return i.ResolutionBlob().UnwrapOr(nil)
1885-
})
1886-
1887-
// With our set of resolution inputs extracted, we'll now decode them
1888-
// in the vPackets we'll use to generate the output to addr.
1889-
vPkts, err := fn.FlatMapErr(
1890-
resolutionBlobs,
1891-
func(b tlv.Blob) ([]*tappsbt.VPacket, error) {
1892-
var res cmsg.ContractResolution
1893-
if err := res.Decode(bytes.NewReader(b)); err != nil {
1894-
return nil, err
1895-
}
2016+
resolutionInfo := fn.Map(
2017+
relevantInputs, newBlobWithWitnessInfo,
2018+
)
18962019

1897-
return res.Vpkts1(), nil
2020+
firstLevelSweeps := lfn.Filter(
2021+
func(info blobWithWitnessInfo) bool {
2022+
return !info.secondLevel
18982023
},
2024+
resolutionInfo,
18992025
)
1900-
if err != nil {
1901-
return lfn.Err[returnType](err)
2026+
secondLevelSweeps := lfn.Filter(
2027+
func(info blobWithWitnessInfo) bool {
2028+
return info.secondLevel
2029+
},
2030+
resolutionInfo,
2031+
)
2032+
2033+
// With our set of resolution inputs extracted, we'll now decode them in
2034+
// the vPackets we'll use to generate the output to addr.
2035+
var vPkts1 []vPktsWithInput
2036+
for _, bRes := range firstLevelSweeps {
2037+
vpkt, err := prepVpkts(bRes, false)
2038+
if err != nil {
2039+
return lfn.Err[sweepVpkts](err)
2040+
}
2041+
2042+
vPkts1 = append(vPkts1, *vpkt)
19022043
}
19032044

1904-
return lfn.Ok(vPkts)
2045+
var vPkts2 []vPktsWithInput
2046+
for _, bRes := range secondLevelSweeps {
2047+
vpkt, err := prepVpkts(bRes, true)
2048+
if err != nil {
2049+
return lfn.Err[sweepVpkts](err)
2050+
}
2051+
2052+
vPkts2 = append(vPkts2, *vpkt)
2053+
}
2054+
2055+
return lfn.Ok(sweepVpkts{
2056+
firstLevel: vPkts1,
2057+
secondLevel: vPkts2,
2058+
})
19052059
}
19062060

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

19262080
// Now that we know we have a relevant input set, extract all the
19272081
// vPackets from the inputs.
1928-
vPkts, err := extractInputVPackets(inputs).Unpack()
2082+
sPkts, err := extractInputVPackets(inputs).Unpack()
19292083
if err != nil {
19302084
return lfn.Err[returnType](err)
19312085
}
19322086

19332087
log.Infof("Generating anchor output for vpkts=%v",
1934-
limitSpewer.Sdump(vPkts))
2088+
limitSpewer.Sdump(sPkts))
2089+
2090+
// If this is a sweep from the local commitment transaction. Then we'll
2091+
// have both the first and second level sweeps. However for the first
2092+
// sweep, it's a broadcast of a pre-signed transaction, so we don't need
2093+
// an anchor output for those.
2094+
directPkts := sPkts.directSpendPkts()
2095+
2096+
// If there're no direct level vPkts, then we can just return a nil
2097+
// error as we don't have a real sweep output to create.
2098+
if len(directPkts) == 0 {
2099+
return lfn.Err[sweep.SweepOutput](nil)
2100+
}
19352101

19362102
// At this point, now that we're about to generate a new output, we'll
19372103
// need an internal key, so we can update all the vPkts.
@@ -1944,17 +2110,34 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19442110
if err != nil {
19452111
return lfn.Err[returnType](err)
19462112
}
1947-
for idx := range vPkts {
1948-
for _, vOut := range vPkts[idx].Outputs {
2113+
for idx := range directPkts {
2114+
for _, vOut := range directPkts[idx].Outputs {
19492115
vOut.SetAnchorInternalKey(
19502116
internalKey, a.cfg.ChainParams.HDCoinType,
19512117
)
19522118
}
19532119
}
19542120

2121+
// For any second level outputs we're sweeping, we'll need to sign for
2122+
// it, as now we know the txid of the sweeping transaction. We'll do
2123+
// this again when we register for the final broadcast, we we need to
2124+
// sign the right prevIDs.
2125+
for _, sweepSet := range sPkts.secondLevel {
2126+
for _, vPkt := range sweepSet.vPkts {
2127+
prevOut := sweepSet.btcInput.OutPoint()
2128+
for _, vIn := range vPkt.Inputs {
2129+
vIn.PrevID.OutPoint = prevOut
2130+
}
2131+
for _, vOut := range vPkt.Outputs {
2132+
//nolint:lll
2133+
vOut.Asset.PrevWitnesses[0].PrevID.OutPoint = prevOut
2134+
}
2135+
}
2136+
}
2137+
19552138
// Now that we have our set of resolutions, we'll make a new commitment
19562139
// out of all the vPackets contained.
1957-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2140+
outCommitments, err := tapsend.CreateOutputCommitments(directPkts)
19582141
if err != nil {
19592142
return lfn.Errf[returnType]("unable to create "+
19602143
"output commitments: %w", err)
@@ -2034,7 +2217,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20342217

20352218
// If we don't have any vPackets that had our resolution data in them,
20362219
// then we can exit early.
2037-
if len(vPkts) == 0 {
2220+
if len(vPkts.firstLevel) == 0 && len(vPkts.secondLevel) == 0 {
20382221
log.Infof("Sweep request had no vPkts, exiting")
20392222
return nil
20402223
}
@@ -2057,16 +2240,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20572240

20582241
// We'll also use the passed in context to set the anchor key again for
20592242
// all the vOuts.
2060-
for idx := range vPkts {
2061-
for _, vOut := range vPkts[idx].Outputs {
2243+
for idx := range vPkts.firstLevelPkts() {
2244+
for _, vOut := range vPkts.firstLevelPkts()[idx].Outputs {
20622245
vOut.SetAnchorInternalKey(
20632246
internalKey, a.cfg.ChainParams.HDCoinType,
20642247
)
20652248
}
20662249
}
20672250

20682251
// Now that we have our vPkts, we'll re-create the output commitments.
2069-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2252+
outCommitments, err := tapsend.CreateOutputCommitments(vPkts.allPkts())
20702253
if err != nil {
20712254
return fmt.Errorf("unable to create output "+
20722255
"commitments: %w", err)
@@ -2088,15 +2271,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20882271
//
20892272
// TODO(roasbeef): base off allocations? then can serialize, then
20902273
// re-use the logic
2091-
for idx := range vPkts {
2092-
vPkt := vPkts[idx]
2274+
allVpkts := vPkts.allPkts()
2275+
for idx := range allVpkts {
2276+
vPkt := allVpkts[idx]
20932277
for outIdx := range vPkt.Outputs {
20942278
exclusionCreator := sweepExclusionProofGen(
20952279
changeInternalKey,
20962280
)
20972281

20982282
proofSuffix, err := tapsend.CreateProofSuffixCustom(
2099-
sweepTx, vPkt, outCommitments, outIdx, vPkts,
2283+
sweepTx, vPkt, outCommitments, outIdx, allVpkts,
21002284
exclusionCreator,
21012285
)
21022286
if err != nil {
@@ -2116,7 +2300,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
21162300
// We pass false for the last arg as we already updated our suffix
21172301
// proofs here.
21182302
return shipChannelTxn(
2119-
a.cfg.TxSender, sweepTx, outCommitments, vPkts, int64(fee),
2303+
a.cfg.TxSender, sweepTx, outCommitments, allVpkts, int64(fee),
21202304
)
21212305
}
21222306

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)