From 6914e6a4a553196bddffefe3ed814414dd1280b0 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 15 Oct 2024 19:18:23 -0700 Subject: [PATCH] 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. --- contractcourt/taproot_briefcase.go | 124 +++++++++++++++++- contractcourt/taproot_briefcase_test.go | 41 ++++++ ...BlobEncodeDecode-20240902140253-81338.fail | 78 +++++++++++ go.mod | 1 + go.sum | 2 + 5 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail diff --git a/contractcourt/taproot_briefcase.go b/contractcourt/taproot_briefcase.go index 0a2beeff7..4b703dd29 100644 --- a/contractcourt/taproot_briefcase.go +++ b/contractcourt/taproot_briefcase.go @@ -39,7 +39,9 @@ type taprootBriefcase struct { // used to sweep a remote party's breached output. BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob] - // TODO(roasbeef): htlc blobs + // HtlcBlobs is an optikonal record that contains the opaque blobs for + // the set of active HTLCs on the commitment transaction. + HtlcBlobs tlv.OptionalRecordT[tlv.TlvType4, htlcAuxBlobs] } // TODO(roasbeef): morph into new tlv record @@ -70,6 +72,9 @@ func (t *taprootBriefcase) EncodeRecords() []tlv.Record { records = append(records, r.Record()) }, ) + t.HtlcBlobs.WhenSome(func(r tlv.RecordT[tlv.TlvType4, htlcAuxBlobs]) { + records = append(records, r.Record()) + }) return records } @@ -96,10 +101,11 @@ func (t *taprootBriefcase) Encode(w io.Writer) error { func (t *taprootBriefcase) Decode(r io.Reader) error { settledCommitBlob := t.SettledCommitBlob.Zero() breachedCommitBlob := t.BreachedCommitBlob.Zero() + htlcBlobs := t.HtlcBlobs.Zero() + records := append( - t.DecodeRecords(), - settledCommitBlob.Record(), - breachedCommitBlob.Record(), + t.DecodeRecords(), settledCommitBlob.Record(), + breachedCommitBlob.Record(), htlcBlobs.Record(), ) stream, err := tlv.NewStream(records...) if err != nil { @@ -117,6 +123,9 @@ func (t *taprootBriefcase) Decode(r io.Reader) error { if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil { t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob) } + if v, ok := typeMap[t.HtlcBlobs.TlvType()]; ok && v == nil { + t.HtlcBlobs = tlv.SomeRecordT(htlcBlobs) + } return nil } @@ -686,3 +695,110 @@ func (t *tapTweaks) Decode(r io.Reader) error { return stream.Decode(r) } + +// htlcAuxBlobs is a map of resolver IDs to their corresponding HTLC blobs. +// This is used to store the resolution blobs for HTLCs that are not yet +// resolved. +type htlcAuxBlobs map[resolverID]tlv.Blob + +// newAuxHtlcBlobs returns a new instance of the htlcAuxBlobs struct. +func newAuxHtlcBlobs() htlcAuxBlobs { + return make(htlcAuxBlobs) +} + +// Encode encodes the set of HTLC blobs into the target writer. +func (h *htlcAuxBlobs) Encode(w io.Writer) error { + var buf [8]byte + + numBlobs := uint64(len(*h)) + if err := tlv.WriteVarInt(w, numBlobs, &buf); err != nil { + return err + } + + for id, blob := range *h { + if _, err := w.Write(id[:]); err != nil { + return err + } + + if err := varBytesEncoder(w, &blob, &buf); err != nil { + return err + } + } + + return nil +} + +// Decode decodes the set of HTLC blobs from the target reader. +func (h *htlcAuxBlobs) Decode(r io.Reader) error { + var buf [8]byte + + numBlobs, err := tlv.ReadVarInt(r, &buf) + if err != nil { + return err + } + + for i := uint64(0); i < numBlobs; i++ { + var id resolverID + if _, err := io.ReadFull(r, id[:]); err != nil { + return err + } + + var blob tlv.Blob + if err := varBytesDecoder(r, &blob, &buf, 0); err != nil { + return err + } + + (*h)[id] = blob + } + + return nil +} + +// eHtlcAuxBlobsEncoder is a custom TLV encoder for the htlcAuxBlobs struct. +func htlcAuxBlobsEncoder(w io.Writer, val any, _ *[8]byte) error { + if t, ok := val.(*htlcAuxBlobs); ok { + return (*t).Encode(w) + } + + return tlv.NewTypeForEncodingErr(val, "htlcAuxBlobs") +} + +// dHtlcAuxBlobsDecoder is a custom TLV decoder for the htlcAuxBlobs struct. +func htlcAuxBlobsDecoder(r io.Reader, val any, _ *[8]byte, + l uint64) error { + + if typ, ok := val.(*htlcAuxBlobs); ok { + blobReader := io.LimitReader(r, int64(l)) + + htlcBlobs := newAuxHtlcBlobs() + err := htlcBlobs.Decode(blobReader) + if err != nil { + return err + } + + *typ = htlcBlobs + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "htlcAuxBlobs", l, l) +} + +// Record returns a tlv.Record for the htlcAuxBlobs struct. +func (h *htlcAuxBlobs) Record() tlv.Record { + recordSize := func() uint64 { + var ( + b bytes.Buffer + buf [8]byte + ) + if err := htlcAuxBlobsEncoder(&b, h, &buf); err != nil { + panic(err) + } + + return uint64(len(b.Bytes())) + } + + return tlv.MakeDynamicRecord( + 0, h, recordSize, htlcAuxBlobsEncoder, htlcAuxBlobsDecoder, + ) +} diff --git a/contractcourt/taproot_briefcase_test.go b/contractcourt/taproot_briefcase_test.go index a7d52d963..441aebf1d 100644 --- a/contractcourt/taproot_briefcase_test.go +++ b/contractcourt/taproot_briefcase_test.go @@ -7,6 +7,7 @@ import ( "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" + "pgregory.net/rapid" ) func randResolverCtrlBlocks(t *testing.T) resolverCtrlBlocks { @@ -53,6 +54,25 @@ func randHtlcTweaks(t *testing.T) htlcTapTweaks { return tweaks } +func randHtlcAuxBlobs(t *testing.T) htlcAuxBlobs { + numBlobs := rand.Int() % 256 + blobs := make(htlcAuxBlobs, numBlobs) + + for i := 0; i < numBlobs; i++ { + var id resolverID + _, err := rand.Read(id[:]) + require.NoError(t, err) + + var blob [100]byte + _, err = rand.Read(blob[:]) + require.NoError(t, err) + + blobs[id] = blob[:] + } + + return blobs +} + // TestTaprootBriefcase tests the encode/decode methods of the taproot // briefcase extension. func TestTaprootBriefcase(t *testing.T) { @@ -93,6 +113,9 @@ func TestTaprootBriefcase(t *testing.T) { BreachedCommitBlob: tlv.SomeRecordT( tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]), ), + HtlcBlobs: tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType4](randHtlcAuxBlobs(t)), + ), } var b bytes.Buffer @@ -103,3 +126,21 @@ func TestTaprootBriefcase(t *testing.T) { require.Equal(t, testCase, &decodedCase) } + +// TestHtlcAuxBlobEncodeDecode tests the encode/decode methods of the HTLC aux +// blobs. +func TestHtlcAuxBlobEncodeDecode(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(t *rapid.T) { + htlcBlobs := rapid.Make[htlcAuxBlobs]().Draw(t, "htlcAuxBlobs") + + var b bytes.Buffer + require.NoError(t, htlcBlobs.Encode(&b)) + + decodedBlobs := newAuxHtlcBlobs() + require.NoError(t, decodedBlobs.Decode(&b)) + + require.Equal(t, htlcBlobs, decodedBlobs) + }) +} diff --git a/contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail b/contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail new file mode 100644 index 000000000..86bb07ed7 --- /dev/null +++ b/contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail @@ -0,0 +1,78 @@ +# 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{}} +# +v0.4.8#15807814492030881602 +0x5555555555555 +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 +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 +0x0 +0x0 \ No newline at end of file diff --git a/go.mod b/go.mod index 57c08f699..288f98f72 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( google.golang.org/protobuf v1.33.0 gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon.v2 v2.0.0 + pgregory.net/rapid v1.1.0 ) require ( diff --git a/go.sum b/go.sum index 20739ffbd..3b3b0f5d0 100644 --- a/go.sum +++ b/go.sum @@ -1069,6 +1069,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=