2024-10-29 18:25:07 +02:00
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"image/color"
|
|
|
|
|
"net"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
2025-09-04 12:03:40 +02:00
|
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
2024-10-29 18:25:07 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2025-09-04 12:03:40 +02:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2024-10-29 18:25:07 +02:00
|
|
|
)
|
|
|
|
|
|
2025-09-01 12:40:15 +02:00
|
|
|
// Node represents an individual vertex/node within the channel graph.
|
2024-10-29 18:25:07 +02:00
|
|
|
// A node is connected to other nodes by one or more channel edges emanating
|
|
|
|
|
// from it. As the graph is directed, a node will also have an incoming edge
|
|
|
|
|
// attached to it for each outgoing edge.
|
2025-09-01 12:40:15 +02:00
|
|
|
type Node struct {
|
2025-09-04 12:03:40 +02:00
|
|
|
// Version is the gossip version that this node was advertised on.
|
|
|
|
|
Version lnwire.GossipVersion
|
|
|
|
|
|
2024-10-29 18:25:07 +02:00
|
|
|
// PubKeyBytes is the raw bytes of the public key of the target node.
|
|
|
|
|
PubKeyBytes [33]byte
|
|
|
|
|
|
|
|
|
|
// LastUpdate is the last time the vertex information for this node has
|
|
|
|
|
// been updated.
|
|
|
|
|
LastUpdate time.Time
|
|
|
|
|
|
2025-10-26 12:05:08 +02:00
|
|
|
// LastBlockHeight is the block height that timestamps the last update
|
|
|
|
|
// we received for this node. This is only used if this is a V2 node
|
|
|
|
|
// announcement.
|
|
|
|
|
LastBlockHeight uint32
|
|
|
|
|
|
2024-10-29 18:25:07 +02:00
|
|
|
// Address is the TCP address this node is reachable over.
|
|
|
|
|
Addresses []net.Addr
|
|
|
|
|
|
|
|
|
|
// Color is the selected color for the node.
|
2025-09-04 12:03:40 +02:00
|
|
|
Color fn.Option[color.RGBA]
|
2024-10-29 18:25:07 +02:00
|
|
|
|
|
|
|
|
// Alias is a nick-name for the node. The alias can be used to confirm
|
|
|
|
|
// a node's identity or to serve as a short ID for an address book.
|
2025-09-04 12:03:40 +02:00
|
|
|
Alias fn.Option[string]
|
2024-10-29 18:25:07 +02:00
|
|
|
|
|
|
|
|
// AuthSigBytes is the raw signature under the advertised public key
|
|
|
|
|
// which serves to authenticate the attributes announced by this node.
|
|
|
|
|
AuthSigBytes []byte
|
|
|
|
|
|
|
|
|
|
// Features is the list of protocol features supported by this node.
|
|
|
|
|
Features *lnwire.FeatureVector
|
|
|
|
|
|
|
|
|
|
// ExtraOpaqueData is the set of data that was appended to this
|
|
|
|
|
// message, some of which we may not actually know how to iterate or
|
|
|
|
|
// parse. By holding onto this data, we ensure that we're able to
|
|
|
|
|
// properly validate the set of signatures that cover these new fields,
|
|
|
|
|
// and ensure we're able to make upgrades to the network in a forwards
|
2025-10-26 12:05:08 +02:00
|
|
|
// compatible manner. This is only used for V1 node announcements.
|
2024-10-29 18:25:07 +02:00
|
|
|
ExtraOpaqueData []byte
|
2025-10-26 12:05:08 +02:00
|
|
|
|
|
|
|
|
// ExtraSignedFields is a map of extra fields that are covered by the
|
|
|
|
|
// node announcement's signature that we have not explicitly parsed.
|
|
|
|
|
// This is only used for version 2 node announcements and beyond.
|
|
|
|
|
ExtraSignedFields map[uint64][]byte
|
2024-10-29 18:25:07 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:03:40 +02:00
|
|
|
// NodeV1Fields houses the fields that are specific to a version 1 node
|
|
|
|
|
// announcement.
|
|
|
|
|
type NodeV1Fields struct {
|
|
|
|
|
// Address is the TCP address this node is reachable over.
|
|
|
|
|
Addresses []net.Addr
|
|
|
|
|
|
|
|
|
|
// AuthSigBytes is the raw signature under the advertised public key
|
|
|
|
|
// which serves to authenticate the attributes announced by this node.
|
|
|
|
|
AuthSigBytes []byte
|
|
|
|
|
|
|
|
|
|
// Features is the list of protocol features supported by this node.
|
|
|
|
|
Features *lnwire.RawFeatureVector
|
|
|
|
|
|
|
|
|
|
// Color is the selected color for the node.
|
|
|
|
|
Color color.RGBA
|
|
|
|
|
|
|
|
|
|
// Alias is a nick-name for the node. The alias can be used to confirm
|
|
|
|
|
// a node's identity or to serve as a short ID for an address book.
|
|
|
|
|
Alias string
|
|
|
|
|
|
|
|
|
|
// LastUpdate is the last time the vertex information for this node has
|
|
|
|
|
// been updated.
|
|
|
|
|
LastUpdate time.Time
|
|
|
|
|
|
|
|
|
|
// ExtraOpaqueData is the set of data that was appended to this
|
|
|
|
|
// message, some of which we may not actually know how to iterate or
|
|
|
|
|
// parse. By holding onto this data, we ensure that we're able to
|
|
|
|
|
// properly validate the set of signatures that cover these new fields,
|
|
|
|
|
// and ensure we're able to make upgrades to the network in a forwards
|
|
|
|
|
// compatible manner.
|
|
|
|
|
ExtraOpaqueData []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewV1Node creates a new version 1 node from the passed fields.
|
|
|
|
|
func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node {
|
|
|
|
|
return &Node{
|
|
|
|
|
Version: lnwire.GossipVersion1,
|
|
|
|
|
PubKeyBytes: pub,
|
|
|
|
|
Addresses: n.Addresses,
|
|
|
|
|
AuthSigBytes: n.AuthSigBytes,
|
|
|
|
|
Features: lnwire.NewFeatureVector(
|
|
|
|
|
n.Features, lnwire.Features,
|
|
|
|
|
),
|
|
|
|
|
Color: fn.Some(n.Color),
|
|
|
|
|
Alias: fn.Some(n.Alias),
|
|
|
|
|
LastUpdate: n.LastUpdate,
|
|
|
|
|
ExtraOpaqueData: n.ExtraOpaqueData,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 12:05:08 +02:00
|
|
|
// NodeV2Fields houses the fields that are specific to a version 2 node
|
|
|
|
|
// announcement.
|
|
|
|
|
type NodeV2Fields struct {
|
|
|
|
|
// LastBlockHeight is the block height that timestamps the last update
|
|
|
|
|
// we received for this node.
|
|
|
|
|
LastBlockHeight uint32
|
|
|
|
|
|
|
|
|
|
// Address is the TCP address this node is reachable over.
|
|
|
|
|
Addresses []net.Addr
|
|
|
|
|
|
|
|
|
|
// Color is the selected color for the node.
|
|
|
|
|
Color fn.Option[color.RGBA]
|
|
|
|
|
|
|
|
|
|
// Alias is a nick-name for the node. The alias can be used to confirm
|
|
|
|
|
// a node's identity or to serve as a short ID for an address book.
|
|
|
|
|
Alias fn.Option[string]
|
|
|
|
|
|
|
|
|
|
// Signature is the schnorr signature under the advertised public key
|
|
|
|
|
// which serves to authenticate the attributes announced by this node.
|
|
|
|
|
Signature []byte
|
|
|
|
|
|
|
|
|
|
// Features is the list of protocol features supported by this node.
|
|
|
|
|
Features *lnwire.RawFeatureVector
|
|
|
|
|
|
|
|
|
|
// ExtraSignedFields is a map of extra fields that are covered by the
|
|
|
|
|
// node announcement's signature that we have not explicitly parsed.
|
|
|
|
|
ExtraSignedFields map[uint64][]byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewV2Node creates a new version 2 node from the passed fields.
|
|
|
|
|
func NewV2Node(pub route.Vertex, n *NodeV2Fields) *Node {
|
|
|
|
|
return &Node{
|
|
|
|
|
Version: lnwire.GossipVersion2,
|
|
|
|
|
PubKeyBytes: pub,
|
|
|
|
|
Addresses: n.Addresses,
|
|
|
|
|
AuthSigBytes: n.Signature,
|
|
|
|
|
Features: lnwire.NewFeatureVector(
|
|
|
|
|
n.Features, lnwire.Features,
|
|
|
|
|
),
|
|
|
|
|
LastBlockHeight: n.LastBlockHeight,
|
|
|
|
|
Color: n.Color,
|
|
|
|
|
Alias: n.Alias,
|
|
|
|
|
LastUpdate: time.Unix(0, 0),
|
|
|
|
|
ExtraSignedFields: n.ExtraSignedFields,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:03:40 +02:00
|
|
|
// NewV1ShellNode creates a new shell version 1 node.
|
|
|
|
|
func NewV1ShellNode(pubKey route.Vertex) *Node {
|
|
|
|
|
return NewShellNode(lnwire.GossipVersion1, pubKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewShellNode creates a new shell node with the given gossip version and
|
|
|
|
|
// public key.
|
|
|
|
|
func NewShellNode(v lnwire.GossipVersion, pubKey route.Vertex) *Node {
|
|
|
|
|
return &Node{
|
|
|
|
|
Version: v,
|
|
|
|
|
PubKeyBytes: pubKey,
|
|
|
|
|
Features: lnwire.EmptyFeatureVector(),
|
|
|
|
|
LastUpdate: time.Unix(0, 0),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 11:09:33 +02:00
|
|
|
// HaveAnnouncement returns true if we have received a node announcement for
|
|
|
|
|
// this node. We determine this by checking if we have a signature for the
|
|
|
|
|
// announcement.
|
|
|
|
|
func (n *Node) HaveAnnouncement() bool {
|
|
|
|
|
return len(n.AuthSigBytes) > 0
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-29 18:25:07 +02:00
|
|
|
// PubKey is the node's long-term identity public key. This key will be used to
|
|
|
|
|
// authenticated any advertisements/updates sent by the node.
|
2025-09-04 11:09:33 +02:00
|
|
|
func (n *Node) PubKey() (*btcec.PublicKey, error) {
|
2025-12-04 11:23:42 +02:00
|
|
|
return btcec.ParsePubKey(n.PubKeyBytes[:])
|
2024-10-29 18:25:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeAnnouncement retrieves the latest node announcement of the node.
|
2025-09-04 11:09:33 +02:00
|
|
|
func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1,
|
2024-10-29 18:25:07 +02:00
|
|
|
error) {
|
|
|
|
|
|
2025-09-04 11:09:33 +02:00
|
|
|
// Error out if we request the signed announcement, but we don't have
|
|
|
|
|
// a signature for this announcement.
|
|
|
|
|
if !n.HaveAnnouncement() && signed {
|
2024-10-29 18:25:07 +02:00
|
|
|
return nil, fmt.Errorf("node does not have node announcement")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:03:40 +02:00
|
|
|
alias, err := lnwire.NewNodeAlias(n.Alias.UnwrapOr(""))
|
2024-10-29 18:25:07 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 11:00:28 +02:00
|
|
|
nodeAnn := &lnwire.NodeAnnouncement1{
|
2025-09-04 11:09:33 +02:00
|
|
|
Features: n.Features.RawFeatureVector,
|
|
|
|
|
NodeID: n.PubKeyBytes,
|
2025-09-04 12:03:40 +02:00
|
|
|
RGBColor: n.Color.UnwrapOr(color.RGBA{}),
|
2024-10-29 18:25:07 +02:00
|
|
|
Alias: alias,
|
2025-09-04 11:09:33 +02:00
|
|
|
Addresses: n.Addresses,
|
|
|
|
|
Timestamp: uint32(n.LastUpdate.Unix()),
|
|
|
|
|
ExtraOpaqueData: n.ExtraOpaqueData,
|
2024-10-29 18:25:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !signed {
|
|
|
|
|
return nodeAnn, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 11:09:33 +02:00
|
|
|
sig, err := lnwire.NewSigFromECDSARawSignature(n.AuthSigBytes)
|
2024-10-29 18:25:07 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nodeAnn.Signature = sig
|
|
|
|
|
|
|
|
|
|
return nodeAnn, nil
|
|
|
|
|
}
|
2025-02-05 08:20:10 +02:00
|
|
|
|
2025-09-01 12:40:15 +02:00
|
|
|
// NodeFromWireAnnouncement creates a Node instance from an
|
multi: drop tor v2 onion production, keep wire codec faithful
Tor stopped serving v2 onion services in October 2021; lnd should not
produce v2 addresses anymore, but it must still verify signatures on
and re-broadcast peer NodeAnnouncement messages that carry v2 entries.
Stop accepting v2 as configuration input (lncfg), strip the legacy
`--tor.v2` flag from the sample config, and remove the
`tor.OnionHostToFakeIP` helper. Operator entry points (`--externalip`,
`--listen`, `lncli connect`, `lncli wtclient towers add`) fail fast on
a v2 `.onion` string, so upgrading nodes must remove any v2 entry from
`lnd.conf` before lnd will start.
Filter persisted v2 state before use without rewriting on-disk records:
the self-announcement builder strips any v2 entry inherited from the
stored self-node; the watchtower client drops v2 entries from each
persisted tower's address list (skipping the tower entirely if no
non-v2 address remains); the autopilot connector, graph bootstrapper,
and static-channel backup restore paths skip v2 entries before
attempting outbound dials. Restrict the Tor controller's ADD_ONION
path to v3 keys, including the encrypted on-disk legacy-key fallback.
For inbound announcements, keep the wire codec wire-faithful:
`lnwire.WriteOnionAddr`, `graph/db.encodeOnionAddr`, and the matching
decoders round-trip v2 bytes so `DataToSign` reproduces the bytes the
remote peer signed, signature validation succeeds, and the announcement
is persisted to the graph DB and re-broadcast across restarts byte-for-
byte. RPC surfaces continue to expose the full address set so external
tools can independently reproduce and verify the signed bytes.
Add a netann regression test that signs a [v3, v2, ipv4] announcement,
round-trips it through Encode/Decode, verifies the signature, and
confirms the resulting models.Node preserves the v2 entry. Add a
graph bootstrapper test asserting v2 entries are skipped while v3 and
plain TCP entries on the same node still surface as bootstrap
candidates.
2026-05-22 09:42:37 -03:00
|
|
|
// lnwire.NodeAnnouncement1 message. The address list from msg.Addresses
|
|
|
|
|
// is copied verbatim, including legacy entries such as tor v2 onion
|
|
|
|
|
// addresses that lnd no longer produces itself. This is required so
|
|
|
|
|
// that Node.NodeAnnouncement can later reconstruct the exact byte
|
|
|
|
|
// sequence the remote peer signed, allowing signature verification and
|
|
|
|
|
// re-broadcast to succeed across restarts.
|
2025-09-22 11:00:28 +02:00
|
|
|
func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node {
|
2025-02-05 08:20:10 +02:00
|
|
|
timestamp := time.Unix(int64(msg.Timestamp), 0)
|
|
|
|
|
|
2025-09-04 12:03:40 +02:00
|
|
|
return NewV1Node(
|
|
|
|
|
msg.NodeID,
|
|
|
|
|
&NodeV1Fields{
|
|
|
|
|
LastUpdate: timestamp,
|
|
|
|
|
Addresses: msg.Addresses,
|
|
|
|
|
Alias: msg.Alias.String(),
|
|
|
|
|
AuthSigBytes: msg.Signature.ToSignatureBytes(),
|
|
|
|
|
Features: msg.Features,
|
|
|
|
|
Color: msg.RGBColor,
|
|
|
|
|
ExtraOpaqueData: msg.ExtraOpaqueData,
|
|
|
|
|
},
|
|
|
|
|
)
|
2025-02-05 08:20:10 +02:00
|
|
|
}
|