Skip to content

Commit a6fa937

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 e94ef6e commit a6fa937

File tree

1 file changed

+169
-23
lines changed

1 file changed

+169
-23
lines changed

tapchannel/aux_sweeper.go

Lines changed: 169 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,61 @@ func (a *AuxSweeper) signSweepVpackets(vPackets []*tappsbt.VPacket,
428428
return nil
429429
}
430430

431+
// applySignDescToVIn couples a vPkt along with the input that contained it.
432+
type vPktsWithInput struct {
433+
// btcInput is the Bitcoin that the vPkt will the spending from (on the
434+
// TAP layer).
435+
btcInput input.Input
436+
437+
// vPkts is the set of vPacket that will be used to spend the input.
438+
vPkts []*tappsbt.VPacket
439+
440+
// tapsigDesc houses the information we'll need to re-sign the vPackets
441+
// above. Note that this is only set if this is a second level packet.
442+
tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
443+
}
444+
445+
// sweepVpkts contains the set of vPkts needed for sweeping an output. Most
446+
// outputs will only have the first level specified. The second level is needed
447+
// for HTLC outputs on our local commitment transaction.
448+
type sweepVpkts struct {
449+
// firstLevel houses vPackets that are used to sweep outputs directly
450+
// from the commitment transaction.
451+
firstLevel []vPktsWithInput
452+
453+
// secondLevel is used to sweep outputs that are created by second level
454+
// HTLC transactions.
455+
secondLevel []vPktsWithInput
456+
}
457+
458+
// isEmpty returns true if the sweepVpkts is empty.
459+
func (s sweepVpkts) isEmpty() bool {
460+
return len(s.firstLevel) == 0 && len(s.secondLevel) == 0
461+
}
462+
463+
// firstLevelPkts returns a slice of the first level pkts.
464+
func (s sweepVpkts) firstLevelPkts() []*tappsbt.VPacket {
465+
return fn.FlatMap(
466+
s.firstLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
467+
return v.vPkts
468+
},
469+
)
470+
}
471+
472+
// secondLevelPkts returns a slice of the second level pkts.
473+
func (s sweepVpkts) secondLevelPkts() []*tappsbt.VPacket {
474+
return fn.FlatMap(
475+
s.secondLevel, func(v vPktsWithInput) []*tappsbt.VPacket {
476+
return v.vPkts
477+
},
478+
)
479+
}
480+
481+
// allPkts returns a slice of both the first and second level pkts.
482+
func (s sweepVpkts) allPkts() []*tappsbt.VPacket {
483+
return append(s.firstLevelPkts(), s.secondLevelPkts()...)
484+
}
485+
431486
// createAndSignSweepVpackets creates vPackets that sweep the funds from the
432487
// channel to the wallet, and then signs them as well.
433488
func (a *AuxSweeper) createAndSignSweepVpackets(
@@ -1837,40 +1892,119 @@ func newBlobWithWitnessInfo(i input.Input) lfn.Result[blobWithWitnessInfo] {
18371892
secondLevel: secondLevel,
18381893
})
18391894
}
1895+
1896+
// prepVpkts decodes the set of vPkts, supplementing them as needed to ensure
1897+
// all inputs can be swept properly.
1898+
func prepVpkts(bRes lfn.Result[blobWithWitnessInfo],
1899+
secondLevel bool) (*vPktsWithInput, error) {
1900+
1901+
b, err := bRes.Unpack()
1902+
if err != nil {
1903+
return nil, err
1904+
}
1905+
var res cmsg.ContractResolution
1906+
1907+
err = res.Decode(bytes.NewReader(b.resolutionBlob))
1908+
if err != nil {
1909+
return nil, err
1910+
}
1911+
1912+
// For each vPacket, if we have a preimage to insert, then we'll we'll
1913+
// update the witness to insert the preimage at the correct index.
1914+
var tapSigDesc lfn.Option[cmsg.TapscriptSigDesc]
1915+
pkts := res.Vpkts1()
1916+
if secondLevel {
1917+
pkts = res.Vpkts2()
1918+
}
1919+
1920+
b.preimageInfo.WhenSome(func(p preimageDesc) {
1921+
vIns := fn.FlatMap(
1922+
pkts,
1923+
func(vPkt *tappsbt.VPacket) []*tappsbt.VInput {
1924+
return vPkt.Inputs
1925+
},
1926+
)
1927+
1928+
for _, vIn := range vIns {
1929+
prevWitness := vIn.Asset().PrevWitnesses[0].TxWitness
1930+
vIn.Asset().PrevWitnesses[0].TxWitness = slices.Insert(
1931+
prevWitness, p.witnessIndex,
1932+
p.preimage[:],
1933+
)
1934+
}
1935+
})
1936+
1937+
return &vPktsWithInput{
1938+
vPkts: pkts,
1939+
btcInput: b.input,
1940+
tapSigDesc: tapSigDesc,
1941+
}, nil
18401942
}
18411943

18421944
// extractInputVPackets extracts the vPackets from the inputs passed in. If
18431945
// none of the inputs have any resolution blobs. Then an empty slice will be
18441946
// returned.
1845-
func extractInputVPackets(inputs []input.Input) lfn.Result[[]*tappsbt.VPacket] {
18461947
// Otherwise, we'll extract the set of resolution blobs from the inputs
18471948
// passed in.
18481949
relevantInputs := fn.Filter(inputs, func(i input.Input) bool {
18491950
return i.ResolutionBlob().IsSome()
18501951
})
1851-
resolutionBlobs := fn.Map(relevantInputs, func(i input.Input) tlv.Blob {
1852-
// We already know this has a blob from the filter above.
1853-
return i.ResolutionBlob().UnwrapOr(nil)
1854-
})
1952+
resolutionInfo := fn.Map(
1953+
relevantInputs,
1954+
func(i input.Input) lfn.Result[blobWithWitnessInfo] {
1955+
return newBlobWithWitnessInfo(i)
1956+
},
1957+
)
18551958

1856-
// With our set of resolution inputs extracted, we'll now decode them
1857-
// in the vPackets we'll use to generate the output to addr.
1858-
vPkts, err := fn.FlatMapErr(
1859-
resolutionBlobs,
1860-
func(b tlv.Blob) ([]*tappsbt.VPacket, error) {
1861-
var res cmsg.ContractResolution
1862-
if err := res.Decode(bytes.NewReader(b)); err != nil {
1863-
return nil, err
1864-
}
1959+
firstLevelSweeps := lfn.Filter(
1960+
func(info lfn.Result[blobWithWitnessInfo]) bool {
1961+
var secondLevel bool
1962+
info.WhenResult(func(i blobWithWitnessInfo) {
1963+
secondLevel = i.secondLevel
1964+
})
18651965

1866-
return res.VPkts(), nil
1966+
return !secondLevel
18671967
},
1968+
resolutionInfo,
18681969
)
1869-
if err != nil {
1870-
return lfn.Err[[]*tappsbt.VPacket](err)
1970+
secondLevelSweeps := lfn.Filter(
1971+
func(info lfn.Result[blobWithWitnessInfo]) bool {
1972+
var secondLevel bool
1973+
info.WhenResult(func(i blobWithWitnessInfo) {
1974+
secondLevel = i.secondLevel
1975+
})
1976+
1977+
return secondLevel
1978+
},
1979+
resolutionInfo,
1980+
)
1981+
1982+
// With our set of resolution inputs extracted, we'll now decode them in
1983+
// the vPackets we'll use to generate the output to addr.
1984+
var vPkts1 []vPktsWithInput
1985+
for _, bRes := range firstLevelSweeps {
1986+
vpkt, err := prepVpkts(bRes, false)
1987+
if err != nil {
1988+
return lfn.Err[sweepVpkts](err)
1989+
}
1990+
1991+
vPkts1 = append(vPkts1, *vpkt)
1992+
}
1993+
1994+
var vPkts2 []vPktsWithInput
1995+
for _, bRes := range secondLevelSweeps {
1996+
vpkt, err := prepVpkts(bRes, true)
1997+
if err != nil {
1998+
return lfn.Err[sweepVpkts](err)
1999+
}
2000+
2001+
vPkts2 = append(vPkts1, *vpkt)
18712002
}
18722003

1873-
return lfn.Ok(vPkts)
2004+
return lfn.Ok(sweepVpkts{
2005+
firstLevel: vPkts1,
2006+
secondLevel: vPkts2,
2007+
})
18742008
}
18752009

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

18932027
// Now that we know we have a relevant input set, extract all the
18942028
// vPackets from the inputs.
1895-
vPkts, err := extractInputVPackets(inputs).Unpack()
2029+
sPkts, err := extractInputVPackets(inputs).Unpack()
18962030
if err != nil {
18972031
return lfn.Err[sweep.SweepOutput](err)
18982032
}
18992033

19002034
log.Infof("Generating anchor output for vpkts=%v",
1901-
limitSpewer.Sdump(vPkts))
2035+
limitSpewer.Sdump(sPkts))
2036+
2037+
// Second level packets will already be anchored to the output assigned
2038+
// to it, so we only need to re-create the commitment for the first
2039+
// level outputs, which can be swept directly into the wallet.
2040+
firstLevelVpkts := sPkts.firstLevelPkts()
2041+
2042+
// If there're no first level vPkts, then we can just return a nil error
2043+
// as we don't have a real sweep output to create.
2044+
if len(firstLevelVpkts) == 0 {
2045+
return lfn.Err[sweep.SweepOutput](nil)
2046+
}
19022047

19032048
// At this point, now that we're about to generate a new output, we'll
19042049
// need an internal key, so we can update all the vPkts.
@@ -1911,8 +2056,8 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19112056
if err != nil {
19122057
return lfn.Err[sweep.SweepOutput](err)
19132058
}
1914-
for idx := range vPkts {
1915-
for _, vOut := range vPkts[idx].Outputs {
2059+
for idx := range firstLevelVpkts {
2060+
for _, vOut := range firstLevelVpkts[idx].Outputs {
19162061
vOut.SetAnchorInternalKey(
19172062
internalKey, a.cfg.ChainParams.HDCoinType,
19182063
)
@@ -1921,10 +2066,11 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input,
19212066

19222067
// Now that we have our set of resolutions, we'll make a new commitment
19232068
// out of all the vPackets contained.
1924-
outCommitments, err := tapsend.CreateOutputCommitments(vPkts)
2069+
outCommitments, err := tapsend.CreateOutputCommitments(firstLevelVpkts)
19252070
if err != nil {
19262071
return lfn.Errf[sweep.SweepOutput]("unable to create output "+
19272072
"commitments: %w", err)
2073+
19282074
}
19292075

19302076
// We should only have a single output commitment at this point.

0 commit comments

Comments
 (0)