9

On April 1st 2023 F2Pool mined block 00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4 (full block) which my node failed with bad-blk-sigops.

Bitcoin Core stops counting as soon as it reaches the 80,000 limit.

I had a hard time counting this, but how many sigops did this block contain?

0

3 Answers 3

7

The answer is 80,003. I arrived at the same number as Sjors by writing a script that goes through every transaction in a block and counts the sigops.

Here is a detailed breakdown:

  • Bare multisig outputs: 71,200. There are 890 and each contributes 80 sigops.
  • P2PKH outputs: 3320. There are 830 and each contributes 4 sigops.
  • P2WPKH-in-P2SH inputs: 2219. Each contributes a single sigop.
  • P2WPKH inputs: 1751. Each contributes a single sigop.
  • P2WSH inputs: 1160.
    • 28 inputs contribute a single sigop.
    • 363 inputs contribute 2 sigops.
    • 128 inputs contribute 3 sigops.
    • 2 inputs contribute 4 sigops.
    • 2 inputs contribute 7 sigops.
  • P2WSH-in-P2SH inputs: 193.
    • 12 inputs contribute a single sigop.
    • 29 inputs contribute 2 sigops.
    • 33 inputs contribute 3 sigops.
    • 6 inputs contribute 4 sigops.
  • P2SH inputs: 160.
    • 1 input contributes 4 sigops.
    • 13 inputs contribute 12 sigops.
4
  • 1
    Can you share the script? Either in your answer or via a Github gist? Apr 6, 2023 at 7:15
  • It's a "script" in a sense that it's a JavaScript (actually TypeScript) file sitting in a larger project of mine using some of its internals as dependencies. If you'd like I can produce a minimal standalone repo. Apr 6, 2023 at 9:15
  • 1
    Even without the dependencies it could be useful to see the script and the queries it makes, so I can compare with what I was doing. Apr 6, 2023 at 11:39
  • 2
6

The correct, but non gratifying, answer is 80,003.

I found it by changing MAX_BLOCK_SIGOPS_COST, recompiling and the using the invalidateblock and reconsiderblock RPC to recheck it.

The block fails validation with the sigops limit set to 80,002 as passes validation when it's set to 80,003.

3
  • 1
    Was this an april fools joke by them? Is there any context as to why this occurred?
    – Poseidon
    Apr 5, 2023 at 19:12
  • 4
    They probably miscounted the sigops on bare multisig transactions.
    – Murch
    Apr 5, 2023 at 19:14
  • 6
    @Poseidon that would be a $150K prank, and they already had a different April Fools joke on social media. So I think it was an accident. I suspect they use custom software to compose the block, not Bitcoin Core's getblocktemplate. See also: twitter.com/provoost/status/1642182022176161795 Apr 5, 2023 at 19:31
3

I was able to do a partial count, but I'm having a hard time counting it for P2SH and SegWit transactions.

Parse the block and store result:

bitcoin-cli getblock 00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4 2 > block.json

This lists transactions like so:

    {
      "txid": "914b24cf50f4bc930c3a11123411031010f010563ed293f65f1ef8cb9bc1e950",
      ...
      "vin": [
        {
          "txid": "cf3349976fedfe34502333310f753d91c16dccb3220d37d5f4c1a11b70eceda2",
          "vout": 1,
          "scriptSig": {
            "asm": "30440220253e9a3fc9cbae7833abc38ea29ae4f7672cb33591c227446c71b26a8199760a022071042cde824b3cfddc51888d49ac8f85062921e1430ee85885252e1a6758fc17[ALL] 02b704c5bac9ed2e23193f28f0a9e1108ff6164e713552fca936c9447566df1d66",
            "hex": "..."
          },
          "sequence": 4294967295
        }
      ],
      "vout": [
        {
          "value": 0.00071520,
          "n": 1,
          "scriptPubKey": {
            "asm": "OP_DUP OP_HASH160 8342c5f824ae0806236e746b951e68192c1b124f OP_EQUALVERIFY OP_CHECKSIG",

            "address": "1Cy3Wd2pArq359z9d3VYMUoBQBM1kkANCn",
            "type": "pubkeyhash"
          }
        }
      ]

I can use jq, grep, sed, awkandwc -l` to count various stuff.

Legacy OP_CHECKSIG:

cat block.json | jq -r '.tx[].vout[].scriptPubKey.asm' | grep OP_CHECKSIG | wc -l
830

Each OP_CHECKSIG used to count as 1 sigop before SegWit, and now counts as 4.

This yields 830 * 4 = 3,320 sigops

Bare multisig:

cat block.json | jq -r '.tx[].vout[].scriptPubKey.asm' | grep OP_CHECKMULTISIG | wc -l
890

Each OP_CHECKMULTISIG counted as 20 sigops before SegWit, regardless of the number of public keys.

This yields 890 * 20 * 4 = 71,200 sigops.

P2SH:

Now it gets a bit more complicated. Because the P2SH script is only revealed when you spend it, we have to look at scriptSig on the inputs. Specifically we're looking for a scriptSig that's not one of these:

  1. A regular OP_CHECKSIG spend, which always starts with a DER encoded signature (^304.*\[.*) and ends with a pubkey (0.*$).
  2. P2SH wrapped v0 SegWit: ^0014.*

We also assume it contains at least one signature, which we recognise by the presence of a sighash marker like [ALL]. The script itself is the last thing pushed on the stack, which is what the awk command gives us. We then pipe that into decodescript.

cat block.json | jq -r '.tx[].vin[].scriptSig.asm'| grep . | grep -v "^304.*\[.* 0.*$" | grep -v "^0014.*" | grep "\[" | awk 'NF>1{print $NF}' | xargs -i -n 1 bitcoin-cli decodescript {} | jq '.asm'

The result is a very short list consisting of 13 2-of-3 multisig transactions and 1 OP_CHECKSIG.

So that yields (13 * 3 + 1) * 4 = 160 sigops

Pay to witness public key hash:

For this we look at the txinwitness field. Specifically we check if the last stack entry is a public key.

cat block.json | jq -r '.tx[].vin[].txinwitness[-1]' | grep . | grep -v null | grep "^0..*" | wc -l
3981

This yields 3,981 sigops. Update: Vojtěch found 2219 + 1751 = 3,970 in his answer

Pay to witness script hash:

We again look at the last item on the witness stack, which we assume to be the script. We do the opposite of P2WPK and drop all matches with a public key. We also drop (taproot) Schnorr signatures which are 64 bytes plus optionally 1 for the SIGHASH. We also drop entries that start with 33 bytes (I think these are Tapleaf script hashes).

cat block.json | jq -r '.tx[].vin[].txinwitness[-1]' | grep . | grep -v null | grep -v "^0..*" | sed -r '/^.{128,130}$/d' | sed -r '/^.{66}$/d' | xargs -i -n 1 bitcoin-cli decodescript {} | jq '.asm' | grep -o "OP_CHECKSIG" | wc -l
231

This yields 231 sigops.

We count multisig in a similar way. First we sort them by N and not which N's are used:

| jq -r '.tx[].vin[].txinwitness[-1]' | grep . | grep -v null | grep -v "^03.*" | sed -r '/^.{128,130}$/d' | sed -r '/^.{66}$/d' | xargs -i -n 1 bitcoin-cli decodescript {} | jq '.asm' | grep "OP_CHECKMULTISIG" | rev | sort | rev

Then count the number of occurrences:

  1. 2 times m-of-7: 2 * 7 = 14 sigops
  2. 1 time m-of-4: 4 sigops
  3. 102 times m-of-3: 306 sigops
  4. 385 times m-of-2: 770 sigops
  5. List item
  6. 27 times 1-of-1: 27 sigops

So that yields 1,159 sigops.

Total

3320 + 71200 + 160 + 3981 + 231 + 1159 = 80,051

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.