Skip to content

Commit b0fe25b

Browse files
committed
tapchannelmsg: expand fields of ContractResolution for 2nd level sweeps
In this commit, we expand the fields of the `ContractResolution` struct to account for second level sweeps. When we have an HTLC that needs to go to the second level, we'll need to create 2 vPkts: one for a direct spend from the commitment txns with the second level txn, and the other for the spend from the sweeping transaction. We can construct that transaction early, but can only sign for it once we know the sweeping transaction. Therefore, we need to keep some extra data for a given resolution, namely the tapTweak and the control block.
1 parent f0fdb9d commit b0fe25b

File tree

3 files changed

+217
-28
lines changed

3 files changed

+217
-28
lines changed

tapchannel/aux_sweeper.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,7 +1226,9 @@ func (a *AuxSweeper) resolveContract(
12261226
// it into a resolution blob to return.
12271227
return lfn.AndThen(
12281228
sPkts, func(vPkts []*tappsbt.VPacket) lfn.Result[tlv.Blob] {
1229-
res := cmsg.NewContractResolution(vPkts)
1229+
res := cmsg.NewContractResolution(
1230+
vPkts, nil, lfn.None[cmsg.TapscriptSigDesc](),
1231+
)
12301232

12311233
var b bytes.Buffer
12321234
if err := res.Encode(&b); err != nil {
@@ -1264,7 +1266,7 @@ func extractInputVPackets(inputs []input.Input) lfn.Result[[]*tappsbt.VPacket] {
12641266
return nil, err
12651267
}
12661268

1267-
return res.VPkts(), nil
1269+
return res.Vpkts1(), nil
12681270
},
12691271
)
12701272
if err != nil {

tapchannelmsg/records.go

Lines changed: 171 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,30 +1994,158 @@ func dVpktList(r io.Reader, val interface{}, buf *[8]byte, _ uint64) error {
19941994
return tlv.NewTypeForEncodingErr(val, "*VpktList")
19951995
}
19961996

1997+
// TapscriptSigDesc contains the information needed to re-sign for a given set
1998+
// of vPkts. For normal tapscript outputs, this is the taptweak and also the
1999+
// serialized control block. These are needed for second level HTLC outputs, as
2000+
// we can't sign the vPkts until we know the sweeping transaction.
2001+
type TapscriptSigDesc struct {
2002+
TapTweak tlv.RecordT[tlv.TlvType0, []byte]
2003+
2004+
CtrlBlock tlv.RecordT[tlv.TlvType1, []byte]
2005+
}
2006+
2007+
// NewTapscriptSigDesc creates a new tapscriptSigDesc with the given tap tweak
2008+
// and ctrlBlock.
2009+
func NewTapscriptSigDesc(tapTweak, ctrlBlock []byte) TapscriptSigDesc {
2010+
return TapscriptSigDesc{
2011+
TapTweak: tlv.NewPrimitiveRecord[tlv.TlvType0](tapTweak),
2012+
CtrlBlock: tlv.NewPrimitiveRecord[tlv.TlvType1](ctrlBlock),
2013+
}
2014+
}
2015+
2016+
// Encode attempts to encode the target tapscriptSigDesc into the passed
2017+
// io.Writer.
2018+
func (t *TapscriptSigDesc) Encode(w io.Writer) error {
2019+
tlvStream, err := tlv.NewStream(
2020+
t.TapTweak.Record(), t.CtrlBlock.Record(),
2021+
)
2022+
if err != nil {
2023+
return err
2024+
}
2025+
2026+
return tlvStream.Encode(w)
2027+
}
2028+
2029+
// Decode attempts to decode the target tapscriptSigDesc from the passed
2030+
// io.Reader.
2031+
func (t *TapscriptSigDesc) Decode(r io.Reader) error {
2032+
tlvStream, err := tlv.NewStream(
2033+
t.TapTweak.Record(), t.CtrlBlock.Record(),
2034+
)
2035+
if err != nil {
2036+
return err
2037+
}
2038+
2039+
return tlvStream.Decode(r)
2040+
}
2041+
2042+
// eTapscriptSigDesc is an encoder for tapscriptSigDesc.
2043+
func eTapscriptSigDesc(w io.Writer, val interface{}, _ *[8]byte) error {
2044+
if v, ok := val.(*TapscriptSigDesc); ok {
2045+
return v.Encode(w)
2046+
}
2047+
2048+
return tlv.NewTypeForEncodingErr(val, "*tapscriptSigDesc")
2049+
}
2050+
2051+
// dTapscriptSigDesc is a decoder for tapscriptSigDesc.
2052+
func dTapscriptSigDesc(r io.Reader, val interface{},
2053+
_ *[8]byte, _ uint64) error {
2054+
2055+
if typ, ok := val.(*TapscriptSigDesc); ok {
2056+
return typ.Decode(r)
2057+
}
2058+
2059+
return tlv.NewTypeForEncodingErr(val, "*tapscriptSigDesc")
2060+
}
2061+
2062+
// Record returns a tlv.Record that represents the tapscriptSigDesc.
2063+
func (t *TapscriptSigDesc) Record() tlv.Record {
2064+
size := func() uint64 {
2065+
var (
2066+
buf bytes.Buffer
2067+
scratch [8]byte
2068+
)
2069+
err := eTapscriptSigDesc(&buf, t, &scratch)
2070+
if err != nil {
2071+
panic(err)
2072+
}
2073+
2074+
return uint64(buf.Len())
2075+
}
2076+
2077+
return tlv.MakeDynamicRecord(
2078+
0, t, size, eTapscriptSigDesc, dTapscriptSigDesc,
2079+
)
2080+
}
2081+
19972082
// ContractResolution houses all the information we need to resolve a contract
19982083
// on chain. This includes a series of pre-populated and pre-signed vPackets.
19992084
// The internal key, and other on-chain anchor information may be missing from
20002085
// these packets.
20012086
type ContractResolution struct {
2002-
// SweepVpkts is a list of pre-signed vPackets that can be anchored
2003-
// into an output in a transaction where the refrnced previous inputs
2004-
// are spent to sweep an asset.
2005-
SweepVpkts tlv.RecordT[tlv.TlvType0, VpktList]
2087+
// firstLevelSweepVpkts is a list of pre-signed vPackets that can be
2088+
// anchored into an output in a transaction where the referenced
2089+
// previous inputs are spent to sweep an asset.
2090+
firstLevelSweepVpkts tlv.RecordT[tlv.TlvType0, VpktList]
2091+
2092+
// secondLevelSweepVpkts is a list of pre-signed vPackets that can be
2093+
// anchored into an output in a transaction where the referenced
2094+
// previous inputs are spent to sweep an asset.
2095+
secondLevelSweepVpkts tlv.OptionalRecordT[tlv.TlvType1, VpktList]
2096+
2097+
// secondLevelSigDescs is a list of tapscriptSigDescs that contain the
2098+
// information we need to sign for each second level vPkt once the
2099+
// sweeping transaction is known.
2100+
secondLevelSigDescs tlv.OptionalRecordT[tlv.TlvType2, TapscriptSigDesc]
20062101
}
20072102

20082103
// NewContractResolution creates a new ContractResolution with the given list
20092104
// of vpkts.
2010-
func NewContractResolution(pkts []*tappsbt.VPacket) ContractResolution {
2011-
return ContractResolution{
2012-
SweepVpkts: tlv.NewRecordT[tlv.TlvType0](NewVpktList(pkts)),
2105+
func NewContractResolution(firstLevelPkts, secondLevelPkts []*tappsbt.VPacket,
2106+
secondLevelSweepDesc lfn.Option[TapscriptSigDesc]) ContractResolution {
2107+
2108+
c := ContractResolution{
2109+
firstLevelSweepVpkts: tlv.NewRecordT[tlv.TlvType0](
2110+
NewVpktList(firstLevelPkts),
2111+
),
2112+
}
2113+
2114+
if len(secondLevelPkts) != 0 {
2115+
c.secondLevelSweepVpkts = tlv.SomeRecordT(
2116+
tlv.NewRecordT[tlv.TlvType1](
2117+
NewVpktList(secondLevelPkts),
2118+
),
2119+
)
20132120
}
2121+
2122+
secondLevelSweepDesc.WhenSome(func(sigDesc TapscriptSigDesc) {
2123+
c.secondLevelSigDescs = tlv.SomeRecordT(
2124+
tlv.NewRecordT[tlv.TlvType2](sigDesc),
2125+
)
2126+
})
2127+
2128+
return c
20142129
}
20152130

20162131
// Records returns the records that make up the ContractResolution.
20172132
func (c *ContractResolution) Records() []tlv.Record {
2018-
return []tlv.Record{
2019-
c.SweepVpkts.Record(),
2133+
records := []tlv.Record{
2134+
c.firstLevelSweepVpkts.Record(),
20202135
}
2136+
2137+
c.secondLevelSweepVpkts.WhenSome(
2138+
func(r tlv.RecordT[tlv.TlvType1, VpktList]) {
2139+
records = append(records, r.Record())
2140+
},
2141+
)
2142+
c.secondLevelSigDescs.WhenSome(
2143+
func(r tlv.RecordT[tlv.TlvType2, TapscriptSigDesc]) {
2144+
records = append(records, r.Record())
2145+
},
2146+
)
2147+
2148+
return records
20212149
}
20222150

20232151
// Encode serializes the ContractResolution to the given io.Writer.
@@ -2032,15 +2160,44 @@ func (c *ContractResolution) Encode(w io.Writer) error {
20322160

20332161
// Decode deserializes the ContractResolution from the given io.Reader.
20342162
func (c *ContractResolution) Decode(r io.Reader) error {
2035-
tlvStream, err := tlv.NewStream(c.Records()...)
2163+
sweepZero := c.secondLevelSweepVpkts.Zero()
2164+
sigZero := c.secondLevelSigDescs.Zero()
2165+
2166+
tlvStream, err := tlv.NewStream(
2167+
c.firstLevelSweepVpkts.Record(),
2168+
sweepZero.Record(),
2169+
sigZero.Record(),
2170+
)
20362171
if err != nil {
20372172
return err
20382173
}
20392174

2040-
return tlvStream.Decode(r)
2175+
tlvs, err := tlvStream.DecodeWithParsedTypes(r)
2176+
if err != nil {
2177+
return err
2178+
}
2179+
2180+
if _, ok := tlvs[sweepZero.TlvType()]; ok {
2181+
c.secondLevelSweepVpkts = tlv.SomeRecordT(sweepZero)
2182+
}
2183+
if _, ok := tlvs[sigZero.TlvType()]; ok {
2184+
c.secondLevelSigDescs = tlv.SomeRecordT(sigZero)
2185+
}
2186+
2187+
return nil
2188+
}
2189+
2190+
// Vpkts1 returns the set of first level Vpkts.
2191+
func (c *ContractResolution) Vpkts1() []*tappsbt.VPacket {
2192+
return c.firstLevelSweepVpkts.Val.Pkts
20412193
}
20422194

2043-
// VPkts returns the list of vPkts in the ContractResolution.
2044-
func (c *ContractResolution) VPkts() []*tappsbt.VPacket {
2045-
return c.SweepVpkts.Val.Pkts
2195+
// Vpkts2 returns the set of first level Vpkts.
2196+
func (c *ContractResolution) Vpkts2() []*tappsbt.VPacket {
2197+
var vPkts []*tappsbt.VPacket
2198+
c.secondLevelSweepVpkts.WhenSomeV(func(v VpktList) {
2199+
vPkts = v.Pkts
2200+
})
2201+
2202+
return vPkts
20462203
}

tapchannelmsg/records_test.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/lightningnetwork/lnd/lnwallet"
2121
"github.com/lightningnetwork/lnd/lnwire"
2222
"github.com/stretchr/testify/require"
23+
"pgregory.net/rapid"
2324
)
2425

2526
const (
@@ -485,20 +486,49 @@ func TestAuxShutdownMsg(t *testing.T) {
485486
func TestContractResolution(t *testing.T) {
486487
t.Parallel()
487488

488-
const numPackets = 10
489+
sigDescGen := rapid.Custom(func(t *rapid.T) TapscriptSigDesc {
490+
byteSliceGen := rapid.SliceOfN(rapid.Byte(), 1, 256)
489491

490-
testPkts := make([]*tappsbt.VPacket, numPackets)
491-
for i := 0; i < numPackets; i++ {
492-
testPkts[i] = tappsbt.RandPacket(t, true)
493-
}
494-
495-
testRes := NewContractResolution(testPkts)
492+
return NewTapscriptSigDesc(
493+
byteSliceGen.Draw(t, "taptweak"),
494+
byteSliceGen.Draw(t, "ctrlBlock"),
495+
)
496+
})
497+
498+
rapid.Check(t, func(r *rapid.T) {
499+
numPackets := rapid.IntRange(1, 10).Draw(r, "numPackets")
500+
501+
testPkts1 := make([]*tappsbt.VPacket, numPackets)
502+
for i := 0; i < numPackets; i++ {
503+
testPkts1[i] = tappsbt.RandPacket(t, true)
504+
}
505+
506+
var testPkts2 []*tappsbt.VPacket
507+
508+
if rapid.Bool().Draw(r, "secondPacketSet") {
509+
testPkts2 = make([]*tappsbt.VPacket, numPackets)
510+
for i := 0; i < numPackets; i++ {
511+
testPkts2[i] = tappsbt.RandPacket(t, true)
512+
}
513+
}
514+
515+
var sigDesc lfn.Option[TapscriptSigDesc]
516+
if rapid.Bool().Draw(r, "sigDescSet") {
517+
sigDesc = lfn.Some(
518+
sigDescGen.Draw(r, "sigDesc"),
519+
)
520+
}
521+
522+
testRes := NewContractResolution(
523+
testPkts1, testPkts2, sigDesc,
524+
)
496525

497-
var b bytes.Buffer
498-
require.NoError(t, testRes.Encode(&b))
526+
var b bytes.Buffer
527+
require.NoError(t, testRes.Encode(&b))
499528

500-
var newRes ContractResolution
501-
require.NoError(t, newRes.Decode(&b))
529+
var newRes ContractResolution
530+
require.NoError(t, newRes.Decode(&b))
502531

503-
require.Equal(t, testRes, newRes)
532+
require.Equal(t, testRes, newRes)
533+
})
504534
}

0 commit comments

Comments
 (0)