1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-24 14:50:46 +01:00

Publish txs with min-relay-fee met (#1687)

When our mempool is full, its min-relay-fee may be constantly changing.
To ensure our txs can be published, we need to check the min-relay-fee when
we fund the transaction, and raise it if necessary.
This commit is contained in:
Bastien Teinturier 2021-02-15 19:17:48 +01:00 committed by GitHub
parent 36e8c056f1
commit 2a359c6a56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 39 additions and 12 deletions

View file

@ -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))
})
})
}

View file

@ -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"),