Fee Provider from config (#2219)

* Fee Provider from config

* Use block targets, add docs

* Add FeeProviderFactory
This commit is contained in:
Ben Carman 2020-11-03 09:06:18 -06:00 committed by GitHub
parent 7c887cc144
commit 641538440f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 170 additions and 17 deletions

View file

@ -5,16 +5,16 @@ import akka.dispatch.Dispatchers
import akka.http.scaladsl.Http
import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.models.{
BlockHeaderDAO,
CompactFilterDAO,
CompactFilterHeaderDAO
}
import org.bitcoins.chain.models._
import org.bitcoins.core.Core
import org.bitcoins.core.api.chain.ChainApi
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.util.{FutureUtil, NetworkUtil}
import org.bitcoins.feeprovider.BitcoinerLiveFeeRateProvider
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.feeprovider.FeeProviderName._
import org.bitcoins.feeprovider.MempoolSpaceTarget.HourFeeTarget
import org.bitcoins.feeprovider._
import org.bitcoins.node._
import org.bitcoins.node.config.NodeAppConfig
import org.bitcoins.node.models.Peer
@ -70,9 +70,10 @@ class BitcoinSServerMain(override val args: Array[String])
node <- nodeF
chainApi <- chainApiF
_ = logger.info("Initialized chain api")
feeProvider = getFeeProviderOrElse(MempoolSpaceProvider(HourFeeTarget))
wallet <- walletConf.createHDWallet(node,
chainApi,
BitcoinerLiveFeeRateProvider(60),
feeProvider,
bip39PasswordOpt)
callbacks <- createCallbacks(wallet)
_ = nodeConf.addCallbacks(callbacks)
@ -132,10 +133,12 @@ class BitcoinSServerMain(override val args: Array[String])
_ = logger.info("Starting bitcoind")
_ <- bitcoindRpcConf.start()
_ = logger.info("Creating wallet")
tmpWallet <- walletConf.createHDWallet(bitcoind,
bitcoind,
bitcoind,
bip39PasswordOpt)
feeProvider = getFeeProviderOrElse(bitcoind)
tmpWallet <- walletConf.createHDWallet(nodeApi = bitcoind,
chainQueryApi = bitcoind,
feeRateApi = feeProvider,
bip39PasswordOpt =
bip39PasswordOpt)
wallet = BitcoindRpcBackendUtil.createWalletWithBitcoindCallbacks(
bitcoind,
tmpWallet)
@ -298,6 +301,39 @@ class BitcoinSServerMain(override val args: Array[String])
}
server.start()
}
/** Gets a Fee Provider from the given wallet app config
* Returns default if there is no config set
*/
def getFeeProviderOrElse(default: => FeeRateApi)(implicit
system: ActorSystem,
walletConf: WalletAppConfig): FeeRateApi = {
val feeProviderNameOpt =
walletConf.feeProviderNameOpt.flatMap(FeeProviderName.fromStringOpt)
val feeProvider =
(feeProviderNameOpt, walletConf.feeProviderTargetOpt) match {
case (None, None) | (None, Some(_)) =>
default
case (Some(BitcoinerLive), None) =>
BitcoinerLiveFeeRateProvider.fromBlockTarget(6)
case (Some(BitcoinerLive), Some(target)) =>
BitcoinerLiveFeeRateProvider.fromBlockTarget(target)
case (Some(BitGo), targetOpt) =>
BitGoFeeRateProvider(targetOpt)
case (Some(MempoolSpace), None) =>
MempoolSpaceProvider(HourFeeTarget)
case (Some(MempoolSpace), Some(target)) =>
MempoolSpaceProvider.fromBlockTarget(target)
case (Some(Constant), Some(num)) =>
ConstantFeeRateProvider(SatoshisPerVirtualByte.fromLong(num))
case (Some(Constant), None) =>
throw new IllegalArgumentException(
"Missing a target for a ConstantFeeRateProvider")
}
logger.info(s"Using fee provider: $feeProvider")
feeProvider
}
}
object BitcoinSServerMain extends App {

View file

@ -158,6 +158,29 @@ bitcoin-s {
}
# Bitcoin-S provides manny different fee providers
# You can configure your server to use any of them
# Below is some examples of different options
fee-provider {
# name = mempoolspace # Uses mempool.space's api
# The target is optional for mempool.space
# It refers to the expected number of blocks until confirmation
# target = 6
# name = bitcoinerlive # Uses bitcoiner.live's api
# The target is optional for Bitcoiner Live
# It refers to the expected number of blocks until confirmation
# target = 6
# name = bitgo # Uses BitGo's api
# The target is optional for BitGo
# It refers to the expected number of blocks until confirmation
# target = 6
# name = constant # A constant fee rate in sats/vbyte
# target = 1 # Will always use 1 sat/vbyte
}
server {
# The port we bind our rpc server on
rpcport = 9999

View file

@ -45,3 +45,11 @@ case class BitGoFeeRateProvider(blockTargetOpt: Option[Int])(implicit
belowLimit.values.min
}
}
object BitGoFeeRateProvider extends FeeProviderFactory[BitGoFeeRateProvider] {
override def fromBlockTarget(blocks: Int)(implicit
system: ActorSystem): BitGoFeeRateProvider = {
BitGoFeeRateProvider(Some(blocks))
}
}

View file

@ -5,6 +5,7 @@ import akka.http.scaladsl.model.Uri
import org.bitcoins.commons.jsonmodels.wallet.BitcoinerLiveResult
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.feeprovider.BitcoinerLiveFeeRateProvider.validMinutes
import play.api.libs.json.{JsError, JsSuccess, Json}
import scala.util.{Failure, Success, Try}
@ -13,11 +14,8 @@ case class BitcoinerLiveFeeRateProvider(minutes: Int)(implicit
override val system: ActorSystem)
extends CachedHttpFeeRateProvider {
private val bitcoinerLiveValidMinutes =
Vector(30, 60, 120, 180, 360, 720, 1440)
require(
bitcoinerLiveValidMinutes.contains(minutes),
s"$minutes is not a valid selection, must be from $bitcoinerLiveValidMinutes")
require(validMinutes.contains(minutes),
s"$minutes is not a valid selection, must be from $validMinutes")
override val uri: Uri =
Uri("https://bitcoiner.live/api/fees/estimates/latest")
@ -34,3 +32,23 @@ case class BitcoinerLiveFeeRateProvider(minutes: Int)(implicit
}
}
}
object BitcoinerLiveFeeRateProvider
extends FeeProviderFactory[BitcoinerLiveFeeRateProvider] {
final val validMinutes =
Vector(30, 60, 120, 180, 360, 720, 1440)
override def fromBlockTarget(blocks: Int)(implicit
system: ActorSystem): BitcoinerLiveFeeRateProvider = {
require(blocks > 0,
s"Cannot have a negative or zero block target, got $blocks")
val blockTargets = validMinutes.map(_ / 10)
// Find closest
val target = blockTargets.minBy(target => Math.abs(target - blocks))
BitcoinerLiveFeeRateProvider(target)
}
}

View file

@ -0,0 +1,8 @@
package org.bitcoins.feeprovider
import akka.actor.ActorSystem
import org.bitcoins.core.api.feeprovider.FeeRateApi
trait FeeProviderFactory[T <: FeeRateApi] {
def fromBlockTarget(blocks: Int)(implicit system: ActorSystem): T
}

View file

@ -0,0 +1,31 @@
package org.bitcoins.feeprovider
import org.bitcoins.crypto.StringFactory
sealed abstract class FeeProviderName
object FeeProviderName extends StringFactory[FeeProviderName] {
final case object BitcoinerLive extends FeeProviderName
final case object BitGo extends FeeProviderName
final case object Constant extends FeeProviderName
final case object MempoolSpace extends FeeProviderName
val all: Vector[FeeProviderName] =
Vector(BitcoinerLive, BitGo, Constant, MempoolSpace)
override def fromStringOpt(str: String): Option[FeeProviderName] = {
all.find(_.toString.toLowerCase == str.toLowerCase)
}
override def fromString(string: String): FeeProviderName = {
fromStringOpt(string) match {
case Some(state) => state
case None =>
sys.error(s"Could not find FeeProviderName for string=$string")
}
}
}

View file

@ -40,6 +40,15 @@ case class MempoolSpaceProvider(target: MempoolSpaceTarget)(implicit
}
}
object MempoolSpaceProvider extends FeeProviderFactory[MempoolSpaceProvider] {
override def fromBlockTarget(blocks: Int)(implicit
system: ActorSystem): MempoolSpaceProvider = {
val target = MempoolSpaceTarget.fromBlockTarget(blocks)
MempoolSpaceProvider(target)
}
}
abstract class MempoolSpaceTarget
object MempoolSpaceTarget {
@ -49,4 +58,17 @@ object MempoolSpaceTarget {
final case object HalfHourFeeTarget extends MempoolSpaceTarget
final case object HourFeeTarget extends MempoolSpaceTarget
def fromBlockTarget(blocks: Int): MempoolSpaceTarget = {
if (blocks <= 0) {
throw new IllegalArgumentException(
s"Cannot have a negative or zero block target, got $blocks")
} else if (blocks < 3) {
FastestFeeTarget
} else if (blocks < 6) {
HalfHourFeeTarget
} else {
HourFeeTarget
}
}
}

View file

@ -14,7 +14,7 @@ import org.bitcoins.core.wallet.keymanagement.{
KeyManagerParams
}
import org.bitcoins.db.DatabaseDriver.{PostgreSQL, SQLite}
import org.bitcoins.db.{AppConfig, AppConfigFactory, JdbcProfileComponent}
import org.bitcoins.db._
import org.bitcoins.keymanager.WalletStorage
import org.bitcoins.keymanager.bip39.{BIP39KeyManager, BIP39LockedKeyManager}
import org.bitcoins.wallet.db.WalletDbManagement
@ -97,6 +97,13 @@ case class WalletAppConfig(
confs
}
lazy val feeProviderNameOpt: Option[String] = {
config.getStringOrNone("bitcoin-s.fee-provider.name")
}
lazy val feeProviderTargetOpt: Option[Int] =
config.getIntOpt("bitcoin-s.fee-provider.target")
override def start(): Future[Unit] = {
for {
_ <- super.start()