Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand All @@ -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
```
Expand Down
2 changes: 1 addition & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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).")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 =>
Expand Down Expand Up @@ -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._

Expand Down Expand Up @@ -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._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down