diff options
Diffstat (limited to 'src/selftest.cpp')
| -rw-r--r-- | src/selftest.cpp | 225 |
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 | ||
| 21 | extern int RunPoolStratumSelfTest(); | 21 | extern int RunPoolStratumSelfTest(); |
| 22 | extern 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. | ||
| 3222 | static 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 | |||
| 3233 | static 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. | ||
| 3252 | static 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. | ||
| 3270 | static 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 | |||
| 3177 | static int RunConsensusLimitsSelfTest() | 3287 | static 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 | } |