mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
Create FeeUnitFactory and functions to calculate tx fee rates (#1990)
* Create FeeUnitFactory and functions to calculate tx fee rates * Remove useless override, use factory * doc * Scaladocs + use vals * Fix scaleFactor being set as default value
This commit is contained in:
parent
74807e867b
commit
e2163c99ff
@ -1,11 +1,7 @@
|
||||
package org.bitcoins.core.wallet.fee
|
||||
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
Transaction,
|
||||
WitnessTransaction
|
||||
}
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
class FeeUnitTest extends BitcoinSUnitTest {
|
||||
@ -13,6 +9,10 @@ class FeeUnitTest extends BitcoinSUnitTest {
|
||||
val tx: Transaction = Transaction(
|
||||
"02000000000101a2619b5d58b209439c937e563018efcf174063ca011e4f177a5b14e5ba76211c0100000017160014614e9b96cbc7477eda98f0936385ded6b636f74efeffffff024e3f57c4000000001600147cf00288c2c1b3c5cdf275db532a1c15c514bb2fae1112000000000016001440efb02597b9e9d9bc968f12cec3347e2e264c570247304402205768c2ac8178539fd44721e2a7541bedd6b55654f095143514624203c133f7e8022060d51f33fc2b5c1f51f26c7f703de21be6246dbb5fb7e1c6919aae6d442610c6012102b99a63f166ef53ca67a5c55ae969e80c33456e07189f8457e3438f000be42c19307d1900")
|
||||
|
||||
// testnet tx d34888da65ece048d41e0c74a59f225bbe7326e5f8c7437a4bf4df487822b3d3
|
||||
val wtx: WitnessTransaction = WitnessTransaction(
|
||||
"02000000000101a2619b5d58b209439c937e563018efcf174063ca011e4f177a5b14e5ba76211c0100000017160014614e9b96cbc7477eda98f0936385ded6b636f74efeffffff024e3f57c4000000001600147cf00288c2c1b3c5cdf275db532a1c15c514bb2fae1112000000000016001440efb02597b9e9d9bc968f12cec3347e2e264c570247304402205768c2ac8178539fd44721e2a7541bedd6b55654f095143514624203c133f7e8022060d51f33fc2b5c1f51f26c7f703de21be6246dbb5fb7e1c6919aae6d442610c6012102b99a63f166ef53ca67a5c55ae969e80c33456e07189f8457e3438f000be42c19307d1900")
|
||||
|
||||
it must "calculate the correct fee with a SatoshisPerByte fee rate" in {
|
||||
val feeRate = SatoshisPerByte(Satoshis(3))
|
||||
|
||||
@ -47,9 +47,6 @@ class FeeUnitTest extends BitcoinSUnitTest {
|
||||
}
|
||||
|
||||
it must "NOT have symmetry for SatoshisPerByte and SatoshisPerVirtualByte with WitnessTransactions" in {
|
||||
val wtx = WitnessTransaction(
|
||||
"02000000000101a2619b5d58b209439c937e563018efcf174063ca011e4f177a5b14e5ba76211c0100000017160014614e9b96cbc7477eda98f0936385ded6b636f74efeffffff024e3f57c4000000001600147cf00288c2c1b3c5cdf275db532a1c15c514bb2fae1112000000000016001440efb02597b9e9d9bc968f12cec3347e2e264c570247304402205768c2ac8178539fd44721e2a7541bedd6b55654f095143514624203c133f7e8022060d51f33fc2b5c1f51f26c7f703de21be6246dbb5fb7e1c6919aae6d442610c6012102b99a63f166ef53ca67a5c55ae969e80c33456e07189f8457e3438f000be42c19307d1900")
|
||||
|
||||
val satoshisPerByte = SatoshisPerByte(Satoshis(3))
|
||||
val satoshisPerVByte = SatoshisPerVirtualByte(Satoshis(3))
|
||||
|
||||
@ -67,4 +64,62 @@ class FeeUnitTest extends BitcoinSUnitTest {
|
||||
|
||||
assert(satPerKb.toSatPerByte == SatoshisPerByte(Satoshis(3)))
|
||||
}
|
||||
|
||||
it must "have matching scaleFactor between class and factory" in {
|
||||
assert(
|
||||
SatoshisPerKiloByte.zero.scaleFactor == SatoshisPerKiloByte.scaleFactor)
|
||||
assert(SatoshisPerKW.zero.scaleFactor == SatoshisPerKW.scaleFactor)
|
||||
assert(SatoshisPerByte.zero.scaleFactor == SatoshisPerByte.scaleFactor)
|
||||
assert(
|
||||
SatoshisPerVirtualByte.zero.scaleFactor == SatoshisPerVirtualByte.scaleFactor)
|
||||
}
|
||||
|
||||
it must "have matching txSizeForCalc between class and factory" in {
|
||||
assert(
|
||||
SatoshisPerKiloByte.zero.txSizeForCalc(wtx) == SatoshisPerKiloByte
|
||||
.txSizeForCalc(wtx))
|
||||
|
||||
assert(
|
||||
SatoshisPerKW.zero.txSizeForCalc(wtx) == SatoshisPerKW.txSizeForCalc(wtx))
|
||||
|
||||
assert(
|
||||
SatoshisPerByte.zero.txSizeForCalc(wtx) == SatoshisPerByte.txSizeForCalc(
|
||||
wtx))
|
||||
|
||||
assert(
|
||||
SatoshisPerVirtualByte.zero.txSizeForCalc(wtx) == SatoshisPerVirtualByte
|
||||
.txSizeForCalc(wtx))
|
||||
}
|
||||
|
||||
it must "correctly calculate the fee rate for the tx in SatoshisPerVirtualByte" in {
|
||||
val inputAmount = Bitcoins(32.95260479)
|
||||
|
||||
assert(
|
||||
SatoshisPerVirtualByte.calc(inputAmount, wtx) == SatoshisPerVirtualByte
|
||||
.fromLong(147))
|
||||
}
|
||||
|
||||
it must "correctly calculate the fee rate for the tx in SatoshisPerByte" in {
|
||||
val inputAmount = Bitcoins(32.95260479)
|
||||
|
||||
assert(
|
||||
SatoshisPerByte.calc(inputAmount, wtx) == SatoshisPerByte
|
||||
.fromLong(98))
|
||||
}
|
||||
|
||||
it must "correctly calculate the fee rate for the tx in SatoshisPerKW" in {
|
||||
val inputAmount = Bitcoins(32.95260479)
|
||||
|
||||
assert(
|
||||
SatoshisPerKW.calc(inputAmount, wtx) == SatoshisPerKW
|
||||
.fromLong(36954))
|
||||
}
|
||||
|
||||
it must "correctly calculate the fee rate for the tx in SatoshisPerKiloByte" in {
|
||||
val inputAmount = Bitcoins(32.95260479)
|
||||
|
||||
assert(
|
||||
SatoshisPerKiloByte.calc(inputAmount, wtx) == SatoshisPerKiloByte
|
||||
.fromLong(98494))
|
||||
}
|
||||
}
|
||||
|
@ -39,16 +39,14 @@ object OutgoingTransactionDb {
|
||||
s"sentAmount ($sentAmount) cannot be greater than the amount the wallet input ($inputAmount)")
|
||||
|
||||
val feePaid = inputAmount - totalOutput
|
||||
|
||||
// Calculate fee rate in satoshis per byte
|
||||
val feeRate = feePaid.satoshis.toLong / tx.byteSize.toDouble
|
||||
val feeRate = SatoshisPerByte.calc(inputAmount, tx)
|
||||
OutgoingTransactionDb(
|
||||
tx.txIdBE,
|
||||
inputAmount,
|
||||
sentAmount,
|
||||
feePaid,
|
||||
expectedFee,
|
||||
SatoshisPerByte.fromLong(Math.round(feeRate))
|
||||
feeRate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bitcoins.core.protocol.transaction
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
@ -103,6 +104,8 @@ sealed abstract class Transaction extends NetworkElement {
|
||||
wtx.witness)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val totalOutput: CurrencyUnit = outputs.map(_.value).sum
|
||||
}
|
||||
|
||||
object Transaction extends Factory[Transaction] {
|
||||
|
@ -15,6 +15,28 @@ sealed abstract class FeeUnit {
|
||||
final def *(long: Long): CurrencyUnit = Satoshis(toLong * long / scaleFactor)
|
||||
def *(tx: Transaction): CurrencyUnit = calc(tx)
|
||||
|
||||
def factory: FeeUnitFactory[FeeUnit]
|
||||
|
||||
/** The coefficient the denominator in the unit is multiplied by,
|
||||
* for example sats/kilobyte -> 1000
|
||||
*/
|
||||
def scaleFactor: Long = factory.scaleFactor
|
||||
|
||||
require(scaleFactor > 0,
|
||||
s"Scale factor cannot be less than or equal to 0, got $scaleFactor")
|
||||
|
||||
/** Takes the given transaction returns a size that will be used for calculating the fee rate.
|
||||
* This is generally the denominator in the unit, ie sats/byte
|
||||
*/
|
||||
def txSizeForCalc(tx: Transaction): Long = factory.txSizeForCalc(tx)
|
||||
|
||||
/** Calculates the fee for the transaction using this fee rate, rounds down satoshis */
|
||||
final def calc(tx: Transaction): CurrencyUnit = this * txSizeForCalc(tx)
|
||||
def toLong: Long = currencyUnit.satoshis.toLong
|
||||
}
|
||||
|
||||
trait FeeUnitFactory[+T <: FeeUnit] {
|
||||
|
||||
/** The coefficient the denominator in the unit is multiplied by,
|
||||
* for example sats/kilobyte -> 1000
|
||||
*/
|
||||
@ -28,9 +50,22 @@ sealed abstract class FeeUnit {
|
||||
*/
|
||||
def txSizeForCalc(tx: Transaction): Long
|
||||
|
||||
/** Calculates the fee for the transaction using this fee rate, rounds down satoshis */
|
||||
final def calc(tx: Transaction): CurrencyUnit = this * txSizeForCalc(tx)
|
||||
def toLong: Long = currencyUnit.satoshis.toLong
|
||||
/** Creates an instance T where the value given is the numerator of the fee unit
|
||||
*/
|
||||
def fromLong(long: Long): T
|
||||
|
||||
/**
|
||||
* Calculates the fee rate from the given Transaction.
|
||||
* Uses Math.round for rounding up, which returns the closest long to the argument
|
||||
* @param totalInputAmount Total amount being spent by the transaction's inputs
|
||||
* @param tx Transaction to calculate the fee rate of
|
||||
*/
|
||||
def calc(totalInputAmount: CurrencyUnit, tx: Transaction): T = {
|
||||
val feePaid = totalInputAmount - tx.totalOutput
|
||||
val feeRate = feePaid.satoshis.toLong / txSizeForCalc(tx).toDouble
|
||||
|
||||
fromLong(Math.round(feeRate * scaleFactor))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,17 +76,30 @@ sealed abstract class BitcoinFeeUnit extends FeeUnit
|
||||
|
||||
case class SatoshisPerByte(currencyUnit: CurrencyUnit) extends BitcoinFeeUnit {
|
||||
|
||||
def toSatPerKb: SatoshisPerKiloByte = {
|
||||
lazy val toSatPerKb: SatoshisPerKiloByte = {
|
||||
SatoshisPerKiloByte(currencyUnit.satoshis * Satoshis(1000))
|
||||
}
|
||||
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.byteSize
|
||||
|
||||
override def scaleFactor: Long = 1
|
||||
override def factory: FeeUnitFactory[SatoshisPerByte] = SatoshisPerByte
|
||||
}
|
||||
|
||||
object SatoshisPerByte {
|
||||
def fromLong(sats: Long): SatoshisPerByte = SatoshisPerByte(Satoshis(sats))
|
||||
object SatoshisPerByte extends FeeUnitFactory[SatoshisPerByte] {
|
||||
|
||||
/** The coefficient the denominator in the unit is multiplied by,
|
||||
* for example sats/kilobyte -> 1000
|
||||
*/
|
||||
override lazy val scaleFactor: Long = 1
|
||||
|
||||
/** Takes the given transaction returns a size that will be used for calculating the fee rate.
|
||||
* This is generally the denominator in the unit, ie sats/byte
|
||||
*/
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.byteSize
|
||||
|
||||
override def fromLong(sats: Long): SatoshisPerByte =
|
||||
SatoshisPerByte(Satoshis(sats))
|
||||
|
||||
val zero: SatoshisPerByte = SatoshisPerByte(Satoshis.zero)
|
||||
val one: SatoshisPerByte = SatoshisPerByte(Satoshis.one)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,9 +128,27 @@ case class SatoshisPerKiloByte(currencyUnit: CurrencyUnit)
|
||||
|
||||
lazy val toSatPerByte: SatoshisPerByte = toSatPerByteExact
|
||||
|
||||
override def factory: FeeUnitFactory[SatoshisPerKiloByte] =
|
||||
SatoshisPerKiloByte
|
||||
}
|
||||
|
||||
object SatoshisPerKiloByte extends FeeUnitFactory[SatoshisPerKiloByte] {
|
||||
|
||||
/** The coefficient the denominator in the unit is multiplied by,
|
||||
* for example sats/kilobyte -> 1000
|
||||
*/
|
||||
override lazy val scaleFactor: Long = 1000
|
||||
|
||||
/** Takes the given transaction returns a size that will be used for calculating the fee rate.
|
||||
* This is generally the denominator in the unit, ie sats/byte
|
||||
*/
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.byteSize
|
||||
|
||||
override def scaleFactor: Long = 1000
|
||||
override def fromLong(sats: Long): SatoshisPerKiloByte =
|
||||
SatoshisPerKiloByte(Satoshis(sats))
|
||||
|
||||
val zero: SatoshisPerKiloByte = SatoshisPerKiloByte(Satoshis.zero)
|
||||
val one: SatoshisPerKiloByte = SatoshisPerKiloByte(Satoshis.one)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,12 +159,25 @@ case class SatoshisPerKiloByte(currencyUnit: CurrencyUnit)
|
||||
*/
|
||||
case class SatoshisPerVirtualByte(currencyUnit: CurrencyUnit)
|
||||
extends BitcoinFeeUnit {
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.vsize
|
||||
|
||||
override def scaleFactor: Long = 1
|
||||
override def factory: FeeUnitFactory[SatoshisPerVirtualByte] =
|
||||
SatoshisPerVirtualByte
|
||||
}
|
||||
|
||||
object SatoshisPerVirtualByte {
|
||||
object SatoshisPerVirtualByte extends FeeUnitFactory[SatoshisPerVirtualByte] {
|
||||
|
||||
/** The coefficient the denominator in the unit is multiplied by,
|
||||
* for example sats/kilobyte -> 1000
|
||||
*/
|
||||
override lazy val scaleFactor: Long = 1
|
||||
|
||||
/** Takes the given transaction returns a size that will be used for calculating the fee rate.
|
||||
* This is generally the denominator in the unit, ie sats/byte
|
||||
*/
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.vsize
|
||||
|
||||
override def fromLong(sats: Long): SatoshisPerVirtualByte =
|
||||
SatoshisPerVirtualByte(Satoshis(sats))
|
||||
|
||||
val zero: SatoshisPerVirtualByte = SatoshisPerVirtualByte(CurrencyUnits.zero)
|
||||
val one: SatoshisPerVirtualByte = SatoshisPerVirtualByte(Satoshis.one)
|
||||
@ -117,12 +196,23 @@ object SatoshisPerVirtualByte {
|
||||
*/
|
||||
case class SatoshisPerKW(currencyUnit: CurrencyUnit) extends BitcoinFeeUnit {
|
||||
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.weight
|
||||
|
||||
override def scaleFactor: Long = 1000
|
||||
override def factory: FeeUnitFactory[SatoshisPerKW] = SatoshisPerKW
|
||||
}
|
||||
|
||||
object SatoshisPerKW {
|
||||
object SatoshisPerKW extends FeeUnitFactory[SatoshisPerKW] {
|
||||
|
||||
/** The coefficient the denominator in the unit is multiplied by,
|
||||
* for example sats/kilobyte -> 1000
|
||||
*/
|
||||
override lazy val scaleFactor: Long = 1000
|
||||
|
||||
/** Takes the given transaction returns a size that will be used for calculating the fee rate.
|
||||
* This is generally the denominator in the unit, ie sats/byte
|
||||
*/
|
||||
override def txSizeForCalc(tx: Transaction): Long = tx.weight
|
||||
|
||||
override def fromLong(sats: Long): SatoshisPerKW =
|
||||
SatoshisPerKW(Satoshis(sats))
|
||||
|
||||
val zero: SatoshisPerKW = SatoshisPerKW(CurrencyUnits.zero)
|
||||
val one: SatoshisPerKW = SatoshisPerKW(Satoshis.one)
|
||||
|
Loading…
Reference in New Issue
Block a user