Skip to content

Commit 9771b2d

Browse files
authored
Configure bitcoind test instances to use bech32m addresses (#3195)
When addres type or change type is not specified, bitcoind will now start with addresstype=bech32m and changetype=bech32m. We take this opportunity to fix feerate tests that failed because of the following reasons: - first of all, we had a bug where we didn't take into account the anchor amount in our fee calculation, so we ended up always adding `330 sats` to the on-chain fees we paid, which was hidden by our tolerance interval, but started appearing with smaller p2tr inputs - then we fix the remaining tests that need manual tweaking of the utxos available in the test wallet, because they end up creating transactions where we don't have a change output (and overpay fees slightly, but not enough to make it worth adding a change output), these tests simply needed to be tweaked to accomodate p2tr weights Co-authored by @sstone
1 parent 7372a87 commit 9771b2d

File tree

7 files changed

+25
-24
lines changed

7 files changed

+25
-24
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ This means that instead of re-implementing them, Eclair benefits from the verifi
6262

6363
* Eclair needs a _synchronized_, _segwit-ready_, **_zeromq-enabled_**, _wallet-enabled_, _non-pruning_, _tx-indexing_ [Bitcoin Core](https://github.com/bitcoin/bitcoin) node.
6464
* 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.
65-
* 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.
65+
* 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.
6666

6767
Run bitcoind with the following minimal `bitcoin.conf`:
6868

@@ -71,8 +71,8 @@ server=1
7171
rpcuser=foo
7272
rpcpassword=bar
7373
txindex=1
74-
addresstype=bech32
75-
changetype=bech32
74+
addresstype=bech32m
75+
changetype=bech32m
7676
zmqpubhashblock=tcp://127.0.0.1:29000
7777
zmqpubrawtx=tcp://127.0.0.1:29000
7878
```

eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ class Setup(val datadir: File,
195195
await(getBitcoinStatus(bitcoinClient), 30 seconds, "bitcoind did not respond after 30 seconds")
196196
}
197197
logger.info(s"bitcoind version=${bitcoinStatus.version}")
198-
assert(bitcoinStatus.version >= 290100, "Eclair requires Bitcoin Core 29.1 or higher")
198+
assert(bitcoinStatus.version >= 290000, "Eclair requires Bitcoin Core 29 or higher")
199199
bitcoinStatus.unspentAddresses.foreach { address =>
200200
val isSegwit = addressToPublicKeyScript(bitcoinStatus.chainHash, address).map(script => Script.isNativeWitnessScript(script)).getOrElse(false)
201201
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).")

eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,8 @@ private class ReplaceableTxFunder(replyTo: ActorRef[ReplaceableTxFunder.FundingR
369369
// pay the expected package feerate.
370370
val packageWeight = commitTx.weight() + anchorTx.commitmentFormat.anchorInputWeight + fundTxResponse.tx.weight()
371371
val expectedFee = Transactions.weight2fee(targetFeerate, packageWeight)
372-
val currentFee = commitFee + fundTxResponse.fee
372+
// Note that we haven't taken into account yet the amount of the anchor output, so we add it here.
373+
val currentFee = commitFee + fundTxResponse.fee + anchorTx.input.txOut.amount
373374
val changeAmount = (fundTxResponse.tx.txOut.map(_.amount).sum - expectedFee + currentFee).max(dustLimit)
374375
WalletInputs(walletInputs, changeOutput_opt = Some(TxOut(changeAmount, changeScript)))
375376
}

eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
5454

5555
implicit val formats: Formats = DefaultFormats
5656

57-
val defaultAddressType_opt: Option[String] = Some("bech32")
57+
val defaultAddressType_opt: Option[String] = Some("bech32m")
5858

5959
override def beforeAll(): Unit = {
6060
// 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
9090
val sender = TestProbe()
9191
val bitcoinClient = makeBitcoinCoreClient()
9292

93-
// wallet is configured with address_type=bech32
93+
// wallet is configured with address_type=bech32m
9494
bitcoinClient.getReceiveAddress(None).pipeTo(sender.ref)
9595
val address = sender.expectMsgType[String]
96-
assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2wpkh).contains(true))
96+
assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2tr).contains(true))
9797

9898
bitcoinClient.getReceiveAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref)
9999
val address1 = sender.expectMsgType[String]
@@ -108,10 +108,10 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
108108
val sender = TestProbe()
109109
val bitcoinClient = makeBitcoinCoreClient()
110110

111-
// wallet is configured with address_type=bech32
111+
// wallet is configured with address_type=bech32m
112112
bitcoinClient.getChangeAddress(None).pipeTo(sender.ref)
113113
val address = sender.expectMsgType[String]
114-
assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2wpkh).contains(true))
114+
assert(addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, address).map(Script.isPay2tr).contains(true))
115115

116116
bitcoinClient.getChangeAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref)
117117
val address1 = sender.expectMsgType[String]
@@ -1746,11 +1746,11 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
17461746
val sender = TestProbe()
17471747
val bitcoinClient = makeBitcoinCoreClient()
17481748

1749-
// eclair on-chain key manager does not yet support taproot descriptors
1749+
// eclair on-chain key manager supports taproot descriptors
17501750
bitcoinClient.getReceiveAddress().pipeTo(sender.ref)
17511751
val defaultAddress = sender.expectMsgType[String]
17521752
val decoded = Bech32.decodeWitnessAddress(defaultAddress)
1753-
assert(decoded.getSecond == 0)
1753+
assert(decoded.getSecond == 1)
17541754

17551755
// But we can explicitly use segwit v0 addresses.
17561756
bitcoinClient.getP2wpkhPubkey().pipeTo(sender.ref)
@@ -2015,13 +2015,13 @@ class BitcoinCoreClientWithEclairSignerSpec extends BitcoinCoreClientSpec {
20152015
assert(wallet.onChainKeyManager_opt.get.masterPubKey(0, AddressType.P2wpkh) == accountXPub)
20162016

20172017
(0 to 10).foreach { _ =>
2018-
wallet.getReceiveAddress().pipeTo(sender.ref)
2018+
wallet.getReceiveAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref)
20192019
val address = sender.expectMsgType[String]
20202020
val bip32path = getBip32Path(wallet, address, sender)
20212021
assert(bip32path.path.length == 5 && bip32path.toString().startsWith("m/84'/1'/0'/0"))
20222022
assert(computeBIP84Address(master.derivePrivateKey(bip32path).publicKey, Block.RegtestGenesisBlock.hash) == address)
20232023

2024-
wallet.getChangeAddress().pipeTo(sender.ref)
2024+
wallet.getChangeAddress(Some(AddressType.P2wpkh)).pipeTo(sender.ref)
20252025
val changeAddress = sender.expectMsgType[String]
20262026
val bip32ChangePath = getBip32Path(wallet, changeAddress, sender)
20272027
assert(bip32ChangePath.path.length == 5 && bip32ChangePath.toString().startsWith("m/84'/1'/0'/1"))

eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ trait BitcoindService extends Logging {
8989
val onChainKeyManager = new LocalOnChainKeyManager("eclair", MnemonicCode.toSeed(mnemonics, passphrase), TimestampSecond.now(), Block.RegtestGenesisBlock.hash)
9090

9191
def startBitcoind(useCookie: Boolean = false,
92-
defaultAddressType_opt: Option[String] = None,
93-
changeAddressType_opt: Option[String] = None,
92+
defaultAddressType_opt: Option[String] = Some("bech32m"),
93+
changeAddressType_opt: Option[String] = Some("bech32m"),
9494
mempoolSize_opt: Option[Int] = None, // mempool size in MB
9595
mempoolMinFeerate_opt: Option[FeeratePerByte] = None, // transactions below this feerate won't be accepted in the mempool
9696
startupFlags: String = ""): Unit = {
@@ -199,7 +199,7 @@ trait BitcoindService extends Logging {
199199
val addressToUse = address match {
200200
case Some(addr) => addr
201201
case None =>
202-
sender.send(bitcoincli, BitcoinReq("getnewaddress", "", "bech32"))
202+
sender.send(bitcoincli, BitcoinReq("getnewaddress", "", "bech32m"))
203203
val JString(address) = sender.expectMsgType[JValue](timeout)
204204
address
205205
}

eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
7777
}
7878

7979
private def createInput(channelId: ByteVector32, serialId: UInt64, amount: Satoshi): TxAddInput = {
80-
val changeScript = Script.write(Script.pay2wpkh(randomKey().publicKey))
80+
val changeScript = Script.write(Script.pay2tr(randomKey().publicKey.xOnly))
8181
val previousTx = Transaction(2, Nil, Seq(TxOut(amount, changeScript), TxOut(amount, changeScript), TxOut(amount, changeScript)), 0)
8282
TxAddInput(channelId, serialId, Some(previousTx), 1, 0)
8383
}
@@ -1463,7 +1463,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
14631463
}
14641464

14651465
test("fund transaction with previous inputs (with new inputs)") {
1466-
val targetFeerate = FeeratePerKw(10_000 sat)
1466+
val targetFeerate = FeeratePerKw(11_000 sat)
14671467
val fundingA = 100_000 sat
14681468
val utxosA = Seq(55_000 sat, 55_000 sat, 55_000 sat)
14691469
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
15471547
val fundingA = 100_000 sat
15481548
val utxosA = Seq(70_000 sat, 60_000 sat)
15491549
val fundingB = 25_000 sat
1550-
val utxosB = Seq(27_500 sat)
1550+
val utxosB = Seq(27_250 sat)
15511551
withFixture(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), fundingA, utxosA, fundingB, utxosB, initialFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
15521552
import f._
15531553

@@ -1892,7 +1892,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
18921892
val targetFeerate = FeeratePerKw(10_000 sat)
18931893
val fundingA = 100_000 sat
18941894
val utxosA = Seq(150_000 sat)
1895-
val fundingB = 92_000 sat
1895+
val fundingB = 93_000 sat
18961896
val utxosB = Seq(50_000 sat, 50_000 sat, 50_000 sat, 50_000 sat)
18971897
withFixture(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), fundingA, utxosA, fundingB, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
18981898
import f._

eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
11191119
htlcSuccessPublisher ! Publish(probe.ref, htlcSuccess)
11201120
val htlcSuccessTx = getMempoolTxs(1).head
11211121
val htlcSuccessTargetFee = Transactions.weight2fee(targetFeerate, htlcSuccessTx.weight.toInt)
1122-
assert(htlcSuccessTargetFee * 0.9 <= htlcSuccessTx.fees && htlcSuccessTx.fees <= htlcSuccessTargetFee * 1.2, s"actualFee=${htlcSuccessTx.fees} targetFee=$htlcSuccessTargetFee")
1122+
assert(htlcSuccessTargetFee * 0.9 <= htlcSuccessTx.fees && htlcSuccessTx.fees <= htlcSuccessTargetFee * 1.1, s"actualFee=${htlcSuccessTx.fees} targetFee=$htlcSuccessTargetFee")
11231123
assert(htlcSuccessTx.fees <= htlcSuccess.txInfo.amountIn)
11241124

11251125
generateBlocks(6)
@@ -1147,7 +1147,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
11471147
setFeerate(targetFeerate) // the feerate is higher than what it was when the channel force-closed
11481148
val htlcTimeoutTx = getMempoolTxs(1).head
11491149
val htlcTimeoutTargetFee = Transactions.weight2fee(targetFeerate, htlcTimeoutTx.weight.toInt)
1150-
assert(htlcTimeoutTargetFee * 0.9 <= htlcTimeoutTx.fees && htlcTimeoutTx.fees <= htlcTimeoutTargetFee * 1.2, s"actualFee=${htlcTimeoutTx.fees} targetFee=$htlcTimeoutTargetFee")
1150+
assert(htlcTimeoutTargetFee * 0.9 <= htlcTimeoutTx.fees && htlcTimeoutTx.fees <= htlcTimeoutTargetFee * 1.1, s"actualFee=${htlcTimeoutTx.fees} targetFee=$htlcTimeoutTargetFee")
11511151
assert(htlcTimeoutTx.fees <= htlcTimeout.txInfo.amountIn)
11521152

11531153
generateBlocks(6)
@@ -1264,7 +1264,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
12641264
withFixture(utxos, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
12651265
import f._
12661266

1267-
val targetFeerate = FeeratePerKw(8_000 sat)
1267+
val targetFeerate = FeeratePerKw(10_000 sat)
12681268
val (commitTx, htlcSuccess, htlcTimeout) = closeChannelWithHtlcs(f, aliceBlockHeight() + 30)
12691269
// NB: we try to get transactions confirmed *before* their confirmation target, so we aim for a more aggressive block target than what's provided.
12701270
setFeerate(targetFeerate, blockTarget = 12)

0 commit comments

Comments
 (0)