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:14:48 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 14:14:48 +0000
commit74c068829967bbff60da3506613844df4da1ae80 (patch)
tree358400d4a170a5777138bd44d048a4cd7f4aaf6b
parente4f898c4a6b9900553c64b9804889b5fdae3e55f (diff)
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.
-rw-r--r--src/main.cpp41
-rw-r--r--src/main.h20
-rw-r--r--src/rpc.cpp34
3 files changed, 81 insertions, 14 deletions
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)
4470 map<uint256, CTxIndex> mapTestPool; 4470 map<uint256, CTxIndex> mapTestPool;
4471 vector<char> vfAlreadyAdded(mapTransactions.size()); 4471 vector<char> vfAlreadyAdded(mapTransactions.size());
4472 bool fFoundSomething = true; 4472 bool fFoundSomething = true;
4473 unsigned int nBlockSize = 0;
4474 // Assembly must stop below the consensus cap that CheckBlock() 4473 // Assembly must stop below the consensus cap that CheckBlock()
4475 // enforces, or we mine a block our own node then rejects. 4474 // enforces, or we mine a block our own node then rejects.
4476 // 4475 //
@@ -4481,9 +4480,19 @@ bool BitcoinMiner(int nThreadId)
4481 // meant solving a block and throwing it away -- with this pool at 4480 // meant solving a block and throwing it away -- with this pool at
4482 // ~90% of hashrate, that is the network stalling, not just us. 4481 // ~90% of hashrate, that is the network stalling, not just us.
4483 // 4482 //
4484 // Half the cap, keeping the original margin: the test runs before 4483 // The budget test then sat only on this outer while, and the
4485 // a transaction is added, so the loop can overshoot by one tx. 4484 // comment claimed that bounded the overshoot to a single
4486 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE/2) 4485 // transaction. It did not. The inner for walks all of
4486 // mapTransactions with no test of its own, so one pass could append
4487 // the entire mempool: the overshoot was bounded by mempool size,
4488 // which is to say not bounded at all. Ask per transaction, before
4489 // appending, and the overshoot is zero rather than small.
4490 //
4491 // Seeded with what the block already weighs -- header, tx count,
4492 // coinbase -- because CheckBlock() weighs the whole serialized
4493 // block and not just the transactions we chose to add to it.
4494 unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK);
4495 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN)
4487 { 4496 {
4488 fFoundSomething = false; 4497 fFoundSomething = false;
4489 unsigned int n = 0; 4498 unsigned int n = 0;
@@ -4495,6 +4504,28 @@ bool BitcoinMiner(int nThreadId)
4495 if (tx.IsCoinBase() || !tx.IsFinal()) 4504 if (tx.IsCoinBase() || !tx.IsFinal())
4496 continue; 4505 continue;
4497 4506
4507 // Skip, not break: a transaction too big for the room left
4508 // says nothing about the next one, which may still fit.
4509 //
4510 // This is also the only ceiling on a single transaction's
4511 // size anywhere on the path into a block. CheckTransaction
4512 // does not cap it and neither does AcceptTransaction, so
4513 // the mempool will hold one of any size the wire allows;
4514 // without this test a lone 1.5 MB transaction offering a
4515 // fat fee is selected, and every template built from that
4516 // mempool is one CheckBlock() throws away. The fee is never
4517 // paid, because the block never confirms.
4518 //
4519 // Deliberately before ConnectInputs and not after:
4520 // ConnectInputs adds this transaction's fee to nFees by
4521 // reference on success. Passing it and then declining the
4522 // transaction would credit the coinbase for a fee no
4523 // transaction in the block pays, and the block would be
4524 // rejected for a wholly different reason.
4525 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
4526 if (!BlockTemplateFits(nBlockSize, nTxSize))
4527 continue;
4528
4498 // Transaction fee requirements, mainly only needed for flood control 4529 // Transaction fee requirements, mainly only needed for flood control
4499 // Under 10K (about 80 inputs) is free for first 100 transactions 4530 // Under 10K (about 80 inputs) is free for first 100 transactions
4500 // Base rate is 0.01 per KB 4531 // Base rate is 0.01 per KB
@@ -4506,7 +4537,7 @@ bool BitcoinMiner(int nThreadId)
4506 swap(mapTestPool, mapTestPoolTmp); 4537 swap(mapTestPool, mapTestPoolTmp);
4507 4538
4508 pblock->vtx.push_back(tx); 4539 pblock->vtx.push_back(tx);
4509 nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); 4540 nBlockSize += nTxSize;
4510 vfAlreadyAdded[n] = true; 4541 vfAlreadyAdded[n] = true;
4511 fFoundSomething = true; 4542 fFoundSomething = true;
4512 } 4543 }
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;
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?
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.
52inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nTxSize)
53{
54 return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN);
55}
36static const int64 COIN = 100000000; 56static const int64 COIN = 100000000;
37// Total Bitflash emission: identical to Bitcoin (21 million). 57// Total Bitflash emission: identical to Bitcoin (21 million).
38// MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in 58// 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()
267 map<uint256, CTxIndex> pool; 267 map<uint256, CTxIndex> pool;
268 vector<char> vfAlreadyAdded(mapTransactions.size()); 268 vector<char> vfAlreadyAdded(mapTransactions.size());
269 bool fFoundSomething = true; 269 bool fFoundSomething = true;
270 unsigned int sz = 0; 270 // The cap is MAX_BLOCK_SIZE_GEN, not MAX_SIZE/2. MAX_SIZE is
271 // The cap is MAX_BLOCK_SIZE/2, not MAX_SIZE/2. MAX_SIZE is the 271 // the 32 MB serialisation ceiling, so half of it is 16 MB --
272 // 32 MB serialisation ceiling, so half of it is 16 MB -- this 272 // this builder would happily assemble a template that its own
273 // builder would happily assemble a template that its own
274 // CheckBlock() rejects at the 1 MB consensus cap. This is the 273 // CheckBlock() rejects at the 1 MB consensus cap. This is the
275 // pool's job builder, so a block over the cap is not one 274 // pool's job builder, so a block over the cap is not one
276 // wasted solve, it is every miner on the pool working a 275 // wasted solve, it is every miner on the pool working a
277 // template the network will reject. The inner break bounds the 276 // template the network will reject.
278 // overshoot to a single transaction within a pass. 277 //
279 while (fFoundSomething && sz < MAX_BLOCK_SIZE / 2) 278 // The inner break used to bound the overshoot to one
279 // transaction per pass. One transaction is still too many:
280 // nothing caps how big a single transaction may be, so that
281 // bound was worth up to MAX_SIZE. Asking whether each one fits
282 // before appending it bounds the overshoot at zero instead.
283 //
284 // Seeded with the header, tx count and coinbase, because
285 // CheckBlock() weighs the whole serialized block.
286 unsigned int sz = ::GetSerializeSize(block, SER_DISK);
287 while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN)
280 { 288 {
281 fFoundSomething = false; 289 fFoundSomething = false;
282 unsigned int n = 0; 290 unsigned int n = 0;
@@ -287,16 +295,24 @@ static bool RebuildJob()
287 continue; 295 continue;
288 CTransaction& tx = (*mi).second; 296 CTransaction& tx = (*mi).second;
289 if (tx.IsCoinBase() || !tx.IsFinal()) continue; 297 if (tx.IsCoinBase() || !tx.IsFinal()) continue;
298 // Skip rather than stop: one transaction too big for
299 // the room left does not mean the next is. Ordered
300 // before ConnectInputs because ConnectInputs credits
301 // nFees by reference on success, and a transaction we
302 // then decline would leave the coinbase claiming a fee
303 // this block does not collect.
304 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
305 if (!BlockTemplateFits(sz, nTxSize))
306 continue;
290 map<uint256, CTxIndex> tmp(pool); 307 map<uint256, CTxIndex> tmp(pool);
291 if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, 308 if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees,
292 false, true, tx.GetMinFee(block.vtx.size() < 100))) 309 false, true, tx.GetMinFee(block.vtx.size() < 100)))
293 continue; 310 continue;
294 swap(pool, tmp); 311 swap(pool, tmp);
295 block.vtx.push_back(tx); 312 block.vtx.push_back(tx);
296 sz += ::GetSerializeSize(tx, SER_NETWORK); 313 sz += nTxSize;
297 vfAlreadyAdded[n] = true; 314 vfAlreadyAdded[n] = true;
298 fFoundSomething = true; 315 fFoundSomething = true;
299 if (sz > MAX_BLOCK_SIZE / 2) break;
300 } 316 }
301 } 317 }
302 } 318 }