Skip to content

Commit 9b8adf5

Browse files
committed
contractcourt: add HtlcBlobs to taprootBriefcase
In this commit, we add the set of HtlcBlobs to the taprootBriefcase struct. This new field will store all the resolution blobs for a given HTLC. We also add some new property based tests along the way for adequate test coverage.
1 parent 1e7c541 commit 9b8adf5

File tree

5 files changed

+242
-4
lines changed

5 files changed

+242
-4
lines changed

contractcourt/taproot_briefcase.go

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ type taprootBriefcase struct {
3939
// used to sweep a remote party's breached output.
4040
BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob]
4141

42-
// TODO(roasbeef): htlc blobs
42+
// HtlcBlobs is an optikonal record that contains the opaque blobs for
43+
// the set of active HTLCs on the commitment transaction.
44+
HtlcBlobs tlv.OptionalRecordT[tlv.TlvType4, htlcAuxBlobs]
4345
}
4446

4547
// TODO(roasbeef): morph into new tlv record
@@ -70,6 +72,9 @@ func (t *taprootBriefcase) EncodeRecords() []tlv.Record {
7072
records = append(records, r.Record())
7173
},
7274
)
75+
t.HtlcBlobs.WhenSome(func(r tlv.RecordT[tlv.TlvType4, htlcAuxBlobs]) {
76+
records = append(records, r.Record())
77+
})
7378

7479
return records
7580
}
@@ -96,10 +101,11 @@ func (t *taprootBriefcase) Encode(w io.Writer) error {
96101
func (t *taprootBriefcase) Decode(r io.Reader) error {
97102
settledCommitBlob := t.SettledCommitBlob.Zero()
98103
breachedCommitBlob := t.BreachedCommitBlob.Zero()
104+
htlcBlobs := t.HtlcBlobs.Zero()
105+
99106
records := append(
100-
t.DecodeRecords(),
101-
settledCommitBlob.Record(),
102-
breachedCommitBlob.Record(),
107+
t.DecodeRecords(), settledCommitBlob.Record(),
108+
breachedCommitBlob.Record(), htlcBlobs.Record(),
103109
)
104110
stream, err := tlv.NewStream(records...)
105111
if err != nil {
@@ -117,6 +123,9 @@ func (t *taprootBriefcase) Decode(r io.Reader) error {
117123
if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil {
118124
t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob)
119125
}
126+
if v, ok := typeMap[t.HtlcBlobs.TlvType()]; ok && v == nil {
127+
t.HtlcBlobs = tlv.SomeRecordT(htlcBlobs)
128+
}
120129

121130
return nil
122131
}
@@ -686,3 +695,110 @@ func (t *tapTweaks) Decode(r io.Reader) error {
686695

687696
return stream.Decode(r)
688697
}
698+
699+
// htlcAuxBlobs is a map of resolver IDs to their corresponding HTLC blobs.
700+
// This is used to store the resolution blobs for HTLCs that are not yet
701+
// resolved.
702+
type htlcAuxBlobs map[resolverID]tlv.Blob
703+
704+
// newAuxHtlcBlobs returns a new instance of the htlcAuxBlobs struct.
705+
func newAuxHtlcBlobs() htlcAuxBlobs {
706+
return make(htlcAuxBlobs)
707+
}
708+
709+
// Encode encodes the set of HTLC blobs into the target writer.
710+
func (h *htlcAuxBlobs) Encode(w io.Writer) error {
711+
var buf [8]byte
712+
713+
numBlobs := uint64(len(*h))
714+
if err := tlv.WriteVarInt(w, numBlobs, &buf); err != nil {
715+
return err
716+
}
717+
718+
for id, blob := range *h {
719+
if _, err := w.Write(id[:]); err != nil {
720+
return err
721+
}
722+
723+
if err := varBytesEncoder(w, &blob, &buf); err != nil {
724+
return err
725+
}
726+
}
727+
728+
return nil
729+
}
730+
731+
// Decode decodes the set of HTLC blobs from the target reader.
732+
func (h *htlcAuxBlobs) Decode(r io.Reader) error {
733+
var buf [8]byte
734+
735+
numBlobs, err := tlv.ReadVarInt(r, &buf)
736+
if err != nil {
737+
return err
738+
}
739+
740+
for i := uint64(0); i < numBlobs; i++ {
741+
var id resolverID
742+
if _, err := io.ReadFull(r, id[:]); err != nil {
743+
return err
744+
}
745+
746+
var blob tlv.Blob
747+
if err := varBytesDecoder(r, &blob, &buf, 0); err != nil {
748+
return err
749+
}
750+
751+
(*h)[id] = blob
752+
}
753+
754+
return nil
755+
}
756+
757+
// eHtlcAuxBlobsEncoder is a custom TLV encoder for the htlcAuxBlobs struct.
758+
func htlcAuxBlobsEncoder(w io.Writer, val any, _ *[8]byte) error {
759+
if t, ok := val.(*htlcAuxBlobs); ok {
760+
return (*t).Encode(w)
761+
}
762+
763+
return tlv.NewTypeForEncodingErr(val, "htlcAuxBlobs")
764+
}
765+
766+
// dHtlcAuxBlobsDecoder is a custom TLV decoder for the htlcAuxBlobs struct.
767+
func htlcAuxBlobsDecoder(r io.Reader, val any, _ *[8]byte,
768+
l uint64) error {
769+
770+
if typ, ok := val.(*htlcAuxBlobs); ok {
771+
blobReader := io.LimitReader(r, int64(l))
772+
773+
htlcBlobs := newAuxHtlcBlobs()
774+
err := htlcBlobs.Decode(blobReader)
775+
if err != nil {
776+
return err
777+
}
778+
779+
*typ = htlcBlobs
780+
781+
return nil
782+
}
783+
784+
return tlv.NewTypeForDecodingErr(val, "htlcAuxBlobs", l, l)
785+
}
786+
787+
// Record returns a tlv.Record for the htlcAuxBlobs struct.
788+
func (h *htlcAuxBlobs) Record() tlv.Record {
789+
recordSize := func() uint64 {
790+
var (
791+
b bytes.Buffer
792+
buf [8]byte
793+
)
794+
if err := htlcAuxBlobsEncoder(&b, h, &buf); err != nil {
795+
panic(err)
796+
}
797+
798+
return uint64(len(b.Bytes()))
799+
}
800+
801+
return tlv.MakeDynamicRecord(
802+
0, h, recordSize, htlcAuxBlobsEncoder, htlcAuxBlobsDecoder,
803+
)
804+
}

contractcourt/taproot_briefcase_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/lightningnetwork/lnd/tlv"
99
"github.com/stretchr/testify/require"
10+
"pgregory.net/rapid"
1011
)
1112

1213
func randResolverCtrlBlocks(t *testing.T) resolverCtrlBlocks {
@@ -53,6 +54,25 @@ func randHtlcTweaks(t *testing.T) htlcTapTweaks {
5354
return tweaks
5455
}
5556

57+
func randHtlcAuxBlobs(t *testing.T) htlcAuxBlobs {
58+
numBlobs := rand.Int() % 256
59+
blobs := make(htlcAuxBlobs, numBlobs)
60+
61+
for i := 0; i < numBlobs; i++ {
62+
var id resolverID
63+
_, err := rand.Read(id[:])
64+
require.NoError(t, err)
65+
66+
var blob [100]byte
67+
_, err = rand.Read(blob[:])
68+
require.NoError(t, err)
69+
70+
blobs[id] = blob[:]
71+
}
72+
73+
return blobs
74+
}
75+
5676
// TestTaprootBriefcase tests the encode/decode methods of the taproot
5777
// briefcase extension.
5878
func TestTaprootBriefcase(t *testing.T) {
@@ -93,6 +113,9 @@ func TestTaprootBriefcase(t *testing.T) {
93113
BreachedCommitBlob: tlv.SomeRecordT(
94114
tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]),
95115
),
116+
HtlcBlobs: tlv.SomeRecordT(
117+
tlv.NewRecordT[tlv.TlvType4](randHtlcAuxBlobs(t)),
118+
),
96119
}
97120

98121
var b bytes.Buffer
@@ -103,3 +126,21 @@ func TestTaprootBriefcase(t *testing.T) {
103126

104127
require.Equal(t, testCase, &decodedCase)
105128
}
129+
130+
// TestHtlcAuxBlobEncodeDecode tests the encode/decode methods of the HTLC aux
131+
// blobs.
132+
func TestHtlcAuxBlobEncodeDecode(t *testing.T) {
133+
t.Parallel()
134+
135+
rapid.Check(t, func(t *rapid.T) {
136+
htlcBlobs := rapid.Make[htlcAuxBlobs]().Draw(t, "htlcAuxBlobs")
137+
138+
var b bytes.Buffer
139+
require.NoError(t, htlcBlobs.Encode(&b))
140+
141+
decodedBlobs := newAuxHtlcBlobs()
142+
require.NoError(t, decodedBlobs.Decode(&b))
143+
144+
require.Equal(t, htlcBlobs, decodedBlobs)
145+
})
146+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# 2024/09/02 14:02:53.354676 [TestHtlcAuxBlobEncodeDecode] [rapid] draw htlcAuxBlobs: contractcourt.htlcAuxBlobs{contractcourt.resolverID{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}:[]uint8{}}
2+
#
3+
v0.4.8#15807814492030881602
4+
0x5555555555555
5+
0x0
6+
0x0
7+
0x0
8+
0x0
9+
0x0
10+
0x0
11+
0x0
12+
0x0
13+
0x0
14+
0x0
15+
0x0
16+
0x0
17+
0x0
18+
0x0
19+
0x0
20+
0x0
21+
0x0
22+
0x0
23+
0x0
24+
0x0
25+
0x0
26+
0x0
27+
0x0
28+
0x0
29+
0x0
30+
0x0
31+
0x0
32+
0x0
33+
0x0
34+
0x0
35+
0x0
36+
0x0
37+
0x0
38+
0x0
39+
0x0
40+
0x0
41+
0x0
42+
0x0
43+
0x0
44+
0x0
45+
0x0
46+
0x0
47+
0x0
48+
0x0
49+
0x0
50+
0x0
51+
0x0
52+
0x0
53+
0x0
54+
0x0
55+
0x0
56+
0x0
57+
0x0
58+
0x0
59+
0x0
60+
0x0
61+
0x0
62+
0x0
63+
0x0
64+
0x0
65+
0x0
66+
0x0
67+
0x0
68+
0x0
69+
0x0
70+
0x0
71+
0x0
72+
0x0
73+
0x0
74+
0x0
75+
0x0
76+
0x0
77+
0x0
78+
0x0

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ require (
6464
google.golang.org/protobuf v1.33.0
6565
gopkg.in/macaroon-bakery.v2 v2.0.1
6666
gopkg.in/macaroon.v2 v2.0.0
67+
pgregory.net/rapid v1.1.0
6768
)
6869

6970
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
10761076
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
10771077
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
10781078
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
1079+
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
1080+
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
10791081
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
10801082
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
10811083
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

0 commit comments

Comments
 (0)