From 74c068829967bbff60da3506613844df4da1ae80 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 14:14:48 +0000 Subject: mine: ask whether a transaction fits before appending it The node miner tested its size budget only on the outer while loop. The inner for walked all of mapTransactions with no test, so a single pass could append the entire mempool -- the overshoot was bounded by mempool size, not by one transaction as the comment claimed. The pool builder did test, but only after the push, which bounds the overshoot at one transaction. Nothing caps how big one transaction may be: neither CheckTransaction nor AcceptTransaction limits it, so the mempool will hold one of any size the wire allows and "one transaction" is worth up to MAX_SIZE. A lone 1.5 MB transaction offering a large fee is therefore enough to make every template unmineable, and the fee is never paid because the block never confirms. Both now ask before appending and skip what does not fit, so the overshoot is zero and an oversized transaction costs the template nothing rather than invalidating it. The budget moves to MAX_BLOCK_SIZE_GEN so the two assemblers cannot drift on it again -- drift is what produced this. The test is ordered before ConnectInputs on purpose: ConnectInputs credits nFees by reference on success, so declining a transaction after calling it would leave the coinbase claiming a fee the block does not collect. Assembly only. CheckBlock, CheckTransaction and AcceptTransaction are untouched: this changes what we produce, not what the network accepts. --- src/main.cpp | 41 ++++++++++++++++++++++++++++++++++++----- src/main.h | 20 ++++++++++++++++++++ src/rpc.cpp | 34 +++++++++++++++++++++++++--------- 3 files changed, 81 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 7b3e555..64eb290 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4470,7 +4470,6 @@ bool BitcoinMiner(int nThreadId) map mapTestPool; vector vfAlreadyAdded(mapTransactions.size()); bool fFoundSomething = true; - unsigned int nBlockSize = 0; // Assembly must stop below the consensus cap that CheckBlock() // enforces, or we mine a block our own node then rejects. // @@ -4481,9 +4480,19 @@ bool BitcoinMiner(int nThreadId) // meant solving a block and throwing it away -- with this pool at // ~90% of hashrate, that is the network stalling, not just us. // - // Half the cap, keeping the original margin: the test runs before - // a transaction is added, so the loop can overshoot by one tx. - while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE/2) + // The budget test then sat only on this outer while, and the + // comment claimed that bounded the overshoot to a single + // transaction. It did not. The inner for walks all of + // mapTransactions with no test of its own, so one pass could append + // the entire mempool: the overshoot was bounded by mempool size, + // which is to say not bounded at all. Ask per transaction, before + // appending, and the overshoot is zero rather than small. + // + // Seeded with what the block already weighs -- header, tx count, + // coinbase -- because CheckBlock() weighs the whole serialized + // block and not just the transactions we chose to add to it. + unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK); + while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN) { fFoundSomething = false; unsigned int n = 0; @@ -4495,6 +4504,28 @@ bool BitcoinMiner(int nThreadId) if (tx.IsCoinBase() || !tx.IsFinal()) continue; + // Skip, not break: a transaction too big for the room left + // says nothing about the next one, which may still fit. + // + // This is also the only ceiling on a single transaction's + // size anywhere on the path into a block. CheckTransaction + // does not cap it and neither does AcceptTransaction, so + // the mempool will hold one of any size the wire allows; + // without this test a lone 1.5 MB transaction offering a + // fat fee is selected, and every template built from that + // mempool is one CheckBlock() throws away. The fee is never + // paid, because the block never confirms. + // + // Deliberately before ConnectInputs and not after: + // ConnectInputs adds this transaction's fee to nFees by + // reference on success. Passing it and then declining the + // transaction would credit the coinbase for a fee no + // transaction in the block pays, and the block would be + // rejected for a wholly different reason. + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); + if (!BlockTemplateFits(nBlockSize, nTxSize)) + continue; + // Transaction fee requirements, mainly only needed for flood control // Under 10K (about 80 inputs) is free for first 100 transactions // Base rate is 0.01 per KB @@ -4506,7 +4537,7 @@ bool BitcoinMiner(int nThreadId) swap(mapTestPool, mapTestPoolTmp); pblock->vtx.push_back(tx); - nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); + nBlockSize += nTxSize; vfAlreadyAdded[n] = true; fFoundSomething = true; } diff --git a/src/main.h b/src/main.h index 382dd9c..1f452b5 100644 --- a/src/main.h +++ b/src/main.h @@ -33,6 +33,26 @@ static const unsigned int MAX_BLOCK_SIGOPS = 20000; // limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block // cap, so this tree inherited no real block-size consensus limit. static const unsigned int MAX_BLOCK_SIZE = 1000000; +// How much of that cap a template we build may fill. Half, which is what both +// assemblers already aimed at -- this only gives the number a name and one +// place to live. They had already drifted on how it was enforced, which is the +// bug this exists to stop repeating. +// +// NOT a consensus rule. CheckBlock() decides what the network accepts; this +// decides what we are willing to produce, and it has to stay under that. +static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2; + +// Room for one more transaction in a template that currently weighs +// nBlockSize bytes? +// +// Asked BEFORE the transaction is appended, never after. A template that has +// already overshot is one CheckBlock() rejects, and asking afterwards only +// measures the waste. Both assemblers call this, so neither can drift into a +// different idea of what fits. +inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nTxSize) +{ + return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN); +} static const int64 COIN = 100000000; // Total Bitflash emission: identical to Bitcoin (21 million). // MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in diff --git a/src/rpc.cpp b/src/rpc.cpp index a4b53ca..7c5ef6c 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -267,16 +267,24 @@ static bool RebuildJob() map pool; vector vfAlreadyAdded(mapTransactions.size()); bool fFoundSomething = true; - unsigned int sz = 0; - // The cap is MAX_BLOCK_SIZE/2, not MAX_SIZE/2. MAX_SIZE is the - // 32 MB serialisation ceiling, so half of it is 16 MB -- this - // builder would happily assemble a template that its own + // The cap is MAX_BLOCK_SIZE_GEN, not MAX_SIZE/2. MAX_SIZE is + // the 32 MB serialisation ceiling, so half of it is 16 MB -- + // this builder would happily assemble a template that its own // CheckBlock() rejects at the 1 MB consensus cap. This is the // pool's job builder, so a block over the cap is not one // wasted solve, it is every miner on the pool working a - // template the network will reject. The inner break bounds the - // overshoot to a single transaction within a pass. - while (fFoundSomething && sz < MAX_BLOCK_SIZE / 2) + // template the network will reject. + // + // The inner break used to bound the overshoot to one + // transaction per pass. One transaction is still too many: + // nothing caps how big a single transaction may be, so that + // bound was worth up to MAX_SIZE. Asking whether each one fits + // before appending it bounds the overshoot at zero instead. + // + // Seeded with the header, tx count and coinbase, because + // CheckBlock() weighs the whole serialized block. + unsigned int sz = ::GetSerializeSize(block, SER_DISK); + while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN) { fFoundSomething = false; unsigned int n = 0; @@ -287,16 +295,24 @@ static bool RebuildJob() continue; CTransaction& tx = (*mi).second; if (tx.IsCoinBase() || !tx.IsFinal()) continue; + // Skip rather than stop: one transaction too big for + // the room left does not mean the next is. Ordered + // before ConnectInputs because ConnectInputs credits + // nFees by reference on success, and a transaction we + // then decline would leave the coinbase claiming a fee + // this block does not collect. + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); + if (!BlockTemplateFits(sz, nTxSize)) + continue; map tmp(pool); if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, false, true, tx.GetMinFee(block.vtx.size() < 100))) continue; swap(pool, tmp); block.vtx.push_back(tx); - sz += ::GetSerializeSize(tx, SER_NETWORK); + sz += nTxSize; vfAlreadyAdded[n] = true; fFoundSomething = true; - if (sz > MAX_BLOCK_SIZE / 2) break; } } } -- cgit v1.2.3 From 2ed8fae6088c99f1a5dfcfd760c1a24c2b2706db Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 14:16:48 +0000 Subject: 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. --- src/main.cpp | 19 ++++++++++++++++--- src/main.h | 15 ++++++++++++--- src/rpc.cpp | 13 ++++++++++--- 3 files changed, 38 insertions(+), 9 deletions(-) (limited to 'src') 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) // coinbase -- because CheckBlock() weighs the whole serialized // block and not just the transactions we chose to add to it. unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK); + // Sigops are a budget too, and CheckBlock() enforces one that no + // assembler here counted against. Bytes do not bound validation + // cost: every OP_CHECKSIG is an elliptic-curve verification, so a + // template well inside the size limit can still be refused for + // demanding more work than a block is allowed to. Counted with + // CTransaction::GetSigOpCount, the function CheckBlock() itself + // sums, so the two cannot disagree about what a transaction costs. + // + // Seeded from the coinbase because CheckBlock() sums over all of + // vtx, coinbase included. + unsigned int nBlockSigOps = pblock->vtx[0].GetSigOpCount(); while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN) { fFoundSomething = false; @@ -4522,8 +4533,9 @@ bool BitcoinMiner(int nThreadId) // transaction would credit the coinbase for a fee no // transaction in the block pays, and the block would be // rejected for a wholly different reason. - unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); - if (!BlockTemplateFits(nBlockSize, nTxSize)) + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); + unsigned int nTxSigOps = tx.GetSigOpCount(); + if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps)) continue; // Transaction fee requirements, mainly only needed for flood control @@ -4537,7 +4549,8 @@ bool BitcoinMiner(int nThreadId) swap(mapTestPool, mapTestPoolTmp); pblock->vtx.push_back(tx); - nBlockSize += nTxSize; + nBlockSize += nTxSize; + nBlockSigOps += nTxSigOps; vfAlreadyAdded[n] = true; fFoundSomething = true; } 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; static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2; // Room for one more transaction in a template that currently weighs -// nBlockSize bytes? +// nBlockSize bytes and spends nBlockSigOps signature operations? // // Asked BEFORE the transaction is appended, never after. A template that has // already overshot is one CheckBlock() rejects, and asking afterwards only // measures the waste. Both assemblers call this, so neither can drift into a // different idea of what fits. -inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nTxSize) +// +// Size gets a margin (half the cap); sigops do not. There is no cheap way to +// spend the sigop budget usefully and no reason to reserve headroom in it, so +// assembly holds itself to the same number CheckBlock() enforces -- and to the +// same comparison. CheckBlock() fails on `> MAX_BLOCK_SIGOPS`, so a block +// sitting exactly on the limit is valid and this has to accept it too. +// Assembly and validation disagreeing by one is how the size bug started. +inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nBlockSigOps, + unsigned int nTxSize, unsigned int nTxSigOps) { - return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN); + return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN && + nBlockSigOps + nTxSigOps <= MAX_BLOCK_SIGOPS); } static const int64 COIN = 100000000; // 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() // Seeded with the header, tx count and coinbase, because // CheckBlock() weighs the whole serialized block. unsigned int sz = ::GetSerializeSize(block, SER_DISK); + // And the sigop budget, which CheckBlock() enforces and this + // builder never counted against. Same counting function + // CheckBlock() sums, seeded from the coinbase because it sums + // over all of vtx. + unsigned int nSigOps = block.vtx[0].GetSigOpCount(); while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN) { fFoundSomething = false; @@ -301,8 +306,9 @@ static bool RebuildJob() // nFees by reference on success, and a transaction we // then decline would leave the coinbase claiming a fee // this block does not collect. - unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); - if (!BlockTemplateFits(sz, nTxSize)) + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); + unsigned int nTxSigOps = tx.GetSigOpCount(); + if (!BlockTemplateFits(sz, nSigOps, nTxSize, nTxSigOps)) continue; map tmp(pool); if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, @@ -310,7 +316,8 @@ static bool RebuildJob() continue; swap(pool, tmp); block.vtx.push_back(tx); - sz += nTxSize; + sz += nTxSize; + nSigOps += nTxSigOps; vfAlreadyAdded[n] = true; fFoundSomething = true; } -- cgit v1.2.3 From ff6af901eaab61b6dbba41bb80f7bb2f40980880 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 14:23:50 +0000 Subject: selftest: cover the assembly half of the block limits consensus-limits only ever asked what CheckBlock() refuses. Nothing asked what the assemblers produce, which is why a miner able to overshoot the cap by an entire mempool sat here unnoticed through both the deployed and candidate builds. Adds the other half: a template built from a 3 MB mempool stays inside the budget and passes CheckSizeLimits(); a transaction too big for the budget is skipped while the ones that fit are still taken; sigop-heavy transactions stop selection at MAX_BLOCK_SIGOPS while the block is still small. Plus the rule's edges, including that sigops exactly on the limit fit -- CheckBlock() fails on `>`, so assembly must accept what validation accepts. The test drives BlockTemplateFits the way the assemblers drive it rather than calling them: BitcoinMiner needs a CTxDB, a wallet key and cs_main, and RebuildJob is static in rpc.cpp. What that proves is the shared rule and the property it exists to give, which is where the defect was -- the budget was right and the enforcement was not. Checked against the bug: with the fit test neutered, eight of these fail, including both templates that CheckBlock() would have rejected. With the sigop comparison stricter than CheckBlock's by one, the boundary case fails. --- src/selftest.cpp | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) (limited to 'src') diff --git a/src/selftest.cpp b/src/selftest.cpp index 9f7106f..d11fdd8 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -1235,6 +1235,74 @@ static CBlock MakeSizedConsensusBlock(size_t nScriptBytes) return block; } +// A distinct transaction of a chosen weight and verification cost. nUnique +// only keeps the txids apart, so a mempool built from these is not a pile of +// duplicates a block could never carry anyway. +static CTransaction MakeSizedTx(size_t nPadBytes, unsigned int nSigOps, int nUnique) +{ + CTransaction tx; + tx.vin.push_back(CTxIn(COutPoint(), CScript() << (int64)nUnique << 1)); + tx.vout.push_back(CTxOut(0, CScript())); + for (unsigned int i = 0; i < nSigOps; i++) + tx.vout[0].scriptPubKey << OP_CHECKSIG; + tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nPadBytes, 0); + return tx; +} + +static CBlock MakeCoinbaseOnlyTemplate() +{ + CTransaction txNew; + txNew.vin.resize(1); + txNew.vin[0].prevout.SetNull(); + txNew.vin[0].scriptSig << 1 << 1 << 1; + txNew.vout.resize(1); + txNew.vout[0].scriptPubKey << OP_CHECKSIG; + + CBlock block; + block.vtx.push_back(txNew); + block.nVersion = 1; + block.nTime = GetAdjustedTime(); + block.nBits = bnProofOfWorkLimit.GetCompact(); + block.nNonce = 0; + return block; +} + +// The sum CheckBlock() takes, spelled the same way. +static unsigned int BlockSigOpCount(const CBlock& block) +{ + unsigned int n = 0; + for (int i = 0; i < (int)block.vtx.size(); i++) + n += block.vtx[i].GetSigOpCount(); + return n; +} + +// Drive the selection rule the way both assemblers drive it: ask +// BlockTemplateFits before appending, skip what does not fit, never stop early +// on one that does not. +// +// This mirrors the loops in BitcoinMiner() and RebuildJob() rather than calling +// them -- one needs a CTxDB, a wallet key and cs_main, the other is static in +// rpc.cpp, so neither is reachable from here. What that leaves proven is the +// rule the two of them share and the property it is meant to guarantee, which +// is where the defect actually lived: the budget was right and the enforcement +// was not. +static void SelectIntoTemplate(CBlock& block, const vector& vMempool) +{ + unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK); + unsigned int nBlockSigOps = block.vtx[0].GetSigOpCount(); + for (int i = 0; i < (int)vMempool.size(); i++) + { + const CTransaction& tx = vMempool[i]; + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); + unsigned int nTxSigOps = tx.GetSigOpCount(); + if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps)) + continue; + block.vtx.push_back(tx); + nBlockSize += nTxSize; + nBlockSigOps += nTxSigOps; + } +} + static int RunConsensusLimitsSelfTest() { fflush(stdout); @@ -1258,6 +1326,95 @@ static int RunConsensusLimitsSelfTest() "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; nFail += Check(!oversized.CheckSizeLimits(), "a block above 1 MB is outside the consensus size limit") ? 0 : 1; + + // The other half of the cap. Everything above asks what validation + // refuses; nothing asked what assembly produces, which is why a miner + // that could overshoot the cap by an entire mempool went unnoticed. + nFail += Check(MAX_BLOCK_SIZE_GEN < MAX_BLOCK_SIZE, + "the assembly budget stays under the consensus cap") ? 0 : 1; + + // The rule itself, at its edges. The sigop comparison has to match + // CheckBlock(), which fails on `> MAX_BLOCK_SIGOPS` and so accepts a + // block sitting exactly on the limit. + nFail += Check(BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 100, 0), + "a transaction that exactly fills the size budget fits") ? 0 : 1; + nFail += Check(!BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 101, 0), + "one byte past the size budget does not fit") ? 0 : 1; + nFail += Check(BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS), + "sigops exactly on the limit fit, as CheckBlock() allows") ? 0 : 1; + nFail += Check(!BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS + 1), + "one sigop past the limit does not fit") ? 0 : 1; + + // A mempool several times the consensus cap, in ordinary-sized + // transactions. The old miner tested its budget only on the outer + // loop, so a single pass appended all of this. + { + CBlock block = MakeCoinbaseOnlyTemplate(); + vector vMempool; + for (int i = 0; i < 300; i++) + vMempool.push_back(MakeSizedTx(10000, 1, i)); + SelectIntoTemplate(block, vMempool); + block.hashMerkleRoot = block.BuildMerkleTree(); + + nFail += Check(block.vtx.size() > 1, + "a full mempool still fills a template") ? 0 : 1; + nFail += Check(::GetSerializeSize(block, SER_DISK) <= MAX_BLOCK_SIZE_GEN, + "a template built from a 3 MB mempool respects the budget") ? 0 : 1; + nFail += Check(block.CheckSizeLimits(), + "that template is one CheckBlock() would accept on size") ? 0 : 1; + } + + // One transaction too big for the budget. Nothing on the path into the + // mempool caps transaction size, so this is a transaction a peer can + // simply send. It must be skipped and the rest still taken -- skipped, + // not stopped on. + { + CBlock block = MakeCoinbaseOnlyTemplate(); + vector vMempool; + vMempool.push_back(MakeSizedTx(1500000, 1, 9999)); + for (int i = 0; i < 10; i++) + vMempool.push_back(MakeSizedTx(1000, 1, i)); + + unsigned int nHugeSize = ::GetSerializeSize(vMempool[0], SER_NETWORK); + nFail += Check(nHugeSize > MAX_BLOCK_SIZE && nHugeSize <= MAX_SIZE, + "the outsized transaction is one the mempool would hold") ? 0 : 1; + + SelectIntoTemplate(block, vMempool); + block.hashMerkleRoot = block.BuildMerkleTree(); + + bool fTookHuge = false; + for (int i = 0; i < (int)block.vtx.size(); i++) + if (block.vtx[i].GetHash() == vMempool[0].GetHash()) + fTookHuge = true; + + nFail += Check(!fTookHuge, + "a transaction larger than the budget is left out") ? 0 : 1; + nFail += Check(block.vtx.size() == 11, + "the ten that do fit are taken anyway") ? 0 : 1; + nFail += Check(block.CheckSizeLimits(), + "one outsized transaction cannot invalidate a template") ? 0 : 1; + } + + // Transactions that are cheap in bytes and expensive to verify. Size + // alone does not bound what a block costs, and CheckBlock() refuses on + // the count regardless of how small the block is. + { + CBlock block = MakeCoinbaseOnlyTemplate(); + vector vMempool; + for (int i = 0; i < 100; i++) + vMempool.push_back(MakeSizedTx(0, 1000, i)); + SelectIntoTemplate(block, vMempool); + block.hashMerkleRoot = block.BuildMerkleTree(); + + nFail += Check(block.vtx.size() > 1, + "sigop-heavy transactions are still selected") ? 0 : 1; + nFail += Check(BlockSigOpCount(block) <= MAX_BLOCK_SIGOPS, + "selection stops at the sigop cap CheckBlock() enforces") ? 0 : 1; + nFail += Check(::GetSerializeSize(block, SER_DISK) < MAX_BLOCK_SIZE_GEN, + "and stops on sigops while still well inside the size budget") ? 0 : 1; + nFail += Check(block.CheckSizeLimits(), + "the sigop-bounded template is within the size limit too") ? 0 : 1; + } } catch (const std::exception& e) { -- cgit v1.2.3