From 004a68e52453786e3972e088e8db8b80597aa668 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 22 Sep 2025 10:37:12 +0200 Subject: [PATCH] lnwire: add OutPoint type with TLV encoding support Add a new OutPoint type that wraps wire.OutPoint and provides TLV encoding/decoding capabilities through the tlv.RecordProducer interface. This enables OutPoint to be used in TLV streams of messages. --- lnwire/outpoint.go | 53 ++++++++++++++++++++ lnwire/outpoint_test.go | 107 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 lnwire/outpoint.go create mode 100644 lnwire/outpoint_test.go diff --git a/lnwire/outpoint.go b/lnwire/outpoint.go new file mode 100644 index 000000000..ec893ee65 --- /dev/null +++ b/lnwire/outpoint.go @@ -0,0 +1,53 @@ +package lnwire + +import ( + "bytes" + "io" + + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/tlv" +) + +// OutPoint describes an outpoint of a transaction via its transaction hash +// and the outpoint index. This is a thin wrapper around the wire.OutPoint to +// provide TLV encoding/decoding. +type OutPoint wire.OutPoint + +// Record returns a TLV record that can be used to encode/decode the OutPoint. +// +// NOTE: this is part of the tlv.RecordProducer interface. +func (o *OutPoint) Record() tlv.Record { + return tlv.MakeStaticRecord(0, o, 34, outpointEncoder, outpointDecoder) +} + +// outpointEncoder is a TLV encoder for OutPoint. +func outpointEncoder(w io.Writer, val any, _ *[8]byte) error { + if v, ok := val.(*OutPoint); ok { + buf := bytes.NewBuffer(nil) + err := WriteOutPoint(buf, wire.OutPoint(*v)) + if err != nil { + return err + } + _, err = w.Write(buf.Bytes()) + + return err + } + + return tlv.NewTypeForEncodingErr(val, "OutPoint") +} + +// outpointDecoder is a TLV decoder for OutPoint. +func outpointDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { + if v, ok := val.(*OutPoint); ok { + var o wire.OutPoint + if err := ReadElement(r, &o); err != nil { + return err + } + + *v = OutPoint(o) + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "OutPoint", l, 34) +} diff --git a/lnwire/outpoint_test.go b/lnwire/outpoint_test.go new file mode 100644 index 000000000..b8ac1a70d --- /dev/null +++ b/lnwire/outpoint_test.go @@ -0,0 +1,107 @@ +package lnwire + +import ( + "bytes" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" + "pgregory.net/rapid" +) + +// TestOutPointTLVEncoding tests the TLV encoding and decoding of OutPoint +// structs using the Record interface. +func TestOutPointTLVEncoding(t *testing.T) { + t.Parallel() + + testOutPoint := OutPoint(wire.OutPoint{ + Hash: chainhash.Hash{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }, + Index: 12345, + }) + + var extraData ExtraOpaqueData + require.NoError(t, extraData.PackRecords(&testOutPoint)) + + var decodedOutPoint OutPoint + tlvs, err := extraData.ExtractRecords(&decodedOutPoint) + require.NoError(t, err) + + require.Contains(t, tlvs, tlv.Type(0)) + require.Equal(t, testOutPoint, decodedOutPoint) +} + +// TestOutPointRecord tests the TLV Record interface of OutPoint +// by directly encoding and decoding using the Record method. +func TestOutPointRecord(t *testing.T) { + t.Parallel() + + testOutPoint := OutPoint(wire.OutPoint{ + Hash: chainhash.Hash{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }, + Index: 65535, + }) + + var buf bytes.Buffer + record := testOutPoint.Record() + require.NoError(t, record.Encode(&buf)) + + var decodedOutPoint OutPoint + decodedRecord := decodedOutPoint.Record() + require.NoError(t, decodedRecord.Decode(&buf, uint64(buf.Len()))) + + require.Equal(t, testOutPoint, decodedOutPoint) +} + +// TestOutPointProperty uses property-based testing to verify that OutPoint +// TLV encoding and decoding is correct for random OutPoint values. +func TestOutPointProperty(t *testing.T) { + t.Parallel() + + scenario := func(t *rapid.T) { + wireOutPoint := RandOutPoint(t) + lnOutPoint := OutPoint(wireOutPoint) + + var buf bytes.Buffer + record := lnOutPoint.Record() + err := record.Encode(&buf) + require.NoError(t, err) + + var decodedOutPoint OutPoint + decodedRecord := decodedOutPoint.Record() + err = decodedRecord.Decode(&buf, uint64(buf.Len())) + require.NoError(t, err) + + require.Equal(t, lnOutPoint, decodedOutPoint) + require.Equal(t, wireOutPoint, wire.OutPoint(decodedOutPoint)) + } + + rapid.Check(t, scenario) +} + +// TestOutPointZeroValues tests that OutPoint handles zero values correctly. +func TestOutPointZeroValues(t *testing.T) { + t.Parallel() + + zeroOutPoint := OutPoint(wire.OutPoint{}) + + var buf bytes.Buffer + record := zeroOutPoint.Record() + require.NoError(t, record.Encode(&buf)) + + var decodedOutPoint OutPoint + decodedRecord := decodedOutPoint.Record() + require.NoError(t, decodedRecord.Decode(&buf, uint64(buf.Len()))) + + require.Equal(t, zeroOutPoint, decodedOutPoint) +}