Skip to content

Commit 7310bb3

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.
1 parent 5d83d0e commit 7310bb3

File tree

1 file changed

+180
-32
lines changed

1 file changed

+180
-32
lines changed

tapchannel/aux_sweeper.go

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

433+
// applySignDescToVIn 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+
// sweepVpkts contains the set of vPkts needed for sweeping an output. Most
448+
// outputs will only have the first level specified. The second level is needed
449+
// for HTLC outputs on our local commitment transaction.
450+
type sweepVpkts struct {
451+
// firstLevel houses vPackets that are used to sweep outputs directly
452+
// from the commitment transaction.
453+
firstLevel []vPktsWithInput
454+
455+
// secondLevel is used to sweep outputs that are created by second level
456+
// HTLC transactions.
457+
secondLevel []vPktsWithInput
458+
}
459+
460+
// isEmpty returns true if the sweepVpkts is empty.
461+
func (s sweepVpkts) isEmpty() bool {
462+
return len(s.firstLevel) == 0 && len(s.secondLevel) == 0
463+
}
464+
465+
// firstLevelPkts returns a slice of the first level pkts.
466+
func (s sweepVpkts) firstLevelPkts() []*tappsbt.VPacket {
467+
return fn.FlatMap(
468+
s.firstLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
469+
return v.vPkts
470+
},
471+
)
472+
}
473+
474+
// secondLevelPkts returns a slice of the second level pkts.
475+
func (s sweepVpkts) secondLevelPkts() []*tappsbt.VPacket {
476+
return fn.FlatMap(
477+
s.secondLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
478+
return v.vPkts
479+
},
480+
)
481+
}
482+
483+
// allPkts returns a slice of both the first and second level pkts.
484+
func (s sweepVpkts) allPkts() []*tappsbt.VPacket {
485+
return append(s.firstLevelPkts(), s.secondLevelPkts()...)
486+
}
487+
433488
// createAndSignSweepVpackets creates vPackets that sweep the funds from the
434489
// channel to the wallet, and then signs them as well.
435490
func (a *AuxSweeper) createAndSignSweepVpackets(
@@ -1844,40 +1899,121 @@ func newBlobWithWitnessInfo(i input.Input) lfn.Result[blobWithWitnessInfo] {
18441899
})
18451900
}
18461901

1902+
// prepVpkts decodes the set of vPkts, supplementing them as needed to ensure
1903+
// all inputs can be swept properly.
1904+
func prepVpkts(bRes lfn.Result[blobWithWitnessInfo],
1905+
secondLevel bool) (*vPktsWithInput, error) {
1906+
1907+
b, err := bRes.Unpack()
1908+
if err != nil {
1909+
return nil, err
1910+
}
1911+
var res cmsg.ContractResolution
1912+
1913+
err = res.Decode(bytes.NewReader(b.resolutionBlob))
1914+
if err != nil {
1915+
return nil, err
1916+
}
1917+
1918+
// For each vPacket, if we have a preimage to insert, then we'll we'll
1919+
// update the witness to insert the preimage at the correct index.
1920+
var tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
1921+
pkts := res.Vpkts1()
1922+
if secondLevel {
1923+
pkts = res.Vpkts2()
1924+
}
1925+
1926+
b.preimageInfo.WhenSome(func(p preimageDesc) {
1927+
vIns := fn.FlatMap(
1928+
pkts,
1929+
func(vPkt *tappsbt.VPacket) []*tappsbt.VInput {
1930+
return vPkt.Inputs
1931+
},
1932+
)
1933+
1934+
for _, vIn := range vIns {
1935+
prevWitness := vIn.Asset().PrevWitnesses[0].TxWitness
1936+
vIn.Asset().PrevWitnesses[0].TxWitness = slices.Insert(
1937+
prevWitness, p.witnessIndex,
1938+
p.preimage[:],
1939+
)
1940+
}
1941+
})
1942+
1943+
return &vPktsWithInput{
1944+
vPkts: pkts,
1945+
btcInput: b.input,
1946+
tapSigDesc: tapSigDesc,
1947+
}, nil
1948+
}
1949+
18471950
// extractInputVPackets extracts the vPackets from the inputs passed in. If
18481951
// none of the inputs have any resolution blobs. Then an empty slice will be
18491952
// returned.
1850-
func extractInputVPackets(inputs []input.Input) lfn.Result[[]*tappsbt.VPacket] {
1851-
type returnType = []*tappsbt.VPacket
1953+
func extractInputVPackets(inputs []input.Input) lfn.Result[sweepVpkts] {
1954+
type returnType = sweepVpkts
18521955

18531956
// Otherwise, we'll extract the set of resolution blobs from the inputs
18541957
// passed in.
18551958
relevantInputs := fn.Filter(inputs, func(i input.Input) bool {
18561959
return i.ResolutionBlob().IsSome()
18571960
})
1858-
resolutionBlobs := fn.Map(relevantInputs, func(i input.Input) tlv.Blob {
1859-
// We already know this has a blob from the filter above.
1860-
return i.ResolutionBlob().UnwrapOr(nil)
1861-
})
1961+
resolutionInfo := fn.Map(
1962+
relevantInputs,
1963+
func(i input.Input) lfn.Result[blobWithWitnessInfo] {
1964+
return newBlobWithWitnessInfo(i)
1965+
},
1966+
)
18621967

1863-
// With our set of resolution inputs extracted, we'll now decode them
1864-
// in the vPackets we'll use to generate the output to addr.
1865-
vPkts, err := fn.FlatMapErr(
1866-
resolutionBlobs,
1867-
func(b tlv.Blob) ([]*tappsbt.VPacket, error) {
1868-
var res cmsg.ContractResolution
1869-
if err := res.Decode(bytes.NewReader(b)); err != nil {
1870-
return nil, err
1871-
}
1968+
firstLevelSweeps := lfn.Filter(
1969+
func(info lfn.Result[blobWithWitnessInfo]) bool {
1970+
var secondLevel bool
1971+
info.WhenResult(func(i blobWithWitnessInfo) {
1972+
secondLevel = i.secondLevel
1973+
})
18721974

1873-
return res.Vpkts1(), nil
1975+
return !secondLevel
18741976
},
1977+
resolutionInfo,
18751978
)
1876-
if err != nil {
1877-
return lfn.Err[returnType](err)
1979+
secondLevelSweeps := lfn.Filter(
1980+
func(info lfn.Result[blobWithWitnessInfo]) bool {
1981+
var secondLevel bool
1982+
info.WhenResult(func(i blobWithWitnessInfo) {
1983+
secondLevel = i.secondLevel
1984+
})
1985+
1986+
return secondLevel
1987+
},
1988+
resolutionInfo,
1989+
)
1990+
1991+
// With our set of resolution inputs extracted, we'll now decode them in
1992+
// the vPackets we'll use to generate the output to addr.
1993+
var vPkts1 []vPktsWithInput
1994+
for _, bRes := range firstLevelSweeps {
1995+
vpkt, err := prepVpkts(bRes, false)
1996+
if err != nil {
1997+
return lfn.Err[sweepVpkts](err)
1998+
}
1999+
2000+
vPkts1 = append(vPkts1, *vpkt)
2001+
}
2002+
2003+
var vPkts2 []vPktsWithInput
2004+
for _, bRes := range secondLevelSweeps {
2005+
vpkt, err := prepVpkts(bRes, true)
2006+
if err != nil {
2007+
return lfn.Err[sweepVpkts](err)
2008+
}
2009+
2010+
vPkts2 = append(vPkts1, *vpkt)
18782011
}
18792012

1880-
return lfn.Ok(vPkts)
2013+
return lfn.Ok(sweepVpkts{
2014+
firstLevel: vPkts1,
2015+
secondLevel: vPkts2,
2016+
})
18812017
}
18822018

18832019
// sweepContracts takes a set of inputs, and the change address we'd use to
@@ -1901,13 +2037,24 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19012037

19022038
// Now that we know we have a relevant input set, extract all the
19032039
// vPackets from the inputs.
1904-
vPkts, err := extractInputVPackets(inputs).Unpack()
2040+
sPkts, err := extractInputVPackets(inputs).Unpack()
19052041
if err != nil {
19062042
return lfn.Err[returnType](err)
19072043
}
19082044

19092045
log.Infof("Generating anchor output for vpkts=%v",
1910-
limitSpewer.Sdump(vPkts))
2046+
limitSpewer.Sdump(sPkts))
2047+
2048+
// Second level packets will already be anchored to the output assigned
2049+
// to it, so we only need to re-create the commitment for the first
2050+
// level outputs, which can be swept directly into the wallet.
2051+
firstLevelVpkts := sPkts.firstLevelPkts()
2052+
2053+
// If there're no first level vPkts, then we can just return a nil error
2054+
// as we don't have a real sweep output to create.
2055+
if len(firstLevelVpkts) == 0 {
2056+
return lfn.Err[sweep.SweepOutput](nil)
2057+
}
19112058

19122059
// At this point, now that we're about to generate a new output, we'll
19132060
// need an internal key, so we can update all the vPkts.
@@ -1920,8 +2067,8 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19202067
if err != nil {
19212068
return lfn.Err[returnType](err)
19222069
}
1923-
for idx := range vPkts {
1924-
for _, vOut := range vPkts[idx].Outputs {
2070+
for idx := range firstLevelVpkts {
2071+
for _, vOut := range firstLevelVpkts[idx].Outputs {
19252072
vOut.SetAnchorInternalKey(
19262073
internalKey, a.cfg.ChainParams.HDCoinType,
19272074
)
@@ -1930,7 +2077,7 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19302077

19312078
// Now that we have our set of resolutions, we'll make a new commitment
19322079
// out of all the vPackets contained.
1933-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2080+
outCommitments, err := tapsend.CreateOutputCommitments(firstLevelVpkts)
19342081
if err != nil {
19352082
return lfn.Errf[returnType]("unable to create "+
19362083
"output commitments: %w", err)
@@ -2010,7 +2157,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20102157

20112158
// If we don't have any vPackets that had our resolution data in them,
20122159
// then we can exit early.
2013-
if len(vPkts) == 0 {
2160+
if len(vPkts.firstLevel) == 0 && len(vPkts.secondLevel) == 0 {
20142161
log.Infof("Sweep request had no vPkts, exiting")
20152162
return nil
20162163
}
@@ -2033,16 +2180,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20332180

20342181
// We'll also use the passed in context to set the anchor key again for
20352182
// all the vOuts.
2036-
for idx := range vPkts {
2037-
for _, vOut := range vPkts[idx].Outputs {
2183+
for idx := range vPkts.firstLevelPkts() {
2184+
for _, vOut := range vPkts.firstLevelPkts()[idx].Outputs {
20382185
vOut.SetAnchorInternalKey(
20392186
internalKey, a.cfg.ChainParams.HDCoinType,
20402187
)
20412188
}
20422189
}
20432190

20442191
// Now that we have our vPkts, we'll re-create the output commitments.
2045-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2192+
outCommitments, err := tapsend.CreateOutputCommitments(vPkts.allPkts())
20462193
if err != nil {
20472194
return fmt.Errorf("unable to create output "+
20482195
"commitments: %w", err)
@@ -2064,15 +2211,16 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20642211
//
20652212
// TODO(roasbeef): base off allocations? then can serialize, then
20662213
// re-use the logic
2067-
for idx := range vPkts {
2068-
vPkt := vPkts[idx]
2214+
allVpkts := vPkts.allPkts()
2215+
for idx := range allVpkts {
2216+
vPkt := allVpkts[idx]
20692217
for outIdx := range vPkt.Outputs {
20702218
exclusionCreator := sweepExclusionProofGen(
20712219
changeInternalKey,
20722220
)
20732221

20742222
proofSuffix, err := tapsend.CreateProofSuffixCustom(
2075-
sweepTx, vPkt, outCommitments, outIdx, vPkts,
2223+
sweepTx, vPkt, outCommitments, outIdx, allVpkts,
20762224
exclusionCreator,
20772225
)
20782226
if err != nil {
@@ -2092,7 +2240,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest,
20922240
// We pass false for the last arg as we already updated our suffix
20932241
// proofs here.
20942242
return shipChannelTxn(
2095-
a.cfg.TxSender, sweepTx, outCommitments, vPkts, int64(fee),
2243+
a.cfg.TxSender, sweepTx, outCommitments, allVpkts, int64(fee),
20962244
)
20972245
}
20982246

0 commit comments

Comments
 (0)