Join 41,168 users and earn money for participation

CPfP, on-chain usage.

1 257 exc boost
Avatar for TomZ
Written by   299
4 months ago

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 in this snippet.

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!

As the search returns we have the result in the 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 application and a spreadsheet with the data is available on https://gitlab.com/snippets/1988611

Numbers by: https://www.flickr.com/photos/larimdame/

17
$ 6.46
$ 1.39 from @TheRandomRewarder
$ 1.00 from @im_uname
+ 6
Avatar for TomZ
Written by   299
4 months ago
Enjoyed this article?  Earn Bitcoin Cash by sharing it! Explain
...and you will also help the author collect more tips.

Comments

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.

because 1spb is the default configuration for ABC miner or some other reason ?

$ 0.00
4 months ago