You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
8.7 KiB
320 lines
8.7 KiB
package channeldb |
|
|
|
import ( |
|
"bytes" |
|
"io" |
|
"net" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/btcec" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/lightningnetwork/lnd/channeldb/kvdb" |
|
) |
|
|
|
var ( |
|
// nodeInfoBucket stores metadata pertaining to nodes that we've had |
|
// direct channel-based correspondence with. This bucket allows one to |
|
// query for all open channels pertaining to the node by exploring each |
|
// node's sub-bucket within the openChanBucket. |
|
nodeInfoBucket = []byte("nib") |
|
) |
|
|
|
// LinkNode stores metadata related to node's that we have/had a direct |
|
// channel open with. Information such as the Bitcoin network the node |
|
// advertised, and its identity public key are also stored. Additionally, this |
|
// struct and the bucket its stored within have store data similar to that of |
|
// Bitcoin's addrmanager. The TCP address information stored within the struct |
|
// can be used to establish persistent connections will all channel |
|
// counterparties on daemon startup. |
|
// |
|
// TODO(roasbeef): also add current OnionKey plus rotation schedule? |
|
// TODO(roasbeef): add bitfield for supported services |
|
// * possibly add a wire.NetAddress type, type |
|
type LinkNode struct { |
|
// Network indicates the Bitcoin network that the LinkNode advertises |
|
// for incoming channel creation. |
|
Network wire.BitcoinNet |
|
|
|
// IdentityPub is the node's current identity public key. Any |
|
// channel/topology related information received by this node MUST be |
|
// signed by this public key. |
|
IdentityPub *btcec.PublicKey |
|
|
|
// LastSeen tracks the last time this node was seen within the network. |
|
// A node should be marked as seen if the daemon either is able to |
|
// establish an outgoing connection to the node or receives a new |
|
// incoming connection from the node. This timestamp (stored in unix |
|
// epoch) may be used within a heuristic which aims to determine when a |
|
// channel should be unilaterally closed due to inactivity. |
|
// |
|
// TODO(roasbeef): replace with block hash/height? |
|
// * possibly add a time-value metric into the heuristic? |
|
LastSeen time.Time |
|
|
|
// Addresses is a list of IP address in which either we were able to |
|
// reach the node over in the past, OR we received an incoming |
|
// authenticated connection for the stored identity public key. |
|
Addresses []net.Addr |
|
|
|
db *DB |
|
} |
|
|
|
// NewLinkNode creates a new LinkNode from the provided parameters, which is |
|
// backed by an instance of channeldb. |
|
func (db *DB) NewLinkNode(bitNet wire.BitcoinNet, pub *btcec.PublicKey, |
|
addrs ...net.Addr) *LinkNode { |
|
|
|
return &LinkNode{ |
|
Network: bitNet, |
|
IdentityPub: pub, |
|
LastSeen: time.Now(), |
|
Addresses: addrs, |
|
db: db, |
|
} |
|
} |
|
|
|
// UpdateLastSeen updates the last time this node was directly encountered on |
|
// the Lightning Network. |
|
func (l *LinkNode) UpdateLastSeen(lastSeen time.Time) error { |
|
l.LastSeen = lastSeen |
|
|
|
return l.Sync() |
|
} |
|
|
|
// AddAddress appends the specified TCP address to the list of known addresses |
|
// this node is/was known to be reachable at. |
|
func (l *LinkNode) AddAddress(addr net.Addr) error { |
|
for _, a := range l.Addresses { |
|
if a.String() == addr.String() { |
|
return nil |
|
} |
|
} |
|
|
|
l.Addresses = append(l.Addresses, addr) |
|
|
|
return l.Sync() |
|
} |
|
|
|
// Sync performs a full database sync which writes the current up-to-date data |
|
// within the struct to the database. |
|
func (l *LinkNode) Sync() error { |
|
|
|
// Finally update the database by storing the link node and updating |
|
// any relevant indexes. |
|
return kvdb.Update(l.db, func(tx kvdb.RwTx) error { |
|
nodeMetaBucket := tx.ReadWriteBucket(nodeInfoBucket) |
|
if nodeMetaBucket == nil { |
|
return ErrLinkNodesNotFound |
|
} |
|
|
|
return putLinkNode(nodeMetaBucket, l) |
|
}) |
|
} |
|
|
|
// putLinkNode serializes then writes the encoded version of the passed link |
|
// node into the nodeMetaBucket. This function is provided in order to allow |
|
// the ability to re-use a database transaction across many operations. |
|
func putLinkNode(nodeMetaBucket kvdb.RwBucket, l *LinkNode) error { |
|
// First serialize the LinkNode into its raw-bytes encoding. |
|
var b bytes.Buffer |
|
if err := serializeLinkNode(&b, l); err != nil { |
|
return err |
|
} |
|
|
|
// Finally insert the link-node into the node metadata bucket keyed |
|
// according to the its pubkey serialized in compressed form. |
|
nodePub := l.IdentityPub.SerializeCompressed() |
|
return nodeMetaBucket.Put(nodePub, b.Bytes()) |
|
} |
|
|
|
// DeleteLinkNode removes the link node with the given identity from the |
|
// database. |
|
func (db *DB) DeleteLinkNode(identity *btcec.PublicKey) error { |
|
return kvdb.Update(db, func(tx kvdb.RwTx) error { |
|
return db.deleteLinkNode(tx, identity) |
|
}) |
|
} |
|
|
|
func (db *DB) deleteLinkNode(tx kvdb.RwTx, identity *btcec.PublicKey) error { |
|
nodeMetaBucket := tx.ReadWriteBucket(nodeInfoBucket) |
|
if nodeMetaBucket == nil { |
|
return ErrLinkNodesNotFound |
|
} |
|
|
|
pubKey := identity.SerializeCompressed() |
|
return nodeMetaBucket.Delete(pubKey) |
|
} |
|
|
|
// FetchLinkNode attempts to lookup the data for a LinkNode based on a target |
|
// identity public key. If a particular LinkNode for the passed identity public |
|
// key cannot be found, then ErrNodeNotFound if returned. |
|
func (db *DB) FetchLinkNode(identity *btcec.PublicKey) (*LinkNode, error) { |
|
var linkNode *LinkNode |
|
err := kvdb.View(db, func(tx kvdb.RTx) error { |
|
node, err := fetchLinkNode(tx, identity) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
linkNode = node |
|
return nil |
|
}, func() { |
|
linkNode = nil |
|
}) |
|
|
|
return linkNode, err |
|
} |
|
|
|
func fetchLinkNode(tx kvdb.RTx, targetPub *btcec.PublicKey) (*LinkNode, error) { |
|
// First fetch the bucket for storing node metadata, bailing out early |
|
// if it hasn't been created yet. |
|
nodeMetaBucket := tx.ReadBucket(nodeInfoBucket) |
|
if nodeMetaBucket == nil { |
|
return nil, ErrLinkNodesNotFound |
|
} |
|
|
|
// If a link node for that particular public key cannot be located, |
|
// then exit early with an ErrNodeNotFound. |
|
pubKey := targetPub.SerializeCompressed() |
|
nodeBytes := nodeMetaBucket.Get(pubKey) |
|
if nodeBytes == nil { |
|
return nil, ErrNodeNotFound |
|
} |
|
|
|
// Finally, decode and allocate a fresh LinkNode object to be returned |
|
// to the caller. |
|
nodeReader := bytes.NewReader(nodeBytes) |
|
return deserializeLinkNode(nodeReader) |
|
} |
|
|
|
// TODO(roasbeef): update link node addrs in server upon connection |
|
|
|
// FetchAllLinkNodes starts a new database transaction to fetch all nodes with |
|
// whom we have active channels with. |
|
func (db *DB) FetchAllLinkNodes() ([]*LinkNode, error) { |
|
var linkNodes []*LinkNode |
|
err := kvdb.View(db, func(tx kvdb.RTx) error { |
|
nodes, err := db.fetchAllLinkNodes(tx) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
linkNodes = nodes |
|
return nil |
|
}, func() { |
|
linkNodes = nil |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return linkNodes, nil |
|
} |
|
|
|
// fetchAllLinkNodes uses an existing database transaction to fetch all nodes |
|
// with whom we have active channels with. |
|
func (db *DB) fetchAllLinkNodes(tx kvdb.RTx) ([]*LinkNode, error) { |
|
nodeMetaBucket := tx.ReadBucket(nodeInfoBucket) |
|
if nodeMetaBucket == nil { |
|
return nil, ErrLinkNodesNotFound |
|
} |
|
|
|
var linkNodes []*LinkNode |
|
err := nodeMetaBucket.ForEach(func(k, v []byte) error { |
|
if v == nil { |
|
return nil |
|
} |
|
|
|
nodeReader := bytes.NewReader(v) |
|
linkNode, err := deserializeLinkNode(nodeReader) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
linkNodes = append(linkNodes, linkNode) |
|
return nil |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return linkNodes, nil |
|
} |
|
|
|
func serializeLinkNode(w io.Writer, l *LinkNode) error { |
|
var buf [8]byte |
|
|
|
byteOrder.PutUint32(buf[:4], uint32(l.Network)) |
|
if _, err := w.Write(buf[:4]); err != nil { |
|
return err |
|
} |
|
|
|
serializedID := l.IdentityPub.SerializeCompressed() |
|
if _, err := w.Write(serializedID); err != nil { |
|
return err |
|
} |
|
|
|
seenUnix := uint64(l.LastSeen.Unix()) |
|
byteOrder.PutUint64(buf[:], seenUnix) |
|
if _, err := w.Write(buf[:]); err != nil { |
|
return err |
|
} |
|
|
|
numAddrs := uint32(len(l.Addresses)) |
|
byteOrder.PutUint32(buf[:4], numAddrs) |
|
if _, err := w.Write(buf[:4]); err != nil { |
|
return err |
|
} |
|
|
|
for _, addr := range l.Addresses { |
|
if err := serializeAddr(w, addr); err != nil { |
|
return err |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func deserializeLinkNode(r io.Reader) (*LinkNode, error) { |
|
var ( |
|
err error |
|
buf [8]byte |
|
) |
|
|
|
node := &LinkNode{} |
|
|
|
if _, err := io.ReadFull(r, buf[:4]); err != nil { |
|
return nil, err |
|
} |
|
node.Network = wire.BitcoinNet(byteOrder.Uint32(buf[:4])) |
|
|
|
var pub [33]byte |
|
if _, err := io.ReadFull(r, pub[:]); err != nil { |
|
return nil, err |
|
} |
|
node.IdentityPub, err = btcec.ParsePubKey(pub[:], btcec.S256()) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if _, err := io.ReadFull(r, buf[:]); err != nil { |
|
return nil, err |
|
} |
|
node.LastSeen = time.Unix(int64(byteOrder.Uint64(buf[:])), 0) |
|
|
|
if _, err := io.ReadFull(r, buf[:4]); err != nil { |
|
return nil, err |
|
} |
|
numAddrs := byteOrder.Uint32(buf[:4]) |
|
|
|
node.Addresses = make([]net.Addr, numAddrs) |
|
for i := uint32(0); i < numAddrs; i++ { |
|
addr, err := deserializeAddr(r) |
|
if err != nil { |
|
return nil, err |
|
} |
|
node.Addresses[i] = addr |
|
} |
|
|
|
return node, nil |
|
}
|
|
|