1

I'm passing an unfunded proposed tx and feeRate param to fundrawtransaction which gives me back the hex of the tx with inputs selected and its fee. I'm not passing a conf_target or estimate_mode. I pass the resulting hex to decoderawtransaction to get the size/vsize of the tx.

I'm trying to check the actual fee rate of the tx by calculating fee * 1000 / vsize but it doesn't seem to match the rate that I passed in as a parameter.

I've tried with a few different values but the actual rate always seems to come out higher than the requested rate by a factor of about 1.59, give or take a little bit.

So am I calculating the observed rate improperly? Is it even possible to have the resulting fee rate exactly match the one provided by the feeRate parameter?

Here's an example of constructing a tx with amount 0.00073749 and feeRate 0.00001000. Proposed tx from createrawtransaction:

{
  "txid": "de380dec00d86611bab1cfa825a7069ffde31d7fb0c5b91e381ef79b94d927e0",
  "hash": "de380dec00d86611bab1cfa825a7069ffde31d7fb0c5b91e381ef79b94d927e0",
  "version": 2,
  "size": 41,
  "vsize": 41,
  "weight": 164,
  "locktime": 0,
  "vin": [
  ],
  "vout": [
    {
      "value": 0.00073749,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 de525f12f17a495b7903c2c5b306ee07ccad309f",
        "hex": "0014de525f12f17a495b7903c2c5b306ee07ccad309f",
        "reqSigs": 1,
        "type": "witness_v0_keyhash",
        "addresses": [
          "bc1qmef97yh30fy4k7grctzmxphwqlx26vyl5lve86"
        ]
      }
    }
  ]
}

And the full tx with inputs returned by fundrawtransaction:

{
  "txid": "f78b268f9386ab47dd5c0d36caf3aaff5fa719c7a2b5658ff08132159cb6b8cd",
  "hash": "f78b268f9386ab47dd5c0d36caf3aaff5fa719c7a2b5658ff08132159cb6b8cd",
  "version": 2,
  "size": 369,
  "vsize": 369,
  "weight": 1476,
  "locktime": 0,
  "vin": [
    {
      "txid": "ca6f25d4484ab525fdb907cbce81d3dc9d1974695fcc80639fddfff4b6bca268",
      "vout": 1,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "9b39ee5637d7b0783715be6ce566283024ee2bea33a9d3bd6f415ae5d8f24e5f",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "2f2b7eaccc06ea3856cd5b4666a15161d0fac9053ab2bcbb94a490593c969246",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "b5b2e4e1287438fd8193e1409cdf87181061ab4a3d95f50d2bf1bc2534b2d49c",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "3de9c3690c77f544b0d953c70d97f50bb9790a847bed9972caca960218c6c8f6",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "5e0021645a37473afdff1720da902f0abe183fb48719169e96b5235bb6b09e25",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "f92dbcf77c13d887d8aa77404dc78120c90d055a32b4d51196849affc3866ade",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    },
    {
      "txid": "e425c6e84bc671bfb3a80bd95d1925007e12483fdcf3a19094bde8a36ae0d9fd",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 0.00073749,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 de525f12f17a495b7903c2c5b306ee07ccad309f",
        "hex": "0014de525f12f17a495b7903c2c5b306ee07ccad309f",
        "reqSigs": 1,
        "type": "witness_v0_keyhash",
        "addresses": [
          "bc1qmef97yh30fy4k7grctzmxphwqlx26vyl5lve86"
        ]
      }
    }
  ]
}

In this case the fee is 0.00000668 so the rate I calculate is 0.0000668 * 1000 / 369 = 0.00001810 as opposed to the requested 0.00001000

  • Does the resulting transaction have a change output? And how large is it (in terms of number of inputs and outputs)? – Pieter Wuille Apr 4 at 6:53
  • Hi Pieter, I just updated the question with an example with just 1 output. – Adam Soltys Apr 4 at 18:59
1

You're seeing two things here: the exact match coin selection algorithm, and the fact that your vsize is computed before adding signatures.

Exact match

The coin selection algorithm in Bitcoin Core tries to find an "exact match" of UTXOs to spend in order to pay, without adding a change output back to you. If the input amount is a bit too large, it is turned into fee. Surprisingly, under certain circumstances this behavior is actually preferable over turning that excess into change.

The observation is that merely creating a change output costs money right now (in terms of fee needed to pay for it), and more money later (in terms of the marginal fee needed to pay for the additional space used by the transaction input that will spend it). That fee is wasteful: if it's possible to not create the change at all, this becomes a cost you never have to pay.

So the conclusion is: if it is possible to construct a transaction without change that overpays less than what it would cost to create and spend change, it is preferable. This is exactly what Bitcoin Core does, using a short-term fee estimation for the creation of the change, and a long-term fee estimation for spending it.

The concept and algorithm for doing this efficiently were first described by Mark Erhard in his master's thesis.

vsize before signatures

You're looking at the vsize of the unsigned raw transaction coming out of fundrawtransaction. For fee decision purposes, fundrawtransaction tries to predict the size of the transaction after it is signed.

| improve this answer | |
  • Thanks for the illuminating response, this all makes sense to me now! – Adam Soltys Apr 4 at 21:45
  • Hi Pieter, in the case of bitcoin everything is working as expected. Now that I get the vsize from the signed tx, the observed rate I'm calculating is almost exactly the rate that was requested. However I'm still seeing a discrepancy between the observed and requested rates for liquidv1 transactions. Could there be an issue with fundrawtransaction not anticipating the signed tx size properly there, or do you have any other insights why things might be different for liquid? – Adam Soltys Apr 4 at 23:08
  • I have no idea, sorry! You may want to open an issue for that. – Pieter Wuille Apr 4 at 23:09
  • Will do, thanks – Adam Soltys Apr 4 at 23:10
  • It turned out I wasn't blinding the liquid transaction before signing it. github.com/ElementsProject/elements/issues/852 – Adam Soltys Apr 12 at 4:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.