upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/selftest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/selftest.cpp')
-rw-r--r--src/selftest.cpp157
1 files changed, 157 insertions, 0 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
index c62d70f..65f215b 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -3135,6 +3135,74 @@ static CBlock MakeSizedConsensusBlock(size_t nScriptBytes)
3135 return block; 3135 return block;
3136} 3136}
3137 3137
3138// A distinct transaction of a chosen weight and verification cost. nUnique
3139// only keeps the txids apart, so a mempool built from these is not a pile of
3140// duplicates a block could never carry anyway.
3141static CTransaction MakeSizedTx(size_t nPadBytes, unsigned int nSigOps, int nUnique)
3142{
3143 CTransaction tx;
3144 tx.vin.push_back(CTxIn(COutPoint(), CScript() << (int64)nUnique << 1));
3145 tx.vout.push_back(CTxOut(0, CScript()));
3146 for (unsigned int i = 0; i < nSigOps; i++)
3147 tx.vout[0].scriptPubKey << OP_CHECKSIG;
3148 tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nPadBytes, 0);
3149 return tx;
3150}
3151
3152static CBlock MakeCoinbaseOnlyTemplate()
3153{
3154 CTransaction txNew;
3155 txNew.vin.resize(1);
3156 txNew.vin[0].prevout.SetNull();
3157 txNew.vin[0].scriptSig << 1 << 1 << 1;
3158 txNew.vout.resize(1);
3159 txNew.vout[0].scriptPubKey << OP_CHECKSIG;
3160
3161 CBlock block;
3162 block.vtx.push_back(txNew);
3163 block.nVersion = 1;
3164 block.nTime = GetAdjustedTime();
3165 block.nBits = bnProofOfWorkLimit.GetCompact();
3166 block.nNonce = 0;
3167 return block;
3168}
3169
3170// The sum CheckBlock() takes, spelled the same way.
3171static unsigned int BlockSigOpCount(const CBlock& block)
3172{
3173 unsigned int n = 0;
3174 for (int i = 0; i < (int)block.vtx.size(); i++)
3175 n += block.vtx[i].GetSigOpCount();
3176 return n;
3177}
3178
3179// Drive the selection rule the way both assemblers drive it: ask
3180// BlockTemplateFits before appending, skip what does not fit, never stop early
3181// on one that does not.
3182//
3183// This mirrors the loops in BitcoinMiner() and RebuildJob() rather than calling
3184// them -- one needs a CTxDB, a wallet key and cs_main, the other is static in
3185// rpc.cpp, so neither is reachable from here. What that leaves proven is the
3186// rule the two of them share and the property it is meant to guarantee, which
3187// is where the defect actually lived: the budget was right and the enforcement
3188// was not.
3189static void SelectIntoTemplate(CBlock& block, const vector<CTransaction>& vMempool)
3190{
3191 unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK);
3192 unsigned int nBlockSigOps = block.vtx[0].GetSigOpCount();
3193 for (int i = 0; i < (int)vMempool.size(); i++)
3194 {
3195 const CTransaction& tx = vMempool[i];
3196 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
3197 unsigned int nTxSigOps = tx.GetSigOpCount();
3198 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
3199 continue;
3200 block.vtx.push_back(tx);
3201 nBlockSize += nTxSize;
3202 nBlockSigOps += nTxSigOps;
3203 }
3204}
3205
3138static int RunConsensusLimitsSelfTest() 3206static int RunConsensusLimitsSelfTest()
3139{ 3207{
3140 fflush(stdout); 3208 fflush(stdout);
@@ -3158,6 +3226,95 @@ static int RunConsensusLimitsSelfTest()
3158 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; 3226 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1;
3159 nFail += Check(!oversized.CheckSizeLimits(), 3227 nFail += Check(!oversized.CheckSizeLimits(),
3160 "a block above 1 MB is outside the consensus size limit") ? 0 : 1; 3228 "a block above 1 MB is outside the consensus size limit") ? 0 : 1;
3229
3230 // The other half of the cap. Everything above asks what validation
3231 // refuses; nothing asked what assembly produces, which is why a miner
3232 // that could overshoot the cap by an entire mempool went unnoticed.
3233 nFail += Check(MAX_BLOCK_SIZE_GEN < MAX_BLOCK_SIZE,
3234 "the assembly budget stays under the consensus cap") ? 0 : 1;
3235
3236 // The rule itself, at its edges. The sigop comparison has to match
3237 // CheckBlock(), which fails on `> MAX_BLOCK_SIGOPS` and so accepts a
3238 // block sitting exactly on the limit.
3239 nFail += Check(BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 100, 0),
3240 "a transaction that exactly fills the size budget fits") ? 0 : 1;
3241 nFail += Check(!BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 101, 0),
3242 "one byte past the size budget does not fit") ? 0 : 1;
3243 nFail += Check(BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS),
3244 "sigops exactly on the limit fit, as CheckBlock() allows") ? 0 : 1;
3245 nFail += Check(!BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS + 1),
3246 "one sigop past the limit does not fit") ? 0 : 1;
3247
3248 // A mempool several times the consensus cap, in ordinary-sized
3249 // transactions. The old miner tested its budget only on the outer
3250 // loop, so a single pass appended all of this.
3251 {
3252 CBlock block = MakeCoinbaseOnlyTemplate();
3253 vector<CTransaction> vMempool;
3254 for (int i = 0; i < 300; i++)
3255 vMempool.push_back(MakeSizedTx(10000, 1, i));
3256 SelectIntoTemplate(block, vMempool);
3257 block.hashMerkleRoot = block.BuildMerkleTree();
3258
3259 nFail += Check(block.vtx.size() > 1,
3260 "a full mempool still fills a template") ? 0 : 1;
3261 nFail += Check(::GetSerializeSize(block, SER_DISK) <= MAX_BLOCK_SIZE_GEN,
3262 "a template built from a 3 MB mempool respects the budget") ? 0 : 1;
3263 nFail += Check(block.CheckSizeLimits(),
3264 "that template is one CheckBlock() would accept on size") ? 0 : 1;
3265 }
3266
3267 // One transaction too big for the budget. Nothing on the path into the
3268 // mempool caps transaction size, so this is a transaction a peer can
3269 // simply send. It must be skipped and the rest still taken -- skipped,
3270 // not stopped on.
3271 {
3272 CBlock block = MakeCoinbaseOnlyTemplate();
3273 vector<CTransaction> vMempool;
3274 vMempool.push_back(MakeSizedTx(1500000, 1, 9999));
3275 for (int i = 0; i < 10; i++)
3276 vMempool.push_back(MakeSizedTx(1000, 1, i));
3277
3278 unsigned int nHugeSize = ::GetSerializeSize(vMempool[0], SER_NETWORK);
3279 nFail += Check(nHugeSize > MAX_BLOCK_SIZE && nHugeSize <= MAX_SIZE,
3280 "the outsized transaction is one the mempool would hold") ? 0 : 1;
3281
3282 SelectIntoTemplate(block, vMempool);
3283 block.hashMerkleRoot = block.BuildMerkleTree();
3284
3285 bool fTookHuge = false;
3286 for (int i = 0; i < (int)block.vtx.size(); i++)
3287 if (block.vtx[i].GetHash() == vMempool[0].GetHash())
3288 fTookHuge = true;
3289
3290 nFail += Check(!fTookHuge,
3291 "a transaction larger than the budget is left out") ? 0 : 1;
3292 nFail += Check(block.vtx.size() == 11,
3293 "the ten that do fit are taken anyway") ? 0 : 1;
3294 nFail += Check(block.CheckSizeLimits(),
3295 "one outsized transaction cannot invalidate a template") ? 0 : 1;
3296 }
3297
3298 // Transactions that are cheap in bytes and expensive to verify. Size
3299 // alone does not bound what a block costs, and CheckBlock() refuses on
3300 // the count regardless of how small the block is.
3301 {
3302 CBlock block = MakeCoinbaseOnlyTemplate();
3303 vector<CTransaction> vMempool;
3304 for (int i = 0; i < 100; i++)
3305 vMempool.push_back(MakeSizedTx(0, 1000, i));
3306 SelectIntoTemplate(block, vMempool);
3307 block.hashMerkleRoot = block.BuildMerkleTree();
3308
3309 nFail += Check(block.vtx.size() > 1,
3310 "sigop-heavy transactions are still selected") ? 0 : 1;
3311 nFail += Check(BlockSigOpCount(block) <= MAX_BLOCK_SIGOPS,
3312 "selection stops at the sigop cap CheckBlock() enforces") ? 0 : 1;
3313 nFail += Check(::GetSerializeSize(block, SER_DISK) < MAX_BLOCK_SIZE_GEN,
3314 "and stops on sigops while still well inside the size budget") ? 0 : 1;
3315 nFail += Check(block.CheckSizeLimits(),
3316 "the sigop-bounded template is within the size limit too") ? 0 : 1;
3317 }
3161 } 3318 }
3162 catch (const std::exception& e) 3319 catch (const std::exception& e)
3163 { 3320 {