dc40662770
This commit fixes a bug wherein the wallet would use the default relay fee to craft transactions. On testnet, this might be insufficient or be rejected all together in a mainnet setting. Therefore, we now pass in the FeeEstimator interface and ensure that it’s consulted in order to set the relay fee the wallet will use to craft transactions. Note that this is a hold over until we have true dynamic fee calculation within lnd which can then be extended to the internal wallets.
376 lines
11 KiB
Go
376 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,
|
|
FeeEstimator: estimator,
|
|
}
|
|
|
|
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.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 := &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))
|
|
}
|