multi: freeze graph SQL migration logic

Copy over all the code that the graph SQL migration needs to a
separate folder. This will let us advance the main graph SQL CRUD code
without worrying about changing the sql migration code. It will also let
us change the SQL queries without changing the migration. In this
commit, only the migration logic is "frozen" but in an upcoming commit,
the sqlc queries & models will be frozen too.
This commit is contained in:
Elle Mouton authored and yyforyongyu committed 2025-11-12 22:54:05 +08:00
1 parent 07d0f0842c
commit 125325f625
17 files changed
+5136 -6

No files matched your search

+324
View File
@@ -0,0 +1,324 @@
package migration1
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)
// addressType specifies the network protocol and version that should be used
// when connecting to a node at a particular address.
type addressType uint8
const (
// tcp4Addr denotes an IPv4 TCP address.
tcp4Addr addressType = 0
// tcp6Addr denotes an IPv6 TCP address.
tcp6Addr addressType = 1
// v2OnionAddr denotes a version 2 Tor onion service address.
v2OnionAddr addressType = 2
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
v3OnionAddr addressType = 3
// opaqueAddrs denotes an address (or a set of addresses) that LND was
// not able to parse since LND is not yet aware of the address type.
opaqueAddrs addressType = 4
// dnsAddr denotes a DNS address type.
dnsAddr addressType = 5
)
// encodeDNSAddr encodes a DNS address.
func encodeDNSAddr(w io.Writer, addr *lnwire.DNSAddress) error {
if _, err := w.Write([]byte{byte(dnsAddr)}); err != nil {
return err
}
// Write the length of the hostname.
hostLen := len(addr.Hostname)
if _, err := w.Write([]byte{byte(hostLen)}); err != nil {
return err
}
if _, err := w.Write([]byte(addr.Hostname)); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], addr.Port)
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// encodeTCPAddr serializes a TCP address into its compact raw bytes
// representation.
func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
var (
addrType byte
ip []byte
)
if addr.IP.To4() != nil {
addrType = byte(tcp4Addr)
ip = addr.IP.To4()
} else {
addrType = byte(tcp6Addr)
ip = addr.IP.To16()
}
if ip == nil {
return fmt.Errorf("unable to encode IP %v", addr.IP)
}
if _, err := w.Write([]byte{addrType}); err != nil {
return err
}
if _, err := w.Write(ip); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// encodeOnionAddr serializes an onion address into its compact raw bytes
// representation.
func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error {
var suffixIndex int
hostLen := len(addr.OnionService)
switch hostLen {
case tor.V2Len:
if _, err := w.Write([]byte{byte(v2OnionAddr)}); err != nil {
return err
}
suffixIndex = tor.V2Len - tor.OnionSuffixLen
case tor.V3Len:
if _, err := w.Write([]byte{byte(v3OnionAddr)}); err != nil {
return err
}
suffixIndex = tor.V3Len - tor.OnionSuffixLen
default:
return errors.New("unknown onion service length")
}
suffix := addr.OnionService[suffixIndex:]
if suffix != tor.OnionSuffix {
return fmt.Errorf("invalid suffix \"%v\"", suffix)
}
host, err := tor.Base32Encoding.DecodeString(
addr.OnionService[:suffixIndex],
)
if err != nil {
return err
}
// Sanity check the decoded length.
switch {
case hostLen == tor.V2Len && len(host) != tor.V2DecodedLen:
return fmt.Errorf("onion service %v decoded to invalid host %x",
addr.OnionService, host)
case hostLen == tor.V3Len && len(host) != tor.V3DecodedLen:
return fmt.Errorf("onion service %v decoded to invalid host %x",
addr.OnionService, host)
}
if _, err := w.Write(host); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// encodeOpaqueAddrs serializes the lnwire.OpaqueAddrs type to a raw set of
// bytes that we will persist.
func encodeOpaqueAddrs(w io.Writer, addr *lnwire.OpaqueAddrs) error {
// Write the type byte.
if _, err := w.Write([]byte{byte(opaqueAddrs)}); err != nil {
return err
}
// Write the length of the payload.
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(addr.Payload)))
if _, err := w.Write(l[:]); err != nil {
return err
}
// Write the payload.
_, err := w.Write(addr.Payload)
return err
}
// DeserializeAddr reads the serialized raw representation of an address and
// deserializes it into the actual address. This allows us to avoid address
// resolution within the channeldb package.
func DeserializeAddr(r io.Reader) (net.Addr, error) {
var addrType [1]byte
if _, err := r.Read(addrType[:]); err != nil {
return nil, err
}
var address net.Addr
switch addressType(addrType[0]) {
case tcp4Addr:
var ip [4]byte
if _, err := r.Read(ip[:]); err != nil {
return nil, err
}
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
case tcp6Addr:
var ip [16]byte
if _, err := r.Read(ip[:]); err != nil {
return nil, err
}
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
case v2OnionAddr:
var h [tor.V2DecodedLen]byte
if _, err := r.Read(h[:]); err != nil {
return nil, err
}
var p [2]byte
if _, err := r.Read(p[:]); err != nil {
return nil, err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
case v3OnionAddr:
var h [tor.V3DecodedLen]byte
if _, err := r.Read(h[:]); err != nil {
return nil, err
}
var p [2]byte
if _, err := r.Read(p[:]); err != nil {
return nil, err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
case dnsAddr:
// Read the length of the hostname.
var hostLen [1]byte
if _, err := r.Read(hostLen[:]); err != nil {
return nil, err
}
// Read the hostname.
hostname := make([]byte, hostLen[0])
if _, err := r.Read(hostname); err != nil {
return nil, err
}
// Read the port.
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &lnwire.DNSAddress{
Hostname: string(hostname),
Port: binary.BigEndian.Uint16(port[:]),
}
case opaqueAddrs:
// Read the length of the payload.
var l [2]byte
if _, err := r.Read(l[:]); err != nil {
return nil, err
}
// Read the payload.
payload := make([]byte, binary.BigEndian.Uint16(l[:]))
if _, err := r.Read(payload); err != nil {
return nil, err
}
address = &lnwire.OpaqueAddrs{
Payload: payload,
}
default:
return nil, ErrUnknownAddressType
}
return address, nil
}
// SerializeAddr serializes an address into its raw bytes representation so that
// it can be deserialized without requiring address resolution.
func SerializeAddr(w io.Writer, address net.Addr) error {
switch addr := address.(type) {
case *net.TCPAddr:
return encodeTCPAddr(w, addr)
case *tor.OnionAddr:
return encodeOnionAddr(w, addr)
case *lnwire.OpaqueAddrs:
return encodeOpaqueAddrs(w, addr)
case *lnwire.DNSAddress:
return encodeDNSAddr(w, addr)
default:
return ErrUnknownAddressType
}
}
+80
View File
@@ -0,0 +1,80 @@
package migration1
import (
"encoding/binary"
"fmt"
"image/color"
"io"
"strconv"
"github.com/btcsuite/btcd/wire"
)
var (
// byteOrder defines the preferred byte order, which is Big Endian.
byteOrder = binary.BigEndian
)
// WriteOutpoint writes an outpoint to the passed writer using the minimal
// amount of bytes possible.
func WriteOutpoint(w io.Writer, o *wire.OutPoint) error {
if _, err := w.Write(o.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, o.Index); err != nil {
return err
}
return nil
}
// ReadOutpoint reads an outpoint from the passed reader that was previously
// written using the WriteOutpoint struct.
func ReadOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
// EncodeHexColor takes a color and returns it in hex code format.
func EncodeHexColor(color color.RGBA) string {
return fmt.Sprintf("#%02x%02x%02x", color.R, color.G, color.B)
}
// DecodeHexColor takes a hex color string like "#rrggbb" and returns a
// color.RGBA.
func DecodeHexColor(hex string) (color.RGBA, error) {
if len(hex) != 7 || hex[0] != '#' {
return color.RGBA{}, fmt.Errorf("invalid hex color string: %s",
hex)
}
r, err := strconv.ParseUint(hex[1:3], 16, 8)
if err != nil {
return color.RGBA{}, fmt.Errorf("invalid red component: %w",
err)
}
g, err := strconv.ParseUint(hex[3:5], 16, 8)
if err != nil {
return color.RGBA{}, fmt.Errorf("invalid green component: %w",
err)
}
b, err := strconv.ParseUint(hex[5:7], 16, 8)
if err != nil {
return color.RGBA{}, fmt.Errorf("invalid blue component: %w",
err)
}
return color.RGBA{
R: uint8(r),
G: uint8(g),
B: uint8(b),
}, nil
}
+84
View File
@@ -0,0 +1,84 @@
package migration1
import (
"errors"
"fmt"
)
var (
// ErrEdgePolicyOptionalFieldNotFound is an error returned if a channel
// policy field is not found in the db even though its message flags
// indicate it should be.
ErrEdgePolicyOptionalFieldNotFound = fmt.Errorf("optional field not " +
"present")
// ErrParsingExtraTLVBytes is returned when we attempt to parse
// extra opaque bytes as a TLV stream, but the parsing fails.
ErrParsingExtraTLVBytes = fmt.Errorf("error parsing extra TLV bytes")
// ErrGraphNotFound is returned when at least one of the components of
// graph doesn't exist.
ErrGraphNotFound = fmt.Errorf("graph bucket not initialized")
// ErrGraphNeverPruned is returned when graph was never pruned.
ErrGraphNeverPruned = fmt.Errorf("graph never pruned")
// ErrSourceNodeNotSet is returned if the source node of the graph
// hasn't been added The source node is the center node within a
// star-graph.
ErrSourceNodeNotSet = fmt.Errorf("source node does not exist")
// ErrGraphNodesNotFound is returned in case none of the nodes has
// been added in graph node bucket.
ErrGraphNodesNotFound = fmt.Errorf("no graph nodes exist")
// ErrGraphNoEdgesFound is returned in case of none of the channel/edges
// has been added in graph edge bucket.
ErrGraphNoEdgesFound = fmt.Errorf("no graph edges exist")
// ErrGraphNodeNotFound is returned when we're unable to find the target
// node.
ErrGraphNodeNotFound = fmt.Errorf("unable to find node")
// ErrZombieEdge is an error returned when we attempt to look up an edge
// but it is marked as a zombie within the zombie index.
ErrZombieEdge = errors.New("edge marked as zombie")
// ErrEdgeNotFound is returned when an edge for the target chanID
// can't be found.
ErrEdgeNotFound = fmt.Errorf("edge not found")
// ErrEdgeAlreadyExist is returned when edge with specific
// channel id can't be added because it already exist.
ErrEdgeAlreadyExist = fmt.Errorf("edge already exist")
// ErrNodeAliasNotFound is returned when alias for node can't be found.
ErrNodeAliasNotFound = fmt.Errorf("alias for node not found")
// ErrClosedScidsNotFound is returned when the closed scid bucket
// hasn't been created.
ErrClosedScidsNotFound = fmt.Errorf("closed scid bucket doesn't exist")
// ErrZombieEdgeNotFound is an error returned when we attempt to find an
// edge in the zombie index which is not there.
ErrZombieEdgeNotFound = errors.New("edge not found in zombie index")
// ErrUnknownAddressType is returned when a node's addressType is not
// an expected value.
ErrUnknownAddressType = fmt.Errorf("address type cannot be resolved")
// ErrCantCheckIfZombieEdgeStr is an error returned when we
// attempt to check if an edge is a zombie but encounter an error.
ErrCantCheckIfZombieEdgeStr = fmt.Errorf("unable to check if edge " +
"is a zombie")
)
// ErrTooManyExtraOpaqueBytes creates an error which should be returned if the
// caller attempts to write an announcement message which bares too many extra
// opaque bytes. We limit this value in order to ensure that we don't waste
// disk space due to nodes unnecessarily padding out their announcements with
// garbage data.
func ErrTooManyExtraOpaqueBytes(numBytes int) error {
return fmt.Errorf("max allowed number of opaque bytes is %v, received "+
"%v bytes", MaxAllowedExtraOpaqueBytes, numBytes)
}
+37
View File
@@ -0,0 +1,37 @@
package migration1
import (
"context"
"github.com/lightningnetwork/lnd/graph/db/migration1/models"
)
// V1Store represents the main interface for the channel graph database for all
// channels and nodes gossiped via the V1 gossip protocol as defined in BOLT 7.
type V1Store interface {
// ForEachNode iterates through all the stored vertices/nodes in the
// graph, executing the passed callback with each node encountered. If
// the callback returns an error, then the transaction is aborted and
// the iteration stops early.
ForEachNode(ctx context.Context, cb func(*models.Node) error,
reset func()) error
// ForEachChannel iterates through all the channel edges stored within
// the graph and invokes the passed callback for each edge. The callback
// takes two edges as since this is a directed graph, both the in/out
// edges are visited. If the callback returns an error, then the
// transaction is aborted and the iteration stops early.
//
// NOTE: If an edge can't be found, or wasn't advertised, then a nil
// pointer for that particular channel edge routing policy will be
// passed into the callback.
ForEachChannel(ctx context.Context, cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error,
reset func()) error
// SourceNode returns the source node of the graph. The source node is
// treated as the center node within a star-graph. This method may be
// used to kick off a path finding algorithm in order to explore the
// reachability of another node based off the source node.
SourceNode(ctx context.Context) (*models.Node, error)
}
File diff suppressed because it is too large. Load diff
+31
View File
@@ -0,0 +1,31 @@
package migration1
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "GRDB"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
@@ -0,0 +1,35 @@
package models
// ChannelAuthProof is the authentication proof (the signature portion) for a
// channel. Using the four signatures contained in the struct, and some
// auxiliary knowledge (the funding script, node identities, and outpoint) nodes
// on the network are able to validate the authenticity and existence of a
// channel. Each of these signatures signs the following digest: chanID ||
// nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len ||
// features.
type ChannelAuthProof struct {
// NodeSig1Bytes are the raw bytes of the first node signature encoded
// in DER format.
NodeSig1Bytes []byte
// NodeSig2Bytes are the raw bytes of the second node signature
// encoded in DER format.
NodeSig2Bytes []byte
// BitcoinSig1Bytes are the raw bytes of the first bitcoin signature
// encoded in DER format.
BitcoinSig1Bytes []byte
// BitcoinSig2Bytes are the raw bytes of the second bitcoin signature
// encoded in DER format.
BitcoinSig2Bytes []byte
}
// IsEmpty check is the authentication proof is empty Proof is empty if at
// least one of the signatures are equal to nil.
func (c *ChannelAuthProof) IsEmpty() bool {
return len(c.NodeSig1Bytes) == 0 ||
len(c.NodeSig2Bytes) == 0 ||
len(c.BitcoinSig1Bytes) == 0 ||
len(c.BitcoinSig2Bytes) == 0
}
@@ -0,0 +1,69 @@
package models
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
// ChannelEdgeInfo represents a fully authenticated channel along with all its
// unique attributes. Once an authenticated channel announcement has been
// processed on the network, then an instance of ChannelEdgeInfo encapsulating
// the channels attributes is stored. The other portions relevant to routing
// policy of a channel are stored within a ChannelEdgePolicy for each direction
// of the channel.
type ChannelEdgeInfo struct {
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// ChainHash is the hash that uniquely identifies the chain that this
// channel was opened within.
ChainHash chainhash.Hash
// NodeKey1Bytes is the raw public key of the first node.
NodeKey1Bytes [33]byte
// NodeKey2Bytes is the raw public key of the first node.
NodeKey2Bytes [33]byte
// BitcoinKey1Bytes is the raw public key of the first node.
BitcoinKey1Bytes [33]byte
// BitcoinKey2Bytes is the raw public key of the first node.
BitcoinKey2Bytes [33]byte
// Features is the list of protocol features supported by this channel
// edge.
Features *lnwire.FeatureVector
// AuthProof is the authentication proof for this channel. This proof
// contains a set of signatures binding four identities, which attests
// to the legitimacy of the advertised channel.
AuthProof *ChannelAuthProof
// ChannelPoint is the funding outpoint of the channel. This can be
// used to uniquely identify the channel within the channel graph.
ChannelPoint wire.OutPoint
// Capacity is the total capacity of the channel, this is determined by
// the value output in the outpoint that created this channel.
Capacity btcutil.Amount
// FundingScript holds the script of the channel's funding transaction.
//
// NOTE: this is not currently persisted and so will not be present if
// the edge object is loaded from the database.
FundingScript fn.Option[[]byte]
// 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
}
@@ -0,0 +1,85 @@
package models
import (
"time"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
// ChannelEdgePolicy represents a *directed* edge within the channel graph. For
// each channel in the database, there are two distinct edges: one for each
// possible direction of travel along the channel. The edges themselves hold
// information concerning fees, and minimum time-lock information which is
// utilized during path finding.
type ChannelEdgePolicy struct {
// SigBytes is the raw bytes of the signature of the channel edge
// policy. We'll only parse these if the caller needs to access the
// signature for validation purposes. Do not set SigBytes directly, but
// use SetSigBytes instead to make sure that the cache is invalidated.
SigBytes []byte
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// LastUpdate is the last time an authenticated edge for this channel
// was received.
LastUpdate time.Time
// MessageFlags is a bitfield which indicates the presence of optional
// fields (like max_htlc) in the policy.
MessageFlags lnwire.ChanUpdateMsgFlags
// ChannelFlags is a bitfield which signals the capabilities of the
// channel as well as the directed edge this update applies to.
ChannelFlags lnwire.ChanUpdateChanFlags
// TimeLockDelta is the number of blocks this node will subtract from
// the expiry of an incoming HTLC. This value expresses the time buffer
// the node would like to HTLC exchanges.
TimeLockDelta uint16
// MinHTLC is the smallest value HTLC this node will forward, expressed
// in millisatoshi.
MinHTLC lnwire.MilliSatoshi
// MaxHTLC is the largest value HTLC this node will forward, expressed
// in millisatoshi.
MaxHTLC lnwire.MilliSatoshi
// FeeBaseMSat is the base HTLC fee that will be charged for forwarding
// ANY HTLC, expressed in mSAT's.
FeeBaseMSat lnwire.MilliSatoshi
// FeeProportionalMillionths is the rate that the node will charge for
// HTLCs for each millionth of a satoshi forwarded.
FeeProportionalMillionths lnwire.MilliSatoshi
// ToNode is the public key of the node that this directed edge leads
// to. Using this pub key, the channel graph can further be traversed.
ToNode [33]byte
// InboundFee is the fee that must be paid for incoming HTLCs.
//
// NOTE: for our kvdb implementation of the graph store, inbound fees
// are still only persisted as part of extra opaque data and so this
// field is not explicitly stored but is rather populated from the
// ExtraOpaqueData field on deserialization. For our SQL implementation,
// this field will be explicitly persisted in the database.
InboundFee fn.Option[lnwire.Fee]
// 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 lnwire.ExtraOpaqueData
}
// IsDisabled determines whether the edge has the disabled bit set.
func (c *ChannelEdgePolicy) IsDisabled() bool {
return c.ChannelFlags.IsDisabled()
}
+146
View File
@@ -0,0 +1,146 @@
package models
import (
"image/color"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// Node represents an individual vertex/node within the channel graph.
// 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.
type Node struct {
// Version is the gossip version that this node was advertised on.
Version lnwire.GossipVersion
// PubKeyBytes is the raw bytes of the public key of the target node.
PubKeyBytes [33]byte
pubKey *btcec.PublicKey
// LastUpdate is the last time the vertex information for this node has
// been updated.
LastUpdate time.Time
// 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]
// 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
// compatible manner.
ExtraOpaqueData []byte
}
// 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,
}
}
// 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),
}
}
// 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
}
// PubKey is the node's long-term identity public key. This key will be used to
// authenticated any advertisements/updates sent by the node.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (n *Node) PubKey() (*btcec.PublicKey, error) {
if n.pubKey != nil {
return n.pubKey, nil
}
key, err := btcec.ParsePubKey(n.PubKeyBytes[:])
if err != nil {
return nil, err
}
n.pubKey = key
return key, nil
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+23
View File
@@ -0,0 +1,23 @@
//go:build !test_db_sqlite && !test_db_postgres
package migration1
import (
"testing"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/stretchr/testify/require"
)
// NewTestDB is a helper function that creates an BBolt database for testing.
func NewTestDB(t testing.TB) V1Store {
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
require.NoError(t, err)
t.Cleanup(backendCleanup)
graphStore, err := NewKVStore(backend)
require.NoError(t, err)
return graphStore
}
+79
View File
@@ -0,0 +1,79 @@
//go:build test_db_postgres && !test_db_sqlite
package migration1
import (
"database/sql"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/stretchr/testify/require"
)
// NewTestDB is a helper function that creates a SQLStore backed by a SQL
// database for testing.
func NewTestDB(t testing.TB) V1Store {
return NewTestDBWithFixture(t, nil)
}
// NewTestDBFixture creates a new sqldb.TestPgFixture for testing purposes.
func NewTestDBFixture(t *testing.T) *sqldb.TestPgFixture {
pgFixture := sqldb.NewTestPgFixture(
t, sqldb.DefaultPostgresFixtureLifetime,
)
t.Cleanup(func() {
pgFixture.TearDown(t)
})
return pgFixture
}
// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a
// SQL database for testing.
func NewTestDBWithFixture(t testing.TB,
pgFixture *sqldb.TestPgFixture) V1Store {
var querier BatchedSQLQueries
if pgFixture == nil {
querier = newBatchQuerier(t)
} else {
querier = newBatchQuerierWithFixture(t, pgFixture)
}
store, err := NewSQLStore(
&SQLStoreConfig{
ChainHash: *chaincfg.MainNetParams.GenesisHash,
QueryCfg: sqldb.DefaultPostgresConfig(),
}, querier,
)
require.NoError(t, err)
return store
}
// newBatchQuerier creates a new BatchedSQLQueries instance for testing
// using a PostgreSQL database fixture.
func newBatchQuerier(t testing.TB) BatchedSQLQueries {
pgFixture := sqldb.NewTestPgFixture(
t, sqldb.DefaultPostgresFixtureLifetime,
)
t.Cleanup(func() {
pgFixture.TearDown(t)
})
return newBatchQuerierWithFixture(t, pgFixture)
}
// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for
// testing using a PostgreSQL database fixture.
func newBatchQuerierWithFixture(t testing.TB,
pgFixture *sqldb.TestPgFixture) BatchedSQLQueries {
db := sqldb.NewTestPostgresDB(t, pgFixture).BaseDB
return sqldb.NewTransactionExecutor(
db, func(tx *sql.Tx) SQLQueries {
return db.WithTx(tx)
},
)
}
+56
View File
@@ -0,0 +1,56 @@
//go:build !test_db_postgres && test_db_sqlite
package migration1
import (
"database/sql"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/stretchr/testify/require"
)
// NewTestDB is a helper function that creates a SQLStore backed by a SQL
// database for testing.
func NewTestDB(t testing.TB) V1Store {
return NewTestDBWithFixture(t, nil)
}
// NewTestDBFixture is a no-op for the sqlite build.
func NewTestDBFixture(_ *testing.T) *sqldb.TestPgFixture {
return nil
}
// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a
// SQL database for testing.
func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture) V1Store {
store, err := NewSQLStore(
&SQLStoreConfig{
ChainHash: *chaincfg.MainNetParams.GenesisHash,
QueryCfg: sqldb.DefaultSQLiteConfig(),
}, newBatchQuerier(t),
)
require.NoError(t, err)
return store
}
// newBatchQuerier creates a new BatchedSQLQueries instance for testing
// using a SQLite database.
func newBatchQuerier(t testing.TB) BatchedSQLQueries {
return newBatchQuerierWithFixture(t, nil)
}
// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for
// testing using a SQLite database.
func newBatchQuerierWithFixture(t testing.TB,
_ *sqldb.TestPgFixture) BatchedSQLQueries {
db := sqldb.NewTestSqliteDB(t).BaseDB
return sqldb.NewTransactionExecutor(
db, func(tx *sql.Tx) SQLQueries {
return db.WithTx(tx)
},
)
}