Invalid F2Pool blocks 783426 and 784121 (April 2023)

The April F2Pool's joke that wasn't a joke
Tuesday, April 2, 2024

My notes on the two bad-blk-sigops: too many sigops invalid blocks, 783426 and 784121, mined by F2Pool in April 2023.

On April 1st, 2023, F2Pool mined an invalid block at height 783426. Bitcoin Core nodes rejected the block with the reason bad-blk-sigops and the note too many sigops. On April 6th, 2023, F2Pool mined another bad-blk-sigops block at height 784121. Mining an invalid block with a valid proof-of-work is an expensive mistake. F2Pool lost more than USD $300k in block rewards with these two invalid blocks.

ERROR: ConnectBlock(): too many sigops
ERROR: ConnectTip: ConnectBlock 00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4 failed, bad-blk-sigops

While invalid blocks are normally not relayed on the Bitcoin P2P network, these blocks were likely announced as BIP-152 compact blocks. To ensure fast block propagation, compact blocks are relayed across BIP-152 high-bandwidth connections before they are fully validated.

BitMex Research reported about the first invalid block and Sjors Provoost began analyzing it by counting sigops. A sigop is a Bitcoin script opcode that performs a signature check. For example, OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKMULTISIG, and OP_CHECKMULTISIGVERIFY. As signature checks are computationally expensive, blocks are limited to 80000 sigops. The sigops in, for example, OP_IF ... OP_ENDIF branches are counted even if they aren’t executed. Similarly, sigops in coinbase inputs are counted but won’t ever be executed 1.

With the activation of SegWit, the sigops limit was raised from 20000 to 80000. At the same time, the cost of sigops in pre-SegWit scripts (e.g., P2PKH, P2SH, ..) was quadrupled. For example, a OP_CHECKSIG in a P2PKH output is counted as 4 sigops but was counted as 1 sigop before SegWit activation. In P2WSH a OP_CHECKSIG is counted as 1 sigop.

Sjors counted 80003 sigops for the first invalid block. Three sigops more than the limit of 80000 sigops allows. While some joked the first block on April 1st was an “April F2Pool’s block”, on April 6th the second invalid F2Pool block arrived and made it clear that this wasn’t a one-time event nor an expensive April F2Pool’s joke.

The second invalid block also counted 80003 sigops. At this point, speculation arose that F2Pool was likely running custom software with a bug. Sjors guessed that F2Pool didn’t correctly count the sigops of the coinbase transaction. In a conversation with an F2Pool engineer, I learned that F2Pool had an old patch to Bitcoin Core, which reduced the sigops reserved for the coinbase transaction to a single sigop. This should probably act as a block-building optimization in cases where the sigops limit is the limiting factor to including a high-fee transaction. Before SegWit, 100 sigops were reserved for the coinbase, and as part of the SegWit changes, nBlockSigOpsCost was increased to 400 sigops.

Reserving only a single sigop in the coinbase was fine before SegWit when using a P2PKH output as the OP_CHECKSIG in the P2PKH output was counted as one sigop. After the activation of SegWit, F2Pool would either have to increase the reserved sigop count from one sigop to four sigops or switch to a P2WPKH output (no sigops) or a P2WSH output with a single sigop 2.

Since increasing the coinbase reserved sigops count, F2Pool hasn’t mined another invalid block. However, they now mine blocks with two P2PKH outputs in the coinbase3. As these both cost 4 sigops, if they had hard-coded their reserved sigop count to 4, adding an extra P2PKH coinbase output might have caused an invalid block again.

The F2Pool engineer told me that F2Pool now reserves 100 sigops for the coinbase (400 is the default). This can be observed on-chain. In the recent past, F2Pool’s blocks 821336, 824444, 824931, and 824934 had a sigop count of 79907. Subtracting the 8 sigops from F2Pool’s coinbase from 79907 results in 79899 sigops. With 100 sigops reserved for the coinbase, this equals 79999 sigops. This matches the Bitcoin Core mining algorithm, which fills blocks to one sigop below the 80000 limit 4.


Other pools, such as Braiins5 (784123), MARA Pool (e.g. 820460), and ViaBTC (e.g. 824442) mined blocks with 79603 sigops. In these blocks, they use a P2PKH output with 4 sigops. With the default nBlockSigOpsCost of 400, these blocks also reached the Bitcoin Core miner limit of 79999 sigops (79603 - 4 + 400). Other pools that use a P2SH-P2WSH, P2WPKH, P2WSH, or even P2TR coinbase output, such as Foundry USA (e.g. 783426), Binance Pool (784124), AntPool (790224), Luxor (821174), (822717), and SBI Crypto (824443) all mined blocks with 79599 sigops in the recent past. Here, the coinbase output does not have sigops, and they use the default coinbase reserved sigops count of 400.

An interesting case is the pool. Here, the largest miners are paid directly to the miner-specified address in the pool’s coinbase output. Thus, their coinbase sigops count is based on which addresses the miners specify. The pool only allows P2PKH, P2SH, SegWit, and Taproot addresses. If they allowed raw P2MS scripts, an attacker could fill the coinbase with 5 P2MS outputs having 80 sigops each to exhaust the default sigops reserved for the coinbase. This would produce an invalid block if the transactions in the block would have more than 79600 sigops. Similarly, 100 P2PKH outputs with 4 sigops each are required to exhaust the sigops reserved for the coinbase. Currently, all but one Ocean block have 20 coinbase outputs, which includes an OP_RETURN SegWit commitment and their pool fee output. At maximum, there were two P2PKH outputs with 4 sigops each in, for example, block 829513.

Looking at the valid blocks between height 760000 (2022-10-23) and 836428 (‎2024-03-26), there are only 99 blocks with more than 70000 sigops. In median, in this time frame, the blocks had 10348 sigops with the 25th percentile having 6407 and the 75th percentile having 13950 sigops.

Block sigops per height
Block sigops per height

If you found this interesting, you might also like my notes on the invalid MARA Pool block 809478.

For future reference, the invalid block at height 783426 had the hash 00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4 (header, full block). The invalid block at height 784121 had the hash 000000000000000000046a2698233ed93bb5e74ba7d2146a68ddb0c2504c980d (header, full block).

  1. This description is based on Pieter Wuille’s answer ↩︎

  2. Mining pools often have large amounts of funds in the same coinbase output address. They might want to wait before switching to a new address version and script type until it’s more battle-tested. ↩︎

  3. F2Pool’s coinbase transactions separate the first few sats of each block to an extra address in an additional P2PKH output. Under the Ordinal Theory, the first Satoshi of each block is an uncommon sat. There are marketplaces where these outputs are offered and sold for more than their bitcoin value. This is MEV. ↩︎

  4. This was surprising to me. During validation and when connecting, Bitcoin Core checks that a block hasn’t more (> MAX_BLOCK_SIGOPS_COST) than 80000 sigops. So a block with 80000 sigops should be allowed. This was introduced in PR 7600. A review comment noted that this check isn’t correct. However, as the sigops limit is rarely ever reached and 400 sigops are reserved for the coinbase, an off-by-one error isn’t too big of a deal. ↩︎

  5. Braiins mined block 824446 with 79599 sigops. They switched to a P2SH-P2WSH output for the coinbase reward. ↩︎

All text and images in this work are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License Creative Commons License


Image for ViaBTC's mutated blocks without witness data

March 18, 2024

ViaBTC's mutated blocks without witness data

I noticed multiple ERROR: AcceptBlock: bad-witness-nonce-size errors in the debug log of my Bitcoin Core node. These indicate that a block my node received is invalid and not accepted. It turned out that these are ViaBTC’s blocks, broadcast by their mining pool software, where transaction …