ckBTC: a canister-issued bitcoin twin token on the IC, 1:1-backed by BTC

Hi everyone :waving_hand:!

I would like to give more details on an issue that we found on the ckBTC minter and that was raised here.

TL;DR

3 transactions ckBTC → BTC (withdrawals) are currently stuck and this is due to 2 bugs:

  1. An extremely low fee per vbyte was chosen by the minter for those transactions, which prevented them from being mined.
  2. There is a deterministic panic occurring in the minter when it tries to resubmit those transactions, which means that those transactions are currently stuck.

:information_source: Note that the funds held by the minter are not at risk and as far as we can tell, excepted for those 3 transactions, the minter works as expected.

Details

Impacted Transactions

All 3 stuck transactions involve withdrawals that happen around 2025.06.21 01:00 - 01:35 UTC

  1. 23e46d53929d513cb1dc1b0fa63f9b142f2676958e6e6f0e45653949def954b2
    1. Request 1
      1. Burn index: 2626383
      2. Received at 1750467876787048648 == 21.06.2025 01:04:36.787
    2. Request 2
      1. Burn index: 2626386
      2. Received at 1750468020708178959== 21.06.2025 01:07:00.708
  2. db9e317d38803b83115959ac857e2005855ce572d446351034080864aa8edeb5
    1. Request 1
      1. Burn index: 2626426
      2. Received at 1750468574497835843== 21.06.2025 01:16:14.497
    2. Request 2
      1. Burn index: 2626428
      2. Received at 1750468707343288824 == 21.06.2025 01:18:27.343
    3. Request 3
      1. Burn index: 2626430
      2. Received at 1750468731927248030 == 21.06.2025 01:18:51.927
    4. Request 4
      1. Burn index: 2626432
      2. Received at 1750468808041112499 == 21.06.2025 01:20:08.041
  3. 422f3115c4f865536f92e94d22cb7b2795b0482e517f7c46561e2234cf03e603
    1. Request 1
      1. Burn index: 2626488
      2. Received at 1750469582867790653== 21.06.2025 01:33:02.867

Analysis

The 3 transactions are stuck due the following two bugs.

Bug 1: Low fee per vbyte

An estimated median fee of 142 millistatoshis per vbyte for Bitcoin Mainnet was used during approximatively 30min, from 2025.06.21 01:09:50 UTC until 2025.06.21 01:39:00 UTC.

However, the median fee around that time should have been in the order of 2000 millistatoshis per vbyte. This extremely low fee was used for the 3 stuck transactions, which was way too low to be picked up by miners and explain why those three transactions were rapidly evicted from the mempool.

Solution

We currently don’t have a satisfying explanation for how this low median fee was computed and are also investigating the bitcoin canister. As a stop-gap solution, we will ensure that the median fee computed by the minter is always above some reasonable minimum value.

Bug 2: Panic when resubmitting

The ckBTC minter tries daily to resubmit transactions that were not finalized. However, currently a panic occurring when building the resubmit transaction prevents the minter from processing further those transactions. This is due to the UTXO selection rule that differs between creating a transaction for the first time or resubmitting it.

In more details, let us look at 422f3115c4f865536f92e94d22cb7b2795b0482e517f7c46561e2234cf03e603. The submitted BTC transaction (can be retrieved from get_events) is

SubmittedBtcTransaction { requests: [ RetrieveBtcRequest { amount: 157990, address: P2pkh([51, 226, 108, 181, 64, 54, 173, 71, 246, 73, 72, 212, 123, 28, 139, 184, 38, 14, 245, 82]), block_index: 2626488, received_at: 1750469582867790653, kyt_provider: None, reimbursement_account: Some(Account { owner: Principal { len: 10, bytes: [0, 0, 0, 0, 1, 32, 205, 190, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, subaccount: None }) } ], txid: Txid([3, 230, 3, 207, 52, 34, 30, 86, 70, 124, 127, 81, 46, 72, 176, 149, 39, 123, 203, 34, 77, 233, 146, 111, 83, 101, 248, 196, 21, 49, 47, 66]), used_utxos: [ Utxo { outpoint: OutPoint { txid: Txid([141, 123, 45, 7, 79, 177, 177, 145, 143, 77, 206, 60, 201, 97, 232, 50, 79, 38, 200, 87, 91, 46, 70, 135, 80, 3, 217, 21, 39, 160, 255, 148]), vout: 0 }, value: 158000, height: 901322 }, Utxo { outpoint: OutPoint { txid: Txid([117, 219, 203, 2, 46, 215, 129, 132, 15, 150, 208, 33, 176, 123, 214, 116, 231, 210, 30, 81, 179, 57, 81, 203, 18, 14, 34, 119, 120, 102, 208, 89]), vout: 0 }, value: 50000, height: 902114 }], submitted_at: 1750469599678571705, change_output: Some(ChangeOutput { vout: 1, value: 50336 }), fee_per_vbyte: Some(142) }

The important things there are:

  1. The target is 157_990
  2. It uses 2 UTXOs
    1. utxo_141 (using the 1st byte of Txid as an identifier) with value 158_000
    2. utxo_117 with value 50_000

Notice that utxo_141.value >= target so that the second UTXO is actually not strictly necessary. The first time the transaction was built, greedy returned utxo_141 only, and utxo_117 was added here to aim at reducing the number of UTXOs managed by the minter (because available_utxos corresponds to all UTXOs available to the minter when building a transaction for the first time, which is around 47k, well above the threshold of UTXOS_COUNT_THRESHOLD which has value 1000).

However, when the minter resubmits the transaction and again calls utxos_selection, this time available_utxos == [utxo_141, utxo_117], so that greedy still returns only utxo_141 and since we only have 2 UTXOs, which is below UTXOS_COUNT_THRESHOLD (1000), the minter does not try to add additional UTXOs. This results in the panic here because utxo_117 was not used.

Solution

The solution is easy: since the resubmit transaction should use the same UTXOs as the original transaction, there is actually no need to go through utxos_selection when resubmitting a transaction.

3 Likes