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-03 21:49:35 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-03 21:49:35 +0000
commitdb44eac5d347c229539946e8d61f1ff0d1f68001 (patch)
tree23db58c0d8454cbab023bf9d94d6a39584b889ab
parentcb8fb6d9d14ecf96b4a71ccc1a51b87bf8dd9bd5 (diff)
parentff6af901eaab61b6dbba41bb80f7bb2f40980880 (diff)
merge: !11 fix/block-assembly-size into deploy/all-2026-08-24
-rw-r--r--src/main.cpp54
-rw-r--r--src/main.h29
-rw-r--r--src/rpc.cpp41
-rw-r--r--src/selftest.cpp157
4 files changed, 267 insertions, 14 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 6b8e0de..018627d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4949,7 +4949,6 @@ bool BitcoinMiner(int nThreadId)
4949 map<uint256, CTxIndex> mapTestPool; 4949 map<uint256, CTxIndex> mapTestPool;
4950 vector<char> vfAlreadyAdded(mapTransactions.size()); 4950 vector<char> vfAlreadyAdded(mapTransactions.size());
4951 bool fFoundSomething = true; 4951 bool fFoundSomething = true;
4952 unsigned int nBlockSize = 0;
4953 // Assembly must stop below the consensus cap that CheckBlock() 4952 // Assembly must stop below the consensus cap that CheckBlock()
4954 // enforces, or we mine a block our own node then rejects. 4953 // enforces, or we mine a block our own node then rejects.
4955 // 4954 //
@@ -4960,9 +4959,30 @@ bool BitcoinMiner(int nThreadId)
4960 // meant solving a block and throwing it away -- with this pool at 4959 // meant solving a block and throwing it away -- with this pool at
4961 // ~90% of hashrate, that is the network stalling, not just us. 4960 // ~90% of hashrate, that is the network stalling, not just us.
4962 // 4961 //
4963 // Half the cap, keeping the original margin: the test runs before 4962 // The budget test then sat only on this outer while, and the
4964 // a transaction is added, so the loop can overshoot by one tx. 4963 // comment claimed that bounded the overshoot to a single
4965 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE/2) 4964 // transaction. It did not. The inner for walks all of
4965 // mapTransactions with no test of its own, so one pass could append
4966 // the entire mempool: the overshoot was bounded by mempool size,
4967 // which is to say not bounded at all. Ask per transaction, before
4968 // appending, and the overshoot is zero rather than small.
4969 //
4970 // Seeded with what the block already weighs -- header, tx count,
4971 // coinbase -- because CheckBlock() weighs the whole serialized
4972 // block and not just the transactions we chose to add to it.
4973 unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK);
4974 // Sigops are a budget too, and CheckBlock() enforces one that no
4975 // assembler here counted against. Bytes do not bound validation
4976 // cost: every OP_CHECKSIG is an elliptic-curve verification, so a
4977 // template well inside the size limit can still be refused for
4978 // demanding more work than a block is allowed to. Counted with
4979 // CTransaction::GetSigOpCount, the function CheckBlock() itself
4980 // sums, so the two cannot disagree about what a transaction costs.
4981 //
4982 // Seeded from the coinbase because CheckBlock() sums over all of
4983 // vtx, coinbase included.
4984 unsigned int nBlockSigOps = pblock->vtx[0].GetSigOpCount();
4985 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN)
4966 { 4986 {
4967 fFoundSomething = false; 4987 fFoundSomething = false;
4968 unsigned int n = 0; 4988 unsigned int n = 0;
@@ -4974,6 +4994,29 @@ bool BitcoinMiner(int nThreadId)
4974 if (tx.IsCoinBase() || !tx.IsFinal()) 4994 if (tx.IsCoinBase() || !tx.IsFinal())
4975 continue; 4995 continue;
4976 4996
4997 // Skip, not break: a transaction too big for the room left
4998 // says nothing about the next one, which may still fit.
4999 //
5000 // This is also the only ceiling on a single transaction's
5001 // size anywhere on the path into a block. CheckTransaction
5002 // does not cap it and neither does AcceptTransaction, so
5003 // the mempool will hold one of any size the wire allows;
5004 // without this test a lone 1.5 MB transaction offering a
5005 // fat fee is selected, and every template built from that
5006 // mempool is one CheckBlock() throws away. The fee is never
5007 // paid, because the block never confirms.
5008 //
5009 // Deliberately before ConnectInputs and not after:
5010 // ConnectInputs adds this transaction's fee to nFees by
5011 // reference on success. Passing it and then declining the
5012 // transaction would credit the coinbase for a fee no
5013 // transaction in the block pays, and the block would be
5014 // rejected for a wholly different reason.
5015 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
5016 unsigned int nTxSigOps = tx.GetSigOpCount();
5017 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
5018 continue;
5019
4977 // Transaction fee requirements, mainly only needed for flood control 5020 // Transaction fee requirements, mainly only needed for flood control
4978 // Under 10K (about 80 inputs) is free for first 100 transactions 5021 // Under 10K (about 80 inputs) is free for first 100 transactions
4979 // Base rate is 0.01 per KB 5022 // Base rate is 0.01 per KB
@@ -4985,7 +5028,8 @@ bool BitcoinMiner(int nThreadId)
4985 swap(mapTestPool, mapTestPoolTmp); 5028 swap(mapTestPool, mapTestPoolTmp);
4986 5029
4987 pblock->vtx.push_back(tx); 5030 pblock->vtx.push_back(tx);
4988 nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); 5031 nBlockSize += nTxSize;
5032 nBlockSigOps += nTxSigOps;
4989 vfAlreadyAdded[n] = true; 5033 vfAlreadyAdded[n] = true;
4990 fFoundSomething = true; 5034 fFoundSomething = true;
4991 } 5035 }
diff --git a/src/main.h b/src/main.h
index 382dd9c..30e25ef 100644
--- a/src/main.h
+++ b/src/main.h
@@ -33,6 +33,35 @@ static const unsigned int MAX_BLOCK_SIGOPS = 20000;
33// limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block 33// limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block
34// cap, so this tree inherited no real block-size consensus limit. 34// cap, so this tree inherited no real block-size consensus limit.
35static const unsigned int MAX_BLOCK_SIZE = 1000000; 35static const unsigned int MAX_BLOCK_SIZE = 1000000;
36// How much of that cap a template we build may fill. Half, which is what both
37// assemblers already aimed at -- this only gives the number a name and one
38// place to live. They had already drifted on how it was enforced, which is the
39// bug this exists to stop repeating.
40//
41// NOT a consensus rule. CheckBlock() decides what the network accepts; this
42// decides what we are willing to produce, and it has to stay under that.
43static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2;
44
45// Room for one more transaction in a template that currently weighs
46// nBlockSize bytes and spends nBlockSigOps signature operations?
47//
48// Asked BEFORE the transaction is appended, never after. A template that has
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
51// different idea of what fits.
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)
61{
62 return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN &&
63 nBlockSigOps + nTxSigOps <= MAX_BLOCK_SIGOPS);
64}
36static const int64 COIN = 100000000; 65static const int64 COIN = 100000000;
37// Total Bitflash emission: identical to Bitcoin (21 million). 66// Total Bitflash emission: identical to Bitcoin (21 million).
38// MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in 67// MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in
diff --git a/src/rpc.cpp b/src/rpc.cpp
index 1d53fb2..3827cd5 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -303,16 +303,29 @@ static bool RebuildJob()
303 map<uint256, CTxIndex> pool; 303 map<uint256, CTxIndex> pool;
304 vector<char> vfAlreadyAdded(mapTransactions.size()); 304 vector<char> vfAlreadyAdded(mapTransactions.size());
305 bool fFoundSomething = true; 305 bool fFoundSomething = true;
306 unsigned int sz = 0; 306 // The cap is MAX_BLOCK_SIZE_GEN, not MAX_SIZE/2. MAX_SIZE is
307 // The cap is MAX_BLOCK_SIZE/2, not MAX_SIZE/2. MAX_SIZE is the 307 // the 32 MB serialisation ceiling, so half of it is 16 MB --
308 // 32 MB serialisation ceiling, so half of it is 16 MB -- this 308 // this builder would happily assemble a template that its own
309 // builder would happily assemble a template that its own
310 // CheckBlock() rejects at the 1 MB consensus cap. This is the 309 // CheckBlock() rejects at the 1 MB consensus cap. This is the
311 // pool's job builder, so a block over the cap is not one 310 // pool's job builder, so a block over the cap is not one
312 // wasted solve, it is every miner on the pool working a 311 // wasted solve, it is every miner on the pool working a
313 // template the network will reject. The inner break bounds the 312 // template the network will reject.
314 // overshoot to a single transaction within a pass. 313 //
315 while (fFoundSomething && sz < MAX_BLOCK_SIZE / 2) 314 // The inner break used to bound the overshoot to one
315 // transaction per pass. One transaction is still too many:
316 // nothing caps how big a single transaction may be, so that
317 // bound was worth up to MAX_SIZE. Asking whether each one fits
318 // before appending it bounds the overshoot at zero instead.
319 //
320 // Seeded with the header, tx count and coinbase, because
321 // CheckBlock() weighs the whole serialized block.
322 unsigned int sz = ::GetSerializeSize(block, SER_DISK);
323 // And the sigop budget, which CheckBlock() enforces and this
324 // builder never counted against. Same counting function
325 // CheckBlock() sums, seeded from the coinbase because it sums
326 // over all of vtx.
327 unsigned int nSigOps = block.vtx[0].GetSigOpCount();
328 while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN)
316 { 329 {
317 fFoundSomething = false; 330 fFoundSomething = false;
318 unsigned int n = 0; 331 unsigned int n = 0;
@@ -323,16 +336,26 @@ static bool RebuildJob()
323 continue; 336 continue;
324 CTransaction& tx = (*mi).second; 337 CTransaction& tx = (*mi).second;
325 if (tx.IsCoinBase() || !tx.IsFinal()) continue; 338 if (tx.IsCoinBase() || !tx.IsFinal()) continue;
339 // Skip rather than stop: one transaction too big for
340 // the room left does not mean the next is. Ordered
341 // before ConnectInputs because ConnectInputs credits
342 // nFees by reference on success, and a transaction we
343 // then decline would leave the coinbase claiming a fee
344 // this block does not collect.
345 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
346 unsigned int nTxSigOps = tx.GetSigOpCount();
347 if (!BlockTemplateFits(sz, nSigOps, nTxSize, nTxSigOps))
348 continue;
326 map<uint256, CTxIndex> tmp(pool); 349 map<uint256, CTxIndex> tmp(pool);
327 if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, 350 if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees,
328 false, true, tx.GetMinFee(block.vtx.size() < 100))) 351 false, true, tx.GetMinFee(block.vtx.size() < 100)))
329 continue; 352 continue;
330 swap(pool, tmp); 353 swap(pool, tmp);
331 block.vtx.push_back(tx); 354 block.vtx.push_back(tx);
332 sz += ::GetSerializeSize(tx, SER_NETWORK); 355 sz += nTxSize;
356 nSigOps += nTxSigOps;
333 vfAlreadyAdded[n] = true; 357 vfAlreadyAdded[n] = true;
334 fFoundSomething = true; 358 fFoundSomething = true;
335 if (sz > MAX_BLOCK_SIZE / 2) break;
336 } 359 }
337 } 360 }
338 } 361 }
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)
1235 return block; 1235 return block;
1236} 1236}
1237 1237
1238// A distinct transaction of a chosen weight and verification cost. nUnique
1239// only keeps the txids apart, so a mempool built from these is not a pile of
1240// duplicates a block could never carry anyway.
1241static CTransaction MakeSizedTx(size_t nPadBytes, unsigned int nSigOps, int nUnique)
1242{
1243 CTransaction tx;
1244 tx.vin.push_back(CTxIn(COutPoint(), CScript() << (int64)nUnique << 1));
1245 tx.vout.push_back(CTxOut(0, CScript()));
1246 for (unsigned int i = 0; i < nSigOps; i++)
1247 tx.vout[0].scriptPubKey << OP_CHECKSIG;
1248 tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nPadBytes, 0);
1249 return tx;
1250}
1251
1252static CBlock MakeCoinbaseOnlyTemplate()
1253{
1254 CTransaction txNew;
1255 txNew.vin.resize(1);
1256 txNew.vin[0].prevout.SetNull();
1257 txNew.vin[0].scriptSig << 1 << 1 << 1;
1258 txNew.vout.resize(1);
1259 txNew.vout[0].scriptPubKey << OP_CHECKSIG;
1260
1261 CBlock block;
1262 block.vtx.push_back(txNew);
1263 block.nVersion = 1;
1264 block.nTime = GetAdjustedTime();
1265 block.nBits = bnProofOfWorkLimit.GetCompact();
1266 block.nNonce = 0;
1267 return block;
1268}
1269
1270// The sum CheckBlock() takes, spelled the same way.
1271static unsigned int BlockSigOpCount(const CBlock& block)
1272{
1273 unsigned int n = 0;
1274 for (int i = 0; i < (int)block.vtx.size(); i++)
1275 n += block.vtx[i].GetSigOpCount();
1276 return n;
1277}
1278
1279// Drive the selection rule the way both assemblers drive it: ask
1280// BlockTemplateFits before appending, skip what does not fit, never stop early
1281// on one that does not.
1282//
1283// This mirrors the loops in BitcoinMiner() and RebuildJob() rather than calling
1284// them -- one needs a CTxDB, a wallet key and cs_main, the other is static in
1285// rpc.cpp, so neither is reachable from here. What that leaves proven is the
1286// rule the two of them share and the property it is meant to guarantee, which
1287// is where the defect actually lived: the budget was right and the enforcement
1288// was not.
1289static void SelectIntoTemplate(CBlock& block, const vector<CTransaction>& vMempool)
1290{
1291 unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK);
1292 unsigned int nBlockSigOps = block.vtx[0].GetSigOpCount();
1293 for (int i = 0; i < (int)vMempool.size(); i++)
1294 {
1295 const CTransaction& tx = vMempool[i];
1296 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
1297 unsigned int nTxSigOps = tx.GetSigOpCount();
1298 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
1299 continue;
1300 block.vtx.push_back(tx);
1301 nBlockSize += nTxSize;
1302 nBlockSigOps += nTxSigOps;
1303 }
1304}
1305
1238static int RunConsensusLimitsSelfTest() 1306static int RunConsensusLimitsSelfTest()
1239{ 1307{
1240 fflush(stdout); 1308 fflush(stdout);
@@ -1258,6 +1326,95 @@ static int RunConsensusLimitsSelfTest()
1258 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; 1326 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1;
1259 nFail += Check(!oversized.CheckSizeLimits(), 1327 nFail += Check(!oversized.CheckSizeLimits(),
1260 "a block above 1 MB is outside the consensus size limit") ? 0 : 1; 1328 "a block above 1 MB is outside the consensus size limit") ? 0 : 1;
1329
1330 // The other half of the cap. Everything above asks what validation
1331 // refuses; nothing asked what assembly produces, which is why a miner
1332 // that could overshoot the cap by an entire mempool went unnoticed.
1333 nFail += Check(MAX_BLOCK_SIZE_GEN < MAX_BLOCK_SIZE,
1334 "the assembly budget stays under the consensus cap") ? 0 : 1;
1335
1336 // The rule itself, at its edges. The sigop comparison has to match
1337 // CheckBlock(), which fails on `> MAX_BLOCK_SIGOPS` and so accepts a
1338 // block sitting exactly on the limit.
1339 nFail += Check(BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 100, 0),
1340 "a transaction that exactly fills the size budget fits") ? 0 : 1;
1341 nFail += Check(!BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 101, 0),
1342 "one byte past the size budget does not fit") ? 0 : 1;
1343 nFail += Check(BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS),
1344 "sigops exactly on the limit fit, as CheckBlock() allows") ? 0 : 1;
1345 nFail += Check(!BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS + 1),
1346 "one sigop past the limit does not fit") ? 0 : 1;
1347
1348 // A mempool several times the consensus cap, in ordinary-sized
1349 // transactions. The old miner tested its budget only on the outer
1350 // loop, so a single pass appended all of this.
1351 {
1352 CBlock block = MakeCoinbaseOnlyTemplate();
1353 vector<CTransaction> vMempool;
1354 for (int i = 0; i < 300; i++)
1355 vMempool.push_back(MakeSizedTx(10000, 1, i));
1356 SelectIntoTemplate(block, vMempool);
1357 block.hashMerkleRoot = block.BuildMerkleTree();
1358
1359 nFail += Check(block.vtx.size() > 1,
1360 "a full mempool still fills a template") ? 0 : 1;
1361 nFail += Check(::GetSerializeSize(block, SER_DISK) <= MAX_BLOCK_SIZE_GEN,
1362 "a template built from a 3 MB mempool respects the budget") ? 0 : 1;
1363 nFail += Check(block.CheckSizeLimits(),
1364 "that template is one CheckBlock() would accept on size") ? 0 : 1;
1365 }
1366
1367 // One transaction too big for the budget. Nothing on the path into the
1368 // mempool caps transaction size, so this is a transaction a peer can
1369 // simply send. It must be skipped and the rest still taken -- skipped,
1370 // not stopped on.
1371 {
1372 CBlock block = MakeCoinbaseOnlyTemplate();
1373 vector<CTransaction> vMempool;
1374 vMempool.push_back(MakeSizedTx(1500000, 1, 9999));
1375 for (int i = 0; i < 10; i++)
1376 vMempool.push_back(MakeSizedTx(1000, 1, i));
1377
1378 unsigned int nHugeSize = ::GetSerializeSize(vMempool[0], SER_NETWORK);
1379 nFail += Check(nHugeSize > MAX_BLOCK_SIZE && nHugeSize <= MAX_SIZE,
1380 "the outsized transaction is one the mempool would hold") ? 0 : 1;
1381
1382 SelectIntoTemplate(block, vMempool);
1383 block.hashMerkleRoot = block.BuildMerkleTree();
1384
1385 bool fTookHuge = false;
1386 for (int i = 0; i < (int)block.vtx.size(); i++)
1387 if (block.vtx[i].GetHash() == vMempool[0].GetHash())
1388 fTookHuge = true;
1389
1390 nFail += Check(!fTookHuge,
1391 "a transaction larger than the budget is left out") ? 0 : 1;
1392 nFail += Check(block.vtx.size() == 11,
1393 "the ten that do fit are taken anyway") ? 0 : 1;
1394 nFail += Check(block.CheckSizeLimits(),
1395 "one outsized transaction cannot invalidate a template") ? 0 : 1;
1396 }
1397
1398 // Transactions that are cheap in bytes and expensive to verify. Size
1399 // alone does not bound what a block costs, and CheckBlock() refuses on
1400 // the count regardless of how small the block is.
1401 {
1402 CBlock block = MakeCoinbaseOnlyTemplate();
1403 vector<CTransaction> vMempool;
1404 for (int i = 0; i < 100; i++)
1405 vMempool.push_back(MakeSizedTx(0, 1000, i));
1406 SelectIntoTemplate(block, vMempool);
1407 block.hashMerkleRoot = block.BuildMerkleTree();
1408
1409 nFail += Check(block.vtx.size() > 1,
1410 "sigop-heavy transactions are still selected") ? 0 : 1;
1411 nFail += Check(BlockSigOpCount(block) <= MAX_BLOCK_SIGOPS,
1412 "selection stops at the sigop cap CheckBlock() enforces") ? 0 : 1;
1413 nFail += Check(::GetSerializeSize(block, SER_DISK) < MAX_BLOCK_SIZE_GEN,
1414 "and stops on sigops while still well inside the size budget") ? 0 : 1;
1415 nFail += Check(block.CheckSizeLimits(),
1416 "the sigop-bounded template is within the size limit too") ? 0 : 1;
1417 }
1261 } 1418 }
1262 catch (const std::exception& e) 1419 catch (const std::exception& e)
1263 { 1420 {