diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index ecf7ae8452..2b55c576fa 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -40,6 +40,8 @@ eclair { // - ignore: eclair will leave these utxos locked and start startup-locked-utxos-behavior = "stop" final-pubkey-refresh-delay = 3 seconds + // If true, eclair will poll bitcoind for 30 seconds at start-up before giving up. + wait-for-bitcoind-up = true } node-alias = "eclair" diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index f5ef2b3253..8b5b19e473 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -171,7 +171,9 @@ class Setup(val datadir: File, case Success(status) => Future.successful(status) case Failure(e) => logger.warn(s"failed to connect to bitcoind (${e.getMessage}), retrying...") - after(5 seconds) { pollBitcoinStatus(bitcoinClient) } + after(5 seconds) { + pollBitcoinStatus(bitcoinClient) + } } } @@ -181,18 +183,22 @@ class Setup(val datadir: File, port = config.getInt("bitcoind.rpcport"), wallet = wallet ) - val BitcoinStatus(bitcoinVersion, chainHash, initialBlockDownload, progress, blocks, headers, unspentAddresses) = await(pollBitcoinStatus(bitcoinClient), 30 seconds, "bitcoind did not respond after 30 seconds") - logger.info(s"bitcoind version=$bitcoinVersion") - assert(bitcoinVersion >= 230000, "Eclair requires Bitcoin Core 23.0 or higher") - assert(unspentAddresses.forall(address => !isPay2PubkeyHash(address)), "Your wallet contains non-segwit UTXOs. You must send those UTXOs to a bech32 address to use Eclair (check out our README for more details).") - if (chainHash != Block.RegtestGenesisBlock.hash) { - assert(!initialBlockDownload, s"bitcoind should be synchronized (initialblockdownload=$initialBlockDownload)") - assert(progress > 0.999, s"bitcoind should be synchronized (progress=$progress)") - assert(headers - blocks <= 1, s"bitcoind should be synchronized (headers=$headers blocks=$blocks)") + val bitcoinStatus = if (config.getBoolean("bitcoind.wait-for-bitcoind-up")) { + await(pollBitcoinStatus(bitcoinClient), 30 seconds, "bitcoind wasn't ready after 30 seconds") + } else { + await(getBitcoinStatus(bitcoinClient), 30 seconds, "bitcoind did not respond after 30 seconds") + } + logger.info(s"bitcoind version=${bitcoinStatus.version}") + assert(bitcoinStatus.version >= 230000, "Eclair requires Bitcoin Core 23.0 or higher") + assert(bitcoinStatus.unspentAddresses.forall(address => !isPay2PubkeyHash(address)), "Your wallet contains non-segwit UTXOs. You must send those UTXOs to a bech32 address to use Eclair (check out our README for more details).") + if (bitcoinStatus.chainHash != Block.RegtestGenesisBlock.hash) { + assert(!bitcoinStatus.initialBlockDownload, s"bitcoind should be synchronized (initialblockdownload=${bitcoinStatus.initialBlockDownload})") + assert(bitcoinStatus.verificationProgress > 0.999, s"bitcoind should be synchronized (progress=${bitcoinStatus.verificationProgress})") + assert(bitcoinStatus.headerCount - bitcoinStatus.blockCount <= 1, s"bitcoind should be synchronized (headers=${bitcoinStatus.headerCount} blocks=${bitcoinStatus.blockCount})") } - logger.info(s"current blockchain height=$blocks") - blockHeight.set(blocks) - (bitcoinClient, chainHash) + logger.info(s"current blockchain height=${bitcoinStatus.blockCount}") + blockHeight.set(bitcoinStatus.blockCount) + (bitcoinClient, bitcoinStatus.chainHash) } val instanceId = UUID.randomUUID() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala index 2d13c8b4c5..ed912203e2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.integration import akka.testkit.TestProbe -import com.typesafe.config.ConfigFactory +import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq import fr.acinq.eclair.blockchain.bitcoind.rpc.{Error, JsonRPCError} import fr.acinq.eclair.{BitcoinDefaultWalletException, BitcoinWalletDisabledException, BitcoinWalletNotLoadedException, TestUtils} @@ -30,41 +30,49 @@ import scala.jdk.CollectionConverters._ class StartupIntegrationSpec extends IntegrationSpec { + private def createConfig(wallet_opt: Option[String]): Config = { + val defaultConfig = ConfigFactory.parseMap(Map("eclair.bitcoind.wait-for-bitcoind-up" -> "false", "eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withDefaultCommitment).withFallback(commonConfig) + wallet_opt match { + case Some(wallet) => ConfigFactory.parseMap(Map("eclair.bitcoind.wallet" -> wallet).asJava).withFallback(defaultConfig) + case None => defaultConfig + } + } + test("no bitcoind wallet configured and one wallet loaded") { - instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.bitcoind.wallet" -> "", "eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withDefaultCommitment).withFallback(commonConfig)) + instantiateEclairNode("A", createConfig(wallet_opt = Some(""))) } test("no bitcoind wallet configured and two wallets loaded") { val sender = TestProbe() - sender.send(bitcoincli, BitcoinReq("createwallet", "")) + sender.send(bitcoincli, BitcoinReq("createwallet", "other_wallet")) sender.expectMsgType[Any] val thrown = intercept[BitcoinDefaultWalletException] { - instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.bitcoind.wallet" -> "", "eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withDefaultCommitment).withFallback(commonConfig)) + instantiateEclairNode("C", createConfig(wallet_opt = Some(""))) } - assert(thrown == BitcoinDefaultWalletException(List(defaultWallet, ""))) + assert(thrown == BitcoinDefaultWalletException(List(defaultWallet, "other_wallet"))) } test("explicit bitcoind wallet configured and two wallets loaded") { val sender = TestProbe() - sender.send(bitcoincli, BitcoinReq("createwallet", "")) + sender.send(bitcoincli, BitcoinReq("createwallet", "other_wallet")) sender.expectMsgType[Any] - instantiateEclairNode("D", ConfigFactory.parseMap(Map("eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withDefaultCommitment).withFallback(commonConfig)) + instantiateEclairNode("D", createConfig(wallet_opt = Some(defaultWallet))) } test("explicit bitcoind wallet configured but not loaded") { val sender = TestProbe() - sender.send(bitcoincli, BitcoinReq("createwallet", "")) + sender.send(bitcoincli, BitcoinReq("createwallet", "other_wallet")) sender.expectMsgType[Any] val thrown = intercept[BitcoinWalletNotLoadedException] { - instantiateEclairNode("E", ConfigFactory.parseMap(Map("eclair.bitcoind.wallet" -> "notloaded", "eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withDefaultCommitment).withFallback(commonConfig)) + instantiateEclairNode("E", createConfig(wallet_opt = Some("not_loaded"))) } - assert(thrown == BitcoinWalletNotLoadedException("notloaded", List(defaultWallet, ""))) + assert(thrown == BitcoinWalletNotLoadedException("not_loaded", List(defaultWallet, "other_wallet"))) } test("bitcoind started with wallets disabled") { restartBitcoind(startupFlags = "-disablewallet", loadWallet = false) val thrown = intercept[BitcoinWalletDisabledException] { - instantiateEclairNode("F", ConfigFactory.load().getConfig("eclair").withFallback(withDefaultCommitment).withFallback(commonConfig)) + instantiateEclairNode("F", createConfig(wallet_opt = Some(""))) } assert(thrown == BitcoinWalletDisabledException(e = JsonRPCError(Error(-32601, "Method not found")))) }