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.cpp225
1 files changed, 213 insertions, 12 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 1a72e3a..357d532 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -19,6 +19,7 @@
19#include "wallet_sqlite.h" 19#include "wallet_sqlite.h"
20 20
21extern int RunPoolStratumSelfTest(); 21extern int RunPoolStratumSelfTest();
22extern int RunPayoutLedgerSelfTest();
22 23
23// Test results go to the terminal, not to debug.log. 24// Test results go to the terminal, not to debug.log.
24// 25//
@@ -2579,7 +2580,11 @@ static int RunWalletBackendDefaultSelfTest()
2579 2580
2580 try 2581 try
2581 { 2582 {
2582 // 1. Fresh datadir, no flag -> new wallet.sqlite, marker "sqlite", no wallet.dat. 2583 // 1. Fresh datadir, no flag -> this fork refuses, where upstream creates.
2584 // An empty datadir on a headless node is far more often a mount or
2585 // backup fault than a first run, and creating a wallet also pins the
2586 // backend marker, after which restoring the real wallet is a no-op.
2587 // Nothing may be left behind: no wallet of either kind, no marker.
2583 string d1 = tmp + "/fresh"; 2588 string d1 = tmp + "/fresh";
2584 nFail += Check(MakeDirLocal(d1), "fresh scenario dir can be created") ? 0 : 1; 2589 nFail += Check(MakeDirLocal(d1), "fresh scenario dir can be created") ? 0 : 1;
2585 vector<string> a1; 2590 vector<string> a1;
@@ -2587,19 +2592,31 @@ static int RunWalletBackendDefaultSelfTest()
2587 a1.push_back("-nomanagedtor"); 2592 a1.push_back("-nomanagedtor");
2588 a1.push_back("-nogui"); 2593 a1.push_back("-nogui");
2589 a1.push_back("-newaddress"); 2594 a1.push_back("-newaddress");
2590 int r1 = RunBitflashChild(strExe, a1); 2595 int r0 = RunBitflashChild(strExe, a1);
2596 nFail += Check(r0 != 0 &&
2597 !FileExists((d1 + "/wallet.sqlite").c_str()) &&
2598 !FileExists((d1 + "/wallet.dat").c_str()) &&
2599 !FileExists((d1 + "/wallet-backend").c_str()),
2600 "a flagless headless start refuses to create a wallet") ? 0 : 1;
2601
2602 // 1a. -createwallet says it was meant: new wallet.sqlite, marker
2603 // "sqlite", no wallet.dat.
2604 vector<string> a1c = a1;
2605 a1c.push_back("-createwallet");
2606 int r1 = RunBitflashChild(strExe, a1c);
2591 nFail += Check(r1 == 0 && 2607 nFail += Check(r1 == 0 &&
2592 FileExists((d1 + "/wallet.sqlite").c_str()) && 2608 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2593 !FileExists((d1 + "/wallet.dat").c_str()) && 2609 !FileExists((d1 + "/wallet.dat").c_str()) &&
2594 FileContainsText(d1 + "/wallet-backend", "sqlite"), 2610 FileContainsText(d1 + "/wallet-backend", "sqlite"),
2595 "a fresh install starts on SQLite (new default) and records the marker") ? 0 : 1; 2611 "-createwallet starts a fresh install on SQLite and records the marker") ? 0 : 1;
2596 2612
2597 // 1b. Second run reuses it, still SQLite, still no wallet.dat. 2613 // 1b. Second run reuses it without the flag -- the refusal only guards
2614 // creation, so an existing wallet still starts flagless.
2598 int r1b = RunBitflashChild(strExe, a1); 2615 int r1b = RunBitflashChild(strExe, a1);
2599 nFail += Check(r1b == 0 && 2616 nFail += Check(r1b == 0 &&
2600 FileExists((d1 + "/wallet.sqlite").c_str()) && 2617 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2601 !FileExists((d1 + "/wallet.dat").c_str()), 2618 !FileExists((d1 + "/wallet.dat").c_str()),
2602 "the recorded SQLite choice survives a restart") ? 0 : 1; 2619 "the recorded SQLite choice survives a flagless restart") ? 0 : 1;
2603 2620
2604 // 2. Existing wallet.dat, no flag -> stays on Berkeley DB. 2621 // 2. Existing wallet.dat, no flag -> stays on Berkeley DB.
2605 string d2 = tmp + "/existing-bdb"; 2622 string d2 = tmp + "/existing-bdb";
@@ -3023,6 +3040,13 @@ static int RunNetMessageSelfTest()
3023 int nFail = 0; 3040 int nFail = 0;
3024 try 3041 try
3025 { 3042 {
3043 // A peer that has not yet been kept waiting must not look like one that
3044 // has been waiting since the epoch. This is the whole bug: the stall
3045 // clock lives in CNode, so only the constructor can start it at zero.
3046 CNode opened(INVALID_SOCKET, CAddress("127.0.0.1"));
3047 nFail += Check(opened.nMsgWaitSince == 0 && opened.nMsgWaitSize == 0,
3048 "a new peer starts with its stall clock stopped") ? 0 : 1;
3049
3026 CNode complete(INVALID_SOCKET, CAddress("127.0.0.1")); 3050 CNode complete(INVALID_SOCKET, CAddress("127.0.0.1"));
3027 complete.nVersion = VERSION; 3051 complete.nVersion = VERSION;
3028 complete.vRecv << CMessageHeader("ping", 0); 3052 complete.vRecv << CMessageHeader("ping", 0);
@@ -3037,20 +3061,38 @@ static int RunNetMessageSelfTest()
3037 nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1; 3061 nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1;
3038 nFail += Check(partial.vRecv.size() == nPartialBefore, 3062 nFail += Check(partial.vRecv.size() == nPartialBefore,
3039 "an incomplete message keeps one header in the buffer") ? 0 : 1; 3063 "an incomplete message keeps one header in the buffer") ? 0 : 1;
3040 nFail += Check(partial.nIncompleteMessageStart != 0, 3064 nFail += Check(partial.nMsgWaitSince != 0,
3041 "an incomplete message starts a timeout clock") ? 0 : 1; 3065 "an incomplete message starts the stall clock") ? 0 : 1;
3066 nFail += Check(partial.nMsgWaitSize == 0,
3067 "the stall clock records how much payload had arrived") ? 0 : 1;
3042 nFail += Check(!partial.fDisconnect, 3068 nFail += Check(!partial.fDisconnect,
3043 "a fresh incomplete message does not disconnect immediately") ? 0 : 1; 3069 "a fresh incomplete message does not disconnect immediately") ? 0 : 1;
3044 3070
3071 // Still arriving, just slowly: more payload is here than when the clock
3072 // was started, so the clock restarts however old it was. Timing the
3073 // wait instead of the stall would drop this peer.
3074 CNode slow(INVALID_SOCKET, CAddress("127.0.0.1"));
3075 slow.nVersion = VERSION;
3076 slow.vRecv << CMessageHeader("block", 100);
3077 slow.vRecv.insert(slow.vRecv.end(), 50, '\0');
3078 slow.nMsgWaitSince = GetTime() - BTF_MSG_WAIT_MAX_SECS - 1;
3079 slow.nMsgWaitSize = 10;
3080 ProcessMessages(&slow);
3081 nFail += Check(!slow.fDisconnect,
3082 "a peer still sending payload is not dropped") ? 0 : 1;
3083 nFail += Check(slow.nMsgWaitSize == 50,
3084 "progress restarts the stall clock") ? 0 : 1;
3085
3086 // Nothing more has arrived since the clock started, and the clock is
3087 // older than the limit.
3045 CNode stale(INVALID_SOCKET, CAddress("127.0.0.1")); 3088 CNode stale(INVALID_SOCKET, CAddress("127.0.0.1"));
3046 stale.nVersion = VERSION; 3089 stale.nVersion = VERSION;
3047 stale.vRecv << CMessageHeader("block", 100); 3090 stale.vRecv << CMessageHeader("block", 100);
3048 stale.nIncompleteMessageStart = GetTime() - BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS - 1; 3091 stale.nMsgWaitSince = GetTime() - BTF_MSG_WAIT_MAX_SECS - 1;
3049 stale.nIncompleteMessageSize = 100; 3092 stale.nMsgWaitSize = 0;
3050 stale.strIncompleteMessageCommand = "block";
3051 ProcessMessages(&stale); 3093 ProcessMessages(&stale);
3052 nFail += Check(stale.fDisconnect, 3094 nFail += Check(stale.fDisconnect,
3053 "a stale incomplete message disconnects the peer") ? 0 : 1; 3095 "a stalled incomplete message disconnects the peer") ? 0 : 1;
3054 3096
3055 CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1")); 3097 CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1"));
3056 oversized.nVersion = VERSION; 3098 oversized.nVersion = VERSION;
@@ -3174,6 +3216,74 @@ static CBlock MakeSizedConsensusBlock(size_t nScriptBytes)
3174 return block; 3216 return block;
3175} 3217}
3176 3218
3219// A distinct transaction of a chosen weight and verification cost. nUnique
3220// only keeps the txids apart, so a mempool built from these is not a pile of
3221// duplicates a block could never carry anyway.
3222static CTransaction MakeSizedTx(size_t nPadBytes, unsigned int nSigOps, int nUnique)
3223{
3224 CTransaction tx;
3225 tx.vin.push_back(CTxIn(COutPoint(), CScript() << (int64)nUnique << 1));
3226 tx.vout.push_back(CTxOut(0, CScript()));
3227 for (unsigned int i = 0; i < nSigOps; i++)
3228 tx.vout[0].scriptPubKey << OP_CHECKSIG;
3229 tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nPadBytes, 0);
3230 return tx;
3231}
3232
3233static CBlock MakeCoinbaseOnlyTemplate()
3234{
3235 CTransaction txNew;
3236 txNew.vin.resize(1);
3237 txNew.vin[0].prevout.SetNull();
3238 txNew.vin[0].scriptSig << 1 << 1 << 1;
3239 txNew.vout.resize(1);
3240 txNew.vout[0].scriptPubKey << OP_CHECKSIG;
3241
3242 CBlock block;
3243 block.vtx.push_back(txNew);
3244 block.nVersion = 1;
3245 block.nTime = GetAdjustedTime();
3246 block.nBits = bnProofOfWorkLimit.GetCompact();
3247 block.nNonce = 0;
3248 return block;
3249}
3250
3251// The sum CheckBlock() takes, spelled the same way.
3252static unsigned int BlockSigOpCount(const CBlock& block)
3253{
3254 unsigned int n = 0;
3255 for (int i = 0; i < (int)block.vtx.size(); i++)
3256 n += block.vtx[i].GetSigOpCount();
3257 return n;
3258}
3259
3260// Drive the selection rule the way both assemblers drive it: ask
3261// BlockTemplateFits before appending, skip what does not fit, never stop early
3262// on one that does not.
3263//
3264// This mirrors the loops in BitcoinMiner() and RebuildJob() rather than calling
3265// them -- one needs a CTxDB, a wallet key and cs_main, the other is static in
3266// rpc.cpp, so neither is reachable from here. What that leaves proven is the
3267// rule the two of them share and the property it is meant to guarantee, which
3268// is where the defect actually lived: the budget was right and the enforcement
3269// was not.
3270static void SelectIntoTemplate(CBlock& block, const vector<CTransaction>& vMempool)
3271{
3272 unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK);
3273 unsigned int nBlockSigOps = block.vtx[0].GetSigOpCount();
3274 for (int i = 0; i < (int)vMempool.size(); i++)
3275 {
3276 const CTransaction& tx = vMempool[i];
3277 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
3278 unsigned int nTxSigOps = tx.GetSigOpCount();
3279 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
3280 continue;
3281 block.vtx.push_back(tx);
3282 nBlockSize += nTxSize;
3283 nBlockSigOps += nTxSigOps;
3284 }
3285}
3286
3177static int RunConsensusLimitsSelfTest() 3287static int RunConsensusLimitsSelfTest()
3178{ 3288{
3179 fflush(stdout); 3289 fflush(stdout);
@@ -3197,6 +3307,95 @@ static int RunConsensusLimitsSelfTest()
3197 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; 3307 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1;
3198 nFail += Check(!oversized.CheckSizeLimits(), 3308 nFail += Check(!oversized.CheckSizeLimits(),
3199 "a block above 1 MB is outside the consensus size limit") ? 0 : 1; 3309 "a block above 1 MB is outside the consensus size limit") ? 0 : 1;
3310
3311 // The other half of the cap. Everything above asks what validation
3312 // refuses; nothing asked what assembly produces, which is why a miner
3313 // that could overshoot the cap by an entire mempool went unnoticed.
3314 nFail += Check(MAX_BLOCK_SIZE_GEN < MAX_BLOCK_SIZE,
3315 "the assembly budget stays under the consensus cap") ? 0 : 1;
3316
3317 // The rule itself, at its edges. The sigop comparison has to match
3318 // CheckBlock(), which fails on `> MAX_BLOCK_SIGOPS` and so accepts a
3319 // block sitting exactly on the limit.
3320 nFail += Check(BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 100, 0),
3321 "a transaction that exactly fills the size budget fits") ? 0 : 1;
3322 nFail += Check(!BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 101, 0),
3323 "one byte past the size budget does not fit") ? 0 : 1;
3324 nFail += Check(BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS),
3325 "sigops exactly on the limit fit, as CheckBlock() allows") ? 0 : 1;
3326 nFail += Check(!BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS + 1),
3327 "one sigop past the limit does not fit") ? 0 : 1;
3328
3329 // A mempool several times the consensus cap, in ordinary-sized
3330 // transactions. The old miner tested its budget only on the outer
3331 // loop, so a single pass appended all of this.
3332 {
3333 CBlock block = MakeCoinbaseOnlyTemplate();
3334 vector<CTransaction> vMempool;
3335 for (int i = 0; i < 300; i++)
3336 vMempool.push_back(MakeSizedTx(10000, 1, i));
3337 SelectIntoTemplate(block, vMempool);
3338 block.hashMerkleRoot = block.BuildMerkleTree();
3339
3340 nFail += Check(block.vtx.size() > 1,
3341 "a full mempool still fills a template") ? 0 : 1;
3342 nFail += Check(::GetSerializeSize(block, SER_DISK) <= MAX_BLOCK_SIZE_GEN,
3343 "a template built from a 3 MB mempool respects the budget") ? 0 : 1;
3344 nFail += Check(block.CheckSizeLimits(),
3345 "that template is one CheckBlock() would accept on size") ? 0 : 1;
3346 }
3347
3348 // One transaction too big for the budget. Nothing on the path into the
3349 // mempool caps transaction size, so this is a transaction a peer can
3350 // simply send. It must be skipped and the rest still taken -- skipped,
3351 // not stopped on.
3352 {
3353 CBlock block = MakeCoinbaseOnlyTemplate();
3354 vector<CTransaction> vMempool;
3355 vMempool.push_back(MakeSizedTx(1500000, 1, 9999));
3356 for (int i = 0; i < 10; i++)
3357 vMempool.push_back(MakeSizedTx(1000, 1, i));
3358
3359 unsigned int nHugeSize = ::GetSerializeSize(vMempool[0], SER_NETWORK);
3360 nFail += Check(nHugeSize > MAX_BLOCK_SIZE && nHugeSize <= MAX_SIZE,
3361 "the outsized transaction is one the mempool would hold") ? 0 : 1;
3362
3363 SelectIntoTemplate(block, vMempool);
3364 block.hashMerkleRoot = block.BuildMerkleTree();
3365
3366 bool fTookHuge = false;
3367 for (int i = 0; i < (int)block.vtx.size(); i++)
3368 if (block.vtx[i].GetHash() == vMempool[0].GetHash())
3369 fTookHuge = true;
3370
3371 nFail += Check(!fTookHuge,
3372 "a transaction larger than the budget is left out") ? 0 : 1;
3373 nFail += Check(block.vtx.size() == 11,
3374 "the ten that do fit are taken anyway") ? 0 : 1;
3375 nFail += Check(block.CheckSizeLimits(),
3376 "one outsized transaction cannot invalidate a template") ? 0 : 1;
3377 }
3378
3379 // Transactions that are cheap in bytes and expensive to verify. Size
3380 // alone does not bound what a block costs, and CheckBlock() refuses on
3381 // the count regardless of how small the block is.
3382 {
3383 CBlock block = MakeCoinbaseOnlyTemplate();
3384 vector<CTransaction> vMempool;
3385 for (int i = 0; i < 100; i++)
3386 vMempool.push_back(MakeSizedTx(0, 1000, i));
3387 SelectIntoTemplate(block, vMempool);
3388 block.hashMerkleRoot = block.BuildMerkleTree();
3389
3390 nFail += Check(block.vtx.size() > 1,
3391 "sigop-heavy transactions are still selected") ? 0 : 1;
3392 nFail += Check(BlockSigOpCount(block) <= MAX_BLOCK_SIGOPS,
3393 "selection stops at the sigop cap CheckBlock() enforces") ? 0 : 1;
3394 nFail += Check(::GetSerializeSize(block, SER_DISK) < MAX_BLOCK_SIZE_GEN,
3395 "and stops on sigops while still well inside the size budget") ? 0 : 1;
3396 nFail += Check(block.CheckSizeLimits(),
3397 "the sigop-bounded template is within the size limit too") ? 0 : 1;
3398 }
3200 } 3399 }
3201 catch (const std::exception& e) 3400 catch (const std::exception& e)
3202 { 3401 {
@@ -3604,6 +3803,8 @@ int RunSelfTest(const std::string& name)
3604 return RunConsensusLimitsSelfTest(); 3803 return RunConsensusLimitsSelfTest();
3605 if (name == "pool-stratum") 3804 if (name == "pool-stratum")
3606 return RunPoolStratumSelfTest(); 3805 return RunPoolStratumSelfTest();
3806 if (name == "payout-ledger")
3807 return RunPayoutLedgerSelfTest();
3607 if (name == "parse-money") 3808 if (name == "parse-money")
3608 return RunParseMoneySelfTest(); 3809 return RunParseMoneySelfTest();
3609 if (name == "socks5-proxy") 3810 if (name == "socks5-proxy")
@@ -3612,6 +3813,6 @@ int RunSelfTest(const std::string& name)
3612 return RunManagedTorSelfTest(); 3813 return RunManagedTorSelfTest();
3613 3814
3614 printf("Unknown self-test '%s'\n", name.c_str()); 3815 printf("Unknown self-test '%s'\n", name.c_str());
3615 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, network-params, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); 3816 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, network-params, consensus-limits, pool-stratum, payout-ledger, parse-money, socks5-proxy, managed-tor\n");
3616 return 1; 3817 return 1;
3617} 3818}