lnd.xprv/chainregistry.go
Olaoluwa Osuntokun 593ba7c8f0
lnd: newChainControlFromConfig can now create light client backed chainControl
This commit finishes up the implementation of newChainControlFromConfig
in order to properly initiate the members of the chainControl struct
when the new light client mode (neutrino). With this lnd is now able to
switch over to either mode with a simple configuration change.
2017-06-05 19:14:41 -07:00

375 lines
11 KiB
Go

package main
import (
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/routing/chainview"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcrpcclient"
"github.com/roasbeef/btcwallet/chain"
"github.com/roasbeef/btcwallet/walletdb"
)
// chainCode is an enum-like structure for keeping track of the chains currently
// supported within lnd.
type chainCode uint32
const (
// bitcoinChain is Bitcoin's testnet chain.
bitcoinChain chainCode = iota
// litecoinChain is Litecoin's testnet chain.
litecoinChain
)
// String returns a string representation of the target chainCode.
func (c chainCode) String() string {
switch c {
case bitcoinChain:
return "bitcoin"
case litecoinChain:
return "litecoin"
default:
return "kekcoin"
}
}
// chainControl couples the three primary interfaces lnd utilizes for a
// particular chain together. A single chainControl instance will exist for all
// the chains lnd is currently active on.
type chainControl struct {
chainIO lnwallet.BlockChainIO
feeEstimator lnwallet.FeeEstimator
signer lnwallet.Signer
msgSigner lnwallet.MessageSigner
chainNotifier chainntnfs.ChainNotifier
chainView chainview.FilteredChainView
wallet *lnwallet.LightningWallet
}
// newChainControlFromConfig attempts to create a chainControl instance
// according to the parameters in the passed lnd configuration. Currently two
// branches of chainControl instances exist: one backed by a running btcd
// full-node, and the other backed by a running neutrino light client instance.
func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB) (*chainControl, func(), error) {
// Set the RPC config from the "home" chain. Multi-chain isn't yet
// active, so we'll restrict usage to a particular chain for now.
homeChainConfig := cfg.Bitcoin
if registeredChains.PrimaryChain() == litecoinChain {
homeChainConfig = cfg.Litecoin
}
ltndLog.Infof("Primary chain is set to: %v",
registeredChains.PrimaryChain())
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50}
walletConfig := &btcwallet.Config{
PrivatePass: []byte("hello"),
DataDir: homeChainConfig.ChainDir,
NetParams: activeNetParams.Params,
}
cc := &chainControl{
feeEstimator: estimator,
}
var (
err error
cleanUp func()
)
// If spv mode is active, then we'll be using a distimnct set of
// chainControl interfaces that interface directly with the p2p network
// of the selected chain.
if cfg.SpvMode.Active {
// First we'll open the database file for neutrino, creating
// the database if needed.
dbName := filepath.Join(cfg.DataDir, "neutrino.db")
nodeDatabase, err := walletdb.Create("bdb", dbName)
if err != nil {
return nil, nil, err
}
// With the database open, we can now create an instance of the
// neutrino light client. We pass in relevant configuration
// parameters required.
config := neutrino.Config{
DataDir: cfg.DataDir,
Database: nodeDatabase,
ChainParams: *activeNetParams.Params,
AddPeers: cfg.SpvMode.AddPeers,
ConnectPeers: cfg.SpvMode.ConnectPeers,
}
neutrino.WaitForMoreCFHeaders = time.Second * 1
neutrino.MaxPeers = 8
neutrino.BanDuration = 5 * time.Second
svc, err := neutrino.NewChainService(config)
if err != nil {
return nil, nil, fmt.Errorf("unable to create neutrino: %v", err)
}
svc.Start()
// Next we'll create the instances of the ChainNotifier and
// FilteredChainView interface which is backed by the neutrino
// light client.
cc.chainNotifier, err = neutrinonotify.New(svc)
if err != nil {
return nil, nil, err
}
cc.chainView, err = chainview.NewCfFilteredChainView(svc)
if err != nil {
return nil, nil, err
}
// Finally, we'll set the chain source for btcwallet, and
// create our clean up function which simply closes the
// database.
walletConfig.ChainSource = chain.NewSPVChain(svc)
cleanUp = func() {
defer nodeDatabase.Close()
}
} else {
// Otherwise, we'll be speaking directly via RPC to a node.
//
// So first we'll load btcd/ltcd's TLS cert for the RPC
// connection. If a raw cert was specified in the config, then
// we'll set that directly. Otherwise, we attempt to read the
// cert from the path specified in the config.
var rpcCert []byte
if homeChainConfig.RawRPCCert != "" {
rpcCert, err = hex.DecodeString(homeChainConfig.RawRPCCert)
if err != nil {
return nil, nil, err
}
} else {
certFile, err := os.Open(homeChainConfig.RPCCert)
if err != nil {
return nil, nil, err
}
rpcCert, err = ioutil.ReadAll(certFile)
if err != nil {
return nil, nil, err
}
if err := certFile.Close(); err != nil {
return nil, nil, err
}
}
// If the specified host for the btcd/ltcd RPC server already
// has a port specified, then we use that directly. Otherwise,
// we assume the default port according to the selected chain
// parameters.
var btcdHost string
if strings.Contains(homeChainConfig.RPCHost, ":") {
btcdHost = homeChainConfig.RPCHost
} else {
btcdHost = fmt.Sprintf("%v:%v", homeChainConfig.RPCHost,
activeNetParams.rpcPort)
}
btcdUser := homeChainConfig.RPCUser
btcdPass := homeChainConfig.RPCPass
rpcConfig := &btcrpcclient.ConnConfig{
Host: btcdHost,
Endpoint: "ws",
User: btcdUser,
Pass: btcdPass,
Certificates: rpcCert,
DisableTLS: false,
DisableConnectOnNew: true,
DisableAutoReconnect: false,
}
cc.chainNotifier, err = btcdnotify.New(rpcConfig)
if err != nil {
return nil, nil, err
}
// Finally, we'll create an instance of the default chain view to be
// used within the routing layer.
cc.chainView, err = chainview.NewBtcdFilteredChainView(*rpcConfig)
if err != nil {
srvrLog.Errorf("unable to create chain view: %v", err)
return nil, nil, err
}
// Create a special websockets rpc client for btcd which will be used
// by the wallet for notifications, calls, etc.
chainRpc, err := chain.NewRPCClient(activeNetParams.Params, btcdHost,
btcdUser, btcdPass, rpcCert, false, 20)
if err != nil {
return nil, nil, err
}
walletConfig.ChainSource = chainRpc
}
wc, err := btcwallet.New(*walletConfig)
if err != nil {
fmt.Printf("unable to create wallet controller: %v\n", err)
return nil, nil, err
}
cc.msgSigner = wc
cc.signer = wc
cc.chainIO = wc
// Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines.
wallet, err := lnwallet.NewLightningWallet(chanDB, cc.chainNotifier, wc,
cc.signer, cc.chainIO, cc.feeEstimator, activeNetParams.Params)
if err != nil {
fmt.Printf("unable to create wallet: %v\n", err)
return nil, nil, err
}
if err := wallet.Startup(); err != nil {
fmt.Printf("unable to start wallet: %v\n", err)
return nil, nil, err
}
ltndLog.Info("LightningWallet opened")
cc.wallet = wallet
return cc, cleanUp, nil
}
var (
// bitcoinGenesis is the genesis hash of Bitcoin's testnet chain.
bitcoinGenesis = chainhash.Hash([chainhash.HashSize]byte{
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
})
// litecoinGenesis is the genesis hash of Litecoin's testnet4 chain.
litecoinGenesis = chainhash.Hash([chainhash.HashSize]byte{
0xa0, 0x29, 0x3e, 0x4e, 0xeb, 0x3d, 0xa6, 0xe6,
0xf5, 0x6f, 0x81, 0xed, 0x59, 0x5f, 0x57, 0x88,
0x0d, 0x1a, 0x21, 0x56, 0x9e, 0x13, 0xee, 0xfd,
0xd9, 0x51, 0x28, 0x4b, 0x5a, 0x62, 0x66, 0x49,
})
// chainMap is a simple index that maps a chain's genesis hash to the
// chainCode enum for that chain.
chainMap = map[chainhash.Hash]chainCode{
bitcoinGenesis: bitcoinChain,
litecoinGenesis: litecoinChain,
}
// reverseChainMap is the inverse of the chainMap above: it maps the
// chain enum for a chain to its genesis hash.
reverseChainMap = map[chainCode]chainhash.Hash{
bitcoinChain: bitcoinGenesis,
litecoinChain: litecoinGenesis,
}
)
// chainRegistry keeps track of the current chains
type chainRegistry struct {
sync.RWMutex
activeChains map[chainCode]*chainControl
netParams map[chainCode]*bitcoinNetParams
primaryChain chainCode
}
// newChainRegistry creates a new chainRegistry.
func newChainRegistry() *chainRegistry {
return &chainRegistry{
activeChains: make(map[chainCode]*chainControl),
netParams: make(map[chainCode]*bitcoinNetParams),
}
}
// RegisterChain assigns an active chainControl instance to a target chain
// identified by its chainCode.
func (c *chainRegistry) RegisterChain(newChain chainCode, cc *chainControl) {
c.Lock()
c.activeChains[newChain] = cc
c.Unlock()
}
// LookupChain attempts to lookup an active chainControl instance for the
// target chain.
func (c *chainRegistry) LookupChain(targetChain chainCode) (*chainControl, bool) {
c.RLock()
cc, ok := c.activeChains[targetChain]
c.RUnlock()
return cc, ok
}
// LookupChainByHash attempts to look up an active chainControl which
// corresponds to the passed genesis hash.
func (c *chainRegistry) LookupChainByHash(chainHash chainhash.Hash) (*chainControl, bool) {
c.RLock()
defer c.RUnlock()
targetChain, ok := chainMap[chainHash]
if !ok {
return nil, ok
}
cc, ok := c.activeChains[targetChain]
return cc, ok
}
// RegisterPrimaryChain sets a target chain as the "home chain" for lnd.
func (c *chainRegistry) RegisterPrimaryChain(cc chainCode) {
c.Lock()
defer c.Unlock()
c.primaryChain = cc
}
// PrimaryChain returns the primary chain for this running lnd instance. The
// primary chain is considered the "home base" while the other registered
// chains are treated as secondary chains.
func (c *chainRegistry) PrimaryChain() chainCode {
c.RLock()
defer c.RUnlock()
return c.primaryChain
}
// ActiveChains returns the total number of active chains.
func (c *chainRegistry) ActiveChains() []chainCode {
c.RLock()
defer c.RUnlock()
chains := make([]chainCode, 0, len(c.activeChains))
for activeChain := range c.activeChains {
chains = append(chains, activeChain)
}
return chains
}
// NumActiveChains returns the total number of active chains.
func (c *chainRegistry) NumActiveChains() uint32 {
c.RLock()
defer c.RUnlock()
return uint32(len(c.activeChains))
}