diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala index 927bed54e4..4b88a003e0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala @@ -18,7 +18,6 @@ package fr.acinq.eclair.blockchain.bitcoind import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin._ -import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinJsonRPCClient, Error, ExtendedBitcoinClient, JsonRPCError} import fr.acinq.eclair.blockchain.fee.{FeeratePerKB, FeeratePerKw} @@ -28,6 +27,7 @@ import org.json4s.JsonAST._ import scodec.bits.ByteVector import scala.concurrent.{ExecutionContext, Future} +import scala.math.BigDecimal.long2bigDecimal import scala.util.{Failure, Success} /** @@ -42,12 +42,20 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC def fundTransaction(tx: Transaction, lockUnspents: Boolean, feeRatePerKw: FeeratePerKw): Future[FundTransactionResponse] = fundTransaction(Transaction.write(tx).toHex, lockUnspents, feeRatePerKw) private def fundTransaction(hex: String, lockUnspents: Boolean, feeRatePerKw: FeeratePerKw): Future[FundTransactionResponse] = { - val feeRatePerKB = BigDecimal(FeeratePerKB(feeRatePerKw).toLong) - rpcClient.invoke("fundrawtransaction", hex, Options(lockUnspents, feeRatePerKB.bigDecimal.scaleByPowerOfTen(-8))).map(json => { - val JString(hex) = json \ "hex" - val JInt(changepos) = json \ "changepos" - val JDecimal(fee) = json \ "fee" - FundTransactionResponse(Transaction.read(hex), changepos.intValue, toSatoshi(fee)) + val requestedFeeRatePerKB = FeeratePerKB(feeRatePerKw) + rpcClient.invoke("getmempoolinfo").map(json => json \ "mempoolminfee" match { + case JDecimal(feerate) => FeeratePerKB(Btc(feerate).toSatoshi).max(requestedFeeRatePerKB) + case JInt(feerate) => FeeratePerKB(Btc(feerate.toLong).toSatoshi).max(requestedFeeRatePerKB) + case other => + logger.warn(s"cannot retrieve mempool minimum fee: $other") + requestedFeeRatePerKB + }).flatMap(feeRatePerKB => { + rpcClient.invoke("fundrawtransaction", hex, Options(lockUnspents, feeRatePerKB.toLong.bigDecimal.scaleByPowerOfTen(-8))).map(json => { + val JString(hex) = json \ "hex" + val JInt(changepos) = json \ "changepos" + val JDecimal(fee) = json \ "fee" + FundTransactionResponse(Transaction.read(hex), changepos.intValue, toSatoshi(fee)) + }) }) } 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 317c248e5c..dff0fb130e 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 @@ -177,6 +177,7 @@ class BitcoinCoreWalletSpec extends TestKitBaseClass with BitcoindService with A port = config.getInt("bitcoind.rpcport")) { override def invoke(method: String, params: Any*)(implicit ec: ExecutionContext): Future[JValue] = method match { case "getbalances" => Future(JObject("mine" -> JObject("trusted" -> apiAmount, "untrusted_pending" -> apiAmount)))(ec) + case "getmempoolinfo" => Future(JObject("mempoolminfee" -> JDecimal(0.0002)))(ec) case "fundrawtransaction" => Future(JObject(List("hex" -> JString(hexOut), "changepos" -> JInt(1), "fee" -> apiAmount)))(ec) case _ => Future.failed(new RuntimeException(s"Test BasicBitcoinJsonRPCClient: method $method is not supported")) } @@ -244,11 +245,11 @@ class BitcoinCoreWalletSpec extends TestKitBaseClass with BitcoindService with A val fundingTxes = for (_ <- 0 to 3) yield { val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey.publicKey, randomKey.publicKey))) - wallet.makeFundingTx(pubkeyScript, MilliBtc(50), FeeratePerKw(200 sat)).pipeTo(sender.ref) // create a tx with an invalid feerate (too little) - val belowFeeFundingTx = sender.expectMsgType[MakeFundingTxResponse].fundingTx - extendedClient.publishTransaction(belowFeeFundingTx).pipeTo(sender.ref) // try publishing the tx - assert(sender.expectMsgType[Failure].cause.asInstanceOf[JsonRPCError].error.message.contains("min relay fee not met")) - wallet.rollback(belowFeeFundingTx).pipeTo(sender.ref) // rollback the locked outputs + wallet.makeFundingTx(pubkeyScript, Satoshi(500), FeeratePerKw(250 sat)).pipeTo(sender.ref) + val fundingTx = sender.expectMsgType[MakeFundingTxResponse].fundingTx + extendedClient.publishTransaction(fundingTx.copy(txIn = Nil)).pipeTo(sender.ref) // try publishing an invalid version of the tx + sender.expectMsgType[Failure] + wallet.rollback(fundingTx).pipeTo(sender.ref) // rollback the locked outputs assert(sender.expectMsgType[Boolean]) // now fund a tx with correct feerate @@ -326,6 +327,24 @@ class BitcoinCoreWalletSpec extends TestKitBaseClass with BitcoindService with A assert(sender.expectMsgType[OnChainBalance].confirmed > 0.sat) } + test("ensure feerate is always above min-relay-fee") { + val bitcoinClient = new BasicBitcoinJsonRPCClient( + user = config.getString("bitcoind.rpcuser"), + password = config.getString("bitcoind.rpcpassword"), + host = config.getString("bitcoind.host"), + port = config.getInt("bitcoind.rpcport")) + val wallet = new BitcoinCoreWallet(bitcoinClient) + val sender = TestProbe() + + val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey.publicKey, randomKey.publicKey))) + // 200 sat/kw is below the min-relay-fee + wallet.makeFundingTx(pubkeyScript, MilliBtc(5), FeeratePerKw(200 sat)).pipeTo(sender.ref) + val MakeFundingTxResponse(fundingTx, _, _) = sender.expectMsgType[MakeFundingTxResponse] + + wallet.commit(fundingTx).pipeTo(sender.ref) + sender.expectMsg(true) + } + test("getReceivePubkey should return the raw pubkey for the receive address") { val bitcoinClient = new BasicBitcoinJsonRPCClient( user = config.getString("bitcoind.rpcuser"),