diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 53c351a7f..c5783bfa5 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -35,6 +35,7 @@ eclair { 72 = 20 } } + min-feerate = 1 // minimum feerate in satoshis per byte (same default value as bitcoin core's minimum relay fee) node-alias = "eclair" node-color = "49daaa" 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 3779e5324..b4f9ffa08 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.router._ import grizzled.slf4j.Logging import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, Future, Promise} /** @@ -67,82 +67,89 @@ class Setup(datadir: File, wallet_opt: Option[EclairWallet] = None, overrideDefa implicit val formats = org.json4s.DefaultFormats implicit val ec = ExecutionContext.Implicits.global - def bootstrap: Future[Kit] = Future { + def bootstrap: Future[Kit] = + for { + _ <- Future.successful(true) + feeratesRetrieved = Promise[Boolean]() - val bitcoin = nodeParams.watcherType match { - case ELECTRUM => - logger.warn("EXPERIMENTAL ELECTRUM MODE ENABLED!!!") - val addressesFile = nodeParams.chainHash match { - case Block.RegtestGenesisBlock.hash => "/electrum/servers_regtest.json" - case Block.TestnetGenesisBlock.hash => "/electrum/servers_testnet.json" - case Block.LivenetGenesisBlock.hash => "/electrum/servers_mainnet.json" - } - val stream = classOf[Setup].getResourceAsStream(addressesFile) - val addresses = ElectrumClientPool.readServerAddresses(stream) - val electrumClient = system.actorOf(SimpleSupervisor.props(Props(new ElectrumClientPool(addresses)), "electrum-client", SupervisorStrategy.Resume)) - Electrum(electrumClient) - case _ => ??? - } + bitcoin = nodeParams.watcherType match { + case ELECTRUM => + logger.warn("EXPERIMENTAL ELECTRUM MODE ENABLED!!!") + val addressesFile = nodeParams.chainHash match { + case Block.RegtestGenesisBlock.hash => "/electrum/servers_regtest.json" + case Block.TestnetGenesisBlock.hash => "/electrum/servers_testnet.json" + case Block.LivenetGenesisBlock.hash => "/electrum/servers_mainnet.json" + } + val stream = classOf[Setup].getResourceAsStream(addressesFile) + val addresses = ElectrumClientPool.readServerAddresses(stream) + val electrumClient = system.actorOf(SimpleSupervisor.props(Props(new ElectrumClientPool(addresses)), "electrum-client", SupervisorStrategy.Resume)) + Electrum(electrumClient) + case _ => ??? + } - val defaultFeerates = FeeratesPerByte(block_1 = config.getLong("default-feerates.delay-blocks.1"), blocks_2 = config.getLong("default-feerates.delay-blocks.2"), blocks_6 = config.getLong("default-feerates.delay-blocks.6"), blocks_12 = config.getLong("default-feerates.delay-blocks.12"), blocks_36 = config.getLong("default-feerates.delay-blocks.36"), blocks_72 = config.getLong("default-feerates.delay-blocks.72")) - Globals.feeratesPerByte.set(defaultFeerates) - Globals.feeratesPerKw.set(FeeratesPerKw(defaultFeerates)) - logger.info(s"initial feeratesPerByte=${Globals.feeratesPerByte.get()}") - val feeProvider = (nodeParams.chainHash, bitcoin) match { - case (Block.RegtestGenesisBlock.hash, _) => new ConstantFeeProvider(defaultFeerates) - case _ => new FallbackFeeProvider(new BitgoFeeProvider(nodeParams.chainHash) :: new EarnDotComFeeProvider() :: new ConstantFeeProvider(defaultFeerates) :: Nil) // order matters! - } - system.scheduler.schedule(0 seconds, 10 minutes)(feeProvider.getFeerates.map { - case feerates: FeeratesPerByte => - Globals.feeratesPerByte.set(feerates) - Globals.feeratesPerKw.set(FeeratesPerKw(feerates)) - system.eventStream.publish(CurrentFeerates(Globals.feeratesPerKw.get)) - logger.info(s"current feeratesPerByte=${Globals.feeratesPerByte.get()}") - }) + defaultFeerates = FeeratesPerByte( + block_1 = config.getLong("default-feerates.delay-blocks.1"), + blocks_2 = config.getLong("default-feerates.delay-blocks.2"), + blocks_6 = config.getLong("default-feerates.delay-blocks.6"), + blocks_12 = config.getLong("default-feerates.delay-blocks.12"), + blocks_36 = config.getLong("default-feerates.delay-blocks.36"), + blocks_72 = config.getLong("default-feerates.delay-blocks.72") + ) + minFeeratePerByte = config.getLong("min-feerate") + feeProvider = (nodeParams.chainHash, bitcoin) match { + case (Block.RegtestGenesisBlock.hash, _) => new ConstantFeeProvider(defaultFeerates) + case _ => new FallbackFeeProvider(new BitgoFeeProvider(nodeParams.chainHash) :: new EarnDotComFeeProvider() :: new ConstantFeeProvider(defaultFeerates) :: Nil, minFeeratePerByte) // order matters! + } + _ = system.scheduler.schedule(0 seconds, 10 minutes)(feeProvider.getFeerates.map { + case feerates: FeeratesPerByte => + Globals.feeratesPerByte.set(feerates) + Globals.feeratesPerKw.set(FeeratesPerKw(feerates)) + system.eventStream.publish(CurrentFeerates(Globals.feeratesPerKw.get)) + logger.info(s"current feeratesPerByte=${Globals.feeratesPerByte.get()}") + feeratesRetrieved.trySuccess(true) + }) + _ <- feeratesRetrieved.future - val watcher = bitcoin match { - case Electrum(electrumClient) => - system.actorOf(SimpleSupervisor.props(Props(new ElectrumWatcher(electrumClient)), "watcher", SupervisorStrategy.Resume)) - case _ => ??? - } + watcher = bitcoin match { + case Electrum(electrumClient) => + system.actorOf(SimpleSupervisor.props(Props(new ElectrumWatcher(electrumClient)), "watcher", SupervisorStrategy.Resume)) + case _ => ??? + } - val wallet = bitcoin match { - case _ if wallet_opt.isDefined => wallet_opt.get - case Electrum(electrumClient) => - val electrumWallet = system.actorOf(ElectrumWallet.props(seed, electrumClient, ElectrumWallet.WalletParameters(nodeParams.chainHash)), "electrum-wallet") - new ElectrumEclairWallet(electrumWallet, nodeParams.chainHash) - case _ => ??? - } + wallet = bitcoin match { + case _ if wallet_opt.isDefined => wallet_opt.get + case Electrum(electrumClient) => + val electrumWallet = system.actorOf(ElectrumWallet.props(seed, electrumClient, ElectrumWallet.WalletParameters(nodeParams.chainHash)), "electrum-wallet") + new ElectrumEclairWallet(electrumWallet, nodeParams.chainHash) + case _ => ??? + } + _ = wallet.getFinalAddress.map { + case address => logger.info(s"initial wallet address=$address") + } - wallet.getFinalAddress.map { - case address => logger.info(s"initial wallet address=$address") - } + paymentHandler = system.actorOf(SimpleSupervisor.props(config.getString("payment-handler") match { + case "local" => LocalPaymentHandler.props(nodeParams) + case "noop" => Props[NoopPaymentHandler] + }, "payment-handler", SupervisorStrategy.Resume)) + register = system.actorOf(SimpleSupervisor.props(Props(new Register), "register", SupervisorStrategy.Resume)) + relayer = system.actorOf(SimpleSupervisor.props(Relayer.props(nodeParams, register, paymentHandler), "relayer", SupervisorStrategy.Resume)) + router = system.actorOf(SimpleSupervisor.props(Router.props(nodeParams, watcher), "router", SupervisorStrategy.Resume)) + authenticator = system.actorOf(SimpleSupervisor.props(Authenticator.props(nodeParams), "authenticator", SupervisorStrategy.Resume)) + switchboard = system.actorOf(SimpleSupervisor.props(Switchboard.props(nodeParams, authenticator, watcher, router, relayer, wallet), "switchboard", SupervisorStrategy.Resume)) + paymentInitiator = system.actorOf(SimpleSupervisor.props(PaymentInitiator.props(nodeParams.privateKey.publicKey, router, register), "payment-initiator", SupervisorStrategy.Restart)) - val paymentHandler = system.actorOf(SimpleSupervisor.props(config.getString("payment-handler") match { - case "local" => LocalPaymentHandler.props(nodeParams) - case "noop" => Props[NoopPaymentHandler] - }, "payment-handler", SupervisorStrategy.Resume)) - val register = system.actorOf(SimpleSupervisor.props(Props(new Register), "register", SupervisorStrategy.Resume)) - val relayer = system.actorOf(SimpleSupervisor.props(Relayer.props(nodeParams, register, paymentHandler), "relayer", SupervisorStrategy.Resume)) - val router = system.actorOf(SimpleSupervisor.props(Router.props(nodeParams, watcher), "router", SupervisorStrategy.Resume)) - val authenticator = system.actorOf(SimpleSupervisor.props(Authenticator.props(nodeParams), "authenticator", SupervisorStrategy.Resume)) - val switchboard = system.actorOf(SimpleSupervisor.props(Switchboard.props(nodeParams, authenticator, watcher, router, relayer, wallet), "switchboard", SupervisorStrategy.Resume)) - val paymentInitiator = system.actorOf(SimpleSupervisor.props(PaymentInitiator.props(nodeParams.privateKey.publicKey, router, register), "payment-initiator", SupervisorStrategy.Restart)) - - val kit = Kit( - nodeParams = nodeParams, - system = system, - watcher = watcher, - paymentHandler = paymentHandler, - register = register, - relayer = relayer, - router = router, - switchboard = switchboard, - paymentInitiator = paymentInitiator, - wallet = wallet) - - kit - } + kit = Kit( + nodeParams = nodeParams, + system = system, + watcher = watcher, + paymentHandler = paymentHandler, + register = register, + relayer = relayer, + router = router, + switchboard = switchboard, + paymentInitiator = paymentInitiator, + wallet = wallet) + } yield kit } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala index 64bea50cf..5e245e886 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala @@ -18,7 +18,8 @@ package fr.acinq.eclair.blockchain.electrum import akka.actor.{ActorRef, ActorSystem} import akka.pattern.ask -import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Block, OP_EQUAL, OP_HASH160, OP_PUSHDATA, Satoshi, Script, Transaction, TxOut} +import fr.acinq.bitcoin.{BinaryData, Satoshi, Script, Transaction, TxOut} +import fr.acinq.eclair.addressToPublicKeyScript import fr.acinq.eclair.blockchain.electrum.ElectrumClient.BroadcastTransaction import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._ import fr.acinq.eclair.blockchain.{EclairWallet, MakeFundingTxResponse} @@ -62,13 +63,7 @@ class ElectrumEclairWallet(val wallet: ActorRef, chainHash: BinaryData)(implicit } def sendPayment(amount: Satoshi, address: String, feeRatePerKw: Long): Future[String] = { - val publicKeyScript = Base58Check.decode(address) match { - case (Base58.Prefix.PubkeyAddressTestnet, pubKeyHash) if chainHash == Block.RegtestGenesisBlock.hash || chainHash == Block.TestnetGenesisBlock.hash => Script.pay2pkh(pubKeyHash) - case (Base58.Prefix.PubkeyAddress, pubKeyHash) if chainHash == Block.LivenetGenesisBlock.hash => Script.pay2pkh(pubKeyHash) - case (Base58.Prefix.ScriptAddressTestnet, scriptHash) if chainHash == Block.RegtestGenesisBlock.hash || chainHash == Block.TestnetGenesisBlock.hash => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil - case (Base58.Prefix.ScriptAddress, scriptHash) if chainHash == Block.LivenetGenesisBlock.hash => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil - case _ => throw new RuntimeException("payment address does not match our blockchain") - } + val publicKeyScript = Script.write(addressToPublicKeyScript(address, chainHash)) val tx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, publicKeyScript) :: Nil, lockTime = 0) (wallet ? CompleteTransaction(tx, feeRatePerKw)) @@ -83,11 +78,8 @@ class ElectrumEclairWallet(val wallet: ActorRef, chainHash: BinaryData)(implicit } def sendAll(address: String, feeRatePerKw: Long): Future[(Transaction, Satoshi)] = { - val publicKeyScript = Base58Check.decode(address) match { - case (Base58.Prefix.PubkeyAddressTestnet, pubKeyHash) => Script.pay2pkh(pubKeyHash) - case (Base58.Prefix.ScriptAddressTestnet, scriptHash) => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil - } - (wallet ? SendAll(Script.write(publicKeyScript), feeRatePerKw)) + val publicKeyScript = Script.write(addressToPublicKeyScript(address, chainHash)) + (wallet ? SendAll(publicKeyScript, feeRatePerKw)) .mapTo[SendAllResponse] .map { case SendAllResponse(tx, fee) => (tx, fee) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala index 135de1d9e..fbf547443 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.blockchain.fee import akka.actor.ActorSystem import fr.acinq.bitcoin.{BinaryData, Block} import fr.acinq.eclair.HttpHelper.get +import fr.acinq.eclair.feerateKbToByte import org.json4s.JsonAST.{JInt, JValue} import scala.concurrent.{ExecutionContext, Future} @@ -47,7 +48,7 @@ object BitgoFeeProvider { val blockTargets = json \ "feeByBlockTarget" blockTargets.foldField(Seq.empty[BlockTarget]) { // we divide by 1024 because bitgo returns estimates in Satoshi/Kb and we use estimates in Satoshi/Byte - case (list, (strBlockTarget, JInt(feePerKb))) => list :+ BlockTarget(strBlockTarget.toInt, feePerKb.longValue() / 1024) + case (list, (strBlockTarget, JInt(feePerKb))) => list :+ BlockTarget(strBlockTarget.toInt, feerateKbToByte(feePerKb.longValue())) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala index 4c61239bf..220ae8d46 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala @@ -55,8 +55,8 @@ object EarnDotComFeeProvider { def extractFeerate(feeRanges: Seq[FeeRange], maxBlockDelay: Int): Long = { // first we keep only fee ranges with a max block delay below the limit val belowLimit = feeRanges.filter(_.maxDelay <= maxBlockDelay) - // out of all the remaining fee ranges, we select the one with the minimum higher bound - belowLimit.minBy(_.maxFee).maxFee + // out of all the remaining fee ranges, we select the one with the minimum higher bound and make sure it is > 0 + Math.max(belowLimit.minBy(_.maxFee).maxFee, 1) } def extractFeerates(feeRanges: Seq[FeeRange]): FeeratesPerByte = diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProvider.scala index d79ebe679..2321cd905 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProvider.scala @@ -20,10 +20,14 @@ import scala.concurrent.{ExecutionContext, Future} /** * This provider will try all child providers in sequence, until one of them works + * + * @param providers a sequence of providers; they will be tried one after the others until one of them succeeds + * @param minFeeratePerByte a configurable minimum value for feerates */ -class FallbackFeeProvider(providers: Seq[FeeProvider])(implicit ec: ExecutionContext) extends FeeProvider { +class FallbackFeeProvider(providers: Seq[FeeProvider], minFeeratePerByte: Long)(implicit ec: ExecutionContext) extends FeeProvider { require(providers.size >= 1, "need at least one fee provider") + require(minFeeratePerByte > 0, "minimum fee rate must be strictly greater than 0") def getFeerates(fallbacks: Seq[FeeProvider]): Future[FeeratesPerByte] = fallbacks match { @@ -31,6 +35,19 @@ class FallbackFeeProvider(providers: Seq[FeeProvider])(implicit ec: ExecutionCon case head +: remaining => head.getFeerates.recoverWith { case _ => getFeerates(remaining) } } - override def getFeerates: Future[FeeratesPerByte] = getFeerates(providers) + override def getFeerates: Future[FeeratesPerByte] = getFeerates(providers).map(FallbackFeeProvider.enforceMinimumFeerate(_, minFeeratePerByte)) + +} + +object FallbackFeeProvider { + + def enforceMinimumFeerate(feeratesPerByte: FeeratesPerByte, minFeeratePerByte: Long) : FeeratesPerByte = feeratesPerByte.copy( + block_1 = Math.max(feeratesPerByte.block_1, minFeeratePerByte), + blocks_2 = Math.max(feeratesPerByte.blocks_2, minFeeratePerByte), + blocks_6 = Math.max(feeratesPerByte.blocks_6, minFeeratePerByte), + blocks_12 = Math.max(feeratesPerByte.blocks_12, minFeeratePerByte), + blocks_36 = Math.max(feeratesPerByte.blocks_36, minFeeratePerByte), + blocks_72 = Math.max(feeratesPerByte.blocks_72, minFeeratePerByte) + ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala index 212c881b8..dbd3880cd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala @@ -29,9 +29,13 @@ trait FeeProvider { } -case class FeeratesPerByte(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) +case class FeeratesPerByte(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) { + require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0") +} -case class FeeratesPerKw(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) +case class FeeratesPerKw(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) { + require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0") +} object FeeratesPerKw { def apply(feerates: FeeratesPerByte): FeeratesPerKw = FeeratesPerKw( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 3ee9869f9..e110f089c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -82,7 +82,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // this will be used to make sure the current commitment fee is up-to-date context.system.eventStream.subscribe(self, classOf[CurrentFeerates]) // we need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network - setTimer(TickRefreshChannelUpdate.toString, TickRefreshChannelUpdate, 1 day, repeat = true) + setTimer(TickRefreshChannelUpdate.toString, TickRefreshChannelUpdate, 7 days, repeat = true) /* 8888888 888b 888 8888888 88888888888 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 0a50edb20..ce5a22f03 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId} +import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId, addressToPublicKeyScript} import scala.concurrent.Await import scala.util.{Failure, Success, Try} @@ -96,8 +96,7 @@ object Helpers { Math.abs((2.0 * (remoteFeeratePerKw - localFeeratePerKw)) / (localFeeratePerKw + remoteFeeratePerKw)) def shouldUpdateFee(commitmentFeeratePerKw: Long, networkFeeratePerKw: Long, updateFeeMinDiffRatio: Double): Boolean = - // negative feerate can happen in regtest mode - networkFeeratePerKw > 0 && feeRateMismatch(networkFeeratePerKw, commitmentFeeratePerKw) > updateFeeMinDiffRatio + feeRateMismatch(networkFeeratePerKw, commitmentFeeratePerKw) > updateFeeMinDiffRatio /** * @@ -107,10 +106,8 @@ object Helpers { * @return true if the difference between local and remote fee rates is too high. * the actual check is |remote - local| / avg(local, remote) > mismatch ratio */ - def isFeeDiffTooHigh(remoteFeeratePerKw: Long, localFeeratePerKw: Long, maxFeerateMismatchRatio: Double): Boolean = { - // negative feerate can happen in regtest mode - remoteFeeratePerKw > 0 && feeRateMismatch(remoteFeeratePerKw, localFeeratePerKw) > maxFeerateMismatchRatio - } + def isFeeDiffTooHigh(remoteFeeratePerKw: Long, localFeeratePerKw: Long, maxFeerateMismatchRatio: Double): Boolean = + feeRateMismatch(remoteFeeratePerKw, localFeeratePerKw) > maxFeerateMismatchRatio def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = { val features = BinaryData.empty // empty features for now @@ -118,16 +115,11 @@ object Helpers { AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig) } - def getFinalScriptPubKey(wallet: EclairWallet): BinaryData = { + def getFinalScriptPubKey(wallet: EclairWallet, chainHash: BinaryData): BinaryData = { import scala.concurrent.duration._ val finalAddress = Await.result(wallet.getFinalAddress, 40 seconds) - val finalScriptPubKey = Base58Check.decode(finalAddress) match { - case (Base58.Prefix.PubkeyAddress, hash) => Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) - case (Base58.Prefix.PubkeyAddressTestnet, hash) => Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) - case (Base58.Prefix.ScriptAddress, hash) => Script.write(OP_HASH160 :: OP_PUSHDATA(hash) :: OP_EQUAL :: Nil) - case (Base58.Prefix.ScriptAddressTestnet, hash) => Script.write(OP_HASH160 :: OP_PUSHDATA(hash) :: OP_EQUAL :: Nil) - } - finalScriptPubKey + + Script.write(addressToPublicKeyScript(finalAddress, chainHash)) } object Funding { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 53a59c426..a4d0f25b9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -28,8 +28,8 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.crypto.TransportHandler.Listener import fr.acinq.eclair.router._ -import fr.acinq.eclair.{wire, _} import fr.acinq.eclair.wire._ +import fr.acinq.eclair.{wire, _} import scala.concurrent.duration._ import scala.util.Random @@ -338,7 +338,7 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor } def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): (ActorRef, LocalParams) = { - val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet) + val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis) val channel = spawnChannel(nodeParams, origin_opt) (channel, localParams) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala index fd976a210..9c9a9bd3d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala @@ -23,6 +23,8 @@ import fr.acinq.bitcoin.{BinaryData, _} import scodec.Attempt import scodec.bits.BitVector +import scala.util.{Failure, Success, Try} + package object eclair { /** @@ -51,6 +53,8 @@ package object eclair { case Attempt.Failure(cause) => throw new RuntimeException(s"serialization error: $cause") } + def feerateKbToByte(feeratePerKb: Long): Long = Math.max(feeratePerKb / 1024, 1) + /** * Converts feerate in satoshi-per-bytes to feerate in satoshi-per-kw * @@ -60,7 +64,7 @@ package object eclair { def feerateByte2Kw(feeratePerByte: Long): Long = feeratePerByte * 1024 / 4 - def isPay2PubkeyHash(address: String) : Boolean = address.startsWith("1") || address.startsWith("m") || address.startsWith("n") + def isPay2PubkeyHash(address: String): Boolean = address.startsWith("1") || address.startsWith("m") || address.startsWith("n") /** * Tests whether the binary data is composed solely of printable ASCII characters (see BOLT 1) @@ -77,4 +81,31 @@ package object eclair { * @return the fee (in msat) that a node should be paid to forward an HTLC of 'amount' millisatoshis */ def nodeFee(baseMsat: Long, proportional: Long, msat: Long): Long = baseMsat + (proportional * msat) / 1000000 + + /** + * + * @param address base58 of bech32 address + * @param chainHash hash of the chain we're on, which will be checked against the input address + * @return the public key script that matches the input address. + */ + + def addressToPublicKeyScript(address: String, chainHash: BinaryData): Seq[ScriptElt] = { + Try(Base58Check.decode(address)) match { + case Success((Base58.Prefix.PubkeyAddressTestnet, pubKeyHash)) if chainHash == Block.TestnetGenesisBlock.hash || chainHash == Block.RegtestGenesisBlock.hash => Script.pay2pkh(pubKeyHash) + case Success((Base58.Prefix.PubkeyAddress, pubKeyHash)) if chainHash == Block.LivenetGenesisBlock.hash => Script.pay2pkh(pubKeyHash) + case Success((Base58.Prefix.ScriptAddressTestnet, scriptHash)) if chainHash == Block.TestnetGenesisBlock.hash || chainHash == Block.RegtestGenesisBlock.hash => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil + case Success((Base58.Prefix.ScriptAddress, scriptHash)) if chainHash == Block.LivenetGenesisBlock.hash => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil + case Success(_) => throw new IllegalArgumentException("base58 address does not match our blockchain") + case Failure(base58error) => + Try(Bech32.decodeWitnessAddress(address)) match { + case Success((_, version, _)) if version != 0.toByte => throw new IllegalArgumentException(s"invalid version $version in bech32 address") + case Success((_, _, bin)) if bin.length != 20 && bin.length != 32 => throw new IllegalArgumentException("hash length in bech32 address must be either 20 or 32 bytes") + case Success(("bc", _, bin)) if chainHash == Block.LivenetGenesisBlock.hash => OP_0 :: OP_PUSHDATA(bin) :: Nil + case Success(("tb", _, bin)) if chainHash == Block.TestnetGenesisBlock.hash => OP_0 :: OP_PUSHDATA(bin) :: Nil + case Success(("bcrt", _, bin)) if chainHash == Block.RegtestGenesisBlock.hash => OP_0 :: OP_PUSHDATA(bin) :: Nil + case Success(_) => throw new IllegalArgumentException("bech32 address does not match our blockchain") + case Failure(bech32error) => throw new IllegalArgumentException(s"$address is neither a valid Base58 address ($base58error) nor a valid Bech32 address ($bech32error)") + } + } + } } \ No newline at end of file diff --git a/eclair-core/src/test/resources/integration/bitcoin.conf b/eclair-core/src/test/resources/integration/bitcoin.conf index d2eb0680e..25e7711fe 100644 --- a/eclair-core/src/test/resources/integration/bitcoin.conf +++ b/eclair-core/src/test/resources/integration/bitcoin.conf @@ -8,4 +8,4 @@ txindex=1 zmqpubrawblock=tcp://127.0.0.1:28334 zmqpubrawtx=tcp://127.0.0.1:28334 rpcworkqueue=64 -addresstype=p2sh-segwit +addresstype=bech32 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala index d301f9b30..7293135ad 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala @@ -16,11 +16,14 @@ package fr.acinq.eclair -import fr.acinq.bitcoin.BinaryData +import fr.acinq.bitcoin.Crypto.PrivateKey +import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, BinaryData, Block, Crypto, Script} import org.junit.runner.RunWith import org.scalatest.FunSuite import org.scalatest.junit.JUnitRunner +import scala.util.Try + /** * Created by PM on 27/01/2017. */ @@ -36,4 +39,66 @@ class PackageSpec extends FunSuite { data.foreach(x => assert(toLongId(x._1, x._2) === x._3)) } + test("decode base58 addresses") { + val priv = PrivateKey(BinaryData("01" * 32), compressed = true) + val pub = priv.publicKey + + // p2pkh + // valid chain + assert(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, pub.hash160), Block.TestnetGenesisBlock.hash) == Script.pay2pkh(pub)) + assert(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, pub.hash160), Block.RegtestGenesisBlock.hash) == Script.pay2pkh(pub)) + assert(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.PubkeyAddress, pub.hash160), Block.LivenetGenesisBlock.hash) == Script.pay2pkh(pub)) + + // wrong chain + intercept[RuntimeException] { + addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.PubkeyAddress, pub.hash160), Block.TestnetGenesisBlock.hash) + } + assert(Try(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.PubkeyAddress, pub.hash160), Block.TestnetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.PubkeyAddress, pub.hash160), Block.RegtestGenesisBlock.hash)).isFailure) + + // p2sh + val script = Script.write(Script.pay2wpkh(pub)) + + // valid chain + assert(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, Crypto.hash160(script)), Block.TestnetGenesisBlock.hash) == Script.pay2sh(script)) + assert(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, Crypto.hash160(script)), Block.RegtestGenesisBlock.hash) == Script.pay2sh(script)) + assert(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.ScriptAddress, Crypto.hash160(script)), Block.LivenetGenesisBlock.hash) == Script.pay2sh(script)) + + // wrong chain + assert(Try(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, Crypto.hash160(script)), Block.LivenetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.ScriptAddress, Crypto.hash160(script)), Block.TestnetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Base58Check.encode(Base58.Prefix.ScriptAddress, Crypto.hash160(script)), Block.RegtestGenesisBlock.hash)).isFailure) + } + + test("decode bech32 addresses") { + val priv = PrivateKey(BinaryData("01" * 32), compressed = true) + val pub = priv.publicKey + + // p2wpkh + assert(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bc", 0, pub.hash160), Block.LivenetGenesisBlock.hash) == Script.pay2wpkh(pub)) + assert(addressToPublicKeyScript(Bech32.encodeWitnessAddress("tb", 0, pub.hash160), Block.TestnetGenesisBlock.hash) == Script.pay2wpkh(pub)) + assert(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bcrt", 0, pub.hash160), Block.RegtestGenesisBlock.hash) == Script.pay2wpkh(pub)) + + // wrong version + assert(Try(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bc", 1, pub.hash160), Block.LivenetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Bech32.encodeWitnessAddress("tb", 1, pub.hash160), Block.TestnetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bcrt", 1, pub.hash160), Block.RegtestGenesisBlock.hash)).isFailure) + + // wrong chain + assert(Try(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bc", 0, pub.hash160), Block.TestnetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Bech32.encodeWitnessAddress("tb", 0, pub.hash160), Block.LivenetGenesisBlock.hash)).isFailure) + assert(Try(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bcrt", 0, pub.hash160), Block.LivenetGenesisBlock.hash)).isFailure) + + val script = Script.write(Script.pay2wpkh(pub)) + assert(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bc", 0, Crypto.sha256(script)), Block.LivenetGenesisBlock.hash) == Script.pay2wsh(script)) + assert(addressToPublicKeyScript(Bech32.encodeWitnessAddress("tb", 0, Crypto.sha256(script)), Block.TestnetGenesisBlock.hash) == Script.pay2wsh(script)) + assert(addressToPublicKeyScript(Bech32.encodeWitnessAddress("bcrt", 0, Crypto.sha256(script)), Block.RegtestGenesisBlock.hash) == Script.pay2wsh(script)) + } + + test("fail to decode invalid addresses") { + val e = intercept[RuntimeException] { + addressToPublicKeyScript("1Qbbbbb", Block.LivenetGenesisBlock.hash) + } + assert(e.getMessage.contains("is neither a valid Base58 address") && e.getMessage.contains("nor a valid Bech32 address")) + } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala index 979c3efb6..1db586a19 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala @@ -44,7 +44,7 @@ abstract class TestkitBaseClass extends TestKit(ActorSystem("test")) with fixtur override def afterAll { TestKit.shutdownActorSystem(system) - Globals.feeratesPerKw.set(FeeratesPerKw.single(0)) + Globals.feeratesPerKw.set(FeeratesPerKw.single(1)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index 70b0ff6fc..6ba1cfdb0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -24,11 +24,11 @@ import akka.actor.{Actor, ActorRef, ActorSystem, Props} import akka.pattern.pipe import akka.testkit.{TestKit, TestProbe} import com.typesafe.config.ConfigFactory -import fr.acinq.bitcoin.{MilliBtc, Satoshi, Script} +import fr.acinq.bitcoin.{Block, MilliBtc, Satoshi, Script} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BasicBitcoinJsonRPCClient -import fr.acinq.eclair.randomKey import fr.acinq.eclair.transactions.Scripts +import fr.acinq.eclair.{addressToPublicKeyScript, randomKey} import grizzled.slf4j.Logging import org.json4s.JsonAST.JValue import org.json4s.{DefaultFormats, JString} @@ -39,6 +39,7 @@ import org.scalatest.{BeforeAndAfterAll, FunSuiteLike} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.sys.process.{Process, _} +import scala.util.Try @RunWith(classOf[JUnitRunner]) class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BeforeAndAfterAll with Logging { @@ -110,7 +111,8 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLi assert(sender.expectMsgType[Satoshi] > Satoshi(0)) wallet.getFinalAddress.pipeTo(sender.ref) - assert(sender.expectMsgType[String].startsWith("2")) + val address = sender.expectMsgType[String] + assert(Try(addressToPublicKeyScript(address, Block.RegtestGenesisBlock.hash)).isSuccess) val fundingTxes = for (i <- 0 to 3) yield { val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey.publicKey, randomKey.publicKey))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala index 185ad4851..128c652f9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala @@ -56,7 +56,7 @@ class FallbackFeeProviderSpec extends FunSuite { val provider5 = new FailingFeeProvider(5, dummyFeerates) // fails after 5 tries val provider7 = new FailingFeeProvider(Int.MaxValue, dummyFeerates) // "never" fails - val fallbackFeeProvider = new FallbackFeeProvider(provider0 :: provider1 :: provider3 :: provider5 :: provider7 :: Nil) + val fallbackFeeProvider = new FallbackFeeProvider(provider0 :: provider1 :: provider3 :: provider5 :: provider7 :: Nil, 1) assert(await(fallbackFeeProvider.getFeerates) === provider1.feeratesPerByte) @@ -74,5 +74,11 @@ class FallbackFeeProviderSpec extends FunSuite { } + test("ensure minimum feerate") { + val constantFeeProvider = new ConstantFeeProvider(FeeratesPerByte(1, 1, 1, 1, 1, 1)) + val fallbackFeeProvider = new FallbackFeeProvider(constantFeeProvider :: Nil, 2) + assert(await(fallbackFeeProvider.getFeerates) === FeeratesPerByte(2, 2, 2, 2, 2, 2)) + } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index aebcfdbb7..99bcb8f90 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1576,16 +1576,6 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { } } - test("recv CurrentFeerate (ignore negative feerate)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - // this happens when in regtest mode - val event = CurrentFeerates(FeeratesPerKw.single(-1)) - sender.send(alice, event) - alice2bob.expectNoMsg(500 millis) - } - } - test("recv BITCOIN_FUNDING_SPENT (their commit w/ htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _) => within(30 seconds) { val sender = TestProbe() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 628279711..548e8011f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -616,16 +616,6 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { } } - test("recv CurrentFeerate (ignore negative feerate)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - // this happens when in regtest mode - val event = CurrentFeerates(FeeratesPerKw.single(-1)) - sender.send(alice, event) - alice2bob.expectNoMsg(500 millis) - } - } - test("recv BITCOIN_FUNDING_SPENT (their commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => within(30 seconds) { // bob publishes his current commit tx, which contains two pending htlcs alice->bob diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index 9c5f07c49..bd02418ec 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -25,8 +25,8 @@ import akka.pattern.pipe import akka.testkit.{TestKit, TestProbe} import com.google.common.net.HostAndPort import com.typesafe.config.{Config, ConfigFactory} -import fr.acinq.bitcoin.Crypto.{PublicKey, sha256} -import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Block, Crypto, MilliSatoshi, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi, Script, ScriptFlags, Transaction} +import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, BinaryData, Block, Crypto, MilliSatoshi, OP_0, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi, Script, ScriptFlags, Transaction} import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinJsonRPCClient, ExtendedBitcoinClient} import fr.acinq.eclair.blockchain.{Watch, WatchConfirmed} import fr.acinq.eclair.channel.Register.Forward @@ -388,6 +388,8 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with FunSuiteLike wit Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, pubKeyHash) case OP_HASH160 :: OP_PUSHDATA(scriptHash, _) :: OP_EQUAL :: Nil => Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, scriptHash) + case OP_0 :: OP_PUSHDATA(pubKeyHash, _) :: Nil if pubKeyHash.length == 20 => Bech32.encodeWitnessAddress("bcrt", 0, pubKeyHash) + case OP_0 :: OP_PUSHDATA(scriptHash, _) :: Nil if scriptHash.length == 32 => Bech32.encodeWitnessAddress("bcrt", 0, scriptHash) case _ => ??? } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala index 12a4524c9..ddee3188b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala @@ -83,7 +83,7 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix } override def afterAll { - Globals.feeratesPerKw.set(FeeratesPerKw.single(0)) + Globals.feeratesPerKw.set(FeeratesPerKw.single(1)) TestKit.shutdownActorSystem(system) }