diff --git a/README.md b/README.md index 74c8a742ee..98c5409fe7 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 29.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. +* Eclair requires Bitcoin Core 29 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`: @@ -71,8 +71,8 @@ server=1 rpcuser=foo rpcpassword=bar txindex=1 -addresstype=bech32 -changetype=bech32 +addresstype=bech32m +changetype=bech32m zmqpubhashblock=tcp://127.0.0.1:29000 zmqpubrawtx=tcp://127.0.0.1:29000 ``` 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 e0e40badcd..4dd9fc4ab1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -195,7 +195,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 >= 290100, "Eclair requires Bitcoin Core 29.1 or higher") + assert(bitcoinStatus.version >= 290000, "Eclair requires Bitcoin Core 29 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/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala index 057113e38e..8d7acc3e8e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala @@ -369,7 +369,8 @@ private class ReplaceableTxFunder(replyTo: ActorRef[ReplaceableTxFunder.FundingR // pay the expected package feerate. val packageWeight = commitTx.weight() + anchorTx.commitmentFormat.anchorInputWeight + fundTxResponse.tx.weight() val expectedFee = Transactions.weight2fee(targetFeerate, packageWeight) - val currentFee = commitFee + fundTxResponse.fee + // Note that we haven't taken into account yet the amount of the anchor output, so we add it here. + val currentFee = commitFee + fundTxResponse.fee + anchorTx.input.txOut.amount val changeAmount = (fundTxResponse.tx.txOut.map(_.amount).sum - expectedFee + currentFee).max(dustLimit) WalletInputs(walletInputs, changeOutput_opt = Some(TxOut(changeAmount, changeScript))) } 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 11db58f7e9..f59e6ccf72 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 @@ -54,7 +54,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A implicit val formats: Formats = DefaultFormats - val defaultAddressType_opt: Option[String] = Some("bech32") + val defaultAddressType_opt: Option[String] = Some("bech32m") override def beforeAll(): Unit = { // Note that we don't specify a default change address type, allowing bitcoind to choose between p2wpkh and p2tr. @@ -90,10 +90,10 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A val sender = TestProbe() val bitcoinClient = makeBitcoinCoreClient() - // wallet is configured with address_type=bech32 + // wallet is configured with address_type=bech32m bitcoinClient.getReceiveAddress(None).pipeTo(sender.ref) val address = sender.expectMsgType[String] - assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2wpkh).contains(true)) + assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2tr).contains(true)) bitcoinClient.getReceiveAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref) val address1 = sender.expectMsgType[String] @@ -108,10 +108,10 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A val sender = TestProbe() val bitcoinClient = makeBitcoinCoreClient() - // wallet is configured with address_type=bech32 + // wallet is configured with address_type=bech32m bitcoinClient.getChangeAddress(None).pipeTo(sender.ref) val address = sender.expectMsgType[String] - assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2wpkh).contains(true)) + assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2tr).contains(true)) bitcoinClient.getChangeAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref) val address1 = sender.expectMsgType[String] @@ -1746,11 +1746,11 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A val sender = TestProbe() val bitcoinClient = makeBitcoinCoreClient() - // eclair on-chain key manager does not yet support taproot descriptors + // eclair on-chain key manager supports taproot descriptors bitcoinClient.getReceiveAddress().pipeTo(sender.ref) val defaultAddress = sender.expectMsgType[String] val decoded = Bech32.decodeWitnessAddress(defaultAddress) - assert(decoded.getSecond == 0) + assert(decoded.getSecond == 1) // But we can explicitly use segwit v0 addresses. bitcoinClient.getP2wpkhPubkey().pipeTo(sender.ref) @@ -2015,13 +2015,13 @@ class BitcoinCoreClientWithEclairSignerSpec extends BitcoinCoreClientSpec { assert(wallet.onChainKeyManager_opt.get.masterPubKey(0, AddressType.P2wpkh) == accountXPub) (0 to 10).foreach { _ => - wallet.getReceiveAddress().pipeTo(sender.ref) + wallet.getReceiveAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref) val address = sender.expectMsgType[String] val bip32path = getBip32Path(wallet, address, sender) assert(bip32path.path.length == 5 && bip32path.toString().startsWith("m/84'/1'/0'/0")) assert(computeBIP84Address(master.derivePrivateKey(bip32path).publicKey, Block.RegtestGenesisBlock.hash) == address) - wallet.getChangeAddress().pipeTo(sender.ref) + wallet.getChangeAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref) val changeAddress = sender.expectMsgType[String] val bip32ChangePath = getBip32Path(wallet, changeAddress, sender) assert(bip32ChangePath.path.length == 5 && bip32ChangePath.toString().startsWith("m/84'/1'/0'/1")) 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 afe2fcea18..7b6d3b8666 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 @@ -89,8 +89,8 @@ trait BitcoindService extends Logging { val onChainKeyManager = new LocalOnChainKeyManager("eclair", MnemonicCode.toSeed(mnemonics, passphrase), TimestampSecond.now(), Block.RegtestGenesisBlock.hash) def startBitcoind(useCookie: Boolean = false, - defaultAddressType_opt: Option[String] = None, - changeAddressType_opt: Option[String] = None, + defaultAddressType_opt: Option[String] = Some("bech32m"), + changeAddressType_opt: Option[String] = Some("bech32m"), mempoolSize_opt: Option[Int] = None, // mempool size in MB mempoolMinFeerate_opt: Option[FeeratePerByte] = None, // transactions below this feerate won't be accepted in the mempool startupFlags: String = ""): Unit = { @@ -199,7 +199,7 @@ trait BitcoindService extends Logging { val addressToUse = address match { case Some(addr) => addr case None => - sender.send(bitcoincli, BitcoinReq("getnewaddress", "", "bech32")) + sender.send(bitcoincli, BitcoinReq("getnewaddress", "", "bech32m")) val JString(address) = sender.expectMsgType[JValue](timeout) address } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index 9d27115cf0..d71ae5c3a6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -77,7 +77,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit } private def createInput(channelId: ByteVector32, serialId: UInt64, amount: Satoshi): TxAddInput = { - val changeScript = Script.write(Script.pay2wpkh(randomKey().publicKey)) + val changeScript = Script.write(Script.pay2tr(randomKey().publicKey.xOnly)) val previousTx = Transaction(2, Nil, Seq(TxOut(amount, changeScript), TxOut(amount, changeScript), TxOut(amount, changeScript)), 0) TxAddInput(channelId, serialId, Some(previousTx), 1, 0) } @@ -1463,7 +1463,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit } test("fund transaction with previous inputs (with new inputs)") { - val targetFeerate = FeeratePerKw(10_000 sat) + val targetFeerate = FeeratePerKw(11_000 sat) val fundingA = 100_000 sat val utxosA = Seq(55_000 sat, 55_000 sat, 55_000 sat) withFixture(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), fundingA, utxosA, 0 sat, Nil, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f => @@ -1547,7 +1547,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val fundingA = 100_000 sat val utxosA = Seq(70_000 sat, 60_000 sat) val fundingB = 25_000 sat - val utxosB = Seq(27_500 sat) + val utxosB = Seq(27_250 sat) withFixture(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), fundingA, utxosA, fundingB, utxosB, initialFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f => import f._ @@ -1892,7 +1892,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val targetFeerate = FeeratePerKw(10_000 sat) val fundingA = 100_000 sat val utxosA = Seq(150_000 sat) - val fundingB = 92_000 sat + val fundingB = 93_000 sat val utxosB = Seq(50_000 sat, 50_000 sat, 50_000 sat, 50_000 sat) withFixture(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), fundingA, utxosA, fundingB, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f => import f._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index b8d84e5407..60c1d74275 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -1119,7 +1119,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w htlcSuccessPublisher ! Publish(probe.ref, htlcSuccess) val htlcSuccessTx = getMempoolTxs(1).head val htlcSuccessTargetFee = Transactions.weight2fee(targetFeerate, htlcSuccessTx.weight.toInt) - assert(htlcSuccessTargetFee * 0.9 <= htlcSuccessTx.fees && htlcSuccessTx.fees <= htlcSuccessTargetFee * 1.2, s"actualFee=${htlcSuccessTx.fees} targetFee=$htlcSuccessTargetFee") + assert(htlcSuccessTargetFee * 0.9 <= htlcSuccessTx.fees && htlcSuccessTx.fees <= htlcSuccessTargetFee * 1.1, s"actualFee=${htlcSuccessTx.fees} targetFee=$htlcSuccessTargetFee") assert(htlcSuccessTx.fees <= htlcSuccess.txInfo.amountIn) generateBlocks(6) @@ -1147,7 +1147,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w setFeerate(targetFeerate) // the feerate is higher than what it was when the channel force-closed val htlcTimeoutTx = getMempoolTxs(1).head val htlcTimeoutTargetFee = Transactions.weight2fee(targetFeerate, htlcTimeoutTx.weight.toInt) - assert(htlcTimeoutTargetFee * 0.9 <= htlcTimeoutTx.fees && htlcTimeoutTx.fees <= htlcTimeoutTargetFee * 1.2, s"actualFee=${htlcTimeoutTx.fees} targetFee=$htlcTimeoutTargetFee") + assert(htlcTimeoutTargetFee * 0.9 <= htlcTimeoutTx.fees && htlcTimeoutTx.fees <= htlcTimeoutTargetFee * 1.1, s"actualFee=${htlcTimeoutTx.fees} targetFee=$htlcTimeoutTargetFee") assert(htlcTimeoutTx.fees <= htlcTimeout.txInfo.amountIn) generateBlocks(6) @@ -1264,7 +1264,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w withFixture(utxos, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f => import f._ - val targetFeerate = FeeratePerKw(8_000 sat) + val targetFeerate = FeeratePerKw(10_000 sat) val (commitTx, htlcSuccess, htlcTimeout) = closeChannelWithHtlcs(f, aliceBlockHeight() + 30) // NB: we try to get transactions confirmed *before* their confirmation target, so we aim for a more aggressive block target than what's provided. setFeerate(targetFeerate, blockTarget = 12)