2015-12-26 21:35:15 +03:00
|
|
|
package channeldb
|
2016-10-26 00:04:42 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/boltdb/bolt"
|
|
|
|
"github.com/roasbeef/btcd/btcec"
|
|
|
|
"github.com/roasbeef/btcd/wire"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// nodeInfoBucket stores meta-data 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")
|
|
|
|
|
|
|
|
// chanIndexBucket...
|
|
|
|
// * stores (chanPoint || nodePub) index
|
|
|
|
// * allows for prefix-ish scan to randomly get info for a channel
|
|
|
|
// * written to after FullSync
|
|
|
|
// * deleted from during channel close
|
|
|
|
// * used to fetch all open channels
|
|
|
|
chanIdentityBucket = []byte("chanidentity")
|
|
|
|
)
|
|
|
|
|
|
|
|
// LinkNode stores meta-data 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
|
|
|
|
// Bitcion's addrmanager. The TCP address information stored within the struct
|
|
|
|
// can be used to establish persistent connections will all channel
|
|
|
|
// counter-parties 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?
|
|
|
|
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.
|
|
|
|
//
|
|
|
|
// TODO(roasbeef): also need to support hidden service addrs
|
|
|
|
// * make map to easily remove addrs?
|
|
|
|
Addresses []*net.TCPAddr
|
|
|
|
|
|
|
|
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,
|
|
|
|
addr *net.TCPAddr) *LinkNode {
|
|
|
|
|
|
|
|
return &LinkNode{
|
|
|
|
Network: bitNet,
|
|
|
|
IdentityPub: pub,
|
|
|
|
LastSeen: time.Now(),
|
|
|
|
Addresses: []*net.TCPAddr{addr},
|
|
|
|
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.TCPAddr) error {
|
|
|
|
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 {
|
|
|
|
// First serialize the LinkNode into its raw-bytes encoding. This is
|
|
|
|
// done outside of the transaction in order to minimize the length of
|
|
|
|
// the DB transaction.
|
|
|
|
var b bytes.Buffer
|
|
|
|
if err := serializeLinkNode(&b, l); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
nodePub := l.IdentityPub.SerializeCompressed()
|
|
|
|
|
|
|
|
// Finally update the database by storing the link node and updating
|
|
|
|
// any relevant indexes.
|
|
|
|
return l.db.store.Update(func(tx *bolt.Tx) error {
|
|
|
|
nodeMetaBucket := tx.Bucket(nodeInfoBucket)
|
|
|
|
if nodeInfoBucket == nil {
|
|
|
|
return fmt.Errorf("node bucket not created")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nodeMetaBucket.Put(nodePub, b.Bytes())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 (
|
|
|
|
node *LinkNode
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
err = db.store.View(func(tx *bolt.Tx) error {
|
|
|
|
// First fetch the bucket for storing node meta-data, bailing
|
|
|
|
// out early if it hasn't been created yet.
|
|
|
|
nodeMetaBucket := tx.Bucket(nodeInfoBucket)
|
|
|
|
if nodeInfoBucket == nil {
|
|
|
|
return fmt.Errorf("node bucket not created")
|
|
|
|
}
|
|
|
|
|
|
|
|
// If a link node for that particular public key cannot be
|
|
|
|
// located, then exit early with a ErrNodeNotFound.
|
|
|
|
pubKey := identity.SerializeCompressed()
|
|
|
|
nodeBytes := nodeMetaBucket.Get(pubKey)
|
|
|
|
if nodeBytes == nil {
|
|
|
|
return ErrNodeNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, decode an allocate a fresh LinkNode object to be
|
|
|
|
// returned to the caller.
|
|
|
|
nodeReader := bytes.NewReader(nodeBytes)
|
|
|
|
node, err = deserializeLinkNode(nodeReader)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FetchAllLinkNodes attempts to fetch all active LinkNodes from the database.
|
|
|
|
func (db *DB) FetchAllLinkNodes() ([]*LinkNode, error) {
|
|
|
|
var linkNodes []*LinkNode
|
|
|
|
|
|
|
|
err := db.store.View(func(tx *bolt.Tx) error {
|
|
|
|
nodeMetaBucket := tx.Bucket(nodeInfoBucket)
|
|
|
|
if nodeInfoBucket == nil {
|
|
|
|
return fmt.Errorf("node bucket not created")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nodeMetaBucket.ForEach(func(k, v []byte) error {
|
|
|
|
nodeReader := bytes.NewReader(v)
|
|
|
|
linkNode, err := deserializeLinkNode(nodeReader)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
linkNodes = append(linkNodes, linkNode)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
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 {
|
|
|
|
addrString := addr.String()
|
|
|
|
if err := wire.WriteVarString(w, 0, addrString); 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.TCPAddr, numAddrs)
|
|
|
|
for i := uint32(0); i < numAddrs; i++ {
|
|
|
|
addrString, err := wire.ReadVarString(r, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
addr, err := net.ResolveTCPAddr("tcp", addrString)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
node.Addresses[i] = addr
|
|
|
|
}
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|