diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index a1a8af79a..1267bc453 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -1,134 +1,134 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include static void addCoin(const Amount nValue, const CWallet &wallet, std::vector> &wtxs) { static int nextLockTime = 0; CMutableTransaction tx; // so all transactions get different hashes tx.nLockTime = nextLockTime++; tx.vout.resize(1); tx.vout[0].nValue = nValue; wtxs.push_back(std::make_unique( &wallet, MakeTransactionRef(std::move(tx)))); } // Simple benchmark for wallet coin selection. Note that it maybe be necessary // to build up more complicated scenarios in order to get meaningful // measurements of performance. From laanwj, "Wallet coin selection is probably // the hardest, as you need a wider selection of scenarios, just testing the // same one over and over isn't too useful. Generating random isn't useful // either for measurements." // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CoinSelection(benchmark::State &state) { SelectParams(CBaseChainParams::REGTEST); auto chain = interfaces::MakeChain(); - const CWallet wallet(Params(), *chain, WalletLocation(), + const CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); std::vector> wtxs; LOCK(wallet.cs_wallet); // Add coins. for (int i = 0; i < 1000; ++i) { addCoin(1000 * COIN, wallet, wtxs); } addCoin(3 * COIN, wallet, wtxs); // Create groups std::vector groups; for (const auto &wtx : wtxs) { COutput output(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); groups.emplace_back(output.GetInputCoin(), 6, false, 0, 0); } const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinSelectionParams coin_selection_params( true, 34, 148, CFeeRate(Amount::zero()), 0); while (state.KeepRunning()) { std::set setCoinsRet; Amount nValueRet; bool bnb_used; bool success = wallet.SelectCoinsMinConf( 1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used); assert(success); assert(nValueRet == 1003 * COIN); assert(setCoinsRet.size() == 2); } } typedef std::set CoinSet; std::vector> wtxn; // Copied from src/wallet/test/coinselector_tests.cpp static void add_coin(const CWallet &wallet, const Amount nValue, int nInput, std::vector &set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; auto wtx = std::make_unique(&wallet, MakeTransactionRef(std::move(tx))); set.emplace_back( COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0); wtxn.emplace_back(std::move(wtx)); } // Copied from src/wallet/test/coinselector_tests.cpp static Amount make_hard_case(const CWallet &wallet, int utxos, std::vector &utxo_pool) { utxo_pool.clear(); Amount target = Amount::zero(); for (int i = 0; i < utxos; ++i) { const Amount base = (int64_t(1) << (utxos + i)) * SATOSHI; target += base; add_coin(wallet, base, 2 * i, utxo_pool); add_coin(wallet, base + (int64_t(1) << (utxos - 1 - i)) * SATOSHI, 2 * i + 1, utxo_pool); } return target; } static void BnBExhaustion(benchmark::State &state) { SelectParams(CBaseChainParams::REGTEST); auto chain = interfaces::MakeChain(); - const CWallet wallet(Params(), *chain, WalletLocation(), + const CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet.cs_wallet); // Setup std::vector utxo_pool; CoinSet selection; Amount value_ret = Amount::zero(); Amount not_input_fees = Amount::zero(); while (state.KeepRunning()) { // Benchmark Amount target = make_hard_case(wallet, 17, utxo_pool); // Should exhaust SelectCoinsBnB(utxo_pool, target, Amount::zero(), selection, value_ret, not_input_fees); // Cleanup utxo_pool.clear(); selection.clear(); } } BENCHMARK(CoinSelection, 650); BENCHMARK(BnBExhaustion, 650); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index cf3ea4087..90c0817b4 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -1,167 +1,167 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { /** * Fill the edit address dialog box with data, submit it, and ensure that * the resulting message meets expectations. */ void EditAddressAndSubmit(EditAddressDialog *dialog, const QString &label, const QString &address, QString expected_msg) { QString warning_text; dialog->findChild("labelEdit")->setText(label); dialog->findChild("addressEdit")->setText(address); ConfirmMessage(&warning_text, 5); dialog->accept(); QCOMPARE(warning_text, expected_msg); } /** * Test adding various send addresses to the address book. * * There are three cases tested: * * - new_address: a new address which should add as a send address * successfully. * - existing_s_address: an existing sending address which won't add * successfully. * - existing_r_address: an existing receiving address which won't add * successfully. * * In each case, verify the resulting state of the address book and optionally * the warning message presented to the user. */ void TestAddAddressesToSendBook() { TestChain100Setup test; auto chain = interfaces::MakeChain(); std::shared_ptr wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateMock()); + Params(), chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); auto build_address = [&wallet]() { CKey key; key.MakeNewKey(true); CTxDestination dest(GetDestinationForKey( key.GetPubKey(), wallet->m_default_address_type)); return std::make_pair( dest, QString::fromStdString(EncodeCashAddr(dest, Params()))); }; CTxDestination r_key_dest, s_key_dest; // Add a preexisting "receive" entry in the address book. QString preexisting_r_address; QString r_label("already here (r)"); // Add a preexisting "send" entry in the address book. QString preexisting_s_address; QString s_label("already here (s)"); // Define a new address (which should add to the address book successfully). QString new_address; std::tie(r_key_dest, preexisting_r_address) = build_address(); std::tie(s_key_dest, preexisting_s_address) = build_address(); std::tie(std::ignore, new_address) = build_address(); { LOCK(wallet->cs_wallet); wallet->SetAddressBook(r_key_dest, r_label.toStdString(), "receive"); wallet->SetAddressBook(s_key_dest, s_label.toStdString(), "send"); } auto check_addbook_size = [&wallet](int expected_size) { QCOMPARE(static_cast(wallet->mapAddressBook.size()), expected_size); }; // We should start with the two addresses we added earlier and nothing else. check_addbook_size(2); // Initialize relevant QT models. std::unique_ptr platformStyle( PlatformStyle::instantiate("other")); auto node = interfaces::MakeNode(); OptionsModel optionsModel(*node); AddWallet(wallet); WalletModel walletModel(std::move(node->getWallets()[0]), *node, platformStyle.get(), &optionsModel); RemoveWallet(wallet); EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); EditAddressAndSubmit( &editAddressDialog, QString("uhoh"), preexisting_r_address, QString( "Address \"%1\" already exists as a receiving address with label " "\"%2\" and so cannot be added as a sending address.") .arg(preexisting_r_address) .arg(r_label)); check_addbook_size(2); EditAddressAndSubmit( &editAddressDialog, QString("uhoh, different"), preexisting_s_address, QString( "The entered address \"%1\" is already in the address book with " "label \"%2\".") .arg(preexisting_s_address) .arg(s_label)); check_addbook_size(2); // Submit a new address which should add successfully - we expect the // warning message to be blank. EditAddressAndSubmit(&editAddressDialog, QString("new"), new_address, QString("")); check_addbook_size(3); } } // namespace void AddressBookTests::addressBookTests() { #ifdef Q_OS_MAC if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, // and fails to handle returned nulls // (https://bugreports.qt.io/browse/QTBUG-49686). QWARN( "Skipping AddressBookTests on mac build with 'minimal' platform " "set due to Qt bugs. To run AppTests, invoke with 'test_bitcoin-qt " "-platform cocoa' on mac, or else use a linux or windows build."); return; } #endif TestAddAddressesToSendBook(); } diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 811030abf..607457be4 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,261 +1,261 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. void ConfirmSend(QString *text = nullptr, bool cancel = false) { QTimer::singleShot(0, Qt::PreciseTimer, [text, cancel]() { for (QWidget *widget : QApplication::topLevelWidgets()) { if (widget->inherits("SendConfirmationDialog")) { SendConfirmationDialog *dialog = qobject_cast(widget); if (text) { *text = dialog->text(); } QAbstractButton *button = dialog->button( cancel ? QMessageBox::Cancel : QMessageBox::Yes); button->setEnabled(true); button->click(); } } }); } //! Send coins to address and return txid. TxId SendCoins(CWallet &wallet, SendCoinsDialog &sendCoinsDialog, const CTxDestination &address, Amount amount) { QVBoxLayout *entries = sendCoinsDialog.findChild("entries"); SendCoinsEntry *entry = qobject_cast(entries->itemAt(0)->widget()); entry->findChild("payTo")->setText( QString::fromStdString(EncodeCashAddr(address, Params()))); entry->findChild("payAmount")->setValue(amount); TxId txid; boost::signals2::scoped_connection c = wallet.NotifyTransactionChanged.connect( [&txid](CWallet *, const TxId &hash, ChangeType status) { if (status == CT_NEW) { txid = hash; } }); ConfirmSend(); bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); assert(invoked); return txid; } //! Find index of txid in transaction list. QModelIndex FindTx(const QAbstractItemModel &model, const uint256 &txid) { QString hash = QString::fromStdString(txid.ToString()); int rows = model.rowCount({}); for (int row = 0; row < rows; ++row) { QModelIndex index = model.index(row, 0, {}); if (model.data(index, TransactionTableModel::TxHashRole) == hash) { return index; } } return {}; } //! Simple qt wallet tests. // // Test widgets can be debugged interactively calling show() on them and // manually running the event loop, e.g.: // // sendCoinsDialog.show(); // QEventLoop().exec(); // // This also requires overriding the default minimal Qt platform: // // src/qt/test/test_bitcoin-qt -platform xcb # Linux // src/qt/test/test_bitcoin-qt -platform windows # Windows // src/qt/test/test_bitcoin-qt -platform cocoa # macOS void TestGUI() { // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock( {}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } auto chain = interfaces::MakeChain(); std::shared_ptr wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateMock()); + Params(), chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); { LOCK(wallet->cs_wallet); wallet->SetAddressBook( GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { auto locked_chain = wallet->chain().lock(); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions( locked_chain->getBlockHash(0), BlockHash(), reserver, true /* fUpdate */); QCOMPARE(result.status, CWallet::ScanResult::SUCCESS); QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); QVERIFY(result.last_failed_block.IsNull()); } wallet->SetBroadcastTransactions(true); // Create widgets for sending coins and listing transactions. std::unique_ptr platformStyle( PlatformStyle::instantiate("other")); auto node = interfaces::MakeNode(); OptionsModel optionsModel(*node); AddWallet(wallet); WalletModel walletModel(std::move(node->getWallets().back()), *node, platformStyle.get(), &optionsModel); RemoveWallet(wallet); // Send two transactions, and verify they are added to transaction list. SendCoinsDialog sendCoinsDialog(platformStyle.get(), &walletModel); TransactionTableModel *transactionTableModel = walletModel.getTransactionTableModel(); QCOMPARE(transactionTableModel->rowCount({}), 105); TxId txid1 = SendCoins(*wallet.get(), sendCoinsDialog, CTxDestination(CKeyID()), 5 * COIN); TxId txid2 = SendCoins(*wallet.get(), sendCoinsDialog, CTxDestination(CKeyID()), 10 * COIN); QCOMPARE(transactionTableModel->rowCount({}), 107); QVERIFY(FindTx(*transactionTableModel, txid1).isValid()); QVERIFY(FindTx(*transactionTableModel, txid2).isValid()); // Check current balance on OverviewPage OverviewPage overviewPage(platformStyle.get()); overviewPage.setWalletModel(&walletModel); QLabel *balanceLabel = overviewPage.findChild("labelBalance"); QString balanceText = balanceLabel->text(); int unit = walletModel.getOptionsModel()->getDisplayUnit(); Amount balance = walletModel.wallet().getBalance(); QString balanceComparison = BitcoinUnits::formatWithUnit( unit, balance, false, BitcoinUnits::separatorAlways); QCOMPARE(balanceText, balanceComparison); // Check Request Payment button ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get()); receiveCoinsDialog.setModel(&walletModel); RecentRequestsTableModel *requestTableModel = walletModel.getRecentRequestsTableModel(); // Label input QLineEdit *labelInput = receiveCoinsDialog.findChild("reqLabel"); labelInput->setText("TEST_LABEL_1"); // Amount input BitcoinAmountField *amountInput = receiveCoinsDialog.findChild("reqAmount"); amountInput->setValue(1 * SATOSHI); // Message input QLineEdit *messageInput = receiveCoinsDialog.findChild("reqMessage"); messageInput->setText("TEST_MESSAGE_1"); int initialRowCount = requestTableModel->rowCount({}); QPushButton *requestPaymentButton = receiveCoinsDialog.findChild("receiveButton"); requestPaymentButton->click(); for (QWidget *widget : QApplication::topLevelWidgets()) { if (widget->inherits("ReceiveRequestDialog")) { ReceiveRequestDialog *receiveRequestDialog = qobject_cast(widget); QTextEdit *rlist = receiveRequestDialog->QObject::findChild("outUri"); QString paymentText = rlist->toPlainText(); QStringList paymentTextList = paymentText.split('\n'); QCOMPARE(paymentTextList.at(0), QString("Payment information")); QVERIFY(paymentTextList.at(1).indexOf(QString("URI: bchreg:")) != -1); QVERIFY(paymentTextList.at(2).indexOf(QString("Address:")) != -1); QCOMPARE(paymentTextList.at(3), QString("Amount: 0.00000001 ") + QString::fromStdString(CURRENCY_UNIT)); QCOMPARE(paymentTextList.at(4), QString("Label: TEST_LABEL_1")); QCOMPARE(paymentTextList.at(5), QString("Message: TEST_MESSAGE_1")); } } // Clear button QPushButton *clearButton = receiveCoinsDialog.findChild("clearButton"); clearButton->click(); QCOMPARE(labelInput->text(), QString("")); QCOMPARE(amountInput->value(), Amount::zero()); QCOMPARE(messageInput->text(), QString("")); // Check addition to history int currentRowCount = requestTableModel->rowCount({}); QCOMPARE(currentRowCount, initialRowCount + 1); // Check Remove button QTableView *table = receiveCoinsDialog.findChild("recentRequestsView"); table->selectRow(currentRowCount - 1); QPushButton *removeRequestButton = receiveCoinsDialog.findChild("removeRequestButton"); removeRequestButton->click(); QCOMPARE(requestTableModel->rowCount({}), currentRowCount - 1); } } // namespace void WalletTests::walletTests() { #ifdef Q_OS_MAC if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, // and fails to handle returned nulls // (https://bugreports.qt.io/browse/QTBUG-49686). QWARN("Skipping WalletTests on mac build with 'minimal' platform set " "due to Qt bugs. To run AppTests, invoke with 'test_bitcoin-qt " "-platform cocoa' on mac, or else use a linux or windows build."); return; } #endif TestGUI(); } diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index ed8859ea5..da02ab562 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -1,776 +1,776 @@ // Copyright (c) 2017 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include // For Params #include #include #include #include #include #include #include #include #include #include BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup) // how many times to run all the tests to have a chance to catch errors that // only show up with particular random shuffles #define RUN_TESTS 100 // some tests fail 1% of the time due to bad luck. // we repeat those tests this many times and only complain if all iterations of // the test fail #define RANDOM_REPEATS 5 std::vector> wtxn; typedef std::set CoinSet; static std::vector vCoins; static Amount balance = Amount::zero(); CoinEligibilityFilter filter_standard(1, 6, 0); CoinEligibilityFilter filter_confirmed(1, 1, 0); CoinEligibilityFilter filter_standard_extra(6, 6, 0); CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(Amount::zero()), 0); static void add_coin(const Amount nValue, int nInput, std::vector &set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; set.emplace_back(MakeTransactionRef(tx), nInput); } static void add_coin(const Amount nValue, int nInput, CoinSet &set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; set.emplace(MakeTransactionRef(tx), nInput); } static void add_coin(CWallet &wallet, const Amount nValue, int nAge = 6 * 24, bool fIsFromMe = false, int nInput = 0) { balance += nValue; static int nextLockTime = 0; CMutableTransaction tx; // so all transactions get different hashes tx.nLockTime = nextLockTime++; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; if (fIsFromMe) { // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if // vin.empty(), so stop vin being empty, and cache a non-zero Debit to // fake out IsFromMe() tx.vin.resize(1); } auto wtx = std::make_unique(&wallet, MakeTransactionRef(std::move(tx))); if (fIsFromMe) { wtx->fDebitCached = true; wtx->nDebitCached = SATOSHI; } COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); wallet.AddToWallet(*wtx.get()); wtxn.emplace_back(std::move(wtx)); } static void empty_wallet() { vCoins.clear(); wtxn.clear(); balance = Amount::zero(); } static bool equal_sets(CoinSet a, CoinSet b) { std::pair ret = mismatch(a.begin(), a.end(), b.begin()); return ret.first == a.end() && ret.second == b.end(); } static Amount make_hard_case(int utxos, std::vector &utxo_pool) { utxo_pool.clear(); Amount target = Amount::zero(); for (int i = 0; i < utxos; ++i) { const Amount base = (int64_t(1) << (utxos + i)) * SATOSHI; target += base; add_coin(base, 2 * i, utxo_pool); add_coin(base + (int64_t(1) << (utxos - 1 - i)) * SATOSHI, 2 * i + 1, utxo_pool); } return target; } inline std::vector & GroupCoins(const std::vector &coins) { static std::vector static_groups; static_groups.clear(); for (auto &coin : coins) { static_groups.emplace_back(coin, 0, true, 0, 0); } return static_groups; } inline std::vector &GroupCoins(const std::vector &coins) { static std::vector static_groups; static_groups.clear(); for (auto &coin : coins) { // HACK: we can't figure out the is_me flag so we use the conditions // defined below; perhaps set safe to false for !fIsFromMe in add_coin() const bool is_me = coin.tx->fDebitCached && coin.tx->nDebitCached == SATOSHI; static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, is_me, 0, 0); } return static_groups; } // Branch and bound coin selection tests BOOST_AUTO_TEST_CASE(bnb_search_test) { LOCK(m_wallet.cs_wallet); // Setup std::vector utxo_pool; CoinSet selection; CoinSet actual_selection; Amount value_ret = Amount::zero(); Amount not_input_fees = Amount::zero(); ///////////////////////// // Known Outcome tests // ///////////////////////// // Empty utxo pool BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, CENT / 2, selection, value_ret, not_input_fees)); selection.clear(); // Add utxos add_coin(1 * CENT, 1, utxo_pool); add_coin(2 * CENT, 2, utxo_pool); add_coin(3 * CENT, 3, utxo_pool); add_coin(4 * CENT, 4, utxo_pool); // Select 1 Cent add_coin(1 * CENT, 1, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 1 * CENT); actual_selection.clear(); selection.clear(); // Select 2 Cent add_coin(2 * CENT, 2, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 2 * CENT); actual_selection.clear(); selection.clear(); // Select 5 Cent add_coin(3 * CENT, 3, actual_selection); add_coin(2 * CENT, 2, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 5 * CENT); actual_selection.clear(); selection.clear(); // Select 11 Cent, not possible BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, CENT / 2, selection, value_ret, not_input_fees)); actual_selection.clear(); selection.clear(); // Select 10 Cent add_coin(5 * CENT, 5, utxo_pool); add_coin(4 * CENT, 4, actual_selection); add_coin(3 * CENT, 3, actual_selection); add_coin(2 * CENT, 2, actual_selection); add_coin(1 * CENT, 1, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 10 * CENT); actual_selection.clear(); selection.clear(); // Negative effective value // Select 10 Cent but have 1 Cent not be possible because too small add_coin(5 * CENT, 5, actual_selection); add_coin(3 * CENT, 3, actual_selection); add_coin(2 * CENT, 2, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 5000 * SATOSHI, selection, value_ret, not_input_fees)); BOOST_CHECK_EQUAL(value_ret, 10 * CENT); // FIXME: this test is redundant with the above, because 1 Cent is selected, // not "too small" BOOST_CHECK(equal_sets(selection, actual_selection)); // Select 0.25 Cent, not possible BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), CENT / 4, CENT / 2, selection, value_ret, not_input_fees)); actual_selection.clear(); selection.clear(); // Iteration exhaustion test Amount target = make_hard_case(17, utxo_pool); // Should exhaust BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, Amount::zero(), selection, value_ret, not_input_fees)); target = make_hard_case(14, utxo_pool); // Should not exhaust BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), target, Amount::zero(), selection, value_ret, not_input_fees)); // Test same value early bailout optimization utxo_pool.clear(); add_coin(7 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, actual_selection); add_coin(2 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, utxo_pool); add_coin(7 * CENT, 7, utxo_pool); add_coin(7 * CENT, 7, utxo_pool); add_coin(7 * CENT, 7, utxo_pool); add_coin(2 * CENT, 7, utxo_pool); for (int i = 0; i < 50000; ++i) { add_coin(5 * CENT, 7, utxo_pool); } BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000 * SATOSHI, selection, value_ret, not_input_fees)); BOOST_CHECK_EQUAL(value_ret, 30 * CENT); BOOST_CHECK(equal_sets(selection, actual_selection)); //////////////////// // Behavior tests // //////////////////// // Select 1 Cent with pool of only greater than 5 Cent utxo_pool.clear(); for (int i = 5; i <= 20; ++i) { add_coin(i * CENT, i, utxo_pool); } // Run 100 times, to make sure it is never finding a solution for (int i = 0; i < 100; ++i) { BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT, selection, value_ret, not_input_fees)); } // Make sure that effective value is working in SelectCoinsMinConf when BnB // is used CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000 * SATOSHI), 0); CoinSet setCoinsRet; Amount nValueRet; bool bnb_used; empty_wallet(); add_coin(m_wallet, SATOSHI); // Make sure that it has a negative effective value. The next check should // assert if this somehow got through. Otherwise it will fail vCoins.at(0).nInputBytes = 40; BOOST_CHECK(!m_wallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); // Make sure that we aren't using BnB when there are preset inputs empty_wallet(); add_coin(m_wallet, 5 * CENT); add_coin(m_wallet, 3 * CENT); add_coin(m_wallet, 2 * CENT); CCoinControl coin_control; coin_control.fAllowOtherInputs = true; coin_control.Select(COutPoint(vCoins.at(0).tx->GetId(), vCoins.at(0).i)); BOOST_CHECK(m_wallet.SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); BOOST_CHECK(!bnb_used); BOOST_CHECK(!coin_selection_params_bnb.use_bnb); } BOOST_AUTO_TEST_CASE(knapsack_solver_test) { auto testChain = interfaces::MakeChain(); - CWallet testWallet(Params(), *testChain, WalletLocation(), + CWallet testWallet(Params(), testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CoinSet setCoinsRet, setCoinsRet2; Amount nValueRet; bool bnb_used; LOCK(testWallet.cs_wallet); // test multiple times to allow for differences in the shuffle order for (int i = 0; i < RUN_TESTS; i++) { empty_wallet(); // with an empty wallet we can't even pay one cent BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // add a new 1 cent coin add_coin(testWallet, 1 * CENT, 4); // with a new 1 cent coin, we still can't find a mature 1 cent BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // but we can find a new 1 cent BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // add a mature 2 cent coin add_coin(testWallet, 2 * CENT); // we can't make 3 cents of mature coins BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we can make 3 cents of new coins BOOST_CHECK(testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); // add a mature 5 cent coin, add_coin(testWallet, 5 * CENT); // a new 10 cent coin sent from one of our own addresses add_coin(testWallet, 10 * CENT, 3, true); // and a mature 20 cent coin add_coin(testWallet, 20 * CENT); // now we have new: 1+10=11 (of which 10 was self-sent), and mature: // 2+5+20=27. total = 38 // we can't make 38 cents only if we disallow new coins: BOOST_CHECK(!testWallet.SelectCoinsMinConf( 38 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we can't even make 37 cents if we don't allow new coins even if // they're from us BOOST_CHECK(!testWallet.SelectCoinsMinConf( 38 * CENT, filter_standard_extra, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // but we can make 37 cents if we accept new coins from ourself BOOST_CHECK(testWallet.SelectCoinsMinConf( 37 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); // and we can make 38 cents if we accept all new coins BOOST_CHECK(testWallet.SelectCoinsMinConf( 38 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); // try making 34 cents from 1,2,5,10,20 - we can't do it exactly BOOST_CHECK(testWallet.SelectCoinsMinConf( 34 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // but 35 cents is closest BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got // included (but possible) BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // when we try making 7 cents, the smaller coins (1,2,5) are enough. We // should see just 2+5 BOOST_CHECK(testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // when we try making 8 cents, the smaller coins (1,2,5) are exactly // enough. BOOST_CHECK(testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(nValueRet == 8 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // when we try making 9 cents, no subset of smaller coins is enough, and // we get the next bigger coin (10) BOOST_CHECK(testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now clear out the wallet and start again to test choosing between // subsets of smaller coins and the next biggest coin empty_wallet(); add_coin(testWallet, 6 * CENT); add_coin(testWallet, 7 * CENT); add_coin(testWallet, 8 * CENT); add_coin(testWallet, 20 * CENT); // now we have 6+7+8+20+30 = 71 cents total add_coin(testWallet, 30 * CENT); // check that we have 71 and not 72 BOOST_CHECK(testWallet.SelectCoinsMinConf( 71 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(!testWallet.SelectCoinsMinConf( 72 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // now try making 16 cents. the best smaller coins can do is 6+7+8 = // 21; not as good at the next biggest coin, 20 BOOST_CHECK(testWallet.SelectCoinsMinConf( 16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 20 in one coin BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now we have 5+6+7+8+20+30 = 75 cents total add_coin(testWallet, 5 * CENT); // now if we try making 16 cents again, the smaller coins can make 5+6+7 // = 18 cents, better than the next biggest coin, 20 BOOST_CHECK(testWallet.SelectCoinsMinConf( 16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // now we have 5+6+7+8+18+20+30 add_coin(testWallet, 18 * CENT); // and now if we try making 16 cents again, the smaller coins can make // 5+6+7 = 18 cents, the same as the next biggest coin, 18 BOOST_CHECK(testWallet.SelectCoinsMinConf( 16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 18 in 1 coin BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // because in the event of a tie, the biggest coin wins BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now try making 11 cents. we should get 5+6 BOOST_CHECK(testWallet.SelectCoinsMinConf( 11 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // check that the smallest bigger coin is used add_coin(testWallet, 1 * COIN); add_coin(testWallet, 2 * COIN); add_coin(testWallet, 3 * COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents add_coin(testWallet, 4 * COIN); BOOST_CHECK(testWallet.SelectCoinsMinConf( 95 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 1 BCH in 1 coin BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); BOOST_CHECK(testWallet.SelectCoinsMinConf( 195 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 2 BCH in 1 coin BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // empty the wallet and start again, now with fractions of a cent, to // test small change avoidance empty_wallet(); add_coin(testWallet, 1 * MIN_CHANGE / 10); add_coin(testWallet, 2 * MIN_CHANGE / 10); add_coin(testWallet, 3 * MIN_CHANGE / 10); add_coin(testWallet, 4 * MIN_CHANGE / 10); add_coin(testWallet, 5 * MIN_CHANGE / 10); // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE // we'll get change smaller than MIN_CHANGE whatever happens, so can // expect MIN_CHANGE exactly BOOST_CHECK(testWallet.SelectCoinsMinConf( MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // but if we add a bigger coin, small change is avoided add_coin(testWallet, 1111 * MIN_CHANGE); // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // if we add more small coins: add_coin(testWallet, 6 * MIN_CHANGE / 10); add_coin(testWallet, 7 * MIN_CHANGE / 10); // and try again to make 1.0 * MIN_CHANGE BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // run the 'mtgox' test (see // http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) // they tried to consolidate 10 50k coins into one 500k coin, and ended // up with 50k in change empty_wallet(); for (int j = 0; j < 20; j++) { add_coin(testWallet, 50000 * COIN); } BOOST_CHECK(testWallet.SelectCoinsMinConf( 500000 * COIN, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // in ten coins BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // if there's not enough in the smaller coins to make at least 1 * // MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), we need to try finding an // exact subset anyway // sometimes it will fail, and so we use the next biggest coin: empty_wallet(); add_coin(testWallet, 5 * MIN_CHANGE / 10); add_coin(testWallet, 6 * MIN_CHANGE / 10); add_coin(testWallet, 7 * MIN_CHANGE / 10); add_coin(testWallet, 1111 * MIN_CHANGE); BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we get the bigger coin BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = // 1.0) empty_wallet(); add_coin(testWallet, 4 * MIN_CHANGE / 10); add_coin(testWallet, 6 * MIN_CHANGE / 10); add_coin(testWallet, 8 * MIN_CHANGE / 10); add_coin(testWallet, 1111 * MIN_CHANGE); BOOST_CHECK(testWallet.SelectCoinsMinConf( MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // in two coins 0.4+0.6 BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // test avoiding small change empty_wallet(); add_coin(testWallet, 5 * MIN_CHANGE / 100); add_coin(testWallet, 1 * MIN_CHANGE); add_coin(testWallet, 100 * MIN_CHANGE); // trying to make 100.01 from these three coins BOOST_CHECK(testWallet.SelectCoinsMinConf( 10001 * MIN_CHANGE / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get all coins BOOST_CHECK_EQUAL(nValueRet, 10105 * MIN_CHANGE / 100); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // but if we try to make 99.9, we should take the bigger of the two // small coins to avoid small change BOOST_CHECK(testWallet.SelectCoinsMinConf( 9990 * MIN_CHANGE / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); } // test with many inputs for (Amount amt = 1500 * SATOSHI; amt < COIN; amt = 10 * amt) { empty_wallet(); // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 // bytes per input) for (uint16_t j = 0; j < 676; j++) { add_coin(testWallet, amt); } // We only create the wallet once to save time, but we still run the // coin selection RUN_TESTS times. for (int i = 0; i < RUN_TESTS; i++) { BOOST_CHECK(testWallet.SelectCoinsMinConf( 2000 * SATOSHI, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); if (amt - 2000 * SATOSHI < MIN_CHANGE) { // needs more than one input: uint16_t returnSize = std::ceil( (2000.0 + (MIN_CHANGE / SATOSHI)) / (amt / SATOSHI)); Amount returnValue = returnSize * amt; BOOST_CHECK_EQUAL(nValueRet, returnValue); BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize); } else { // one input is sufficient: BOOST_CHECK_EQUAL(nValueRet, amt); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); } } } // test randomness { empty_wallet(); for (int i2 = 0; i2 < 100; i2++) { add_coin(testWallet, COIN); } // Again, we only create the wallet once to save time, but we still run // the coin selection RUN_TESTS times. for (int i = 0; i < RUN_TESTS; i++) { // picking 50 from 100 coins doesn't depend on the shuffle, but does // depend on randomness in the stochastic approximation code BOOST_CHECK(testWallet.SelectCoinsMinConf( 50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(testWallet.SelectCoinsMinConf( 50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2)); int fails = 0; for (int j = 0; j < RANDOM_REPEATS; j++) { // selecting 1 from 100 identical coins depends on the shuffle; // this test will fail 1% of the time run the test // RANDOM_REPEATS times and only complain if all of them fail BOOST_CHECK(testWallet.SelectCoinsMinConf( COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(testWallet.SelectCoinsMinConf( COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used)); if (equal_sets(setCoinsRet, setCoinsRet2)) { fails++; } } BOOST_CHECK_NE(fails, RANDOM_REPEATS); } // add 75 cents in small change. not enough to make 90 cents, then // try making 90 cents. there are multiple competing "smallest // bigger" coins, one of which should be picked at random add_coin(testWallet, 5 * CENT); add_coin(testWallet, 10 * CENT); add_coin(testWallet, 15 * CENT); add_coin(testWallet, 20 * CENT); add_coin(testWallet, 25 * CENT); for (int i = 0; i < RUN_TESTS; i++) { int fails = 0; for (int j = 0; j < RANDOM_REPEATS; j++) { // selecting 1 from 100 identical coins depends on the shuffle; // this test will fail 1% of the time run the test // RANDOM_REPEATS times and only complain if all of them fail BOOST_CHECK(testWallet.SelectCoinsMinConf( 90 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(testWallet.SelectCoinsMinConf( 90 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used)); if (equal_sets(setCoinsRet, setCoinsRet2)) { fails++; } } BOOST_CHECK_NE(fails, RANDOM_REPEATS); } } empty_wallet(); } BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { CoinSet setCoinsRet; Amount nValueRet; bool bnb_used; LOCK(m_wallet.cs_wallet); empty_wallet(); // Test vValue sort order for (int i = 0; i < 1000; i++) { add_coin(m_wallet, 1000 * COIN); } add_coin(m_wallet, 3 * COIN); BOOST_CHECK(m_wallet.SelectCoinsMinConf( 1003 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); empty_wallet(); } // Tests that with the ideal conditions, the coin selector will always be able // to find a solution that can pay the target value BOOST_AUTO_TEST_CASE(SelectCoins_test) { auto testChain = interfaces::MakeChain(); - CWallet testWallet(Params(), *testChain, WalletLocation(), + CWallet testWallet(Params(), testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); // Random generator stuff std::default_random_engine generator; std::exponential_distribution distribution(100); FastRandomContext rand; // Run this test 100 times for (int i = 0; i < 100; ++i) { empty_wallet(); // Make a wallet with 1000 exponentially distributed random inputs for (int j = 0; j < 1000; ++j) { add_coin(testWallet, int64_t(10000000 * distribution(generator)) * SATOSHI); } // Generate a random fee rate in the range of 100 - 400 CFeeRate rate(int64_t(rand.randrange(300) + 100) * SATOSHI); // Generate a random target value between 1000 and wallet balance Amount target = int64_t(rand.randrange(balance / SATOSHI - 1000) + 1000) * SATOSHI; // Perform selection CoinSelectionParams coin_selection_params_knapsack( false, 34, 148, CFeeRate(Amount::zero()), 0); CoinSelectionParams coin_selection_params_bnb( true, 34, 148, CFeeRate(Amount::zero()), 0); CoinSet out_set; Amount out_value = Amount::zero(); bool bnb_used = false; BOOST_CHECK(testWallet.SelectCoinsMinConf( target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_bnb, bnb_used) || testWallet.SelectCoinsMinConf( target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_knapsack, bnb_used)); BOOST_CHECK_GE(out_value, target); } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index e701dc8a2..a45a5e9b3 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -1,23 +1,24 @@ // Copyright (c) 2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include WalletTestingSetup::WalletTestingSetup(const std::string &chainName) - : TestingSetup(chainName), m_wallet(Params(), *m_chain, WalletLocation(), - WalletDatabase::CreateMock()) { + : TestingSetup(chainName), + m_wallet(Params(), m_chain.get(), WalletLocation(), + WalletDatabase::CreateMock()) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); m_wallet.m_chain_notifications_handler = m_chain->handleNotifications(m_wallet); m_chain_client->registerRpcs(); } diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 0a073f570..41e61e5e5 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -1,496 +1,500 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static void AddKey(CWallet &wallet, const CKey &key) { LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(key, key.GetPubKey()); } 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. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions accommodates a null start block. { - CWallet wallet(Params(), *chain, WalletLocation(), + CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( BlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, Amount::zero()); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(Params(), *chain, WalletLocation(), + CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 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(Params(), *chain, WalletLocation(), + CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); } // Prune the remaining block file. PruneOneBlockFile(newTip->GetBlockPos().nFile); UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions scans no blocks. { - CWallet wallet(Params(), *chain, WalletLocation(), + CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, Amount::zero()); } } BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // 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 wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr wallet = + std::make_shared(Params(), chain.get(), 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(GetConfig(), 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); } } // Verify importwallet RPC starts rescan at earliest block with timestamp // greater or equal than key birthday. Previously there was a bug where // importwallet RPC would start the scan at the latest block with timestamp less // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5; SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); // Set key birthday to block time increased by the timestamp window, so // rescan will start at the block time. const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW; SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); auto locked_chain = chain->lock(); std::string backup_file = (GetDataDir() / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { - std::shared_ptr wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr wallet = + std::make_shared(Params(), chain.get(), WalletLocation(), + WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); ::dumpwallet(GetConfig(), request); RemoveWallet(wallet); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - std::shared_ptr wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr wallet = + std::make_shared(Params(), chain.get(), WalletLocation(), + WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); ::importwallet(GetConfig(), request); RemoveWallet(wallet); LOCK(wallet->cs_wallet); BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); for (size_t i = 0; i < m_coinbase_txns.size(); ++i) { bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetId()); bool expected = i >= 100; BOOST_CHECK_EQUAL(found, expected); } } } // Check that GetImmatureCredit() returns a newly calculated value instead of // the cached value after a MarkDirty() call. // // This is a regression test written to verify a bugfix for the immature credit // function. Similar tests probably should be written for the other credit and // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { auto chain = interfaces::MakeChain(); - CWallet wallet(Params(), *chain, WalletLocation(), + CWallet wallet(Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LOCK(wallet.cs_wallet); wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash(); wtx.nIndex = 0; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), Amount::zero()); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50 * COIN); } static int64_t AddTx(CWallet &wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) { CMutableTransaction tx; tx.nLockTime = lockTime; SetMockTime(mockTime); CBlockIndex *block = nullptr; if (blockTime > 0) { LockAnnotation lock(::cs_main); auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(BlockHash(GetRandHash()), new CBlockIndex); assert(inserted.second); const BlockHash &hash = inserted.first->first; block = inserted.first->second; block->nTime = blockTime; block->phashBlock = &hash; } CWalletTx wtx(&wallet, MakeTransactionRef(tx)); if (block) { wtx.SetMerkleBranch(block->GetBlockHash(), 0); } { LOCK(cs_main); wallet.AddToWallet(wtx); } LOCK(wallet.cs_wallet); return wallet.mapWallet.at(wtx.GetId()).nTimeSmart; } // Simple test to verify assignment of CWalletTx::nSmartTime value. Could be // expanded to cover more corner cases of smart time logic. BOOST_AUTO_TEST_CASE(ComputeTimeSmart) { // New transaction should use clock time if lower than block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100); // Test that updating existing transaction does not change smart time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100); // New transaction should use clock time if there's no block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300); // New transaction should use block time if lower than clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400); // New transaction should use latest entry time if higher than // min(block time, clock time). BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400); // If there are future entries, new transaction should use time of the // newest entry that is no more than 300 seconds ahead of the clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300); // Reset mock time for other tests. SetMockTime(0); } BOOST_AUTO_TEST_CASE(LoadReceiveRequests) { CTxDestination dest = CKeyID(); LOCK(m_wallet.cs_wallet); m_wallet.AddDestData(dest, "misc", "val_misc"); m_wallet.AddDestData(dest, "rr0", "val_rr0"); m_wallet.AddDestData(dest, "rr1", "val_rr1"); auto values = m_wallet.GetDestValues("rr"); BOOST_CHECK_EQUAL(values.size(), 2U); BOOST_CHECK_EQUAL(values[0], "val_rr0"); BOOST_CHECK_EQUAL(values[1], "val_rr1"); } class ListCoinsTestingSetup : public TestChain100Setup { public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = std::make_unique(Params(), *m_chain, WalletLocation(), - WalletDatabase::CreateMock()); + wallet = + std::make_unique(Params(), m_chain.get(), WalletLocation(), + WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions( ::ChainActive().Genesis()->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, ::ChainActive().Height()); BOOST_CHECK(result.last_failed_block.IsNull()); } ~ListCoinsTestingSetup() { wallet.reset(); } CWalletTx &AddTx(CRecipient recipient) { CTransactionRef tx; CReserveKey reservekey(wallet.get()); Amount fee; int changePos = -1; std::string error; CCoinControl dummy; { auto locked_chain = m_chain->lock(); BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); } CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetId()).tx); } CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetId()); BOOST_CHECK(it != wallet->mapWallet.end()); it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } std::unique_ptr m_chain = interfaces::MakeChain(); std::unique_ptr wallet; }; BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey // address. std::map> list; { auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); list = wallet->ListCoins(*locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U); // Check initial balance from one mature coinbase transaction. BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); // Add a transaction creating a change address, and confirm ListCoins still // returns the coin associated with the change address underneath the // coinbaseKey pubkey, even though the change address has a different // pubkey. AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); list = wallet->ListCoins(*locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); // Lock both coins. Confirm number of available coins drops to 0. { auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); std::vector available; wallet->AvailableCoins(*locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto &group : list) { for (const auto &coin : group.second) { LOCK(wallet->cs_wallet); wallet->LockCoin(COutPoint(coin.tx->GetId(), coin.i)); } } { auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); std::vector available; wallet->AvailableCoins(*locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); list = wallet->ListCoins(*locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); } BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { auto chain = interfaces::MakeChain(); std::shared_ptr wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); + Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/walletdb_tests.cpp b/src/wallet/test/walletdb_tests.cpp index 8fbfde3fb..664b9ffee 100644 --- a/src/wallet/test/walletdb_tests.cpp +++ b/src/wallet/test/walletdb_tests.cpp @@ -1,123 +1,123 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include namespace { static std::unique_ptr LoadWallet(WalletBatch &batch) { auto chain = interfaces::MakeChain(); std::unique_ptr wallet = std::make_unique( - Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); + Params(), chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); DBErrors res = batch.LoadWallet(wallet.get()); BOOST_CHECK(res == DBErrors::LOAD_OK); return wallet; } } // namespace BOOST_FIXTURE_TEST_SUITE(walletdb_tests, WalletTestingSetup) BOOST_AUTO_TEST_CASE(write_erase_name) { WalletBatch batch(m_wallet.GetDBHandle(), "cr+"); CTxDestination dst1 = CKeyID(uint160S("c0ffee")); CTxDestination dst2 = CKeyID(uint160S("f00d")); BOOST_CHECK(batch.WriteName(dst1, "name1")); BOOST_CHECK(batch.WriteName(dst2, "name2")); { auto w = LoadWallet(batch); BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst1)); BOOST_CHECK_EQUAL("name1", w->mapAddressBook[dst1].name); BOOST_CHECK_EQUAL("name2", w->mapAddressBook[dst2].name); } batch.EraseName(dst1); { auto w = LoadWallet(batch); BOOST_CHECK_EQUAL(0, w->mapAddressBook.count(dst1)); BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst2)); } } BOOST_AUTO_TEST_CASE(write_erase_purpose) { WalletBatch batch(m_wallet.GetDBHandle(), "cr+"); CTxDestination dst1 = CKeyID(uint160S("c0ffee")); CTxDestination dst2 = CKeyID(uint160S("f00d")); BOOST_CHECK(batch.WritePurpose(dst1, "purpose1")); BOOST_CHECK(batch.WritePurpose(dst2, "purpose2")); { auto w = LoadWallet(batch); BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst1)); BOOST_CHECK_EQUAL("purpose1", w->mapAddressBook[dst1].purpose); BOOST_CHECK_EQUAL("purpose2", w->mapAddressBook[dst2].purpose); } batch.ErasePurpose(dst1); { auto w = LoadWallet(batch); BOOST_CHECK_EQUAL(0, w->mapAddressBook.count(dst1)); BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst2)); } } BOOST_AUTO_TEST_CASE(write_erase_destdata) { WalletBatch batch(m_wallet.GetDBHandle(), "cr+"); CTxDestination dst1 = CKeyID(uint160S("c0ffee")); CTxDestination dst2 = CKeyID(uint160S("f00d")); BOOST_CHECK(batch.WriteDestData(dst1, "key1", "value1")); BOOST_CHECK(batch.WriteDestData(dst1, "key2", "value2")); BOOST_CHECK(batch.WriteDestData(dst2, "key1", "value3")); BOOST_CHECK(batch.WriteDestData(dst2, "key2", "value4")); { auto w = LoadWallet(batch); std::string val; BOOST_CHECK(w->GetDestData(dst1, "key1", &val)); BOOST_CHECK_EQUAL("value1", val); BOOST_CHECK(w->GetDestData(dst1, "key2", &val)); BOOST_CHECK_EQUAL("value2", val); BOOST_CHECK(w->GetDestData(dst2, "key1", &val)); BOOST_CHECK_EQUAL("value3", val); BOOST_CHECK(w->GetDestData(dst2, "key2", &val)); BOOST_CHECK_EQUAL("value4", val); } batch.EraseDestData(dst1, "key2"); { auto w = LoadWallet(batch); std::string dummy; BOOST_CHECK(w->GetDestData(dst1, "key1", &dummy)); BOOST_CHECK(!w->GetDestData(dst1, "key2", &dummy)); BOOST_CHECK(w->GetDestData(dst2, "key1", &dummy)); BOOST_CHECK(w->GetDestData(dst2, "key2", &dummy)); } } BOOST_AUTO_TEST_CASE(no_dest_fails) { WalletBatch batch(m_wallet.GetDBHandle(), "cr+"); CTxDestination dst = CNoDestination{}; BOOST_CHECK(!batch.WriteName(dst, "name")); BOOST_CHECK(!batch.WritePurpose(dst, "purpose")); BOOST_CHECK(!batch.WriteDestData(dst, "key", "value")); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 669b7afbb..5b3b278f9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,4926 +1,4925 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include