diff options
Diffstat (limited to 'src/selftest.cpp')
| -rw-r--r-- | src/selftest.cpp | 157 |
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. | ||
| 1241 | static 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 | |||
| 1252 | static 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. | ||
| 1271 | static 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. | ||
| 1289 | static 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 | |||
| 1238 | static int RunConsensusLimitsSelfTest() | 1306 | static 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 | { |