diff --git a/README.md b/README.md index 9edebaa1a9..4d4006e393 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ This means that instead of re-implementing them, Eclair benefits from the verifi * Eclair needs a _synchronized_, _segwit-ready_, **_zeromq-enabled_**, _wallet-enabled_, _non-pruning_, _tx-indexing_ [Bitcoin Core](https://github.com/bitcoin/bitcoin) node. * You must configure your Bitcoin node to use `bech32` or `bech32m` (segwit) addresses. If your wallet has "non-segwit UTXOs" (outputs that are neither `p2sh-segwit`, `bech32` or `bech32m`), you must send them to a `bech32` or `bech32m` address before running Eclair. -* Eclair requires Bitcoin Core 27.2 or higher. If you are upgrading an existing wallet, you may need to create a new address and send all your funds to that address. +* Eclair requires Bitcoin Core 28.1 or higher. If you are upgrading an existing wallet, you may need to create a new address and send all your funds to that address. Run bitcoind with the following minimal `bitcoin.conf`: diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 5562da1032..865329e87a 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -4,7 +4,10 @@ ## Major changes - +### Update minimal version of Bitcoin Core + +With this release, eclair requires using Bitcoin Core 28.1. +Newer versions of Bitcoin Core may be used, but have not been extensively tested. ### API changes diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index 05526216a2..b49eba0414 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -88,9 +88,9 @@ true - https://bitcoincore.org/bin/bitcoin-core-27.2/bitcoin-27.2-x86_64-linux-gnu.tar.gz - c6dcec7ce5c43dafa48fe459911a8049 - 4342a03bbcc98d81fca2c4fb404f96d5dbae4e10 + https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-linux-gnu.tar.gz + 2c915b5ea3a7e6662dd059d720109d7a + e0fd253757e5f8d7d9c9cd73936e92cc6e168558 @@ -101,9 +101,9 @@ - https://bitcoincore.org/bin/bitcoin-core-27.2/bitcoin-27.2-x86_64-apple-darwin.tar.gz - 25857522febc428160bc4eedf46eb6db - 574d753359ef2b5c1bc0ef1e028d516da86392af + https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-apple-darwin.tar.gz + d4ad664051072de807d4a864d58ccd2a + f3e10c839a04929da870fea16829567d26dbecd8 @@ -114,9 +114,9 @@ - https://bitcoincore.org/bin/bitcoin-core-27.2/bitcoin-27.2-win64.zip - 1a05b7880a01c0437e5e0b7e13a02635 - 84c0b8d1a02d3c024881a180e8a3c670c1e0073a + https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-win64.zip + 29748a873277cbb112b96c3662dcb3a4 + a7815c48d8f879f3728b50ad06a5fa1f1e10e0dc 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 73b2c0f358..bd5566b08e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -188,7 +188,7 @@ class Setup(val datadir: File, await(getBitcoinStatus(bitcoinClient), 30 seconds, "bitcoind did not respond after 30 seconds") } logger.info(s"bitcoind version=${bitcoinStatus.version}") - assert(bitcoinStatus.version >= 270200, "Eclair requires Bitcoin Core 27.2 or higher") + assert(bitcoinStatus.version >= 280000, "Eclair requires Bitcoin Core 28.x or higher") bitcoinStatus.unspentAddresses.foreach { address => val isSegwit = addressToPublicKeyScript(bitcoinStatus.chainHash, address).map(script => Script.isNativeWitnessScript(script)).getOrElse(false) assert(isSegwit, s"Your wallet contains non-segwit UTXOs (e.g. address=$address). You must send those UTXOs to a segwit address to use Eclair (check out our README for more details).") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala index dfde6aa86e..c8b48dac50 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala @@ -500,22 +500,26 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey))) val bitcoinClient = makeBitcoinCoreClient() - // create a huge tx so we make sure it has > 2 inputs + // Create a huge tx so we make sure it has > 2 inputs without publishing it. bitcoinClient.makeFundingTx(pubkeyScript, 250 btc, FeeratePerKw(1000 sat)).pipeTo(sender.ref) - val MakeFundingTxResponse(fundingTx, outputIndex, _) = sender.expectMsgType[MakeFundingTxResponse] + val fundingTx = sender.expectMsgType[MakeFundingTxResponse].fundingTx assert(fundingTx.txIn.length > 2) - // spend the first 2 inputs + // Double-spend the first 2 inputs. + val amountIn = fundingTx.txIn.take(2).map(txIn => { + bitcoinClient.getTransaction(txIn.outPoint.txid).pipeTo(sender.ref) + sender.expectMsgType[Transaction].txOut(txIn.outPoint.index.toInt).amount + }).sum val tx1 = fundingTx.copy( txIn = fundingTx.txIn.take(2), - txOut = fundingTx.txOut.updated(outputIndex, fundingTx.txOut(outputIndex).copy(amount = 50 btc)) + txOut = Seq(TxOut(amountIn - 15_000.sat, Script.pay2wpkh(randomKey().publicKey))) ) bitcoinClient.signPsbt(new Psbt(tx1), tx1.txIn.indices, Nil).pipeTo(sender.ref) val tx2 = sender.expectMsgType[ProcessPsbtResponse].finalTx_opt.toOption.get bitcoinClient.commit(tx2).pipeTo(sender.ref) sender.expectMsg(true) - // fundingTx inputs are still locked except for the first 2 that were just spent + // The inputs of the first transaction are still locked except for the first 2 that were just spent. val expectedLocks = fundingTx.txIn.drop(2).map(_.outPoint).toSet assert(expectedLocks.nonEmpty) awaitAssert({ @@ -523,11 +527,11 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A sender.expectMsg(expectedLocks) }, max = 10 seconds, interval = 1 second) - // publishing fundingTx will fail as its first 2 inputs are already spent by tx above in the mempool + // Publishing the first transaction will fail as its first 2 inputs are already spent by the second transaction. bitcoinClient.commit(fundingTx).pipeTo(sender.ref) sender.expectMsg(false) - // and all locked inputs should now be unlocked + // And all locked inputs should now be unlocked. awaitAssert({ bitcoinClient.listLockedOutpoints().pipeTo(sender.ref) sender.expectMsg(Set.empty[OutPoint]) @@ -594,15 +598,12 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A assert(sender.expectMsgType[Failure].cause.getMessage.contains("Transaction not in mempool")) wallet.getMempoolTx(anchorTx2.txid).pipeTo(sender.ref) sender.expectMsgType[MempoolTx] - val txNotFunded = Transaction(2, Nil, Seq(TxOut(150_000 sat, Script.pay2wpkh(priv.publicKey))), 0) - wallet.fundTransaction(txNotFunded, FeeratePerKw(1000 sat), replaceable = true).pipeTo(sender.ref) - assert(sender.expectMsgType[Failure].cause.getMessage.contains("Insufficient funds")) - // The second anchor transaction confirms, which frees up the wallet input of the first anchor transaction. - generateBlocks(1) + // Bitcoin Core automatically detects that the wallet input of the first anchor transaction is available again. wallet.listUnspent().pipeTo(sender.ref) val walletUtxos = sender.expectMsgType[Seq[Utxo]] assert(walletUtxos.exists(_.txid == walletInput1.txid)) + val txNotFunded = Transaction(2, Nil, Seq(TxOut(150_000 sat, Script.pay2wpkh(priv.publicKey))), 0) wallet.fundTransaction(txNotFunded, FeeratePerKw(1000 sat), replaceable = true).pipeTo(sender.ref) sender.expectMsgType[FundTransactionResponse] } @@ -668,15 +669,12 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A assert(sender.expectMsgType[Failure].cause.getMessage.contains("Transaction not in mempool")) miner.getMempoolTx(htlcTimeoutTx.txid).pipeTo(sender.ref) sender.expectMsgType[MempoolTx] - val txNotFunded = Transaction(2, Nil, Seq(TxOut(150_000 sat, Script.pay2wpkh(priv.publicKey))), 0) - wallet1.fundTransaction(txNotFunded, FeeratePerKw(1000 sat), replaceable = true).pipeTo(sender.ref) - assert(sender.expectMsgType[Failure].cause.getMessage.contains("Insufficient funds")) - // The second anchor transaction confirms, which frees up the wallet input of the first anchor transaction. - generateBlocks(1) + // Bitcoin Core automatically detects that the wallet input of the first HTLC transaction is available again. wallet1.listUnspent().pipeTo(sender.ref) val walletUtxos = sender.expectMsgType[Seq[Utxo]] assert(walletUtxos.exists(_.txid == walletInput1.txid)) + val txNotFunded = Transaction(2, Nil, Seq(TxOut(150_000 sat, Script.pay2wpkh(priv.publicKey))), 0) wallet1.fundTransaction(txNotFunded, FeeratePerKw(1000 sat), replaceable = true).pipeTo(sender.ref) sender.expectMsgType[FundTransactionResponse] } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala index 8f87d409ba..8c34e6610b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala @@ -62,7 +62,7 @@ trait BitcoindService extends Logging { val PATH_BITCOIND = sys.env.get("BITCOIND_DIR") match { case Some(customBitcoinDir) => new File(customBitcoinDir, "bitcoind") - case None => new File(TestUtils.BUILD_DIRECTORY, "bitcoin-27.2/bin/bitcoind") + case None => new File(TestUtils.BUILD_DIRECTORY, "bitcoin-28.0/bin/bitcoind") } logger.info(s"using bitcoind: $PATH_BITCOIND") val PATH_BITCOIND_DATADIR = new File(INTEGRATION_TMP_DIR, "datadir-bitcoin")