/*! * \param[in] data Data for the current frame. * \param[in] sel Selection element being evaluated. * \param[in] g Group for which \p sel should be evaluated. * \returns 0 on success, a non-zero error code on error. * * Short-circuiting evaluation of logical OR expressions. * * Starts by evaluating the first child element in the group \p g. * For each subsequent child, finds the part of \p g that is not * included the value of any previous child, and evaluates the child * in that group until the last child is evaluated or all of \p g * is included in some child value. * The value of \p sel is set to the union of all the (evaluated) * child values. * * If the first child does not have an evaluation function, its value is * used without evaluation. * This happens if the first child is a constant expression, the selection * has been compiled, and the evaluation group is the same for each frame. * In this case, the compiler has taken care of that the child value is a * subset of \p g, making it unnecessary to evaluate it. * * This function is used as \c t_selelem::evaluate for \ref SEL_BOOLEAN * elements with \ref BOOL_OR. */ void _gmx_sel_evaluate_or(gmx_sel_evaluate_t *data, t_selelem *sel, gmx_ana_index_t *g) { t_selelem *child; gmx_ana_index_t tmp, tmp2; child = sel->child; if (child->evaluate) { MempoolSelelemReserver reserver(child, g->isize); child->evaluate(data, child, g); gmx_ana_index_partition(sel->v.u.g, &tmp, g, child->v.u.g); } else { gmx_ana_index_partition(sel->v.u.g, &tmp, g, child->v.u.g); } child = child->next; while (child && tmp.isize > 0) { tmp.name = NULL; { MempoolSelelemReserver reserver(child, tmp.isize); child->evaluate(data, child, &tmp); gmx_ana_index_partition(&tmp, &tmp2, &tmp, child->v.u.g); } sel->v.u.g->isize += tmp.isize; tmp.isize = tmp2.isize; tmp.index = tmp2.index; child = child->next; } gmx_ana_index_sort(sel->v.u.g); }
/*! * \param[in] data Data for the current frame. * \param[in] sel Selection element being evaluated. * \param[in] g Group for which \p sel should be evaluated. * \returns 0 on success, a non-zero error code on error. * * Short-circuiting evaluation of logical AND expressions. * * Starts by evaluating the first child element in the group \p g. * The each following child element is evaluated in the intersection * of all the previous values until all children have been evaluated * or the intersection becomes empty. * The value of \p sel is set to the intersection of all the (evaluated) * child values. * * If the first child does not have an evaluation function, it is skipped * and the evaluation is started at the second child. * This happens if the first child is a constant expression and during * compilation it was detected that the evaluation group is always a subset * of the constant group * (currently, the compiler never detects this). * * This function is used as \c t_selelem::evaluate for \ref SEL_BOOLEAN * elements with \ref BOOL_AND. */ void _gmx_sel_evaluate_and(gmx_sel_evaluate_t *data, t_selelem *sel, gmx_ana_index_t *g) { t_selelem *child; child = sel->child; /* Skip the first child if it does not have an evaluation function. */ if (!child->evaluate) { child = child->next; } /* Evaluate the first child */ { MempoolSelelemReserver reserver(child, g->isize); child->evaluate(data, child, g); gmx_ana_index_copy(sel->v.u.g, child->v.u.g, false); } child = child->next; while (child && sel->v.u.g->isize > 0) { MempoolSelelemReserver reserver(child, sel->v.u.g->isize); child->evaluate(data, child, sel->v.u.g); gmx_ana_index_intersection(sel->v.u.g, sel->v.u.g, child->v.u.g); child = child->next; } }
/*! * \param[in] data Data for the current frame. * \param[in] sel Selection element being evaluated. * \param[in] g Group for which \p sel should be evaluated. * \returns 0 on success, a non-zero error code on error. * * Evaluates the child element (there should be only one) in the group * \p g, and then sets the value of \p sel to the complement of the * child value. * * This function is used as \c t_selelem::evaluate for \ref SEL_BOOLEAN * elements with \ref BOOL_NOT. */ void _gmx_sel_evaluate_not(gmx_sel_evaluate_t *data, t_selelem *sel, gmx_ana_index_t *g) { MempoolSelelemReserver reserver(sel->child, g->isize); sel->child->evaluate(data, sel->child, g); gmx_ana_index_difference(sel->v.u.g, g, sel->child->v.u.g); }
ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver); }
ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); g_address_type = OUTPUT_TYPE_DEFAULT; g_change_type = OUTPUT_TYPE_DEFAULT; wallet = MakeUnique<CWallet>("mock", CWalletDBWrapper::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver); }
ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); ::bitdb.MakeMock(); g_address_type = OUTPUT_TYPE_DEFAULT; g_change_type = OUTPUT_TYPE_DEFAULT; wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat")))); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver); }
ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = MakeUnique<CWallet>(*m_chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); const CBlockIndex* const null_block = nullptr; const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(stop_block, chainActive.Tip()); BOOST_CHECK_EQUAL(failed_block, null_block); }
/*! * \param[in] data Data for the current frame. * \param[in] sel Selection element being evaluated. * \param[in] g Group for which \p sel should be evaluated. * \returns 0 on success, a non-zero error code on error. * * Finds the part of \p g for which the subexpression * has not yet been evaluated by comparing \p g to \p sel->u.cgrp. * If the part is not empty, the child expression is evaluated for this * part, and the results merged to the old values of the child. * The value of \p sel itself is undefined after the call. * * \todo * The call to gmx_ana_index_difference() can take quite a lot of unnecessary * time if the subexpression is evaluated either several times for the same * group or for completely distinct groups. * However, in the majority of cases, these situations occur when * _gmx_sel_evaluate_subexpr_staticeval() can be used, so this should not be a * major problem. */ void _gmx_sel_evaluate_subexpr(gmx_sel_evaluate_t *data, t_selelem *sel, gmx_ana_index_t *g) { gmx_ana_index_t gmiss; MempoolGroupReserver gmissreserver(data->mp); if (sel->u.cgrp.isize == 0) { { SelelemTemporaryValueAssigner assigner(sel->child, sel); sel->child->evaluate(data, sel->child, g); } /* We need to keep the name for the cgrp across the copy to avoid * problems if g has a name set. */ char *name = sel->u.cgrp.name; gmx_ana_index_copy(&sel->u.cgrp, g, false); sel->u.cgrp.name = name; gmiss.isize = 0; } else { gmissreserver.reserve(&gmiss, g->isize); gmx_ana_index_difference(&gmiss, g, &sel->u.cgrp); } if (gmiss.isize > 0) { MempoolSelelemReserver reserver(sel->child, gmiss.isize); /* Evaluate the missing values for the child */ sel->child->evaluate(data, sel->child, &gmiss); /* Merge the missing values to the existing ones. */ if (sel->v.type == GROUP_VALUE) { gmx_ana_index_merge(sel->v.u.g, sel->child->v.u.g, sel->v.u.g); } else { int i, j, k; i = sel->u.cgrp.isize - 1; j = gmiss.isize - 1; /* TODO: This switch is kind of ugly, but it may be difficult to * do this portably without C++ templates. */ switch (sel->v.type) { case INT_VALUE: for (k = sel->u.cgrp.isize + gmiss.isize - 1; k >= 0; k--) { if (i < 0 || (j >= 0 && sel->u.cgrp.index[i] < gmiss.index[j])) { sel->v.u.i[k] = sel->v.u.i[j--]; } else { sel->v.u.i[k] = sel->child->v.u.i[i--]; } } break; case REAL_VALUE: for (k = sel->u.cgrp.isize + gmiss.isize - 1; k >= 0; k--) { if (i < 0 || (j >= 0 && sel->u.cgrp.index[i] < gmiss.index[j])) { sel->v.u.r[k] = sel->v.u.r[j--]; } else { sel->v.u.r[k] = sel->child->v.u.r[i--]; } } break; case STR_VALUE: for (k = sel->u.cgrp.isize + gmiss.isize - 1; k >= 0; k--) { if (i < 0 || (j >= 0 && sel->u.cgrp.index[i] < gmiss.index[j])) { sel->v.u.s[k] = sel->v.u.s[j--]; } else { sel->v.u.s[k] = sel->child->v.u.s[i--]; } } break; case POS_VALUE: /* TODO: Implement this */ GMX_THROW(gmx::NotImplementedError("position subexpressions not implemented properly")); case NO_VALUE: case GROUP_VALUE: GMX_THROW(gmx::InternalError("Invalid subexpression type")); } } gmx_ana_index_merge(&sel->u.cgrp, &sel->u.cgrp, &gmiss); } }
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. CBlockIndex* const nullBlock = nullptr; CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); LOCK(cs_main); // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(request); BOOST_CHECK_EQUAL(response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Rescan failed for key with creation " "timestamp %d. There was an error reading a block from time %d, which is after or within %d " "seconds of key creation, and could contain transactions pertaining to the key. As a result, " "transactions and coins using this key may not appear in the wallet. This error could be caused " "by pruning or data corruption (see bitcoind log for details) and could be dealt with by " "downloading and rescanning the relevant blocks (see -reindex and -rescan " "options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); RemoveWallet(wallet); } }
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. const CBlockIndex* const null_block = nullptr; CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions accomodates a null start block. { CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(failed_block, null_block); BOOST_CHECK_EQUAL(stop_block, null_block); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(failed_block, null_block); BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(failed_block, oldTip); BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } // Prune the remaining block file. PruneOneBlockFile(newTip->GetBlockPos().nFile); UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions scans no blocks. { CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(failed_block, newTip); BOOST_CHECK_EQUAL(stop_block, null_block); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); } }