upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/rpc.cpp
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 /src/rpc.cpp
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.
Diffstat (limited to 'src/rpc.cpp')
-rw-r--r--src/rpc.cpp34
1 files changed, 25 insertions, 9 deletions
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 }