Merge pull request #838 from cfromknecht/litecoind-backend

Litecoind Backend
This commit is contained in:
Olaoluwa Osuntokun 2018-03-14 18:17:27 -07:00 committed by GitHub
commit 695b09e32b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 189 additions and 73 deletions

@ -22,23 +22,46 @@ import (
"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"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/rpcclient"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcwallet/chain"
"github.com/roasbeef/btcwallet/walletdb"
)
// defaultChannelConstraints is the default set of channel constraints that are
// meant to be used when initially funding a channel.
const (
defaultBitcoinMinHTLCMSat = lnwire.MilliSatoshi(1000)
defaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
defaultBitcoinFeeRate = lnwire.MilliSatoshi(1)
defaultBitcoinTimeLockDelta = 144
defaultBitcoinStaticFeeRate = lnwallet.SatPerVByte(50)
defaultLitecoinMinHTLCMSat = lnwire.MilliSatoshi(1000)
defaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
defaultLitecoinFeeRate = lnwire.MilliSatoshi(1)
defaultLitecoinTimeLockDelta = 576
defaultLitecoinStaticFeeRate = lnwallet.SatPerVByte(200)
defaultLitecoinMinRelayFee = btcutil.Amount(1000)
)
// defaultBtcChannelConstraints is the default set of channel constraints that are
// meant to be used when initially funding a Bitcoin channel.
//
// TODO(roasbeef): have one for both chains
// TODO(halseth): make configurable at startup?
var defaultChannelConstraints = channeldb.ChannelConstraints{
var defaultBtcChannelConstraints = channeldb.ChannelConstraints{
DustLimit: lnwallet.DefaultDustLimit(),
MaxAcceptedHtlcs: lnwallet.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: defaultLitecoinMinRelayFee,
MaxAcceptedHtlcs: lnwallet.MaxHTLCNumber / 2,
}
// chainCode is an enum-like structure for keeping track of the chains
// currently supported within lnd.
type chainCode uint32
@ -111,7 +134,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
}
cc.feeEstimator = lnwallet.StaticFeeEstimator{
FeeRate: 50,
FeeRate: defaultBitcoinStaticFeeRate,
}
case litecoinChain:
cc.routingPolicy = htlcswitch.ForwardingPolicy{
@ -121,7 +144,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
TimeLockDelta: cfg.Litecoin.TimeLockDelta,
}
cc.feeEstimator = lnwallet.StaticFeeEstimator{
FeeRate: 100,
FeeRate: defaultLitecoinStaticFeeRate,
}
default:
return nil, nil, fmt.Errorf("Default routing policy for "+
@ -223,17 +246,24 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// database.
walletConfig.ChainSource = chain.NewNeutrinoClient(svc)
cleanUp = func() {
defer nodeDatabase.Close()
nodeDatabase.Close()
}
case "bitcoind", "litecoind":
var bitcoindMode *bitcoindConfig
switch {
case cfg.Bitcoin.Active:
bitcoindMode = cfg.BitcoindMode
case cfg.Litecoin.Active:
bitcoindMode = cfg.LitecoindMode
}
case "bitcoind":
// 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(cfg.BitcoindMode.RPCHost, ":") {
bitcoindHost = cfg.BitcoindMode.RPCHost
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
@ -245,13 +275,13 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
}
rpcPort -= 2
bitcoindHost = fmt.Sprintf("%v:%d",
cfg.BitcoindMode.RPCHost, rpcPort)
if cfg.Bitcoin.RegTest {
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",
cfg.BitcoindMode.RPCHost,
bitcoindMode.RPCHost,
rpcPort)
} else {
conn.Close()
@ -259,8 +289,8 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
}
}
bitcoindUser := cfg.BitcoindMode.RPCUser
bitcoindPass := cfg.BitcoindMode.RPCPass
bitcoindUser := bitcoindMode.RPCUser
bitcoindPass := bitcoindMode.RPCPass
rpcConfig := &rpcclient.ConnConfig{
Host: bitcoindHost,
User: bitcoindUser,
@ -271,7 +301,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
HTTPPostMode: true,
}
cc.chainNotifier, err = bitcoindnotify.New(rpcConfig,
cfg.BitcoindMode.ZMQPath, *activeNetParams.Params)
bitcoindMode.ZMQPath, *activeNetParams.Params)
if err != nil {
return nil, nil, err
}
@ -279,7 +309,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// Next, we'll create an instance of the bitcoind chain view to
// be used within the routing layer.
cc.chainView, err = chainview.NewBitcoindFilteredChainView(
*rpcConfig, cfg.BitcoindMode.ZMQPath,
*rpcConfig, bitcoindMode.ZMQPath,
*activeNetParams.Params)
if err != nil {
srvrLog.Errorf("unable to create chain view: %v", err)
@ -290,7 +320,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// used by the wallet for notifications, calls, etc.
bitcoindConn, err = chain.NewBitcoindClient(
activeNetParams.Params, bitcoindHost, bitcoindUser,
bitcoindPass, cfg.BitcoindMode.ZMQPath,
bitcoindPass, bitcoindMode.ZMQPath,
time.Millisecond*100)
if err != nil {
return nil, nil, err
@ -300,7 +330,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// If we're not in regtest mode, then we'll attempt to use a
// proper fee estimator for testnet.
if !cfg.Bitcoin.RegTest {
if cfg.Bitcoin.Active && !cfg.Bitcoin.RegTest {
ltndLog.Infof("Initializing bitcoind backed fee estimator")
// Finally, we'll re-initialize the fee estimator, as
@ -317,8 +347,25 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
if err := cc.feeEstimator.Start(); err != nil {
return nil, 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.SatPerVByte(25)
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
*rpcConfig, fallBackFeeRate,
)
if err != nil {
return nil, nil, err
}
if err := cc.feeEstimator.Start(); err != nil {
return nil, nil, err
}
}
case "btcd":
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
@ -437,6 +484,12 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
cc.signer = wc
cc.chainIO = 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,
)
@ -451,7 +504,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
FeeEstimator: cc.feeEstimator,
SecretKeyRing: keyRing,
ChainIO: cc.chainIO,
DefaultConstraints: defaultChannelConstraints,
DefaultConstraints: channelConstraints,
NetParams: *activeNetParams.Params,
}
wallet, err := lnwallet.NewLightningWallet(walletCfg)

@ -52,16 +52,6 @@ const (
// HTLCs on our channels.
minTimeLockDelta = 4
defaultBitcoinMinHTLCMSat = 1000
defaultBitcoinBaseFeeMSat = 1000
defaultBitcoinFeeRate = 1
defaultBitcoinTimeLockDelta = 144
defaultLitecoinMinHTLCMSat = 1000
defaultLitecoinBaseFeeMSat = 1000
defaultLitecoinFeeRate = 1
defaultLitecoinTimeLockDelta = 576
defaultAlias = ""
defaultColor = "#3399FF"
)
@ -82,14 +72,15 @@ var (
defaultLtcdDir = btcutil.AppDataDir("ltcd", false)
defaultLtcdRPCCertFile = filepath.Join(defaultLtcdDir, "rpc.cert")
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
defaultLitecoindDir = btcutil.AppDataDir("litecoin", false)
)
type chainConfig struct {
Active bool `long:"active" description:"If the chain should be active or not."`
ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino"`
Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"ltcd" choice:"litecoind"`
TestNet3 bool `long:"testnet" description:"Use the test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
@ -182,8 +173,9 @@ type config struct {
BitcoindMode *bitcoindConfig `group:"bitcoind" namespace:"bitcoind"`
NeutrinoMode *neutrinoConfig `group:"neutrino" namespace:"neutrino"`
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"`
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"`
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
LitecoindMode *bitcoindConfig `group:"litecoind" namespace:"litecoind"`
Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"`
@ -241,13 +233,17 @@ func loadConfig() (*config, error) {
BaseFee: defaultLitecoinBaseFeeMSat,
FeeRate: defaultLitecoinFeeRate,
TimeLockDelta: defaultLitecoinTimeLockDelta,
Node: "btcd",
Node: "ltcd",
},
LtcdMode: &btcdConfig{
Dir: defaultLtcdDir,
RPCHost: defaultRPCHost,
RPCCert: defaultLtcdRPCCertFile,
},
LitecoindMode: &bitcoindConfig{
Dir: defaultLitecoindDir,
RPCHost: defaultRPCHost,
},
MaxPendingChannels: defaultMaxPendingChannels,
NoEncryptWallet: defaultNoEncryptWallet,
Autopilot: &autoPilotConfig{
@ -333,6 +329,7 @@ func loadConfig() (*config, error) {
cfg.BtcdMode.Dir = cleanAndExpandPath(cfg.BtcdMode.Dir)
cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir)
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir)
// Setup dial and DNS resolution functions depending on the specified
// options. The default is to use the standard golang "net" package
@ -398,17 +395,16 @@ func loadConfig() (*config, error) {
str := "%s: simnet mode for litecoin not currently supported"
return nil, fmt.Errorf(str, funcName)
}
if cfg.Litecoin.RegTest {
str := "%s: regnet mode for litecoin not currently supported"
return nil, fmt.Errorf(str, funcName)
}
if cfg.Litecoin.TimeLockDelta < minTimeLockDelta {
return nil, fmt.Errorf("timelockdelta must be at least %v",
minTimeLockDelta)
}
if cfg.Litecoin.Node != "btcd" {
str := "%s: only ltcd (`btcd`) mode supported for litecoin at this time"
return nil, fmt.Errorf(str, funcName)
}
// The litecoin chain is the current active chain. However
// throughout the codebase we required chaincfg.Params. So as a
// temporary hack, we'll mutate the default net params for
@ -417,12 +413,31 @@ func loadConfig() (*config, error) {
applyLitecoinParams(&paramCopy)
activeNetParams = paramCopy
err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode, litecoinChain,
funcName)
if err != nil {
err := fmt.Errorf("unable to load RPC credentials for "+
"ltcd: %v", err)
return nil, err
switch cfg.Litecoin.Node {
case "ltcd":
err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode,
litecoinChain, funcName)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for ltcd: %v", err)
return nil, err
}
case "litecoind":
if cfg.Litecoin.SimNet {
return nil, fmt.Errorf("%s: litecoind does not "+
"support simnet", funcName)
}
err := parseRPCParams(cfg.Litecoin, cfg.LitecoindMode,
litecoinChain, funcName)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for litecoind: %v", err)
return nil, err
}
default:
str := "%s: only ltcd and litecoind mode supported for " +
"litecoin at this time"
return nil, fmt.Errorf(str, funcName)
}
cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir,
@ -485,6 +500,10 @@ func loadConfig() (*config, error) {
}
case "neutrino":
// No need to get RPC parameters.
default:
str := "%s: only btcd, bitcoind, and neutrino mode " +
"supported for bitcoin at this time"
return nil, fmt.Errorf(str, funcName)
}
cfg.Bitcoin.ChainDir = filepath.Join(cfg.DataDir,
@ -762,9 +781,16 @@ func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
"bitcoind.zmqpath")
}
daemonName = "bitcoind"
confDir = conf.Dir
confFile = "bitcoin"
switch net {
case bitcoinChain:
daemonName = "bitcoind"
confDir = conf.Dir
confFile = "bitcoin"
case litecoinChain:
daemonName = "litecoind"
confDir = conf.Dir
confFile = "litecoin"
}
}
// If we're in simnet mode, then the running btcd instance won't read
@ -780,7 +806,7 @@ func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf", confFile))
switch cConfig.Node {
case "btcd":
case "btcd", "ltcd":
nConf := nodeConfig.(*btcdConfig)
rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
if err != nil {
@ -789,7 +815,7 @@ func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
err)
}
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
case "bitcoind":
case "bitcoind", "litecoind":
nConf := nodeConfig.(*bitcoindConfig)
rpcUser, rpcPass, zmqPath, err := extractBitcoindRPCParams(confFile)
if err != nil {

@ -178,17 +178,33 @@ installing `lnd` in preparation for the
lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --btcd.rpcuser=kek --btcd.rpcpass=kek --externalip=X.X.X.X
```
#### Running lnd using the bitcoind backend
#### Running lnd using the bitcoind or litecoind backend
To configure your bitcoind backend for use with lnd, first complete and verify the following:
The configuration for bitcoind and litecoind are nearly identical, the following
steps can be mirrored with loss of generality to enable a litecoind backend.
Setup will be described in regards to `bitciond`, but note that `lnd` uses a
distinct `litecoin.node=litecoind` argument and analogous subconfigurations
prefixed by `litecoind`.
- The `bitcoind` instance must
be configured with `--txindex` just like `btcd` above
- Additionally, since `lnd` uses [ZeroMQ](https://github.com/bitcoin/bitcoin/blob/master/doc/zmq.md) to interface with `bitcoind`, *your `bitcoind` installation must be compiled with ZMQ*. If you installed it from source, this is likely the case, but if you installed it via Homebrew in the past it may not be included ([this has now been fixed](https://github.com/Homebrew/homebrew-core/pull/23088) in the latest Homebrew recipe for bitcoin)
- Configure the `bitcoind` instance for ZMQ with `--zmqpubrawblock` and `--zmqpubrawtx`
(the latter is optional but allows you to see unconfirmed transactions in your
wallet). They must be combined in the same ZMQ socket address (e.g. `--zmqpubrawblock=tcp://127.0.0.1:28332` and `--zmqpubrawtx=tcp://127.0.0.1:28332`).
- Start `bitcoind` running against testnet, and let it complete a full sync with the testnet chain (alternatively, use `--bitcoind.regtest` instead).
To configure your bitcoind backend for use with lnd, first complete and verify
the following:
- The `bitcoind` instance must be configured with `--txindex` just like `btcd`
above
- Additionally, since `lnd` uses
[ZeroMQ](https://github.com/bitcoin/bitcoin/blob/master/doc/zmq.md) to
interface with `bitcoind`, *your `bitcoind` installation must be compiled with
ZMQ*. If you installed it from source, this is likely the case, but if you
installed it via Homebrew in the past it may not be included ([this has now
been fixed](https://github.com/Homebrew/homebrew-core/pull/23088) in the
latest Homebrew recipe for bitcoin)
- Configure the `bitcoind` instance for ZMQ with `--zmqpubrawblock` and
`--zmqpubrawtx` (the latter is optional but allows you to see unconfirmed
transactions in your wallet). They must be combined in the same ZMQ socket
address (e.g. `--zmqpubrawblock=tcp://127.0.0.1:28332` and
`--zmqpubrawtx=tcp://127.0.0.1:28332`).
- Start `bitcoind` running against testnet, and let it complete a full sync with
the testnet chain (alternatively, use `--bitcoind.regtest` instead).
Here's a sample `bitcoin.conf` for use with lnd:
```
@ -268,8 +284,8 @@ Notice the `[Bitcoin]` section. This section houses the parameters for the
Bitcoin chain. `lnd` also supports Litecoin testnet4 (but not both BTC and LTC
at the same time), so when working with Litecoin be sure to set to parameters
for Litecoin accordingly. For node configuration, the sections are called
`[Btcd]`, `[Bitcoind]`, `[Neutrino]`, and `[Ltcd]` depending on which chain
and node type you're using.
`[Btcd]`, `[Bitcoind]`, `[Neutrino]`, `[Ltcd]`, and `[Litecoind]` depending on
which chain and node type you're using.
# Accurate as of:
- _roasbeef/btcd commit:_ `f8c02aff4e7a807ba0c1349e2db03695d8e790e8`

@ -42,12 +42,19 @@ const (
// TODO(roasbeef): add command line param to modify
maxFundingAmount = btcutil.Amount(1 << 24)
// minRemoteDelay and maxRemoteDelay is the extremes of the CSV delay
// we will require the remote to use for its commitment transaction.
// The actual delay we will require will be somewhere between these
// values, depending on channel size.
minRemoteDelay = 144
maxRemoteDelay = 2016
// minBtcRemoteDelay and maxBtcRemoteDelay is the extremes of the
// Bitcoin CSV delay we will require the remote to use for its
// commitment transaction. The actual delay we will require will be
// somewhere between these values, depending on channel size.
minBtcRemoteDelay uint16 = 144
maxBtcRemoteDelay uint16 = 2016
// minLtcRemoteDelay and maxLtcRemoteDelay is the extremes of the
// Litecoin CSV delay we will require the remote to use for its
// commitment transaction. The actual delay we will require will be
// somewhere between these values, depending on channel size.
minLtcRemoteDelay uint16 = 576
maxLtcRemoteDelay uint16 = 8064
// maxWaitNumBlocksFundingConf is the maximum number of blocks to wait
// for the funding transaction to be confirmed before forgetting about

@ -166,7 +166,7 @@ func createTestWallet(cdb *channeldb.DB, netParams *chaincfg.Params,
ChainIO: bio,
FeeEstimator: estimator,
NetParams: *netParams,
DefaultConstraints: defaultChannelConstraints,
DefaultConstraints: defaultBtcChannelConstraints,
})
if err != nil {
return nil, err

17
lnd.go

@ -238,6 +238,17 @@ func lndMain() error {
primaryChain := registeredChains.PrimaryChain()
registeredChains.RegisterChain(primaryChain, activeChainControl)
// Select the configuration and furnding parameters for Bitcoin or
// Litecoin, depending on the primary registered chain.
chainCfg := cfg.Bitcoin
minRemoteDelay := minBtcRemoteDelay
maxRemoteDelay := maxBtcRemoteDelay
if primaryChain == litecoinChain {
chainCfg = cfg.Litecoin
minRemoteDelay = minLtcRemoteDelay
maxRemoteDelay = maxLtcRemoteDelay
}
// TODO(roasbeef): add rotation
idPrivKey, err := activeChainControl.wallet.DerivePrivKey(keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
@ -340,7 +351,7 @@ func lndMain() error {
// In case the user has explicitly specified
// a default value for the number of
// confirmations, we use it.
defaultConf := uint16(cfg.Bitcoin.DefaultNumChanConfs)
defaultConf := uint16(chainCfg.DefaultNumChanConfs)
if defaultConf != 0 {
return defaultConf
}
@ -373,13 +384,13 @@ func lndMain() error {
// In case the user has explicitly specified
// a default value for the remote delay, we
// use it.
defaultDelay := uint16(cfg.Bitcoin.DefaultRemoteDelay)
defaultDelay := uint16(chainCfg.DefaultRemoteDelay)
if defaultDelay > 0 {
return defaultDelay
}
// If not we scale according to channel size.
delay := uint16(maxRemoteDelay *
delay := uint16(btcutil.Amount(maxRemoteDelay) *
chanAmt / maxFundingAmount)
if delay < minRemoteDelay {
delay = minRemoteDelay

@ -2095,6 +2095,9 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
default:
// TODO(roasbeef): assumes set delta between versions
defaultDelta := cfg.Bitcoin.TimeLockDelta
if registeredChains.PrimaryChain() == litecoinChain {
defaultDelta = cfg.Litecoin.TimeLockDelta
}
options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta)))
}