diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 14:16:48 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 14:16:48 +0000 |
| commit | 2ed8fae6088c99f1a5dfcfd760c1a24c2b2706db (patch) | |
| tree | f23023e737fc73cdb384e4840c033cfc5ac08e10 /src | |
| parent | 74c068829967bbff60da3506613844df4da1ae80 (diff) | |
mine: count sigops while selecting, not only when validating
CheckBlock() caps a block at MAX_BLOCK_SIGOPS and neither assembler counted
against it. Bytes do not bound what a block costs to verify: every OP_CHECKSIG
is an elliptic-curve verification, so a template comfortably inside the size
budget can still be one the network refuses for demanding more work than a
block is allowed to.
Counted with CTransaction::GetSigOpCount -- the same function CheckBlock()
sums, not a second implementation of it -- and seeded from the coinbase,
because CheckBlock() sums over all of vtx. The comparison matches too:
CheckBlock() fails on `> MAX_BLOCK_SIGOPS`, so a block exactly on the limit is
valid and selection accepts it. Assembly and validation differing by one is
the shape of the bug this series started with.
Unlike the size budget this takes no margin. There is no use for headroom in a
sigop count, so assembly holds itself to the consensus number exactly.
Assembly only; MAX_BLOCK_SIGOPS and the CheckBlock() test are unchanged.
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 19 | ||||
| -rw-r--r-- | src/main.h | 15 | ||||
| -rw-r--r-- | src/rpc.cpp | 13 |
3 files changed, 38 insertions, 9 deletions
diff --git a/src/main.cpp b/src/main.cpp index 64eb290..4d8031d 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -4492,6 +4492,17 @@ bool BitcoinMiner(int nThreadId) | |||
| 4492 | // coinbase -- because CheckBlock() weighs the whole serialized | 4492 | // coinbase -- because CheckBlock() weighs the whole serialized |
| 4493 | // block and not just the transactions we chose to add to it. | 4493 | // block and not just the transactions we chose to add to it. |
| 4494 | unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK); | 4494 | unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK); |
| 4495 | // Sigops are a budget too, and CheckBlock() enforces one that no | ||
| 4496 | // assembler here counted against. Bytes do not bound validation | ||
| 4497 | // cost: every OP_CHECKSIG is an elliptic-curve verification, so a | ||
| 4498 | // template well inside the size limit can still be refused for | ||
| 4499 | // demanding more work than a block is allowed to. Counted with | ||
| 4500 | // CTransaction::GetSigOpCount, the function CheckBlock() itself | ||
| 4501 | // sums, so the two cannot disagree about what a transaction costs. | ||
| 4502 | // | ||
| 4503 | // Seeded from the coinbase because CheckBlock() sums over all of | ||
| 4504 | // vtx, coinbase included. | ||
| 4505 | unsigned int nBlockSigOps = pblock->vtx[0].GetSigOpCount(); | ||
| 4495 | while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN) | 4506 | while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN) |
| 4496 | { | 4507 | { |
| 4497 | fFoundSomething = false; | 4508 | fFoundSomething = false; |
| @@ -4522,8 +4533,9 @@ bool BitcoinMiner(int nThreadId) | |||
| 4522 | // transaction would credit the coinbase for a fee no | 4533 | // transaction would credit the coinbase for a fee no |
| 4523 | // transaction in the block pays, and the block would be | 4534 | // transaction in the block pays, and the block would be |
| 4524 | // rejected for a wholly different reason. | 4535 | // rejected for a wholly different reason. |
| 4525 | unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); | 4536 | unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); |
| 4526 | if (!BlockTemplateFits(nBlockSize, nTxSize)) | 4537 | unsigned int nTxSigOps = tx.GetSigOpCount(); |
| 4538 | if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps)) | ||
| 4527 | continue; | 4539 | continue; |
| 4528 | 4540 | ||
| 4529 | // Transaction fee requirements, mainly only needed for flood control | 4541 | // Transaction fee requirements, mainly only needed for flood control |
| @@ -4537,7 +4549,8 @@ bool BitcoinMiner(int nThreadId) | |||
| 4537 | swap(mapTestPool, mapTestPoolTmp); | 4549 | swap(mapTestPool, mapTestPoolTmp); |
| 4538 | 4550 | ||
| 4539 | pblock->vtx.push_back(tx); | 4551 | pblock->vtx.push_back(tx); |
| 4540 | nBlockSize += nTxSize; | 4552 | nBlockSize += nTxSize; |
| 4553 | nBlockSigOps += nTxSigOps; | ||
| 4541 | vfAlreadyAdded[n] = true; | 4554 | vfAlreadyAdded[n] = true; |
| 4542 | fFoundSomething = true; | 4555 | fFoundSomething = true; |
| 4543 | } | 4556 | } |
| @@ -43,15 +43,24 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000; | |||
| 43 | static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2; | 43 | static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2; |
| 44 | 44 | ||
| 45 | // Room for one more transaction in a template that currently weighs | 45 | // Room for one more transaction in a template that currently weighs |
| 46 | // nBlockSize bytes? | 46 | // nBlockSize bytes and spends nBlockSigOps signature operations? |
| 47 | // | 47 | // |
| 48 | // Asked BEFORE the transaction is appended, never after. A template that has | 48 | // Asked BEFORE the transaction is appended, never after. A template that has |
| 49 | // already overshot is one CheckBlock() rejects, and asking afterwards only | 49 | // already overshot is one CheckBlock() rejects, and asking afterwards only |
| 50 | // measures the waste. Both assemblers call this, so neither can drift into a | 50 | // measures the waste. Both assemblers call this, so neither can drift into a |
| 51 | // different idea of what fits. | 51 | // different idea of what fits. |
| 52 | inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nTxSize) | 52 | // |
| 53 | // Size gets a margin (half the cap); sigops do not. There is no cheap way to | ||
| 54 | // spend the sigop budget usefully and no reason to reserve headroom in it, so | ||
| 55 | // assembly holds itself to the same number CheckBlock() enforces -- and to the | ||
| 56 | // same comparison. CheckBlock() fails on `> MAX_BLOCK_SIGOPS`, so a block | ||
| 57 | // sitting exactly on the limit is valid and this has to accept it too. | ||
| 58 | // Assembly and validation disagreeing by one is how the size bug started. | ||
| 59 | inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nBlockSigOps, | ||
| 60 | unsigned int nTxSize, unsigned int nTxSigOps) | ||
| 53 | { | 61 | { |
| 54 | return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN); | 62 | return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN && |
| 63 | nBlockSigOps + nTxSigOps <= MAX_BLOCK_SIGOPS); | ||
| 55 | } | 64 | } |
| 56 | static const int64 COIN = 100000000; | 65 | static const int64 COIN = 100000000; |
| 57 | // Total Bitflash emission: identical to Bitcoin (21 million). | 66 | // Total Bitflash emission: identical to Bitcoin (21 million). |
diff --git a/src/rpc.cpp b/src/rpc.cpp index 7c5ef6c..62eec7a 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -284,6 +284,11 @@ static bool RebuildJob() | |||
| 284 | // Seeded with the header, tx count and coinbase, because | 284 | // Seeded with the header, tx count and coinbase, because |
| 285 | // CheckBlock() weighs the whole serialized block. | 285 | // CheckBlock() weighs the whole serialized block. |
| 286 | unsigned int sz = ::GetSerializeSize(block, SER_DISK); | 286 | unsigned int sz = ::GetSerializeSize(block, SER_DISK); |
| 287 | // And the sigop budget, which CheckBlock() enforces and this | ||
| 288 | // builder never counted against. Same counting function | ||
| 289 | // CheckBlock() sums, seeded from the coinbase because it sums | ||
| 290 | // over all of vtx. | ||
| 291 | unsigned int nSigOps = block.vtx[0].GetSigOpCount(); | ||
| 287 | while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN) | 292 | while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN) |
| 288 | { | 293 | { |
| 289 | fFoundSomething = false; | 294 | fFoundSomething = false; |
| @@ -301,8 +306,9 @@ static bool RebuildJob() | |||
| 301 | // nFees by reference on success, and a transaction we | 306 | // nFees by reference on success, and a transaction we |
| 302 | // then decline would leave the coinbase claiming a fee | 307 | // then decline would leave the coinbase claiming a fee |
| 303 | // this block does not collect. | 308 | // this block does not collect. |
| 304 | unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); | 309 | unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); |
| 305 | if (!BlockTemplateFits(sz, nTxSize)) | 310 | unsigned int nTxSigOps = tx.GetSigOpCount(); |
| 311 | if (!BlockTemplateFits(sz, nSigOps, nTxSize, nTxSigOps)) | ||
| 306 | continue; | 312 | continue; |
| 307 | map<uint256, CTxIndex> tmp(pool); | 313 | map<uint256, CTxIndex> tmp(pool); |
| 308 | if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, | 314 | if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, |
| @@ -310,7 +316,8 @@ static bool RebuildJob() | |||
| 310 | continue; | 316 | continue; |
| 311 | swap(pool, tmp); | 317 | swap(pool, tmp); |
| 312 | block.vtx.push_back(tx); | 318 | block.vtx.push_back(tx); |
| 313 | sz += nTxSize; | 319 | sz += nTxSize; |
| 320 | nSigOps += nTxSigOps; | ||
| 314 | vfAlreadyAdded[n] = true; | 321 | vfAlreadyAdded[n] = true; |
| 315 | fFoundSomething = true; | 322 | fFoundSomething = true; |
| 316 | } | 323 | } |