upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 14:16:48 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 14:16:48 +0000
commit2ed8fae6088c99f1a5dfcfd760c1a24c2b2706db (patch)
treef23023e737fc73cdb384e4840c033cfc5ac08e10
parent74c068829967bbff60da3506613844df4da1ae80 (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.
-rw-r--r--src/main.cpp19
-rw-r--r--src/main.h15
-rw-r--r--src/rpc.cpp13
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 }
diff --git a/src/main.h b/src/main.h
index 1f452b5..30e25ef 100644
--- a/src/main.h
+++ b/src/main.h
@@ -43,15 +43,24 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000;
43static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2; 43static 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.
52inline 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.
59inline 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}
56static const int64 COIN = 100000000; 65static 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 }