62dc6b81d2
This let the caller of newChainControlFromConfig set the password to be used when creating or unlocking the wallet database. The provided password is used both as private and public password.
443 lines
13 KiB
Go
443 lines
13 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/htlcswitch"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing/chainview"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
"github.com/roasbeef/btcd/rpcclient"
|
|
"github.com/roasbeef/btcwallet/chain"
|
|
"github.com/roasbeef/btcwallet/walletdb"
|
|
)
|
|
|
|
// defaultBitcoinForwardingPolicy is the default forwarding policy used for
|
|
// Bitcoin channels.
|
|
var defaultBitcoinForwardingPolicy = htlcswitch.ForwardingPolicy{
|
|
MinHTLC: 0,
|
|
BaseFee: lnwire.NewMSatFromSatoshis(1),
|
|
FeeRate: 1,
|
|
TimeLockDelta: 144,
|
|
}
|
|
|
|
// defaultLitecoinForwardingPolicy is the default forwarding policy used for
|
|
// Litecoin channels.
|
|
var defaultLitecoinForwardingPolicy = htlcswitch.ForwardingPolicy{
|
|
MinHTLC: 0,
|
|
BaseFee: 1,
|
|
FeeRate: 1,
|
|
TimeLockDelta: 576,
|
|
}
|
|
|
|
// defaultChannelConstraints is the default set of channel constraints that are
|
|
// meant to be used when initially funding a channel.
|
|
//
|
|
// TODO(roasbeef): have one for both chains
|
|
var defaultChannelConstraints = channeldb.ChannelConstraints{
|
|
DustLimit: lnwallet.DefaultDustLimit(),
|
|
MaxAcceptedHtlcs: lnwallet.MaxHTLCNumber / 2,
|
|
}
|
|
|
|
// 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
|
|
|
|
routingPolicy htlcswitch.ForwardingPolicy
|
|
}
|
|
|
|
// 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,
|
|
walletPw []byte) (*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())
|
|
|
|
cc := &chainControl{}
|
|
|
|
switch registeredChains.PrimaryChain() {
|
|
case bitcoinChain:
|
|
cc.routingPolicy = defaultBitcoinForwardingPolicy
|
|
cc.feeEstimator = lnwallet.StaticFeeEstimator{
|
|
FeeRate: 50,
|
|
}
|
|
case litecoinChain:
|
|
cc.routingPolicy = defaultLitecoinForwardingPolicy
|
|
cc.feeEstimator = lnwallet.StaticFeeEstimator{
|
|
FeeRate: 100,
|
|
}
|
|
default:
|
|
return nil, nil, fmt.Errorf("Default routing policy for "+
|
|
"chain %v is unknown", registeredChains.PrimaryChain())
|
|
}
|
|
|
|
walletConfig := &btcwallet.Config{
|
|
PrivatePass: walletPw,
|
|
PublicPass: walletPw,
|
|
DataDir: homeChainConfig.ChainDir,
|
|
NetParams: activeNetParams.Params,
|
|
FeeEstimator: cc.feeEstimator,
|
|
}
|
|
|
|
var (
|
|
err error
|
|
cleanUp func()
|
|
)
|
|
|
|
// If spv mode is active, then we'll be using a distinct set of
|
|
// chainControl interfaces that interface directly with the p2p network
|
|
// of the selected chain.
|
|
if cfg.NeutrinoMode.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.NeutrinoMode.AddPeers,
|
|
ConnectPeers: cfg.NeutrinoMode.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.NewNeutrinoClient(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 := &rpcclient.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.
|
|
walletCfg := lnwallet.Config{
|
|
Database: chanDB,
|
|
Notifier: cc.chainNotifier,
|
|
WalletController: wc,
|
|
Signer: cc.signer,
|
|
FeeEstimator: cc.feeEstimator,
|
|
ChainIO: cc.chainIO,
|
|
DefaultConstraints: defaultChannelConstraints,
|
|
NetParams: *activeNetParams.Params,
|
|
}
|
|
wallet, err := lnwallet.NewLightningWallet(walletCfg)
|
|
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,
|
|
}
|
|
|
|
// chainDNSSeeds is a map of a chain's hash to the set of DNS seeds
|
|
// that will be use to bootstrap peers upon first startup.
|
|
//
|
|
// TODO(roasbeef): extend and collapse these and chainparams.go into
|
|
// struct like chaincfg.Params
|
|
chainDNSSeeds = map[chainhash.Hash][]string{
|
|
bitcoinGenesis: {
|
|
"nodes.lightning.directory",
|
|
//"lseed.bitcoinstats.com",
|
|
},
|
|
}
|
|
)
|
|
|
|
// 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))
|
|
}
|