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.
This commit is contained in:
Olaoluwa Osuntokun 2024-10-15 19:18:23 -07:00
parent 1e7c5415ae
commit 9b8adf5f5c
No known key found for this signature in database
GPG Key ID: 90525F7DEEE0AD86
5 changed files with 242 additions and 4 deletions

View File

@ -39,7 +39,9 @@ type taprootBriefcase struct {
// used to sweep a remote party's breached output. // used to sweep a remote party's breached output.
BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob] 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 // TODO(roasbeef): morph into new tlv record
@ -70,6 +72,9 @@ func (t *taprootBriefcase) EncodeRecords() []tlv.Record {
records = append(records, r.Record()) records = append(records, r.Record())
}, },
) )
t.HtlcBlobs.WhenSome(func(r tlv.RecordT[tlv.TlvType4, htlcAuxBlobs]) {
records = append(records, r.Record())
})
return records return records
} }
@ -96,10 +101,11 @@ func (t *taprootBriefcase) Encode(w io.Writer) error {
func (t *taprootBriefcase) Decode(r io.Reader) error { func (t *taprootBriefcase) Decode(r io.Reader) error {
settledCommitBlob := t.SettledCommitBlob.Zero() settledCommitBlob := t.SettledCommitBlob.Zero()
breachedCommitBlob := t.BreachedCommitBlob.Zero() breachedCommitBlob := t.BreachedCommitBlob.Zero()
htlcBlobs := t.HtlcBlobs.Zero()
records := append( records := append(
t.DecodeRecords(), t.DecodeRecords(), settledCommitBlob.Record(),
settledCommitBlob.Record(), breachedCommitBlob.Record(), htlcBlobs.Record(),
breachedCommitBlob.Record(),
) )
stream, err := tlv.NewStream(records...) stream, err := tlv.NewStream(records...)
if err != nil { 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 { if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil {
t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob) t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob)
} }
if v, ok := typeMap[t.HtlcBlobs.TlvType()]; ok && v == nil {
t.HtlcBlobs = tlv.SomeRecordT(htlcBlobs)
}
return nil return nil
} }
@ -686,3 +695,110 @@ func (t *tapTweaks) Decode(r io.Reader) error {
return stream.Decode(r) 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,
)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"pgregory.net/rapid"
) )
func randResolverCtrlBlocks(t *testing.T) resolverCtrlBlocks { func randResolverCtrlBlocks(t *testing.T) resolverCtrlBlocks {
@ -53,6 +54,25 @@ func randHtlcTweaks(t *testing.T) htlcTapTweaks {
return tweaks 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 // TestTaprootBriefcase tests the encode/decode methods of the taproot
// briefcase extension. // briefcase extension.
func TestTaprootBriefcase(t *testing.T) { func TestTaprootBriefcase(t *testing.T) {
@ -93,6 +113,9 @@ func TestTaprootBriefcase(t *testing.T) {
BreachedCommitBlob: tlv.SomeRecordT( BreachedCommitBlob: tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]), tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]),
), ),
HtlcBlobs: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType4](randHtlcAuxBlobs(t)),
),
} }
var b bytes.Buffer var b bytes.Buffer
@ -103,3 +126,21 @@ func TestTaprootBriefcase(t *testing.T) {
require.Equal(t, testCase, &decodedCase) 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)
})
}

View File

@ -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

1
go.mod
View File

@ -64,6 +64,7 @@ require (
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.33.0
gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon-bakery.v2 v2.0.1
gopkg.in/macaroon.v2 v2.0.0 gopkg.in/macaroon.v2 v2.0.0
pgregory.net/rapid v1.1.0
) )
require ( require (

2
go.sum
View File

@ -1076,6 +1076,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 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 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 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/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=