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:
parent
36e8c056f1
commit
2a359c6a56
2 changed files with 39 additions and 12 deletions
|
@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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"),
|
||||
|
|
Loading…
Add table
Reference in a new issue