Inconsistent UTXO Block Height

I’ve run into an issue where Bitcoin Testnet4 and mempool.space return different block heights for the same UTXO’s OutPoint:

For address

tb1q837dfu2xmthlx6a6c59dvw6v4t0erg6c4mn4e2

and transaction ID

08292261f6708591ba76ae8dbdddaf48462b26825e920476417a76a911a8916c

on mempool.space (Testnet4) you can see its height is 81836:

But when I call the Testnet4 Bitcoin canister I get:

Utxo { 
  outpoint: OutPoint { 
    txid: Txid([108, 145, 168, 17, 169, 118, 122, 65, 118, 4, 146, 94, 130, 38, 43, 70, 72, 175, 221, 189, 141, 174, 118, 186, 145, 133, 112, 246, 97, 34, 41, 8]), 
    vout: 2 
  }, 
  value: 36672737, 
  height: 81835, 
}

which shows the OutPoint’s height as 81835. What’s causing this discrepancy? Is it a bug?

1 Like

I just made a request, and the UTXO’s outpoint height has reverted to 81836, matching mempool.space.
However, yesterday for a prolonged period (around 3–4 hours) it remained inconsistent with mempool.space, even though at that time the latest block hash tracked by the Bitcoin canister matched mempool.space (indicating the canister did not undergo a fork). I’m not sure what caused this issue.

Description

I’ve encountered an issue on Bitcoin Testnet4 where, for the same address, the Bitcoin canister’s tip height matches mempool.space (both height and block hash are identical), yet the reported height for the same UTXO’s OutPoint differs between the two sources. The timestamp of occurrence was UTC 2025-05-13T04:01:18.610673Z.

github issue:

Details
BTC Address:
tb1pjay9hs03f2fqe6wks38kygvzjq6pr2j5mnxst4dngnwjmdqtlc8q5zj52c

Tip Height & Block Hash (from Bitcoin canister):
[12, 167, 65, 176, 216, 187, 187, 192, 229, 180, 98, 104, 214, 206, 176, 1,
157, 235, 129, 237, 108, 194, 161, 59, 18, 228, 251, 2, 0, 0, 0, 0]
→ Hex: 0000000002fbe4123ba1c26ced81eb9d01b0ced66862b4e5c0bbbbd8b041a70c
→ Height: 82071 (matches mempool.space)
Mempool.space:

UTXO from mempool.space:
Utxo {
outpoint: OutPoint {
txid: Txid([124,252,75,8,13,214,27,132,22,228,190,130,189,221,209,233,
18,78,245,95,52,117,31,73,154,99,86,222,135,21,4,43]),
vout: 3
},
value: 273336,
height: 81973
}

Same UTXO from Bitcoin canister:
Utxo {
outpoint: OutPoint {
txid: Txid([124,252,75,8,13,214,27,132,22,228,190,130,189,221,209,233,
18,78,245,95,52,117,31,73,154,99,86,222,135,21,4,43]),
vout: 3
},
value: 273336,
height: 81969
}

Expected Behavior
The UTXO’s height reported by the Bitcoin canister should match the one shown on mempool.space (i.e., 81973).

Actual Behavior
The Bitcoin canister reports the UTXO’s height as 81969, while mempool.space reports 81973.

Questions
• Is this discrepancy indicative of a bug in the Bitcoin canister?
• If not a bug, what might cause the canister to report an outdated height for this OutPoint?

Network: Bitcoin Testnet4

@THLO

1 Like

Thanks for reporting this!
The discrepancy is probably due to the output occurring in multiple blocks on different forks at different heights. As @maksym showed here, there can be many forks on Testnet4.

It is possible that blockchain explorers and the Bitcoin canister occasionally see a slightly different state when there are several forks and temporarily disagree on what blocks are part of the main chain.
As you observed, once there is agreement on the main chain, the discrepancy goes away and all sources report the same information.
Unfortunately, there is no way to guarantee that this never happens.

I hope this helps!

2 Likes

Hi all, let me provide a bit more context to the issue.

Often mempool and bitcoin canister heights are not in sync. Bitcoin canister often lags behind a little bit, which is mostly caused by several competing branches, when it’s not possible to decide what is the main branch, so the canister decides on some earlier ‘safe’ block. The bitcoin canister tip height may even go back and forth.

The original issue depends heavily on the timing of 3 data points:

  1. refreshing mempool page
  2. reading current tip heigh from bitcoin canister
  3. reading address with transactions from bitcoin canister

Calls 2. and 3. are different and cannot be done in the same time. So my assumption is that the developer sees the same block hash and height from data points 1. and 2., then at some point it receives different heights from the call 3., which looks like a bug.

My current evaluation – the culprit is ‘bitcoin canister lagging behind due to competing branches on testnet4’. And the pattern of ‘different heights’ should look like on the picture above. It is also supported by the fact that after some time when the block becomes stable the height values do converge.

To rule out this hypothesis one may want to do a more rigorous testing with many data points (all 3, or at least many points 2.+3.) that show a pattern different from the one I provided above.

I hope it helped to fill the gaps.

2 Likes

Hi,
I saw it mentioned here that the stability threshold for unstable blocks is 144 blocks.

I’d like to ask: what stability threshold is currently set for the Bitcoin canister on Testnet4?
And can UTXOs at heights earlier than (tip height − stability threshold) returned by the Bitcoin canister be considered fully confirmed and up-to-date?

Stability Threshold for Testnets Background

Unfortunately ‘stability threshold’ setting only makes sense in context of Mainnet, not for Testnet4. 144=24*6 is the number of blocks generated every 10 minutes for 24 hours.

Back in the days when we supported Testnet3 it was possible to see many blocks generated with low difficulty, so internally we changed the formula for ‘stability threshold’ to account for difficulty (many blocks of low difficulty should not overtake few blocks of high difficulty). But this change also broke the direct mental connection between the ‘number of blocks’ and ‘stability threshold’ for testnets.

You can see the current settings for Bitcoin Testnet Canister on the dashboard page by calling get_config query.

Current value is 100, but since on Testnet4 blocks can have high and minimum difficulty it turns to about 200-250 blocks. This number also changes depending on the activity on the testnet, so I don’t think this parameter is meaningful for the developers to rely on.

Question

I would assume that the real question developers might have is actually – how can I know that the block is stable enough so I can rely on it?

The block becomes stable for the bitcoin canister when it moves from ‘unstable blocks tree’ structure (with potentially many branches) to a stable chain structure (never changes after that), which takes 200-250 blocks currently or 1.5-2 days. I understand that this is a bit long.

But there’s another observation made on Testnet4 that shows high difficulty blocks appear roughly every 3 blocks, so you should multiply it by your Mainnet confirmation value (say 12 blocks) which turns into 6 hours. After that it is highly unlikely that another competing branch will take over.

Here’s a chart of number of blocks by which bitcoin canister on testnet4 lags behind (past 30 days). You can see the 50 pct value is -3 blocks with minimum of -21 blocks.

Unfortunately this is all the nature of current testnet and we cannot do much about it. At least it should give you some intuition on how long to wait before considering the block reliable.

3 Likes

Thank you for the explanation. I think it’s reasonable to allow for more compromises and understanding on the testnet.
It won’t be the same on mainnet as it is on Testnet4.

would you prefer signet over testnet4 for stability?

Ah, don’t worry, it’s not a big issue.
A friend of mine came across this problem while testing UTXOs on testnet4, and I’m equally puzzled, so I’m here to seek some help.