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.
748 lines
22 KiB
748 lines
22 KiB
package lnd |
|
|
|
import ( |
|
"encoding/hex" |
|
"fmt" |
|
"io/ioutil" |
|
"net" |
|
"os" |
|
"path/filepath" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/rpcclient" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/btcsuite/btcwallet/chain" |
|
"github.com/btcsuite/btcwallet/wallet" |
|
"github.com/btcsuite/btcwallet/walletdb" |
|
"github.com/lightninglabs/neutrino" |
|
"github.com/lightningnetwork/lnd/chainntnfs" |
|
"github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify" |
|
"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/input" |
|
"github.com/lightningnetwork/lnd/keychain" |
|
"github.com/lightningnetwork/lnd/lnwallet" |
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet" |
|
"github.com/lightningnetwork/lnd/lnwire" |
|
"github.com/lightningnetwork/lnd/routing/chainview" |
|
) |
|
|
|
const ( |
|
defaultBitcoinMinHTLCMSat = lnwire.MilliSatoshi(1000) |
|
defaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000) |
|
defaultBitcoinFeeRate = lnwire.MilliSatoshi(1) |
|
defaultBitcoinTimeLockDelta = 40 |
|
|
|
defaultLitecoinMinHTLCMSat = lnwire.MilliSatoshi(1000) |
|
defaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000) |
|
defaultLitecoinFeeRate = lnwire.MilliSatoshi(1) |
|
defaultLitecoinTimeLockDelta = 576 |
|
defaultLitecoinDustLimit = btcutil.Amount(54600) |
|
|
|
// defaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte |
|
// expressed in sat/kw. |
|
defaultBitcoinStaticFeePerKW = lnwallet.SatPerKWeight(12500) |
|
|
|
// defaultLitecoinStaticFeePerKW is the fee rate of 200 sat/vbyte |
|
// expressed in sat/kw. |
|
defaultLitecoinStaticFeePerKW = lnwallet.SatPerKWeight(50000) |
|
|
|
// btcToLtcConversionRate is a fixed ratio used in order to scale up |
|
// payments when running on the Litecoin chain. |
|
btcToLtcConversionRate = 60 |
|
) |
|
|
|
// defaultBtcChannelConstraints is the default set of channel constraints that are |
|
// meant to be used when initially funding a Bitcoin channel. |
|
// |
|
// TODO(halseth): make configurable at startup? |
|
var defaultBtcChannelConstraints = channeldb.ChannelConstraints{ |
|
DustLimit: lnwallet.DefaultDustLimit(), |
|
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2, |
|
} |
|
|
|
// defaultLtcChannelConstraints is the default set of channel constraints that are |
|
// meant to be used when initially funding a Litecoin channel. |
|
var defaultLtcChannelConstraints = channeldb.ChannelConstraints{ |
|
DustLimit: defaultLitecoinDustLimit, |
|
MaxAcceptedHtlcs: input.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 input.Signer |
|
|
|
keyRing keychain.SecretKeyRing |
|
|
|
wc lnwallet.WalletController |
|
|
|
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 three |
|
// branches of chainControl instances exist: one backed by a running btcd |
|
// full-node, another backed by a running bitcoind full-node, and the other |
|
// backed by a running neutrino light client instance. When running with a |
|
// neutrino light client instance, `neutrinoCS` must be non-nil. |
|
func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, |
|
privateWalletPw, publicWalletPw []byte, birthday time.Time, |
|
recoveryWindow uint32, wallet *wallet.Wallet, |
|
neutrinoCS *neutrino.ChainService) (*chainControl, 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 = htlcswitch.ForwardingPolicy{ |
|
MinHTLC: cfg.Bitcoin.MinHTLC, |
|
BaseFee: cfg.Bitcoin.BaseFee, |
|
FeeRate: cfg.Bitcoin.FeeRate, |
|
TimeLockDelta: cfg.Bitcoin.TimeLockDelta, |
|
} |
|
cc.feeEstimator = lnwallet.NewStaticFeeEstimator( |
|
defaultBitcoinStaticFeePerKW, 0, |
|
) |
|
case litecoinChain: |
|
cc.routingPolicy = htlcswitch.ForwardingPolicy{ |
|
MinHTLC: cfg.Litecoin.MinHTLC, |
|
BaseFee: cfg.Litecoin.BaseFee, |
|
FeeRate: cfg.Litecoin.FeeRate, |
|
TimeLockDelta: cfg.Litecoin.TimeLockDelta, |
|
} |
|
cc.feeEstimator = lnwallet.NewStaticFeeEstimator( |
|
defaultLitecoinStaticFeePerKW, 0, |
|
) |
|
default: |
|
return nil, fmt.Errorf("Default routing policy for chain %v is "+ |
|
"unknown", registeredChains.PrimaryChain()) |
|
} |
|
|
|
walletConfig := &btcwallet.Config{ |
|
PrivatePass: privateWalletPw, |
|
PublicPass: publicWalletPw, |
|
Birthday: birthday, |
|
RecoveryWindow: recoveryWindow, |
|
DataDir: homeChainConfig.ChainDir, |
|
NetParams: activeNetParams.Params, |
|
CoinType: activeNetParams.CoinType, |
|
Wallet: wallet, |
|
} |
|
|
|
var err error |
|
|
|
// Initialize the height hint cache within the chain directory. |
|
hintCache, err := chainntnfs.NewHeightHintCache(chanDB) |
|
if err != nil { |
|
return nil, fmt.Errorf("unable to initialize height hint "+ |
|
"cache: %v", err) |
|
} |
|
|
|
// 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. |
|
switch homeChainConfig.Node { |
|
case "neutrino": |
|
// We'll create ChainNotifier and FilteredChainView instances, |
|
// along with the wallet's ChainSource, which are all backed by |
|
// the neutrino light client. |
|
cc.chainNotifier = neutrinonotify.New( |
|
neutrinoCS, hintCache, hintCache, |
|
) |
|
cc.chainView, err = chainview.NewCfFilteredChainView(neutrinoCS) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// If the user provided an API for fee estimation, activate it now. |
|
if cfg.NeutrinoMode.FeeURL != "" { |
|
ltndLog.Infof("Using API fee estimator!") |
|
|
|
estimator := lnwallet.NewWebAPIFeeEstimator( |
|
lnwallet.SparseConfFeeSource{ |
|
URL: cfg.NeutrinoMode.FeeURL, |
|
}, |
|
defaultBitcoinStaticFeePerKW, |
|
) |
|
|
|
if err := estimator.Start(); err != nil { |
|
return nil, err |
|
} |
|
cc.feeEstimator = estimator |
|
} |
|
|
|
walletConfig.ChainSource = chain.NewNeutrinoClient( |
|
activeNetParams.Params, neutrinoCS, |
|
) |
|
|
|
case "bitcoind", "litecoind": |
|
var bitcoindMode *bitcoindConfig |
|
switch { |
|
case cfg.Bitcoin.Active: |
|
bitcoindMode = cfg.BitcoindMode |
|
case cfg.Litecoin.Active: |
|
bitcoindMode = cfg.LitecoindMode |
|
} |
|
// Otherwise, we'll be speaking directly via RPC and ZMQ to a |
|
// bitcoind node. 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 bitcoindHost string |
|
if strings.Contains(bitcoindMode.RPCHost, ":") { |
|
bitcoindHost = bitcoindMode.RPCHost |
|
} else { |
|
// The RPC ports specified in chainparams.go assume |
|
// btcd, which picks a different port so that btcwallet |
|
// can use the same RPC port as bitcoind. We convert |
|
// this back to the btcwallet/bitcoind port. |
|
rpcPort, err := strconv.Atoi(activeNetParams.rpcPort) |
|
if err != nil { |
|
return nil, err |
|
} |
|
rpcPort -= 2 |
|
bitcoindHost = fmt.Sprintf("%v:%d", |
|
bitcoindMode.RPCHost, rpcPort) |
|
if cfg.Bitcoin.Active && cfg.Bitcoin.RegTest { |
|
conn, err := net.Dial("tcp", bitcoindHost) |
|
if err != nil || conn == nil { |
|
rpcPort = 18443 |
|
bitcoindHost = fmt.Sprintf("%v:%d", |
|
bitcoindMode.RPCHost, |
|
rpcPort) |
|
} else { |
|
conn.Close() |
|
} |
|
} |
|
} |
|
|
|
// Establish the connection to bitcoind and create the clients |
|
// required for our relevant subsystems. |
|
bitcoindConn, err := chain.NewBitcoindConn( |
|
activeNetParams.Params, bitcoindHost, |
|
bitcoindMode.RPCUser, bitcoindMode.RPCPass, |
|
bitcoindMode.ZMQPubRawBlock, bitcoindMode.ZMQPubRawTx, |
|
100*time.Millisecond, |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if err := bitcoindConn.Start(); err != nil { |
|
return nil, fmt.Errorf("unable to connect to bitcoind: "+ |
|
"%v", err) |
|
} |
|
|
|
cc.chainNotifier = bitcoindnotify.New( |
|
bitcoindConn, activeNetParams.Params, hintCache, hintCache, |
|
) |
|
cc.chainView = chainview.NewBitcoindFilteredChainView(bitcoindConn) |
|
walletConfig.ChainSource = bitcoindConn.NewBitcoindClient() |
|
|
|
// If we're not in regtest mode, then we'll attempt to use a |
|
// proper fee estimator for testnet. |
|
rpcConfig := &rpcclient.ConnConfig{ |
|
Host: bitcoindHost, |
|
User: bitcoindMode.RPCUser, |
|
Pass: bitcoindMode.RPCPass, |
|
DisableConnectOnNew: true, |
|
DisableAutoReconnect: false, |
|
DisableTLS: true, |
|
HTTPPostMode: true, |
|
} |
|
if cfg.Bitcoin.Active && !cfg.Bitcoin.RegTest { |
|
ltndLog.Infof("Initializing bitcoind backed fee estimator") |
|
|
|
// Finally, we'll re-initialize the fee estimator, as |
|
// if we're using bitcoind as a backend, then we can |
|
// use live fee estimates, rather than a statically |
|
// coded value. |
|
fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) |
|
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( |
|
*rpcConfig, fallBackFeeRate.FeePerKWeight(), |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := cc.feeEstimator.Start(); err != nil { |
|
return nil, err |
|
} |
|
} else if cfg.Litecoin.Active { |
|
ltndLog.Infof("Initializing litecoind backed fee estimator") |
|
|
|
// Finally, we'll re-initialize the fee estimator, as |
|
// if we're using litecoind as a backend, then we can |
|
// use live fee estimates, rather than a statically |
|
// coded value. |
|
fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) |
|
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( |
|
*rpcConfig, fallBackFeeRate.FeePerKWeight(), |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := cc.feeEstimator.Start(); err != nil { |
|
return nil, err |
|
} |
|
} |
|
case "btcd", "ltcd": |
|
// 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 btcdMode *btcdConfig |
|
switch { |
|
case cfg.Bitcoin.Active: |
|
btcdMode = cfg.BtcdMode |
|
case cfg.Litecoin.Active: |
|
btcdMode = cfg.LtcdMode |
|
} |
|
var rpcCert []byte |
|
if btcdMode.RawRPCCert != "" { |
|
rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} else { |
|
certFile, err := os.Open(btcdMode.RPCCert) |
|
if err != nil { |
|
return nil, err |
|
} |
|
rpcCert, err = ioutil.ReadAll(certFile) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := certFile.Close(); err != nil { |
|
return 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(btcdMode.RPCHost, ":") { |
|
btcdHost = btcdMode.RPCHost |
|
} else { |
|
btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost, |
|
activeNetParams.rpcPort) |
|
} |
|
|
|
btcdUser := btcdMode.RPCUser |
|
btcdPass := btcdMode.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, activeNetParams.Params, hintCache, hintCache, |
|
) |
|
if err != nil { |
|
return 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, 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, err |
|
} |
|
|
|
walletConfig.ChainSource = chainRPC |
|
|
|
// If we're not in simnet or regtest mode, then we'll attempt |
|
// to use a proper fee estimator for testnet. |
|
if !cfg.Bitcoin.SimNet && !cfg.Litecoin.SimNet && |
|
!cfg.Bitcoin.RegTest && !cfg.Litecoin.RegTest { |
|
|
|
ltndLog.Infof("Initializing btcd backed fee estimator") |
|
|
|
// Finally, we'll re-initialize the fee estimator, as |
|
// if we're using btcd as a backend, then we can use |
|
// live fee estimates, rather than a statically coded |
|
// value. |
|
fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) |
|
cc.feeEstimator, err = lnwallet.NewBtcdFeeEstimator( |
|
*rpcConfig, fallBackFeeRate.FeePerKWeight(), |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := cc.feeEstimator.Start(); err != nil { |
|
return nil, err |
|
} |
|
} |
|
default: |
|
return nil, fmt.Errorf("unknown node type: %s", |
|
homeChainConfig.Node) |
|
} |
|
|
|
wc, err := btcwallet.New(*walletConfig) |
|
if err != nil { |
|
fmt.Printf("unable to create wallet controller: %v\n", err) |
|
return nil, err |
|
} |
|
|
|
cc.msgSigner = wc |
|
cc.signer = wc |
|
cc.chainIO = wc |
|
cc.wc = wc |
|
|
|
// Select the default channel constraints for the primary chain. |
|
channelConstraints := defaultBtcChannelConstraints |
|
if registeredChains.PrimaryChain() == litecoinChain { |
|
channelConstraints = defaultLtcChannelConstraints |
|
} |
|
|
|
keyRing := keychain.NewBtcWalletKeyRing( |
|
wc.InternalWallet(), activeNetParams.CoinType, |
|
) |
|
cc.keyRing = keyRing |
|
|
|
// 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, |
|
SecretKeyRing: keyRing, |
|
ChainIO: cc.chainIO, |
|
DefaultConstraints: channelConstraints, |
|
NetParams: *activeNetParams.Params, |
|
} |
|
lnWallet, err := lnwallet.NewLightningWallet(walletCfg) |
|
if err != nil { |
|
fmt.Printf("unable to create wallet: %v\n", err) |
|
return nil, err |
|
} |
|
if err := lnWallet.Startup(); err != nil { |
|
fmt.Printf("unable to start wallet: %v\n", err) |
|
return nil, err |
|
} |
|
|
|
ltndLog.Info("LightningWallet opened") |
|
|
|
cc.wallet = lnWallet |
|
|
|
return cc, nil |
|
} |
|
|
|
var ( |
|
// bitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet |
|
// chain. |
|
bitcoinTestnetGenesis = chainhash.Hash([chainhash.HashSize]byte{ |
|
0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71, |
|
0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae, |
|
0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad, |
|
0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00, |
|
}) |
|
|
|
// bitcoinMainnetGenesis is the genesis hash of Bitcoin's main chain. |
|
bitcoinMainnetGenesis = 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, |
|
}) |
|
|
|
// litecoinTestnetGenesis is the genesis hash of Litecoin's testnet4 |
|
// chain. |
|
litecoinTestnetGenesis = 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, |
|
}) |
|
|
|
// litecoinMainnetGenesis is the genesis hash of Litecoin's main chain. |
|
litecoinMainnetGenesis = chainhash.Hash([chainhash.HashSize]byte{ |
|
0xe2, 0xbf, 0x04, 0x7e, 0x7e, 0x5a, 0x19, 0x1a, |
|
0xa4, 0xef, 0x34, 0xd3, 0x14, 0x97, 0x9d, 0xc9, |
|
0x98, 0x6e, 0x0f, 0x19, 0x25, 0x1e, 0xda, 0xba, |
|
0x59, 0x40, 0xfd, 0x1f, 0xe3, 0x65, 0xa7, 0x12, |
|
}) |
|
|
|
// chainMap is a simple index that maps a chain's genesis hash to the |
|
// chainCode enum for that chain. |
|
chainMap = map[chainhash.Hash]chainCode{ |
|
bitcoinTestnetGenesis: bitcoinChain, |
|
litecoinTestnetGenesis: litecoinChain, |
|
|
|
bitcoinMainnetGenesis: bitcoinChain, |
|
litecoinMainnetGenesis: litecoinChain, |
|
} |
|
|
|
// 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. |
|
// |
|
// The first item in the array is the primary host we'll use to attempt |
|
// the SRV lookup we require. If we're unable to receive a response |
|
// over UDP, then we'll fall back to manual TCP resolution. The second |
|
// item in the array is a special A record that we'll query in order to |
|
// receive the IP address of the current authoritative DNS server for |
|
// the network seed. |
|
// |
|
// TODO(roasbeef): extend and collapse these and chainparams.go into |
|
// struct like chaincfg.Params |
|
chainDNSSeeds = map[chainhash.Hash][][2]string{ |
|
bitcoinMainnetGenesis: { |
|
{ |
|
"nodes.lightning.directory", |
|
"soa.nodes.lightning.directory", |
|
}, |
|
}, |
|
|
|
bitcoinTestnetGenesis: { |
|
{ |
|
"test.nodes.lightning.directory", |
|
"soa.nodes.lightning.directory", |
|
}, |
|
}, |
|
|
|
litecoinMainnetGenesis: { |
|
{ |
|
"ltc.nodes.lightning.directory", |
|
"soa.nodes.lightning.directory", |
|
}, |
|
}, |
|
} |
|
) |
|
|
|
// 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 a slice containing the 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)) |
|
} |
|
|
|
// initNeutrinoBackend inits a new instance of the neutrino light client |
|
// backend given a target chain directory to store the chain state. |
|
func initNeutrinoBackend(chainDir string) (*neutrino.ChainService, func(), error) { |
|
// First we'll open the database file for neutrino, creating the |
|
// database if needed. We append the normalized network name here to |
|
// match the behavior of btcwallet. |
|
dbPath := filepath.Join( |
|
chainDir, |
|
normalizeNetwork(activeNetParams.Name), |
|
) |
|
|
|
// Ensure that the neutrino db path exists. |
|
if err := os.MkdirAll(dbPath, 0700); err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
dbName := filepath.Join(dbPath, "neutrino.db") |
|
db, err := walletdb.Create("bdb", dbName) |
|
if err != nil { |
|
return nil, nil, fmt.Errorf("unable to create neutrino "+ |
|
"database: %v", 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: dbPath, |
|
Database: db, |
|
ChainParams: *activeNetParams.Params, |
|
AddPeers: cfg.NeutrinoMode.AddPeers, |
|
ConnectPeers: cfg.NeutrinoMode.ConnectPeers, |
|
Dialer: func(addr net.Addr) (net.Conn, error) { |
|
return cfg.net.Dial(addr.Network(), addr.String()) |
|
}, |
|
NameResolver: func(host string) ([]net.IP, error) { |
|
addrs, err := cfg.net.LookupHost(host) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
ips := make([]net.IP, 0, len(addrs)) |
|
for _, strIP := range addrs { |
|
ip := net.ParseIP(strIP) |
|
if ip == nil { |
|
continue |
|
} |
|
|
|
ips = append(ips, ip) |
|
} |
|
|
|
return ips, nil |
|
}, |
|
} |
|
|
|
neutrino.MaxPeers = 8 |
|
neutrino.BanDuration = time.Hour * 48 |
|
|
|
neutrinoCS, err := neutrino.NewChainService(config) |
|
if err != nil { |
|
return nil, nil, fmt.Errorf("unable to create neutrino light "+ |
|
"client: %v", err) |
|
} |
|
|
|
if err := neutrinoCS.Start(); err != nil { |
|
db.Close() |
|
return nil, nil, err |
|
} |
|
|
|
cleanUp := func() { |
|
neutrinoCS.Stop() |
|
db.Close() |
|
} |
|
|
|
return neutrinoCS, cleanUp, nil |
|
}
|
|
|