upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/selftest.cpp157
1 files changed, 157 insertions, 0 deletions
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 {