lnwire: define NodeAnnouncement2

In this commit, the lnwire.NodeAnnouncement2 type is defined. This will
be used to represent the `node_announcement_2` message used in the
Gossip 2 (1.75) protocol.
This commit is contained in:
Elle Mouton
2024-12-02 12:47:56 +02:00
parent e24dd2f9e0
commit b8ff9fbef3
6 changed files with 873 additions and 0 deletions

View File

@@ -244,6 +244,30 @@ func FuzzNodeAnnouncement(f *testing.F) {
})
}
func FuzzNodeAnnouncement2(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
// We can't use require.Equal for Features, since we consider
// the empty map and nil to be equivalent.
assertEq := func(t *testing.T, x, y any) {
require.IsType(t, &NodeAnnouncement2{}, x)
first, _ := x.(*NodeAnnouncement2)
require.IsType(t, &NodeAnnouncement2{}, y)
second, _ := y.(*NodeAnnouncement2)
require.True(
t,
first.Features.Val.Equals(&second.Features.Val),
)
first.Features.Val = *NewRawFeatureVector()
second.Features.Val = *NewRawFeatureVector()
require.Equal(t, first, second)
}
wireMsgHarnessCustom(t, data, MsgNodeAnnouncement2, assertEq)
})
}
func FuzzOpenChannel(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
// We can't use require.Equal for UpfrontShutdownScript, since

View File

@@ -64,6 +64,7 @@ const (
MsgReplyChannelRange = 264
MsgGossipTimestampRange = 265
MsgChannelAnnouncement2 = 267
MsgNodeAnnouncement2 = 269
MsgChannelUpdate2 = 271
MsgKickoffSig = 777
@@ -193,6 +194,8 @@ func (t MessageType) String() string {
return "MsgAnnounceSignatures2"
case MsgChannelAnnouncement2:
return "ChannelAnnouncement2"
case MsgNodeAnnouncement2:
return "NodeAnnouncement2"
case MsgChannelUpdate2:
return "ChannelUpdate2"
default:
@@ -355,6 +358,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) {
msg = &AnnounceSignatures2{}
case MsgChannelAnnouncement2:
msg = &ChannelAnnouncement2{}
case MsgNodeAnnouncement2:
msg = &NodeAnnouncement2{}
case MsgChannelUpdate2:
msg = &ChannelUpdate2{}
default:

View File

@@ -0,0 +1,581 @@
package lnwire
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"image/color"
"io"
"net"
"unicode/utf8"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/tor"
)
// NodeAnnouncement2 message is used to announce the presence of a Lightning
// node and also to signal that the node is accepting incoming connections.
// Each NodeAnnouncement2 authenticating the advertised information within the
// announcement via a signature using the advertised node pubkey.
type NodeAnnouncement2 struct {
// Features is the feature vector that encodes the features supported
// by the target node.
Features tlv.RecordT[tlv.TlvType0, RawFeatureVector]
// Color is an optional field used to customize a node's appearance in
// maps and graphs.
Color tlv.OptionalRecordT[tlv.TlvType1, Color]
// BlockHeight allows ordering in the case of multiple announcements. We
// should ignore the message if block height is not greater than the
// last-received. The block height must always be greater or equal to
// the block height that the channel funding transaction was confirmed
// in.
BlockHeight tlv.RecordT[tlv.TlvType2, uint32]
// Alias is used to customize their node's appearance in maps and
// graphs.
Alias tlv.OptionalRecordT[tlv.TlvType3, NodeAlias2]
// NodeID is the public key of the node creating the announcement.
NodeID tlv.RecordT[tlv.TlvType4, [33]byte]
// IPV4Addrs is an optional list of ipv4 addresses that the node is
// reachable at.
IPV4Addrs tlv.OptionalRecordT[tlv.TlvType5, IPV4Addrs]
// IPV6Addrs is an optional list of ipv6 addresses that the node is
// reachable at.
IPV6Addrs tlv.OptionalRecordT[tlv.TlvType7, IPV6Addrs]
// TorV3Addrs is an optional list of tor v3 addresses that the node is
// reachable at.
TorV3Addrs tlv.OptionalRecordT[tlv.TlvType9, TorV3Addrs]
// DNSHostName is an optional DNS hostname that the node is reachable
// at.
DNSHostName tlv.OptionalRecordT[tlv.TlvType11, DNSAddress]
// Signature is used to validate the announced data and prove the
// ownership of node id.
Signature tlv.RecordT[tlv.TlvType160, Sig]
// Any extra fields in the signed range that we do not yet know about,
// but we need to keep them for signature validation and to produce a
// valid message.
ExtraSignedFields
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (n *NodeAnnouncement2) SerializedSize() (uint32, error) {
return MessageSerializedSize(n)
}
// AllRecords returns all the TLV records for the message. This will include all
// the records we know about along with any that we don't know about but that
// fall in the signed TLV range.
//
// NOTE: this is part of the PureTLVMessage interface.
func (n *NodeAnnouncement2) AllRecords() []tlv.Record {
recordProducers := []tlv.RecordProducer{
&n.Features,
&n.BlockHeight,
&n.NodeID,
&n.Signature,
}
n.Color.WhenSome(func(r tlv.RecordT[tlv.TlvType1, Color]) {
recordProducers = append(recordProducers, &r)
})
n.Alias.WhenSome(func(a tlv.RecordT[tlv.TlvType3, NodeAlias2]) {
recordProducers = append(recordProducers, &a)
})
n.IPV4Addrs.WhenSome(func(r tlv.RecordT[tlv.TlvType5, IPV4Addrs]) {
recordProducers = append(recordProducers, &r)
})
n.IPV6Addrs.WhenSome(func(r tlv.RecordT[tlv.TlvType7, IPV6Addrs]) {
recordProducers = append(recordProducers, &r)
})
n.TorV3Addrs.WhenSome(func(r tlv.RecordT[tlv.TlvType9, TorV3Addrs]) {
recordProducers = append(recordProducers, &r)
})
n.DNSHostName.WhenSome(func(r tlv.RecordT[tlv.TlvType11, DNSAddress]) {
recordProducers = append(recordProducers, &r)
})
recordProducers = append(recordProducers, RecordsAsProducers(
tlv.MapToRecords(n.ExtraSignedFields),
)...)
return ProduceRecordsSorted(recordProducers...)
}
// Decode deserializes a serialized NodeAnnouncement2 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (n *NodeAnnouncement2) Decode(r io.Reader, _ uint32) error {
var (
color = tlv.ZeroRecordT[tlv.TlvType1, Color]()
alias = tlv.ZeroRecordT[tlv.TlvType3, NodeAlias2]()
ipv4 = tlv.ZeroRecordT[tlv.TlvType5, IPV4Addrs]()
ipv6 = tlv.ZeroRecordT[tlv.TlvType7, IPV6Addrs]()
torV3 = tlv.ZeroRecordT[tlv.TlvType9, TorV3Addrs]()
dns = tlv.ZeroRecordT[tlv.TlvType11, DNSAddress]()
)
stream, err := tlv.NewStream(ProduceRecordsSorted(
&n.Features,
&n.BlockHeight,
&n.NodeID,
&n.Signature,
&alias,
&color,
&ipv4,
&ipv6,
&torV3,
&dns,
)...)
if err != nil {
return err
}
n.Signature.Val.ForceSchnorr()
typeMap, err := stream.DecodeWithParsedTypesP2P(r)
if err != nil {
return err
}
if _, ok := typeMap[n.Alias.TlvType()]; ok {
n.Alias = tlv.SomeRecordT(alias)
}
if _, ok := typeMap[n.Color.TlvType()]; ok {
n.Color = tlv.SomeRecordT(color)
}
if _, ok := typeMap[n.IPV4Addrs.TlvType()]; ok {
n.IPV4Addrs = tlv.SomeRecordT(ipv4)
}
if _, ok := typeMap[n.IPV6Addrs.TlvType()]; ok {
n.IPV6Addrs = tlv.SomeRecordT(ipv6)
}
if _, ok := typeMap[n.TorV3Addrs.TlvType()]; ok {
n.TorV3Addrs = tlv.SomeRecordT(torV3)
}
if _, ok := typeMap[n.DNSHostName.TlvType()]; ok {
n.DNSHostName = tlv.SomeRecordT(dns)
}
n.ExtraSignedFields = ExtraSignedFieldsFromTypeMap(typeMap)
return nil
}
// Encode serializes the target NodeAnnouncement2 into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (n *NodeAnnouncement2) Encode(w *bytes.Buffer, _ uint32) error {
return EncodePureTLVMessage(n, w)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (n *NodeAnnouncement2) MsgType() MessageType {
return MsgNodeAnnouncement2
}
// A compile-time check to ensure NodeAnnouncement2 implements the Message
// interface.
var _ Message = (*NodeAnnouncement2)(nil)
// A compile-time check to ensure NodeAnnouncement2 implements the
// PureTLVMessage interface.
var _ PureTLVMessage = (*NodeAnnouncement2)(nil)
// A compile time check to ensure ChannelAnnouncement2 implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*NodeAnnouncement2)(nil)
// NodeAlias2 represents a UTF-8 encoded node alias with a maximum length of 32
// bytes.
type NodeAlias2 []byte
// Record returns a TLV record for encoding/decoding NodeAlias2.
func (n *NodeAlias2) Record() tlv.Record {
sizeFunc := func() uint64 {
return uint64(len(*n))
}
return tlv.MakeDynamicRecord(
0, n, sizeFunc, nodeAlias2Encoder, nodeAlias2Decoder,
)
}
// ValidateNodeAlias2 validates that the node alias is valid UTF-8 and within
// the 32-byte limit.
func ValidateNodeAlias2(alias []byte) error {
if len(alias) == 0 {
return errors.New("node alias cannot be empty")
}
if len(alias) > 32 {
return fmt.Errorf("node alias exceeds 32 bytes: length %d",
len(alias))
}
if !utf8.Valid(alias) {
return errors.New("node alias contains invalid UTF-8")
}
return nil
}
// nodeAlias2Encoder encodes a NodeAlias2 as raw UTF-8 bytes.
func nodeAlias2Encoder(w io.Writer, val any, _ *[8]byte) error {
if v, ok := val.(*NodeAlias2); ok {
if err := ValidateNodeAlias2(*v); err != nil {
return err
}
_, err := w.Write(*v)
return err
}
return tlv.NewTypeForEncodingErr(val, "NodeAlias2")
}
// nodeAlias2Decoder decodes raw bytes into a NodeAlias2.
func nodeAlias2Decoder(r io.Reader, val any, _ *[8]byte, l uint64) error {
if v, ok := val.(*NodeAlias2); ok {
if l > 32 {
return fmt.Errorf("node alias exceeds 32 bytes: %d", l)
}
aliasBytes := make([]byte, l)
if _, err := io.ReadFull(r, aliasBytes); err != nil {
return err
}
if err := ValidateNodeAlias2(aliasBytes); err != nil {
return err
}
*v = aliasBytes
return nil
}
return tlv.NewTypeForDecodingErr(val, "NodeAlias2", l, 0)
}
// Color is a custom type to represent a color.RGBA that can be encoded/decoded
// as a TLV record.
type Color color.RGBA
// Record returns a TLV record for encoding/decoding Color.
func (c *Color) Record() tlv.Record {
return tlv.MakeStaticRecord(0, c, 3, rgbEncoder, rgbDecoder)
}
// rgbEncoder encodes a Color as RGB bytes.
func rgbEncoder(w io.Writer, val any, _ *[8]byte) error {
if v, ok := val.(*Color); ok {
buf := bytes.NewBuffer(nil)
err := WriteColorRGBA(buf, color.RGBA(*v))
if err != nil {
return err
}
_, err = w.Write(buf.Bytes())
return err
}
return tlv.NewTypeForEncodingErr(val, "Color")
}
// rgbDecoder decodes RGB bytes into a Color.
func rgbDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error {
if v, ok := val.(*Color); ok {
return ReadElements(r, &v.R, &v.G, &v.B)
}
return tlv.NewTypeForDecodingErr(val, "Color", l, 3)
}
// ipv4AddrEncodedSize is the number of bytes required to encode a single ipv4
// address. Four bytes are used to encode the IP address and two bytes for the
// port number.
const ipv4AddrEncodedSize = 4 + 2
// IPV4Addrs is a list of ipv4 addresses that can be encoded as a TLV record.
type IPV4Addrs []*net.TCPAddr
// Record returns a Record that can be used to encode/decode a IPV4Addrs
// to/from a TLV stream.
func (a *IPV4Addrs) Record() tlv.Record {
return tlv.MakeDynamicRecord(
0, a, a.encodedSize, ipv4AddrsEncoder, ipv4AddrsDecoder,
)
}
// encodedSize returns the number of bytes required to encode an IPV4Addrs
// variable.
func (a *IPV4Addrs) encodedSize() uint64 {
return uint64(len(*a) * ipv4AddrEncodedSize)
}
// ipv4AddrsEncoder encodes IPv4 addresses as TLV bytes.
func ipv4AddrsEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*IPV4Addrs); ok {
for _, ip := range *v {
_, err := w.Write(ip.IP.To4())
if err != nil {
return err
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(ip.Port))
_, err = w.Write(port[:])
if err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.IPV4Addrs")
}
// ipv4AddrsDecoder decodes TLV bytes into IPv4 addresses.
func ipv4AddrsDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*IPV4Addrs); ok {
if l%(ipv4AddrEncodedSize) != 0 {
return fmt.Errorf("invalid ipv4 list encoding")
}
var (
numAddrs = int(l / ipv4AddrEncodedSize)
addrs = make([]*net.TCPAddr, 0, numAddrs)
ip [4]byte
port [2]byte
)
for len(addrs) < numAddrs {
_, err := r.Read(ip[:])
if err != nil {
return err
}
_, err = r.Read(port[:])
if err != nil {
return err
}
addrs = append(addrs, &net.TCPAddr{
IP: ip[:],
Port: int(binary.BigEndian.Uint16(port[:])),
})
}
*v = addrs
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.IPV4Addrs")
}
// IPV6Addrs is a list of ipv6 addresses that can be encoded as a TLV record.
type IPV6Addrs []*net.TCPAddr
// Record returns a Record that can be used to encode/decode a IPV4Addrs
// to/from a TLV stream.
func (a *IPV6Addrs) Record() tlv.Record {
return tlv.MakeDynamicRecord(
0, a, a.encodedSize, ipv6AddrsEncoder, ipv6AddrsDecoder,
)
}
// ipv6AddrEncodedSize is the number of bytes required to encode a single ipv6
// address. Sixteen bytes are used to encode the IP address and two bytes for
// the port number.
const ipv6AddrEncodedSize = 16 + 2
// encodedSize returns the number of bytes required to encode an IPV6Addrs
// variable.
func (a *IPV6Addrs) encodedSize() uint64 {
return uint64(len(*a) * ipv6AddrEncodedSize)
}
// ipv6AddrsEncoder encodes IPv6 addresses as TLV bytes.
func ipv6AddrsEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*IPV6Addrs); ok {
for _, ip := range *v {
_, err := w.Write(ip.IP.To16())
if err != nil {
return err
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(ip.Port))
_, err = w.Write(port[:])
if err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.IPV6Addrs")
}
// ipv6AddrsDecoder decodes TLV bytes into IPv6 addresses.
func ipv6AddrsDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*IPV6Addrs); ok {
if l%(ipv6AddrEncodedSize) != 0 {
return fmt.Errorf("invalid ipv6 list encoding")
}
var (
numAddrs = int(l / ipv6AddrEncodedSize)
addrs = make([]*net.TCPAddr, 0, numAddrs)
ip [16]byte
port [2]byte
)
for len(addrs) < numAddrs {
_, err := r.Read(ip[:])
if err != nil {
return err
}
_, err = r.Read(port[:])
if err != nil {
return err
}
addrs = append(addrs, &net.TCPAddr{
IP: ip[:],
Port: int(binary.BigEndian.Uint16(port[:])),
})
}
*v = addrs
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.IPV6Addrs")
}
// TorV3Addrs is a list of tor v3 addresses that can be encoded as a TLV record.
type TorV3Addrs []*tor.OnionAddr
// torV3AddrEncodedSize is the number of bytes required to encode a single tor
// v3 address.
const torV3AddrEncodedSize = tor.V3DecodedLen + 2
// encodedSize returns the number of bytes required to encode an TorV3Addrs
// variable.
func (a *TorV3Addrs) encodedSize() uint64 {
return uint64(len(*a) * torV3AddrEncodedSize)
}
// Record returns a Record that can be used to encode/decode a IPV4Addrs
// to/from a TLV stream.
func (a *TorV3Addrs) Record() tlv.Record {
return tlv.MakeDynamicRecord(
0, a, a.encodedSize, torV3AddrsEncoder, torV3AddrsDecoder,
)
}
// torV3AddrsEncoder encodes Tor v3 addresses as TLV bytes.
func torV3AddrsEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*TorV3Addrs); ok {
for _, addr := range *v {
encodedHostLen := tor.V3Len - tor.OnionSuffixLen
host, err := tor.Base32Encoding.DecodeString(
addr.OnionService[:encodedHostLen],
)
if err != nil {
return err
}
if len(host) != tor.V3DecodedLen {
return fmt.Errorf("expected a tor v3 host "+
"length of %d, got: %d",
tor.V3DecodedLen, len(host))
}
if _, err = w.Write(host); err != nil {
return err
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(addr.Port))
if _, err = w.Write(port[:]); err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.TorV3Addrs")
}
// torV3AddrsDecoder decodes TLV bytes into Tor v3 addresses.
func torV3AddrsDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*TorV3Addrs); ok {
if l%torV3AddrEncodedSize != 0 {
return fmt.Errorf("invalid tor v3 list encoding")
}
var (
numAddrs = int(l / torV3AddrEncodedSize)
addrs = make([]*tor.OnionAddr, 0, numAddrs)
ip [tor.V3DecodedLen]byte
p [2]byte
)
for len(addrs) < numAddrs {
_, err := r.Read(ip[:])
if err != nil {
return err
}
_, err = r.Read(p[:])
if err != nil {
return err
}
onionService := tor.Base32Encoding.EncodeToString(ip[:])
onionService += tor.OnionSuffix
if len(onionService) != tor.V3Len {
return fmt.Errorf("expected a tor v3 onion "+
"service length of %d, got: %d",
tor.V3Len, len(onionService))
}
port := int(binary.BigEndian.Uint16(p[:]))
addrs = append(addrs, &tor.OnionAddr{
OnionService: onionService,
Port: port,
})
}
*v = addrs
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.TorV3Addrs")
}

View File

@@ -0,0 +1,119 @@
package lnwire
import (
"bytes"
"testing"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/require"
)
// TestNodeAnn2EncodeDecode tests the encoding and decoding of the
// NodeAnnouncement2 message using hardcoded byte slices.
func TestNodeAnn2EncodeDecode(t *testing.T) {
t.Parallel()
// We'll create a raw byte stream that represents a valid
// NodeAnnouncement2 message with various known and unknown fields in
// the signed TLV ranges along with the signature in the unsigned range.
rawBytes := []byte{
// Features record.
0x00, // type.
0x02, // length.
0x1, 0x2, // value.
// Color record (optional).
0x01, // type.
0x03, // length.
0xff, 0x00, 0x80, // value: RGB(255, 0, 128).
// BlockHeight record.
0x02, // type.
0x04, // length.
0x00, 0x01, 0x86, 0xa0, // value: 100000.
// Alias record (optional).
0x03, // type.
0x20, // length (32).
0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
0x67, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x54,
0x65, 0x73, 0x74, 0x20, 0x41, 0x6c, 0x69, 0x61,
0x73, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x21, 0x00, // value.
// NodeID record.
0x04, // type.
0x21, // length.
0x02, 0x28, 0xf2, 0xaf, 0xa, 0xbe, 0x32, 0x24, 0x3, 0x48, 0xf,
0xb3, 0xee, 0x17, 0x2f, 0x7f, 0x16, 0x1, 0xe6, 0x7d, 0x1d, 0xa6,
0xca, 0xd4, 0xb, 0x54, 0xc4, 0x46, 0x8d, 0x48, 0x23, 0x6c, 0x39,
// IPV4Addrs record (optional).
0x05, // type.
0x0c, // length (12 bytes).
0x0a, 0x00, 0x00, 0x01, 0x23, 0x28, // 10.0.0.1:9000
0x0a, 0x00, 0x00, 0x01, 0x1f, 0x90, // 10.0.0.1:8080
// IPV6Addrs record (optional).
0x07, // type.
0x12, // length.
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, // IPv6 address
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 2001:db8::1
0x1f, 0x40, // port 8000
// TorV3Addrs record (optional).
0x09, // type.
0x25, // length (37 bytes = 1 address).
// value: 35-byte tor v3 address + 2-byte port.
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,
0x21, 0x22, 0x23,
0x23, 0x28, // port 9000.
// DNSHostName record (optional).
0x0b, // type (11).
0x17, // length (23).
0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
0x67, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, // value.
0x65, 0x2e, 0x63, 0x6f, 0x6d,
0x23, 0x28, // port 9000.
// Signature.
0xa0, // type.
0x40, // length.
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
0x3f, // value.
}
secondSignedRangeType := new(bytes.Buffer)
var buf [8]byte
err := tlv.WriteVarInt(
secondSignedRangeType, pureTLVSignedSecondRangeStart+1, &buf,
)
require.NoError(t, err)
rawBytes = append(rawBytes, secondSignedRangeType.Bytes()...) // type.
rawBytes = append(rawBytes, []byte{
0x02, // length.
0x79, 0x79, // value.
}...)
// Now, create a new empty message and decode the raw bytes into it.
msg := &NodeAnnouncement2{}
r := bytes.NewReader(rawBytes)
err = msg.Decode(r, 0)
require.NoError(t, err)
// Next, encode the message back into a new byte buffer.
var b bytes.Buffer
err = msg.Encode(&b, 0)
require.NoError(t, err)
// The re-encoded bytes should be exactly the same as the original raw
// bytes.
require.Equal(t, rawBytes, b.Bytes())
}

View File

@@ -5,12 +5,14 @@ import (
"fmt"
"image/color"
"math"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/tor"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
)
@@ -1231,6 +1233,10 @@ func (ks *KickoffSig) RandTestMessage(t *rapid.T) Message {
// lnwire.TestMessage interface.
var _ TestMessage = (*NodeAnnouncement1)(nil)
// A compile time check to ensure NodeAnnouncement2 implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*NodeAnnouncement2)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
@@ -1262,6 +1268,131 @@ func (a *NodeAnnouncement1) RandTestMessage(t *rapid.T) Message {
}
}
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (n *NodeAnnouncement2) RandTestMessage(t *rapid.T) Message {
features := RandFeatureVector(t)
blockHeight := uint32(rapid.IntRange(0, 1000000).Draw(t, "blockHeight"))
// Generate random compressed public key for node ID
pubKey := RandPubKey(t)
var nodeID [33]byte
copy(nodeID[:], pubKey.SerializeCompressed())
msg := &NodeAnnouncement2{
Features: tlv.NewRecordT[tlv.TlvType0](*features),
BlockHeight: tlv.NewPrimitiveRecord[tlv.TlvType2](
blockHeight,
),
NodeID: tlv.NewPrimitiveRecord[tlv.TlvType4](nodeID),
ExtraSignedFields: make(map[uint64][]byte),
}
msg.Signature.Val = RandSignature(t)
msg.Signature.Val.ForceSchnorr()
// Test optional fields one by one
if rapid.Bool().Draw(t, "includeColor") {
rgbColor := Color{
R: uint8(rapid.IntRange(0, 255).Draw(t, "rgbR")),
G: uint8(rapid.IntRange(0, 255).Draw(t, "rgbG")),
B: uint8(rapid.IntRange(0, 255).Draw(t, "rgbB")),
}
colorRecord := tlv.ZeroRecordT[tlv.TlvType1, Color]()
colorRecord.Val = rgbColor
msg.Color = tlv.SomeRecordT(colorRecord)
}
if rapid.Bool().Draw(t, "includeAlias") {
aliasBytes := RandNodeAlias2(t)
aliasRecord := tlv.ZeroRecordT[tlv.TlvType3, NodeAlias2]()
aliasRecord.Val = aliasBytes
msg.Alias = tlv.SomeRecordT(aliasRecord)
}
if rapid.Bool().Draw(t, "includeIPV4Addrs") {
ipv4Addrs := make(IPV4Addrs, 1)
ip := make(net.IP, 4)
ip[0] = uint8(rapid.IntRange(1, 223).Draw(t, "ip4_0"))
ip[1] = uint8(rapid.IntRange(0, 255).Draw(t, "ip4_1"))
ip[2] = uint8(rapid.IntRange(0, 255).Draw(t, "ip4_2"))
ip[3] = uint8(rapid.IntRange(1, 254).Draw(t, "ip4_3"))
ipv4Addrs[0] = &net.TCPAddr{
IP: ip,
Port: rapid.IntRange(1, 65535).Draw(t, "port4"),
}
ipv4Record := tlv.ZeroRecordT[tlv.TlvType5, IPV4Addrs]()
ipv4Record.Val = ipv4Addrs
msg.IPV4Addrs = tlv.SomeRecordT(ipv4Record)
}
if rapid.Bool().Draw(t, "includeIPV6Addrs") {
ipv6Addrs := make(IPV6Addrs, 1)
ip := make(net.IP, 16)
// Generate random IPv6 address.
for j := 0; j < 16; j++ {
ip[j] = uint8(rapid.IntRange(0, 255).Draw(
t, fmt.Sprintf("ip6_%d", j)),
)
}
ipv6Addrs[0] = &net.TCPAddr{
IP: ip,
Port: rapid.IntRange(1, 65535).Draw(t, "port6"),
}
ipv6Record := tlv.ZeroRecordT[tlv.TlvType7, IPV6Addrs]()
ipv6Record.Val = ipv6Addrs
msg.IPV6Addrs = tlv.SomeRecordT(ipv6Record)
}
if rapid.Bool().Draw(t, "includeTorV3Addrs") {
torV3Addrs := make(TorV3Addrs, 1)
onionBytes := rapid.SliceOfN(rapid.Byte(), 35, 35).Draw(
t, "onion",
)
onionService := tor.Base32Encoding.EncodeToString(onionBytes) +
tor.OnionSuffix
torV3Addrs[0] = &tor.OnionAddr{
OnionService: onionService,
Port: rapid.IntRange(1, 65535).Draw(
t, "torPort",
),
}
torV3Record := tlv.ZeroRecordT[tlv.TlvType9, TorV3Addrs]()
torV3Record.Val = torV3Addrs
msg.TorV3Addrs = tlv.SomeRecordT(torV3Record)
}
if rapid.Bool().Draw(t, "includeDNSHostName") {
// Generate a valid DNS hostname.
hostname := genValidHostname(t)
port := rapid.Uint16Range(1, 65535).Draw(t, "dnsPort")
dnsAddr := DNSAddress{
Hostname: hostname,
Port: port,
}
dnsRecord := tlv.ZeroRecordT[tlv.TlvType11, DNSAddress]()
dnsRecord.Val = dnsAddr
msg.DNSHostName = tlv.SomeRecordT(dnsRecord)
}
randRecs, _ := RandSignedRangeRecords(t)
if len(randRecs) > 0 {
msg.ExtraSignedFields = ExtraSignedFields(randRecs)
}
return msg
}
// A compile time check to ensure OpenChannel implements the TestMessage
// interface.
var _ TestMessage = (*OpenChannel)(nil)

View File

@@ -152,6 +152,19 @@ func RandNodeAlias(t *rapid.T) NodeAlias {
return alias
}
// RandNodeAlias2 generates a random node alias that is compatible with
// NodeAlias2 validation (non-empty, max 32 bytes, valid UTF-8).
func RandNodeAlias2(t *rapid.T) NodeAlias2 {
// Ensure length is at least 1 to satisfy NodeAlias2 validation
aliasLength := rapid.IntRange(1, 32).Draw(t, "aliasLength")
aliasBytes := rapid.SliceOfN(
rapid.SampledFrom([]rune(charset)), aliasLength, aliasLength,
).Draw(t, "alias")
return []byte(string(aliasBytes))
}
// RandNetAddrs generates random network addresses.
func RandNetAddrs(t *rapid.T) []net.Addr {
numAddresses := rapid.IntRange(0, 5).Draw(t, "numAddresses")