Jul 14, 2020
In Bitcoin there is a concept called “Child Pays for Parent” (CPfP). It allows transactions that may be stuck to get “un-stuck” in a simple manner. (background)
In the Bitcoin Cash case the usage of this method is much less needed than on a saturated chain where the queue is perpetually full. On Bitcoin Cash the need for CPfP is mostly a political one.
The lack of actual progress towards a seemingly unrelated goal was stalled with CPfP stated as the main difficulty. And when vague reasons are given as an answer why innovations are blocked we need to shine the light on this with actual data. So, I set out to find out if CPfP is actively in-use on the Bitcoin Cash blockchain.
Usecase
As further explained on stack-overflow here, the main case where people can benefit from CPfP is when a transaction isn’t being mined and they want to speed it up. On Bitcoin Cash (as opposed to the saturated chain BTC) there is essentially only one reason for a transaction to not get mined while it is valid and accepted by a percentage of the network. This is because a transaction pays less than 1 satoshi-per-byte in fee. To make such a transaction get mined anyway you craft a special transaction that you chain to the first one and in the second you pay so much fee that it covers the cost for both transactions.
As such there is a clear pattern we can look for on the blockchain and check if transactions follow this pattern. One normal transaction followed by a transaction that increases the fee.
Using FloweeJS I wrote a small application that fetches the needed blockchain information from the Hub and then checks each individual transaction to see if it matches with our expected pattern.
Then when a transaction possibly matches I fetch a lot more data about this transaction, for instance the inputs, so I can calculate the fee. The CPfP transaction should actually increase the fee, after all.
Conclusion
Notice that we put the conclusion above the code section since I assume not all readers will be interested in learning how they can write their own blockchain research scripts.
Running the last 3 months of blockchain data we see 2992 transactions that fall in our definition of CPfP, but almost none (7) that started with too low fee and actually would end up being delayed in the Bitcoin Cash system.
From eyeing the results I noticed that often when there were a cluster of CPfP transactions in one block, that that block was a slow block. Meaning it took substantially longer than 10 minutes to be found. There may have been people that used the CPfP feature of their wallet because this works for the BTC chain. This perceived correlation would benefit from further block-analysis to see if my observation is statistically relevant.
We can speculate that many overpaying CPfP transactions were created using software that has bad defaults or just isn’t adjusted for Bitcoin Cash.
The 7 transactions in 3 months that used CPfP to lift their transaction above the 1-sat-per-byte limit did really benefit from the concept and benefited from the miners using this feature. But with a total of nearly 3000 transactions using it for no reason, along with the delaying of a fix for the chain-depth limit, you have to wonder the cost/benefit equation.
Code
The full code is available 2020-07-cpfp.js.
First we fetch a block and make Flowee the Hub (the server) filter the data it sends us. In FloweeJS we need a blockheight and an ‘offset-in-block’ to identify any transaction, and our algorithm uses the inputs and the txid.
var findPotentials = {
jobs: [{
value: height,
type: Flowee.Job.FetchBlockOfTx,
txFilter: [Flowee.IncludeOffsetInBlock, Flowee.IncludeInputs,
Flowee.IncludeTxid]
}]
};
The actual search happens on the next line:
Flowee.search(findPotentials).then(function(findPotentials) {
var hashes = {}; // txids in the block we are looking at
for (tx of findPotentials.transactions) {
if (tx.isCoinbase === false)
hashes[tx.txid] = tx;
}
for (tx of findPotentials.transactions) {
if (tx.isCoinbase == false && tx.inputs.length == 1) {
var prev = hashes[tx.inputs[0].previousTxid];
if (typeof prev !== 'undefined') {
// This is a potential CPfP Tx!
findPotentials.transactions
list.
Now, still in the above code-snippet, to make a transaction possible to mine using a second transaction implies that both will end up in the same block. So we remember the transaction-ids of each transaction first and then we iterate a second time to find out if a transaction has exactly one input that is a transaction in this block.
The next step (below) is to fetch a lot more about about transactions. Notice how we get the parsed amounts, inputs etc as well as the full transaction data. The latter is a byte-array and really the only thing we do with that is to see the size. Its a bit wasteful to do it this way, but as we only check one or two transactions every block which already match our pattern this is Ok.
let check = {
jobs: [{
// get more info about our bad boy
type: Flowee.Job.FetchTx,
value: tx.blockHeight,
value2: tx.offsetInBlock,
txFilter: [Flowee.IncludeOffsetInBlock,
Flowee.IncludeOutputAmounts,
Flowee.IncludeInputs,
Flowee.IncludeFullTxData]
}]};
Additionally (not shown here) we add several more jobs to the ‘check’ object to fetch more transactions and balances we need to calculate the fee.
Flowee.search(check).then(function(check) {
var childTx = check.transactions[0];
var parentTx = check.transactions[1];
// a CPFP only has one output.
if (childTx.outputs.length !== 1)
return;
// Looks like someone was experimenting or something.
if (childTx.outputs[0].amount == 0)
return;
let childFeeTotal = parentTx.outputs[childTx.inputs[0].outputIndex].amount
- childTx.outputs[0].amount;
let childFee = childFeeTot / childTx.fullTxData.length;
The way to calculate a fee is to take the total amount going into a transaction, minus the total amount going out of a transaction. The difference is collected by the miner.
A transaction does not actually contain a field stating what the amount is of an input, to find out you need to find the matching output of the previous transaction and check there. Which is that the following code does: parentTx.outputs[childTx.inputs[0].outputIndex].amount
The final result is printed only if the fee of the child is higher than the fee of the parent.
The full sources are at 2020-07-cpfp.js.
You can download the results directly on CPfP-data.ods.
Numbers image by: larimdame