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 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. | ||
| 3141 | static 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 | |||
| 3152 | static 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. | ||
| 3171 | static 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. | ||
| 3189 | static 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 | |||
| 3138 | static int RunConsensusLimitsSelfTest() | 3206 | static 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 | { |