Initial Vexta Core v0.1.0 release
This commit is contained in:
commit
25c454329d
2481 changed files with 689289 additions and 0 deletions
137
src/wallet/test/accounting_tests.cpp
Normal file
137
src/wallet/test/accounting_tests.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// Copyright (c) 2009-2019 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2019 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <wallet/test/wallet_test_fixture.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup)
|
||||
|
||||
static void
|
||||
GetResults(CWallet& wallet, std::map<CAmount, CAccountingEntry>& results)
|
||||
{
|
||||
std::list<CAccountingEntry> aes;
|
||||
|
||||
results.clear();
|
||||
BOOST_CHECK(wallet.ReorderTransactions() == DBErrors::LOAD_OK);
|
||||
wallet.ListAccountCreditDebit("", aes);
|
||||
for (CAccountingEntry& ae : aes)
|
||||
{
|
||||
results[ae.nOrderPos] = ae;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(acc_orderupgrade)
|
||||
{
|
||||
std::vector<CWalletTx*> vpwtx;
|
||||
CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
|
||||
CAccountingEntry ae;
|
||||
std::map<CAmount, CAccountingEntry> results;
|
||||
|
||||
LOCK(m_wallet.cs_wallet);
|
||||
|
||||
ae.strAccount = "";
|
||||
ae.nCreditDebit = 1;
|
||||
ae.nTime = 1333333333;
|
||||
ae.strOtherAccount = "b";
|
||||
ae.strComment = "";
|
||||
m_wallet.AddAccountingEntry(ae);
|
||||
|
||||
wtx.mapValue["comment"] = "z";
|
||||
m_wallet.AddToWallet(wtx);
|
||||
vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
|
||||
vpwtx[0]->nTimeReceived = (unsigned int)1333333335;
|
||||
vpwtx[0]->nOrderPos = -1;
|
||||
|
||||
ae.nTime = 1333333336;
|
||||
ae.strOtherAccount = "c";
|
||||
m_wallet.AddAccountingEntry(ae);
|
||||
|
||||
GetResults(m_wallet, results);
|
||||
|
||||
BOOST_CHECK(m_wallet.nOrderPosNext == 3);
|
||||
BOOST_CHECK(2 == results.size());
|
||||
BOOST_CHECK(results[0].nTime == 1333333333);
|
||||
BOOST_CHECK(results[0].strComment.empty());
|
||||
BOOST_CHECK(1 == vpwtx[0]->nOrderPos);
|
||||
BOOST_CHECK(results[2].nTime == 1333333336);
|
||||
BOOST_CHECK(results[2].strOtherAccount == "c");
|
||||
|
||||
|
||||
ae.nTime = 1333333330;
|
||||
ae.strOtherAccount = "d";
|
||||
ae.nOrderPos = m_wallet.IncOrderPosNext();
|
||||
m_wallet.AddAccountingEntry(ae);
|
||||
|
||||
GetResults(m_wallet, results);
|
||||
|
||||
BOOST_CHECK(results.size() == 3);
|
||||
BOOST_CHECK(m_wallet.nOrderPosNext == 4);
|
||||
BOOST_CHECK(results[0].nTime == 1333333333);
|
||||
BOOST_CHECK(1 == vpwtx[0]->nOrderPos);
|
||||
BOOST_CHECK(results[2].nTime == 1333333336);
|
||||
BOOST_CHECK(results[3].nTime == 1333333330);
|
||||
BOOST_CHECK(results[3].strComment.empty());
|
||||
|
||||
|
||||
wtx.mapValue["comment"] = "y";
|
||||
{
|
||||
CMutableTransaction tx(*wtx.tx);
|
||||
++tx.nLockTime; // Just to change the hash :)
|
||||
wtx.SetTx(MakeTransactionRef(std::move(tx)));
|
||||
}
|
||||
m_wallet.AddToWallet(wtx);
|
||||
vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
|
||||
vpwtx[1]->nTimeReceived = (unsigned int)1333333336;
|
||||
|
||||
wtx.mapValue["comment"] = "x";
|
||||
{
|
||||
CMutableTransaction tx(*wtx.tx);
|
||||
++tx.nLockTime; // Just to change the hash :)
|
||||
wtx.SetTx(MakeTransactionRef(std::move(tx)));
|
||||
}
|
||||
m_wallet.AddToWallet(wtx);
|
||||
vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
|
||||
vpwtx[2]->nTimeReceived = (unsigned int)1333333329;
|
||||
vpwtx[2]->nOrderPos = -1;
|
||||
|
||||
GetResults(m_wallet, results);
|
||||
|
||||
BOOST_CHECK(results.size() == 3);
|
||||
BOOST_CHECK(m_wallet.nOrderPosNext == 6);
|
||||
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
|
||||
BOOST_CHECK(results[1].nTime == 1333333333);
|
||||
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
|
||||
BOOST_CHECK(results[3].nTime == 1333333336);
|
||||
BOOST_CHECK(results[4].nTime == 1333333330);
|
||||
BOOST_CHECK(results[4].strComment.empty());
|
||||
BOOST_CHECK(5 == vpwtx[1]->nOrderPos);
|
||||
|
||||
|
||||
ae.nTime = 1333333334;
|
||||
ae.strOtherAccount = "e";
|
||||
ae.nOrderPos = -1;
|
||||
m_wallet.AddAccountingEntry(ae);
|
||||
|
||||
GetResults(m_wallet, results);
|
||||
|
||||
BOOST_CHECK(results.size() == 4);
|
||||
BOOST_CHECK(m_wallet.nOrderPosNext == 7);
|
||||
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
|
||||
BOOST_CHECK(results[1].nTime == 1333333333);
|
||||
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
|
||||
BOOST_CHECK(results[3].nTime == 1333333336);
|
||||
BOOST_CHECK(results[3].strComment.empty());
|
||||
BOOST_CHECK(results[4].nTime == 1333333330);
|
||||
BOOST_CHECK(results[4].strComment.empty());
|
||||
BOOST_CHECK(results[5].nTime == 1333333334);
|
||||
BOOST_CHECK(6 == vpwtx[1]->nOrderPos);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
654
src/wallet/test/coinselector_tests.cpp
Normal file
654
src/wallet/test/coinselector_tests.cpp
Normal file
|
|
@ -0,0 +1,654 @@
|
|||
// Copyright (c) 2017-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2017-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <amount.h>
|
||||
#include <node/context.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <random.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <wallet/coinselection.h>
|
||||
#include <wallet/test/wallet_test_fixture.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <random>
|
||||
|
||||
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
|
||||
|
||||
typedef std::set<CInputCoin> CoinSet;
|
||||
|
||||
static std::vector<COutput> vCoins;
|
||||
static NodeContext testNode;
|
||||
static auto testChain = interfaces::MakeChain(testNode);
|
||||
static CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
|
||||
static CAmount balance = 0;
|
||||
|
||||
CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||
CoinEligibilityFilter filter_confirmed(1, 1, 0);
|
||||
CoinEligibilityFilter filter_standard_extra(6, 6, 0);
|
||||
CoinSelectionParams coin_selection_params(/* change_output_size= */ 0,
|
||||
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_no_inputs_size= */ 0, /* avoid_partial= */ false);
|
||||
|
||||
static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
tx.vout.resize(nInput + 1);
|
||||
tx.vout[nInput].nValue = nValue;
|
||||
set.emplace_back(MakeTransactionRef(tx), nInput);
|
||||
}
|
||||
|
||||
static void add_coin(const CAmount& 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 CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
|
||||
{
|
||||
balance += nValue;
|
||||
static int nextLockTime = 0;
|
||||
CMutableTransaction tx;
|
||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||
tx.vout.resize(nInput + 1);
|
||||
tx.vout[nInput].nValue = nValue;
|
||||
if (spendable) {
|
||||
CTxDestination dest;
|
||||
std::string error;
|
||||
const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
|
||||
assert(destination_ok);
|
||||
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
|
||||
}
|
||||
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);
|
||||
}
|
||||
CWalletTx* wtx = wallet.AddToWallet(MakeTransactionRef(std::move(tx)), /* confirm= */ {});
|
||||
if (fIsFromMe)
|
||||
{
|
||||
wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
|
||||
wtx->m_is_cache_empty = false;
|
||||
}
|
||||
COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
|
||||
vCoins.push_back(output);
|
||||
}
|
||||
static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
|
||||
{
|
||||
add_coin(testWallet, nValue, nAge, fIsFromMe, nInput, spendable);
|
||||
}
|
||||
|
||||
static void empty_wallet(void)
|
||||
{
|
||||
vCoins.clear();
|
||||
balance = 0;
|
||||
}
|
||||
|
||||
static bool equal_sets(CoinSet a, CoinSet b)
|
||||
{
|
||||
std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
|
||||
return ret.first == a.end() && ret.second == b.end();
|
||||
}
|
||||
|
||||
static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
|
||||
{
|
||||
utxo_pool.clear();
|
||||
CAmount target = 0;
|
||||
for (int i = 0; i < utxos; ++i) {
|
||||
target += (CAmount)1 << (utxos+i);
|
||||
add_coin((CAmount)1 << (utxos+i), 2*i, utxo_pool);
|
||||
add_coin(((CAmount)1 << (utxos+i)) + ((CAmount)1 << (utxos-1-i)), 2*i + 1, utxo_pool);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
|
||||
{
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups.clear();
|
||||
for (auto& coin : coins) {
|
||||
static_groups.emplace_back();
|
||||
static_groups.back().Insert(coin, 0, true, 0, 0, false);
|
||||
}
|
||||
return static_groups;
|
||||
}
|
||||
|
||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
|
||||
{
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups.clear();
|
||||
for (auto& coin : coins) {
|
||||
static_groups.emplace_back();
|
||||
static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
|
||||
}
|
||||
return static_groups;
|
||||
}
|
||||
|
||||
// Branch and bound coin selection tests
|
||||
BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
{
|
||||
|
||||
LOCK(testWallet.cs_wallet);
|
||||
testWallet.SetupLegacyScriptPubKeyMan();
|
||||
|
||||
// Setup
|
||||
std::vector<CInputCoin> utxo_pool;
|
||||
CoinSet selection;
|
||||
CoinSet actual_selection;
|
||||
CAmount value_ret = 0;
|
||||
|
||||
/////////////////////////
|
||||
// Known Outcome tests //
|
||||
/////////////////////////
|
||||
|
||||
// Empty utxo pool
|
||||
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret));
|
||||
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, 0.5 * CENT, selection, value_ret));
|
||||
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, 0.5 * CENT, selection, value_ret));
|
||||
BOOST_CHECK(equal_sets(selection, actual_selection));
|
||||
BOOST_CHECK_EQUAL(value_ret, 2 * CENT);
|
||||
actual_selection.clear();
|
||||
selection.clear();
|
||||
|
||||
// Select 5 Cent
|
||||
add_coin(4 * CENT, 4, actual_selection);
|
||||
add_coin(1 * CENT, 1, actual_selection);
|
||||
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT, selection, value_ret));
|
||||
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, 0.5 * CENT, selection, value_ret));
|
||||
actual_selection.clear();
|
||||
selection.clear();
|
||||
|
||||
// Cost of change is greater than the difference between target value and utxo sum
|
||||
add_coin(1 * CENT, 1, actual_selection);
|
||||
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0.5 * CENT, selection, value_ret));
|
||||
BOOST_CHECK_EQUAL(value_ret, 1 * CENT);
|
||||
BOOST_CHECK(equal_sets(selection, actual_selection));
|
||||
actual_selection.clear();
|
||||
selection.clear();
|
||||
|
||||
// Cost of change is less than the difference between target value and utxo sum
|
||||
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0, selection, value_ret));
|
||||
actual_selection.clear();
|
||||
selection.clear();
|
||||
|
||||
// Select 10 Cent
|
||||
add_coin(5 * CENT, 5, utxo_pool);
|
||||
add_coin(5 * CENT, 5, actual_selection);
|
||||
add_coin(4 * CENT, 4, actual_selection);
|
||||
add_coin(1 * CENT, 1, actual_selection);
|
||||
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT, selection, value_ret));
|
||||
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, selection, value_ret));
|
||||
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), 0.25 * CENT, 0.5 * CENT, selection, value_ret));
|
||||
actual_selection.clear();
|
||||
selection.clear();
|
||||
|
||||
// Iteration exhaustion test
|
||||
CAmount target = make_hard_case(17, utxo_pool);
|
||||
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0, selection, value_ret)); // Should exhaust
|
||||
target = make_hard_case(14, utxo_pool);
|
||||
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), target, 0, selection, value_ret)); // Should not exhaust
|
||||
|
||||
// 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, selection, value_ret));
|
||||
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));
|
||||
}
|
||||
|
||||
// Make sure that effective value is working in AttemptSelection when BnB is used
|
||||
CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0,
|
||||
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000),
|
||||
/* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000),
|
||||
/* tx_no_inputs_size= */ 0, /* avoid_partial= */ false);
|
||||
CoinSet setCoinsRet;
|
||||
CAmount nValueRet;
|
||||
empty_wallet();
|
||||
add_coin(1);
|
||||
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
||||
BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
|
||||
|
||||
// Test fees subtracted from output:
|
||||
empty_wallet();
|
||||
add_coin(1 * CENT);
|
||||
vCoins.at(0).nInputBytes = 40;
|
||||
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
||||
BOOST_CHECK(testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
||||
|
||||
// Make sure that can use BnB when there are preset inputs
|
||||
empty_wallet();
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
wallet->SetupLegacyScriptPubKeyMan();
|
||||
LOCK(wallet->cs_wallet);
|
||||
add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true);
|
||||
add_coin(*wallet, 3 * CENT, 6 * 24, false, 0, true);
|
||||
add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true);
|
||||
CCoinControl coin_control;
|
||||
coin_control.fAllowOtherInputs = true;
|
||||
coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
|
||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
|
||||
BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
{
|
||||
CoinSet setCoinsRet, setCoinsRet2;
|
||||
CAmount nValueRet;
|
||||
|
||||
LOCK(testWallet.cs_wallet);
|
||||
testWallet.SetupLegacyScriptPubKeyMan();
|
||||
|
||||
// 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.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
|
||||
add_coin(1*CENT, 4); // add a new 1 cent coin
|
||||
|
||||
// with a new 1 cent coin, we still can't find a mature 1 cent
|
||||
BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
|
||||
// but we can find a new 1 cent
|
||||
BOOST_CHECK( testWallet.AttemptSelection( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
||||
|
||||
add_coin(2*CENT); // add a mature 2 cent coin
|
||||
|
||||
// we can't make 3 cents of mature coins
|
||||
BOOST_CHECK(!testWallet.AttemptSelection( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
|
||||
// we can make 3 cents of new coins
|
||||
BOOST_CHECK( testWallet.AttemptSelection( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
|
||||
|
||||
add_coin(5*CENT); // add a mature 5 cent coin,
|
||||
add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
|
||||
add_coin(20*CENT); // and a mature 20 cent coin
|
||||
|
||||
// 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.AttemptSelection(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
// we can't even make 37 cents if we don't allow new coins even if they're from us
|
||||
BOOST_CHECK(!testWallet.AttemptSelection(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
// but we can make 37 cents if we accept new coins from ourself
|
||||
BOOST_CHECK( testWallet.AttemptSelection(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
|
||||
// and we can make 38 cents if we accept all new coins
|
||||
BOOST_CHECK( testWallet.AttemptSelection(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
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.AttemptSelection(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
|
||||
|
||||
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
|
||||
BOOST_CHECK( testWallet.AttemptSelection( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
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.AttemptSelection( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
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.AttemptSelection( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
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( 6*CENT);
|
||||
add_coin( 7*CENT);
|
||||
add_coin( 8*CENT);
|
||||
add_coin(20*CENT);
|
||||
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
|
||||
|
||||
// check that we have 71 and not 72
|
||||
BOOST_CHECK( testWallet.AttemptSelection(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK(!testWallet.AttemptSelection(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
|
||||
// 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.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||
|
||||
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
|
||||
|
||||
// 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.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
|
||||
|
||||
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
|
||||
|
||||
// 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.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
|
||||
|
||||
// now try making 11 cents. we should get 5+6
|
||||
BOOST_CHECK( testWallet.AttemptSelection(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
||||
|
||||
// check that the smallest bigger coin is used
|
||||
add_coin( 1*COIN);
|
||||
add_coin( 2*COIN);
|
||||
add_coin( 3*COIN);
|
||||
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
|
||||
BOOST_CHECK( testWallet.AttemptSelection(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 DGB in 1 coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
|
||||
|
||||
BOOST_CHECK( testWallet.AttemptSelection(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 DGB in 1 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(MIN_CHANGE * 1 / 10);
|
||||
add_coin(MIN_CHANGE * 2 / 10);
|
||||
add_coin(MIN_CHANGE * 3 / 10);
|
||||
add_coin(MIN_CHANGE * 4 / 10);
|
||||
add_coin(MIN_CHANGE * 5 / 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.AttemptSelection(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
|
||||
|
||||
// but if we add a bigger coin, small change is avoided
|
||||
add_coin(1111*MIN_CHANGE);
|
||||
|
||||
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
|
||||
BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
|
||||
|
||||
// if we add more small coins:
|
||||
add_coin(MIN_CHANGE * 6 / 10);
|
||||
add_coin(MIN_CHANGE * 7 / 10);
|
||||
|
||||
// and try again to make 1.0 * MIN_CHANGE
|
||||
BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
|
||||
|
||||
// run the 'mtgox' test (see https://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(50000 * COIN);
|
||||
|
||||
BOOST_CHECK( testWallet.AttemptSelection(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
|
||||
|
||||
// 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(MIN_CHANGE * 5 / 10);
|
||||
add_coin(MIN_CHANGE * 6 / 10);
|
||||
add_coin(MIN_CHANGE * 7 / 10);
|
||||
add_coin(1111 * MIN_CHANGE);
|
||||
BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
|
||||
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(MIN_CHANGE * 4 / 10);
|
||||
add_coin(MIN_CHANGE * 6 / 10);
|
||||
add_coin(MIN_CHANGE * 8 / 10);
|
||||
add_coin(1111 * MIN_CHANGE);
|
||||
BOOST_CHECK( testWallet.AttemptSelection(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
|
||||
|
||||
// test avoiding small change
|
||||
empty_wallet();
|
||||
add_coin(MIN_CHANGE * 5 / 100);
|
||||
add_coin(MIN_CHANGE * 1);
|
||||
add_coin(MIN_CHANGE * 100);
|
||||
|
||||
// trying to make 100.01 from these three coins
|
||||
BOOST_CHECK(testWallet.AttemptSelection(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
|
||||
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.AttemptSelection(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
|
||||
}
|
||||
|
||||
// test with many inputs
|
||||
for (CAmount amt=1500; amt < COIN; amt*=10) {
|
||||
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(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.AttemptSelection(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
|
||||
if (amt - 2000 < MIN_CHANGE) {
|
||||
// needs more than one input:
|
||||
uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
|
||||
CAmount returnValue = amt * returnSize;
|
||||
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(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(KnapsackSolver(50 * COIN, GroupCoins(vCoins), setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(KnapsackSolver(50 * COIN, GroupCoins(vCoins), setCoinsRet2, nValueRet));
|
||||
BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
|
||||
|
||||
int fails = 0;
|
||||
for (int j = 0; j < RANDOM_REPEATS; j++)
|
||||
{
|
||||
// Test that the KnapsackSolver selects randomly from equivalent coins (same value and same input size).
|
||||
// When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
|
||||
// which will cause it to fail.
|
||||
// To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
|
||||
BOOST_CHECK(KnapsackSolver(COIN, GroupCoins(vCoins), setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(KnapsackSolver(COIN, GroupCoins(vCoins), setCoinsRet2, nValueRet));
|
||||
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(5 * CENT);
|
||||
add_coin(10 * CENT);
|
||||
add_coin(15 * CENT);
|
||||
add_coin(20 * CENT);
|
||||
add_coin(25 * CENT);
|
||||
|
||||
for (int i = 0; i < RUN_TESTS; i++) {
|
||||
int fails = 0;
|
||||
for (int j = 0; j < RANDOM_REPEATS; j++)
|
||||
{
|
||||
BOOST_CHECK(KnapsackSolver(90*CENT, GroupCoins(vCoins), setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(KnapsackSolver(90*CENT, GroupCoins(vCoins), setCoinsRet2, nValueRet));
|
||||
if (equal_sets(setCoinsRet, setCoinsRet2))
|
||||
fails++;
|
||||
}
|
||||
BOOST_CHECK_NE(fails, RANDOM_REPEATS);
|
||||
}
|
||||
}
|
||||
|
||||
empty_wallet();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
||||
{
|
||||
CoinSet setCoinsRet;
|
||||
CAmount nValueRet;
|
||||
|
||||
LOCK(testWallet.cs_wallet);
|
||||
testWallet.SetupLegacyScriptPubKeyMan();
|
||||
|
||||
empty_wallet();
|
||||
|
||||
// Test vValue sort order
|
||||
for (int i = 0; i < 1000; i++)
|
||||
add_coin(1000 * COIN);
|
||||
add_coin(3 * COIN);
|
||||
|
||||
BOOST_CHECK(testWallet.AttemptSelection(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
|
||||
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)
|
||||
{
|
||||
LOCK(testWallet.cs_wallet);
|
||||
testWallet.SetupLegacyScriptPubKeyMan();
|
||||
|
||||
// Random generator stuff
|
||||
std::default_random_engine generator;
|
||||
std::exponential_distribution<double> 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((CAmount)(distribution(generator)*10000000));
|
||||
}
|
||||
|
||||
// Generate a random fee rate in the range of 100 - 400
|
||||
CFeeRate rate(rand.randrange(300) + 100);
|
||||
|
||||
// Generate a random target value between 1000 and wallet balance
|
||||
CAmount target = rand.randrange(balance - 1000) + 1000;
|
||||
|
||||
// Perform selection
|
||||
CoinSelectionParams cs_params(/* change_output_size= */ 34,
|
||||
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_no_inputs_size= */ 0, /* avoid_partial= */ false);
|
||||
CoinSet out_set;
|
||||
CAmount out_value = 0;
|
||||
CCoinControl cc;
|
||||
BOOST_CHECK(testWallet.SelectCoins(vCoins, target, out_set, out_value, cc, cs_params));
|
||||
BOOST_CHECK_GE(out_value, target);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
83
src/wallet/test/db_tests.cpp
Normal file
83
src/wallet/test/db_tests.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) 2018-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <fs.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <wallet/bdb.h>
|
||||
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
|
||||
|
||||
static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, std::string& database_filename)
|
||||
{
|
||||
fs::path data_file = BDBDataFile(path);
|
||||
database_filename = data_file.filename().string();
|
||||
return GetBerkeleyEnv(data_file.parent_path());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_file)
|
||||
{
|
||||
std::string test_name = "test_name.dat";
|
||||
const fs::path datadir = gArgs.GetDataDirNet();
|
||||
fs::path file_path = datadir / test_name;
|
||||
#if BOOST_VERSION >= 107700
|
||||
std::ofstream f(BOOST_FILESYSTEM_C_STR(file_path));
|
||||
#else
|
||||
std::ofstream f(file_path.BOOST_FILESYSTEM_C_STR);
|
||||
#endif // BOOST_VERSION >= 107700
|
||||
f.close();
|
||||
|
||||
std::string filename;
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
|
||||
BOOST_CHECK_EQUAL(filename, test_name);
|
||||
BOOST_CHECK_EQUAL(env->Directory(), datadir);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_directory)
|
||||
{
|
||||
std::string expected_name = "wallet.dat";
|
||||
const fs::path datadir = gArgs.GetDataDirNet();
|
||||
|
||||
std::string filename;
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
|
||||
BOOST_CHECK_EQUAL(filename, expected_name);
|
||||
BOOST_CHECK_EQUAL(env->Directory(), datadir);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
|
||||
{
|
||||
fs::path datadir = gArgs.GetDataDirNet() / "1";
|
||||
fs::path datadir_2 = gArgs.GetDataDirNet() / "2";
|
||||
std::string filename;
|
||||
|
||||
std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env_3 = GetWalletEnv(datadir_2, filename);
|
||||
|
||||
BOOST_CHECK(env_1 == env_2);
|
||||
BOOST_CHECK(env_2 != env_3);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance)
|
||||
{
|
||||
fs::path datadir = gArgs.GetDataDirNet() / "1";
|
||||
fs::path datadir_2 = gArgs.GetDataDirNet() / "2";
|
||||
std::string filename;
|
||||
|
||||
std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename);
|
||||
env_1_a.reset();
|
||||
|
||||
std::shared_ptr<BerkeleyEnvironment> env_1_b = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env_2_b = GetWalletEnv(datadir_2, filename);
|
||||
|
||||
BOOST_CHECK(env_1_a != env_1_b);
|
||||
BOOST_CHECK(env_2_a == env_2_b);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
54
src/wallet/test/init_test_fixture.cpp
Normal file
54
src/wallet/test/init_test_fixture.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2018-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <fs.h>
|
||||
#include <univalue.h>
|
||||
#include <util/check.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <wallet/test/init_test_fixture.h>
|
||||
|
||||
InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
|
||||
{
|
||||
m_wallet_client = MakeWalletClient(*m_node.chain, *Assert(m_node.args));
|
||||
|
||||
std::string sep;
|
||||
sep += fs::path::preferred_separator;
|
||||
|
||||
m_datadir = gArgs.GetDataDirNet();
|
||||
m_cwd = fs::current_path();
|
||||
|
||||
m_walletdir_path_cases["default"] = m_datadir / "wallets";
|
||||
m_walletdir_path_cases["custom"] = m_datadir / "my_wallets";
|
||||
m_walletdir_path_cases["nonexistent"] = m_datadir / "path_does_not_exist";
|
||||
m_walletdir_path_cases["file"] = m_datadir / "not_a_directory.dat";
|
||||
m_walletdir_path_cases["trailing"] = m_datadir / "wallets" / sep;
|
||||
m_walletdir_path_cases["trailing2"] = m_datadir / "wallets" / sep / sep;
|
||||
|
||||
fs::current_path(m_datadir);
|
||||
m_walletdir_path_cases["relative"] = "wallets";
|
||||
|
||||
fs::create_directories(m_walletdir_path_cases["default"]);
|
||||
fs::create_directories(m_walletdir_path_cases["custom"]);
|
||||
fs::create_directories(m_walletdir_path_cases["relative"]);
|
||||
#if BOOST_VERSION >= 107700
|
||||
std::ofstream f(BOOST_FILESYSTEM_C_STR(m_walletdir_path_cases["file"]));
|
||||
#else
|
||||
std::ofstream f(m_walletdir_path_cases["file"].BOOST_FILESYSTEM_C_STR);
|
||||
#endif // BOOST_VERSION >= 107700
|
||||
f.close();
|
||||
}
|
||||
|
||||
InitWalletDirTestingSetup::~InitWalletDirTestingSetup()
|
||||
{
|
||||
gArgs.LockSettings([&](util::Settings& settings) {
|
||||
settings.forced_settings.erase("walletdir");
|
||||
});
|
||||
fs::current_path(m_cwd);
|
||||
}
|
||||
|
||||
void InitWalletDirTestingSetup::SetWalletDir(const fs::path& walletdir_path)
|
||||
{
|
||||
gArgs.ForceSetArg("-walletdir", walletdir_path.string());
|
||||
}
|
||||
25
src/wallet/test/init_test_fixture.h
Normal file
25
src/wallet/test/init_test_fixture.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2018-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_WALLET_TEST_INIT_TEST_FIXTURE_H
|
||||
#define DIGIBYTE_WALLET_TEST_INIT_TEST_FIXTURE_H
|
||||
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <node/context.h>
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
|
||||
struct InitWalletDirTestingSetup: public BasicTestingSetup {
|
||||
explicit InitWalletDirTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
|
||||
~InitWalletDirTestingSetup();
|
||||
void SetWalletDir(const fs::path& walletdir_path);
|
||||
|
||||
fs::path m_datadir;
|
||||
fs::path m_cwd;
|
||||
std::map<std::string, fs::path> m_walletdir_path_cases;
|
||||
std::unique_ptr<interfaces::WalletClient> m_wallet_client;
|
||||
};
|
||||
|
||||
#endif // DIGIBYTE_WALLET_TEST_INIT_TEST_FIXTURE_H
|
||||
85
src/wallet/test/init_tests.cpp
Normal file
85
src/wallet/test/init_tests.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) 2018-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <noui.h>
|
||||
#include <test/util/logging.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/system.h>
|
||||
#include <wallet/test/init_test_fixture.h>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(init_tests, InitWalletDirTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["default"]);
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == true);
|
||||
fs::path walletdir = gArgs.GetArg("-walletdir", "");
|
||||
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
|
||||
BOOST_CHECK_EQUAL(walletdir, expected_path);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["custom"]);
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == true);
|
||||
fs::path walletdir = gArgs.GetArg("-walletdir", "");
|
||||
fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]);
|
||||
BOOST_CHECK_EQUAL(walletdir, expected_path);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["nonexistent"]);
|
||||
{
|
||||
ASSERT_DEBUG_LOG("does not exist");
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == false);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["file"]);
|
||||
{
|
||||
ASSERT_DEBUG_LOG("is not a directory");
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == false);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["relative"]);
|
||||
{
|
||||
ASSERT_DEBUG_LOG("is a relative path");
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == false);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["trailing"]);
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == true);
|
||||
fs::path walletdir = gArgs.GetArg("-walletdir", "");
|
||||
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
|
||||
BOOST_CHECK_EQUAL(walletdir, expected_path);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2)
|
||||
{
|
||||
SetWalletDir(m_walletdir_path_cases["trailing2"]);
|
||||
bool result = m_wallet_client->verify();
|
||||
BOOST_CHECK(result == true);
|
||||
fs::path walletdir = gArgs.GetArg("-walletdir", "");
|
||||
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
|
||||
BOOST_CHECK_EQUAL(walletdir, expected_path);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
419
src/wallet/test/ismine_tests.cpp
Normal file
419
src/wallet/test/ismine_tests.cpp
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
// Copyright (c) 2017-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <key.h>
|
||||
#include <node/context.h>
|
||||
#include <script/script.h>
|
||||
#include <script/standard.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <wallet/ismine.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(ismine_tests, BasicTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ismine_standard)
|
||||
{
|
||||
CKey keys[2];
|
||||
CPubKey pubkeys[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
keys[i].MakeNewKey(true);
|
||||
pubkeys[i] = keys[i].GetPubKey();
|
||||
}
|
||||
|
||||
CKey uncompressedKey;
|
||||
uncompressedKey.MakeNewKey(false);
|
||||
CPubKey uncompressedPubkey = uncompressedKey.GetPubKey();
|
||||
std::unique_ptr<interfaces::Chain>& chain = m_node.chain;
|
||||
|
||||
CScript scriptPubKey;
|
||||
isminetype result;
|
||||
|
||||
// P2PK compressed
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
scriptPubKey = GetScriptForRawPubKey(pubkeys[0]);
|
||||
|
||||
// Keystore does not have key
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has key
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2PK uncompressed
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey);
|
||||
|
||||
// Keystore does not have key
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has key
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2PKH compressed
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0]));
|
||||
|
||||
// Keystore does not have key
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has key
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2PKH uncompressed
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey));
|
||||
|
||||
// Keystore does not have key
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has key
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2SH
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
CScript redeemScript = GetScriptForDestination(PKHash(pubkeys[0]));
|
||||
scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
|
||||
|
||||
// Keystore does not have redeemScript or key
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has redeemScript but no key
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has redeemScript and key
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// (P2PKH inside) P2SH inside P2SH (invalid)
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
CScript redeemscript_inner = GetScriptForDestination(PKHash(pubkeys[0]));
|
||||
CScript redeemscript = GetScriptForDestination(ScriptHash(redeemscript_inner));
|
||||
scriptPubKey = GetScriptForDestination(ScriptHash(redeemscript));
|
||||
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript_inner));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// (P2PKH inside) P2SH inside P2WSH (invalid)
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
CScript redeemscript = GetScriptForDestination(PKHash(pubkeys[0]));
|
||||
CScript witnessscript = GetScriptForDestination(ScriptHash(redeemscript));
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript));
|
||||
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// P2WPKH inside P2WSH (invalid)
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
CScript witnessscript = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0]));
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript));
|
||||
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// (P2PKH inside) P2WSH inside P2WSH (invalid)
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
CScript witnessscript_inner = GetScriptForDestination(PKHash(pubkeys[0]));
|
||||
CScript witnessscript = GetScriptForDestination(WitnessV0ScriptHash(witnessscript_inner));
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript));
|
||||
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript_inner));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// P2WPKH compressed
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0]));
|
||||
|
||||
// Keystore implicitly has key and P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2WPKH uncompressed
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(uncompressedPubkey));
|
||||
|
||||
// Keystore has key, but no P2SH redeemScript
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has key and P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// scriptPubKey multisig
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
scriptPubKey = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
|
||||
|
||||
// Keystore does not have any keys
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has 1/2 keys
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has 2/2 keys
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has 2/2 keys and the script
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// P2SH multisig
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
|
||||
|
||||
CScript redeemScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
|
||||
scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
|
||||
|
||||
// Keystore has no redeemScript
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2WSH multisig with compressed keys
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
|
||||
|
||||
CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]});
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
|
||||
|
||||
// Keystore has keys, but no witnessScript or P2SH redeemScript
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has keys and witnessScript, but no P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has keys, witnessScript, P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// P2WSH multisig with uncompressed key
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
|
||||
|
||||
CScript witnessScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
|
||||
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
|
||||
|
||||
// Keystore has keys, but no witnessScript or P2SH redeemScript
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has keys and witnessScript, but no P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has keys, witnessScript, P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// P2WSH multisig wrapped in P2SH
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
|
||||
CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]});
|
||||
CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
|
||||
scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
|
||||
|
||||
// Keystore has no witnessScript, P2SH redeemScript, or keys
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has witnessScript and P2SH redeemScript, but no keys
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
|
||||
// Keystore has keys, witnessScript, P2SH redeemScript
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
// OP_RETURN
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
scriptPubKey.clear();
|
||||
scriptPubKey << OP_RETURN << ToByteVector(pubkeys[0]);
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// witness unspendable
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
scriptPubKey.clear();
|
||||
scriptPubKey << OP_0 << ToByteVector(ParseHex("aabb"));
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// witness unknown
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
scriptPubKey.clear();
|
||||
scriptPubKey << OP_16 << ToByteVector(ParseHex("aabb"));
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
|
||||
// Nonstandard
|
||||
{
|
||||
CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
|
||||
keystore.SetupLegacyScriptPubKeyMan();
|
||||
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
|
||||
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
|
||||
|
||||
scriptPubKey.clear();
|
||||
scriptPubKey << OP_9 << OP_ADD << OP_11 << OP_EQUAL;
|
||||
|
||||
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
|
||||
BOOST_CHECK_EQUAL(result, ISMINE_NO);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
152
src/wallet/test/psbt_wallet_tests.cpp
Normal file
152
src/wallet/test/psbt_wallet_tests.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2017-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2017-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <key_io.h>
|
||||
#include <util/bip32.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <wallet/test/wallet_test_fixture.h>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(psbt_updater_test)
|
||||
{
|
||||
auto spk_man = m_wallet.GetOrCreateLegacyScriptPubKeyMan();
|
||||
LOCK2(m_wallet.cs_wallet, spk_man->cs_KeyStore);
|
||||
|
||||
// Create prevtxs and add to wallet
|
||||
CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION);
|
||||
CTransactionRef prev_tx1;
|
||||
s_prev_tx1 >> prev_tx1;
|
||||
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx1));
|
||||
|
||||
CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION);
|
||||
CTransactionRef prev_tx2;
|
||||
s_prev_tx2 >> prev_tx2;
|
||||
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx2));
|
||||
|
||||
// Add scripts
|
||||
CScript rs1;
|
||||
CDataStream s_rs1(ParseHex("475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae"), SER_NETWORK, PROTOCOL_VERSION);
|
||||
s_rs1 >> rs1;
|
||||
spk_man->AddCScript(rs1);
|
||||
|
||||
CScript rs2;
|
||||
CDataStream s_rs2(ParseHex("2200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"), SER_NETWORK, PROTOCOL_VERSION);
|
||||
s_rs2 >> rs2;
|
||||
spk_man->AddCScript(rs2);
|
||||
|
||||
CScript ws1;
|
||||
CDataStream s_ws1(ParseHex("47522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"), SER_NETWORK, PROTOCOL_VERSION);
|
||||
s_ws1 >> ws1;
|
||||
spk_man->AddCScript(ws1);
|
||||
|
||||
// Add hd seed
|
||||
CKey key = DecodeSecret("5KSSJQ7UNfFGwVgpCZDSHm5rVNhMFcFtvWM3zQ8mW4qNDEN7LFd"); // Mainnet and uncompressed form of cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T
|
||||
CPubKey master_pub_key = spk_man->DeriveNewSeed(key);
|
||||
spk_man->SetHDSeed(master_pub_key);
|
||||
spk_man->NewKeyPool();
|
||||
|
||||
// Call FillPSBT
|
||||
PartiallySignedTransaction psbtx;
|
||||
CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssData >> psbtx;
|
||||
|
||||
// Fill transaction with our data
|
||||
bool complete = true;
|
||||
BOOST_REQUIRE_EQUAL(TransactionError::OK, m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false, true));
|
||||
|
||||
// Get the final tx
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << psbtx;
|
||||
std::string final_hex = HexStr(ssTx);
|
||||
BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001008a020000000158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8876500000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000");
|
||||
|
||||
// Mutate the transaction so that one of the inputs is invalid
|
||||
psbtx.tx->vin[0].prevout.n = 2;
|
||||
|
||||
// Try to sign the mutated input
|
||||
SignatureData sigdata;
|
||||
BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_hd_keypath)
|
||||
{
|
||||
std::vector<uint32_t> keypath;
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("///////////////////////////", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1'/1", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("//////////////////////////'/", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("1///////////////////////////", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1'/", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("1/'//////////////////////////", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath(" ", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("0", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("O", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("0000'/0000'/0000'", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("0000,/0000,/0000,", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("01234", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("0x1234", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("1", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath(" 1", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("42", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m42", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("4294967295", keypath)); // 4294967295 == 0xFFFFFFFF (uint32_t max)
|
||||
BOOST_CHECK(!ParseHDKeypath("4294967296", keypath)); // 4294967296 == 0xFFFFFFFF (uint32_t max) + 1
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("n", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("n/", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("n/0", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0'", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m/0''", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0'/0'", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m/'0/0'", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0/0", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("n/0/0", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0/0/00", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m/0/0/f00", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0/0/000000000000000000000000000000000000000000000000000000000000000000000000000000000000", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m/1/1/111111111111111111111111111111111111111111111111111111111111111111111111111111111111", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0/00/0", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m/0'/00/'0", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/1/", keypath));
|
||||
BOOST_CHECK(!ParseHDKeypath("m/1//", keypath));
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/0/4294967295", keypath)); // 4294967295 == 0xFFFFFFFF (uint32_t max)
|
||||
BOOST_CHECK(!ParseHDKeypath("m/0/4294967296", keypath)); // 4294967296 == 0xFFFFFFFF (uint32_t max) + 1
|
||||
|
||||
BOOST_CHECK(ParseHDKeypath("m/4294967295", keypath)); // 4294967295 == 0xFFFFFFFF (uint32_t max)
|
||||
BOOST_CHECK(!ParseHDKeypath("m/4294967296", keypath)); // 4294967296 == 0xFFFFFFFF (uint32_t max) + 1
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
41
src/wallet/test/scriptpubkeyman_tests.cpp
Normal file
41
src/wallet/test/scriptpubkeyman_tests.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <key.h>
|
||||
#include <script/standard.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <wallet/scriptpubkeyman.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup)
|
||||
|
||||
// Test LegacyScriptPubKeyMan::CanProvide behavior, making sure it returns true
|
||||
// for recognized scripts even when keys may not be available for signing.
|
||||
BOOST_AUTO_TEST_CASE(CanProvide)
|
||||
{
|
||||
// Set up wallet and keyman variables.
|
||||
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan();
|
||||
|
||||
// Make a 1 of 2 multisig script
|
||||
std::vector<CKey> keys(2);
|
||||
std::vector<CPubKey> pubkeys;
|
||||
for (CKey& key : keys) {
|
||||
key.MakeNewKey(true);
|
||||
pubkeys.emplace_back(key.GetPubKey());
|
||||
}
|
||||
CScript multisig_script = GetScriptForMultisig(1, pubkeys);
|
||||
CScript p2sh_script = GetScriptForDestination(ScriptHash(multisig_script));
|
||||
SignatureData data;
|
||||
|
||||
// Verify the p2sh(multisig) script is not recognized until the multisig
|
||||
// script is added to the keystore to make it solvable
|
||||
BOOST_CHECK(!keyman.CanProvide(p2sh_script, data));
|
||||
keyman.AddCScript(multisig_script);
|
||||
BOOST_CHECK(keyman.CanProvide(p2sh_script, data));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
127
src/wallet/test/wallet_crypto_tests.cpp
Normal file
127
src/wallet/test/wallet_crypto_tests.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright (c) 2009-2019 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2019 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <wallet/crypter.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(wallet_crypto_tests, BasicTestingSetup)
|
||||
|
||||
class TestCrypter
|
||||
{
|
||||
public:
|
||||
static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
|
||||
const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
|
||||
const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
|
||||
{
|
||||
CCrypter crypt;
|
||||
crypt.SetKeyFromPassphrase(passphrase, vchSalt, rounds, 0);
|
||||
|
||||
if(!correctKey.empty())
|
||||
BOOST_CHECK_MESSAGE(memcmp(crypt.vchKey.data(), correctKey.data(), crypt.vchKey.size()) == 0, \
|
||||
HexStr(crypt.vchKey) + std::string(" != ") + HexStr(correctKey));
|
||||
if(!correctIV.empty())
|
||||
BOOST_CHECK_MESSAGE(memcmp(crypt.vchIV.data(), correctIV.data(), crypt.vchIV.size()) == 0,
|
||||
HexStr(crypt.vchIV) + std::string(" != ") + HexStr(correctIV));
|
||||
}
|
||||
|
||||
static void TestPassphrase(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
|
||||
const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
|
||||
const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
|
||||
{
|
||||
TestPassphraseSingle(vchSalt, passphrase, rounds, correctKey, correctIV);
|
||||
for(SecureString::const_iterator i(passphrase.begin()); i != passphrase.end(); ++i)
|
||||
TestPassphraseSingle(vchSalt, SecureString(i, passphrase.end()), rounds);
|
||||
}
|
||||
|
||||
static void TestDecrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchCiphertext, \
|
||||
const std::vector<unsigned char>& vchPlaintext = std::vector<unsigned char>())
|
||||
{
|
||||
CKeyingMaterial vchDecrypted;
|
||||
crypt.Decrypt(vchCiphertext, vchDecrypted);
|
||||
if (vchPlaintext.size())
|
||||
BOOST_CHECK(CKeyingMaterial(vchPlaintext.begin(), vchPlaintext.end()) == vchDecrypted);
|
||||
}
|
||||
|
||||
static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchPlaintext,
|
||||
const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
|
||||
{
|
||||
std::vector<unsigned char> vchCiphertext;
|
||||
crypt.Encrypt(vchPlaintext, vchCiphertext);
|
||||
|
||||
if (!vchCiphertextCorrect.empty())
|
||||
BOOST_CHECK(vchCiphertext == vchCiphertextCorrect);
|
||||
|
||||
const std::vector<unsigned char> vchPlaintext2(vchPlaintext.begin(), vchPlaintext.end());
|
||||
TestDecrypt(crypt, vchCiphertext, vchPlaintext2);
|
||||
}
|
||||
|
||||
static void TestEncrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchPlaintextIn, \
|
||||
const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
|
||||
{
|
||||
TestEncryptSingle(crypt, CKeyingMaterial(vchPlaintextIn.begin(), vchPlaintextIn.end()), vchCiphertextCorrect);
|
||||
for(std::vector<unsigned char>::const_iterator i(vchPlaintextIn.begin()); i != vchPlaintextIn.end(); ++i)
|
||||
TestEncryptSingle(crypt, CKeyingMaterial(i, vchPlaintextIn.end()));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(passphrase) {
|
||||
// These are expensive.
|
||||
|
||||
TestCrypter::TestPassphrase(ParseHex("0000deadbeef0000"), "test", 25000, \
|
||||
ParseHex("fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"), \
|
||||
ParseHex("cf2f2691526dd1aa220896fb8bf7c369"));
|
||||
|
||||
std::string hash(GetRandHash().ToString());
|
||||
std::vector<unsigned char> vchSalt(8);
|
||||
GetRandBytes(vchSalt.data(), vchSalt.size());
|
||||
uint32_t rounds = InsecureRand32();
|
||||
if (rounds > 30000)
|
||||
rounds = 30000;
|
||||
TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(encrypt) {
|
||||
std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
|
||||
BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
|
||||
CCrypter crypt;
|
||||
crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
|
||||
TestCrypter::TestEncrypt(crypt, ParseHex("22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d"));
|
||||
|
||||
for (int i = 0; i != 100; i++)
|
||||
{
|
||||
uint256 hash(GetRandHash());
|
||||
TestCrypter::TestEncrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(decrypt) {
|
||||
std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
|
||||
BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
|
||||
CCrypter crypt;
|
||||
crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
|
||||
|
||||
// Some corner cases the came up while testing
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("de096f4a8f9bd97db012aa9d90d74de8cdea779c3ee8bc7633d8b5d6da703486"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("32d0a8974e3afd9c6c3ebf4d66aa4e6419f8c173de25947f98cf8b7ace49449c"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("e7c055cca2faa78cb9ac22c9357a90b4778ded9b2cc220a14cea49f931e596ea"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("b88efddd668a6801d19516d6830da4ae9811988ccbaf40df8fbb72f3f4d335fd"));
|
||||
TestCrypter::TestDecrypt(crypt,ParseHex("8cae76aa6a43694e961ebcb28c8ca8f8540b84153d72865e8561ddd93fa7bfa9"));
|
||||
|
||||
for (int i = 0; i != 100; i++)
|
||||
{
|
||||
uint256 hash(GetRandHash());
|
||||
TestCrypter::TestDecrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
15
src/wallet/test/wallet_test_fixture.cpp
Normal file
15
src/wallet/test/wallet_test_fixture.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <wallet/test/wallet_test_fixture.h>
|
||||
|
||||
WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
|
||||
: TestingSetup(chainName),
|
||||
m_wallet(m_node.chain.get(), "", CreateMockWalletDatabase())
|
||||
{
|
||||
m_wallet.LoadWallet();
|
||||
m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} });
|
||||
m_wallet_client->registerRpcs();
|
||||
}
|
||||
29
src/wallet/test/wallet_test_fixture.h
Normal file
29
src/wallet/test/wallet_test_fixture.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2016-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2016-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_WALLET_TEST_WALLET_TEST_FIXTURE_H
|
||||
#define DIGIBYTE_WALLET_TEST_WALLET_TEST_FIXTURE_H
|
||||
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <node/context.h>
|
||||
#include <util/check.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
/** Testing setup and teardown for wallet.
|
||||
*/
|
||||
struct WalletTestingSetup : public TestingSetup {
|
||||
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
|
||||
|
||||
std::unique_ptr<interfaces::WalletClient> m_wallet_client = interfaces::MakeWalletClient(*m_node.chain, *Assert(m_node.args));
|
||||
CWallet m_wallet;
|
||||
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
|
||||
};
|
||||
|
||||
#endif // DIGIBYTE_WALLET_TEST_WALLET_TEST_FIXTURE_H
|
||||
830
src/wallet/test/wallet_tests.cpp
Normal file
830
src/wallet/test/wallet_tests.cpp
Normal file
|
|
@ -0,0 +1,830 @@
|
|||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <any>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <node/context.h>
|
||||
#include <policy/policy.h>
|
||||
#include <rpc/server.h>
|
||||
#include <test/util/logging.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <wallet/test/wallet_test_fixture.h>
|
||||
#include <policy/policy.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <univalue.h>
|
||||
|
||||
RPCHelpMan importmulti();
|
||||
RPCHelpMan dumpwallet();
|
||||
RPCHelpMan importwallet();
|
||||
|
||||
extern RecursiveMutex cs_wallets;
|
||||
|
||||
// Ensure that fee levels defined in the wallet are at least as high
|
||||
// as the default levels for node policy.
|
||||
static_assert(DEFAULT_TRANSACTION_MINFEE >= DEFAULT_MIN_RELAY_TX_FEE, "wallet minimum fee is smaller than default relay fee");
|
||||
static_assert(WALLET_INCREMENTAL_RELAY_FEE >= DEFAULT_INCREMENTAL_RELAY_FEE, "wallet incremental fee is smaller than default incremental relay fee");
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
|
||||
|
||||
static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain* chain)
|
||||
{
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
bilingual_str error;
|
||||
std::vector<bilingual_str> warnings;
|
||||
auto database = MakeWalletDatabase("", options, status, error);
|
||||
auto wallet = CWallet::Create(chain, "", std::move(database), options.create_flags, error, warnings);
|
||||
if (chain) {
|
||||
wallet->postInitProcess();
|
||||
}
|
||||
return wallet;
|
||||
}
|
||||
|
||||
static void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
|
||||
{
|
||||
SyncWithValidationInterfaceQueue();
|
||||
wallet->m_chain_notifications_handler.reset();
|
||||
UnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
static CMutableTransaction TestSimpleSpend(const CTransaction& from, uint32_t index, const CKey& key, const CScript& pubkey)
|
||||
{
|
||||
CMutableTransaction mtx;
|
||||
mtx.vout.push_back({from.vout[index].nValue - DEFAULT_TRANSACTION_MAXFEE, pubkey});
|
||||
mtx.vin.push_back({CTxIn{from.GetHash(), index}});
|
||||
FillableSigningProvider keystore;
|
||||
keystore.AddKey(key);
|
||||
std::map<COutPoint, Coin> coins;
|
||||
coins[mtx.vin[0].prevout].out = from.vout[index];
|
||||
std::map<int, std::string> input_errors;
|
||||
BOOST_CHECK(SignTransaction(mtx, &keystore, coins, SIGHASH_ALL, input_errors));
|
||||
return mtx;
|
||||
}
|
||||
|
||||
static void AddKey(CWallet& wallet, const CKey& key)
|
||||
{
|
||||
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
|
||||
LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
|
||||
spk_man->AddKeyPubKey(key, key.GetPubKey());
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
{
|
||||
// Cap last block file size, and mine new block in a new block file.
|
||||
CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip();
|
||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip();
|
||||
|
||||
// Verify ScanForWalletTransactions fails to read an unknown start block.
|
||||
{
|
||||
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
}
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet);
|
||||
reserver.reserve();
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
|
||||
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, 0);
|
||||
}
|
||||
|
||||
// Verify ScanForWalletTransactions picks up transactions in both the old
|
||||
// and new block files.
|
||||
{
|
||||
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
}
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet);
|
||||
reserver.reserve();
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, 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, 2 * 72000 * COIN);
|
||||
}
|
||||
|
||||
// Prune the older block file.
|
||||
{
|
||||
LOCK(cs_main);
|
||||
Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(oldTip->GetBlockPos().nFile);
|
||||
}
|
||||
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
|
||||
|
||||
// Verify ScanForWalletTransactions only picks transactions in the new block
|
||||
// file.
|
||||
{
|
||||
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
}
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet);
|
||||
reserver.reserve();
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, 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, 72000 * COIN);
|
||||
}
|
||||
|
||||
// Prune the remaining block file.
|
||||
{
|
||||
LOCK(cs_main);
|
||||
Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(newTip->GetBlockPos().nFile);
|
||||
}
|
||||
UnlinkPrunedFiles({newTip->GetBlockPos().nFile});
|
||||
|
||||
// Verify ScanForWalletTransactions scans no blocks.
|
||||
{
|
||||
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
}
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet);
|
||||
reserver.reserve();
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, 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, 0);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
|
||||
{
|
||||
// Cap last block file size, and mine new block in a new block file.
|
||||
CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip();
|
||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip();
|
||||
|
||||
// Prune the older block file.
|
||||
{
|
||||
LOCK(cs_main);
|
||||
Assert(m_node.chainman)->m_blockman.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<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
wallet->SetupLegacyScriptPubKeyMan();
|
||||
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
|
||||
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().HandleRequest(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 vextad 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, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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 = m_node.chainman->ActiveChain().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]);
|
||||
|
||||
std::string backup_file = (gArgs.GetDataDirNet() / "wallet.backup").string();
|
||||
|
||||
// Import key into wallet and call dumpwallet to create backup file.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
{
|
||||
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
|
||||
LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
|
||||
spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
|
||||
spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
|
||||
|
||||
AddWallet(wallet);
|
||||
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
}
|
||||
JSONRPCRequest request;
|
||||
request.params.setArray();
|
||||
request.params.push_back(backup_file);
|
||||
|
||||
::dumpwallet().HandleRequest(request);
|
||||
RemoveWallet(wallet, std::nullopt);
|
||||
}
|
||||
|
||||
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
|
||||
// were scanned, and no prior blocks were scanned.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetupLegacyScriptPubKeyMan();
|
||||
|
||||
JSONRPCRequest request;
|
||||
request.params.setArray();
|
||||
request.params.push_back(backup_file);
|
||||
AddWallet(wallet);
|
||||
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
::importwallet().HandleRequest(request);
|
||||
RemoveWallet(wallet, std::nullopt);
|
||||
|
||||
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]->GetHash());
|
||||
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)
|
||||
{
|
||||
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
|
||||
CWalletTx wtx(&wallet, m_coinbase_txns.back());
|
||||
|
||||
LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
|
||||
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
|
||||
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 0);
|
||||
wtx.m_confirm = confirm;
|
||||
|
||||
// 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(), 0);
|
||||
|
||||
// Invalidate the cached value, add the key, and make sure a new immature
|
||||
// credit amount is calculated.
|
||||
wtx.MarkDirty();
|
||||
BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()));
|
||||
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 72000*COIN);
|
||||
}
|
||||
|
||||
static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
CWalletTx::Confirmation confirm;
|
||||
tx.nLockTime = lockTime;
|
||||
SetMockTime(mockTime);
|
||||
CBlockIndex* block = nullptr;
|
||||
if (blockTime > 0) {
|
||||
LOCK(cs_main);
|
||||
auto inserted = chainman.BlockIndex().emplace(GetRandHash(), new CBlockIndex);
|
||||
assert(inserted.second);
|
||||
const uint256& hash = inserted.first->first;
|
||||
block = inserted.first->second;
|
||||
block->nTime = blockTime;
|
||||
block->phashBlock = &hash;
|
||||
confirm = {CWalletTx::Status::CONFIRMED, block->nHeight, hash, 0};
|
||||
}
|
||||
|
||||
// If transaction is already in map, to avoid inconsistencies, unconfirmation
|
||||
// is needed before confirm again with different block.
|
||||
return wallet.AddToWallet(MakeTransactionRef(tx), confirm, [&](CWalletTx& wtx, bool /* new_tx */) {
|
||||
wtx.setUnconfirmed();
|
||||
return true;
|
||||
})->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_node.chainman, m_wallet, 1, 100, 120), 100);
|
||||
|
||||
// Test that updating existing transaction does not change smart time.
|
||||
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 1, 200, 220), 100);
|
||||
|
||||
// New transaction should use clock time if there's no block time.
|
||||
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 2, 300, 0), 300);
|
||||
|
||||
// New transaction should use block time if lower than clock time.
|
||||
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, 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_node.chainman, 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_node.chainman, m_wallet, 5, 50, 600), 300);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
|
||||
{
|
||||
CTxDestination dest = PKHash();
|
||||
LOCK(m_wallet.cs_wallet);
|
||||
WalletBatch batch{m_wallet.GetDatabase()};
|
||||
m_wallet.SetAddressUsed(batch, dest, true);
|
||||
m_wallet.SetAddressReceiveRequest(batch, dest, "0", "val_rr0");
|
||||
m_wallet.SetAddressReceiveRequest(batch, dest, "1", "val_rr1");
|
||||
|
||||
auto values = m_wallet.GetAddressReceiveRequests();
|
||||
BOOST_CHECK_EQUAL(values.size(), 2U);
|
||||
BOOST_CHECK_EQUAL(values[0], "val_rr0");
|
||||
BOOST_CHECK_EQUAL(values[1], "val_rr1");
|
||||
}
|
||||
|
||||
// Test some watch-only LegacyScriptPubKeyMan methods by the procedure of loading (LoadWatchOnly),
|
||||
// checking (HaveWatchOnly), getting (GetWatchPubKey) and removing (RemoveWatchOnly) a
|
||||
// given PubKey, resp. its corresponding P2PK Script. Results of the the impact on
|
||||
// the address -> PubKey map is dependent on whether the PubKey is a point on the curve
|
||||
static void TestWatchOnlyPubKey(LegacyScriptPubKeyMan* spk_man, const CPubKey& add_pubkey)
|
||||
{
|
||||
CScript p2pk = GetScriptForRawPubKey(add_pubkey);
|
||||
CKeyID add_address = add_pubkey.GetID();
|
||||
CPubKey found_pubkey;
|
||||
LOCK(spk_man->cs_KeyStore);
|
||||
|
||||
// all Scripts (i.e. also all PubKeys) are added to the general watch-only set
|
||||
BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk));
|
||||
spk_man->LoadWatchOnly(p2pk);
|
||||
BOOST_CHECK(spk_man->HaveWatchOnly(p2pk));
|
||||
|
||||
// only PubKeys on the curve shall be added to the watch-only address -> PubKey map
|
||||
bool is_pubkey_fully_valid = add_pubkey.IsFullyValid();
|
||||
if (is_pubkey_fully_valid) {
|
||||
BOOST_CHECK(spk_man->GetWatchPubKey(add_address, found_pubkey));
|
||||
BOOST_CHECK(found_pubkey == add_pubkey);
|
||||
} else {
|
||||
BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey));
|
||||
BOOST_CHECK(found_pubkey == CPubKey()); // passed key is unchanged
|
||||
}
|
||||
|
||||
spk_man->RemoveWatchOnly(p2pk);
|
||||
BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk));
|
||||
|
||||
if (is_pubkey_fully_valid) {
|
||||
BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey));
|
||||
BOOST_CHECK(found_pubkey == add_pubkey); // passed key is unchanged
|
||||
}
|
||||
}
|
||||
|
||||
// Cryptographically invalidate a PubKey whilst keeping length and first byte
|
||||
static void PollutePubKey(CPubKey& pubkey)
|
||||
{
|
||||
std::vector<unsigned char> pubkey_raw(pubkey.begin(), pubkey.end());
|
||||
std::fill(pubkey_raw.begin()+1, pubkey_raw.end(), 0);
|
||||
pubkey = CPubKey(pubkey_raw);
|
||||
assert(!pubkey.IsFullyValid());
|
||||
assert(pubkey.IsValid());
|
||||
}
|
||||
|
||||
// Test watch-only logic for PubKeys
|
||||
BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys)
|
||||
{
|
||||
CKey key;
|
||||
CPubKey pubkey;
|
||||
LegacyScriptPubKeyMan* spk_man = m_wallet.GetOrCreateLegacyScriptPubKeyMan();
|
||||
|
||||
BOOST_CHECK(!spk_man->HaveWatchOnly());
|
||||
|
||||
// uncompressed valid PubKey
|
||||
key.MakeNewKey(false);
|
||||
pubkey = key.GetPubKey();
|
||||
assert(!pubkey.IsCompressed());
|
||||
TestWatchOnlyPubKey(spk_man, pubkey);
|
||||
|
||||
// uncompressed cryptographically invalid PubKey
|
||||
PollutePubKey(pubkey);
|
||||
TestWatchOnlyPubKey(spk_man, pubkey);
|
||||
|
||||
// compressed valid PubKey
|
||||
key.MakeNewKey(true);
|
||||
pubkey = key.GetPubKey();
|
||||
assert(pubkey.IsCompressed());
|
||||
TestWatchOnlyPubKey(spk_man, pubkey);
|
||||
|
||||
// compressed cryptographically invalid PubKey
|
||||
PollutePubKey(pubkey);
|
||||
TestWatchOnlyPubKey(spk_man, pubkey);
|
||||
|
||||
// invalid empty PubKey
|
||||
pubkey = CPubKey();
|
||||
TestWatchOnlyPubKey(spk_man, pubkey);
|
||||
}
|
||||
|
||||
class ListCoinsTestingSetup : public TestChain100Setup
|
||||
{
|
||||
public:
|
||||
ListCoinsTestingSetup()
|
||||
{
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
|
||||
{
|
||||
LOCK2(wallet->cs_wallet, ::cs_main);
|
||||
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
}
|
||||
wallet->LoadWallet();
|
||||
AddKey(*wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(*wallet);
|
||||
reserver.reserve();
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(m_node.chainman->ActiveChain().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(result.last_scanned_block, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.last_scanned_height, m_node.chainman->ActiveChain().Height());
|
||||
BOOST_CHECK(result.last_failed_block.IsNull());
|
||||
}
|
||||
|
||||
~ListCoinsTestingSetup()
|
||||
{
|
||||
wallet.reset();
|
||||
}
|
||||
|
||||
CWalletTx& AddTx(CRecipient recipient)
|
||||
{
|
||||
CTransactionRef tx;
|
||||
CAmount fee;
|
||||
int changePos = -1;
|
||||
bilingual_str error;
|
||||
CCoinControl dummy;
|
||||
FeeCalculation fee_calc_out;
|
||||
{
|
||||
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy, fee_calc_out));
|
||||
}
|
||||
wallet->CommitTransaction(tx, {}, {});
|
||||
CMutableTransaction blocktx;
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).tx);
|
||||
}
|
||||
CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
||||
auto it = wallet->mapWallet.find(tx->GetHash());
|
||||
BOOST_CHECK(it != wallet->mapWallet.end());
|
||||
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 1);
|
||||
it->second.m_confirm = confirm;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::unique_ptr<CWallet> 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<CTxDestination, std::vector<COutput>> list;
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
list = wallet->ListCoins();
|
||||
}
|
||||
BOOST_CHECK_EQUAL(list.size(), 1U);
|
||||
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
|
||||
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
|
||||
|
||||
// Check initial balance from one mature coinbase transaction.
|
||||
BOOST_CHECK_EQUAL(72000 * 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 */});
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
list = wallet->ListCoins();
|
||||
}
|
||||
BOOST_CHECK_EQUAL(list.size(), 1U);
|
||||
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
|
||||
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
|
||||
|
||||
// Lock both coins. Confirm number of available coins drops to 0.
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::vector<COutput> available;
|
||||
wallet->AvailableCoins(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->GetHash(), coin.i));
|
||||
}
|
||||
}
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::vector<COutput> available;
|
||||
wallet->AvailableCoins(available);
|
||||
BOOST_CHECK_EQUAL(available.size(), 0U);
|
||||
}
|
||||
// Confirm ListCoins still returns same result as before, despite coins
|
||||
// being locked.
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
list = wallet->ListCoins();
|
||||
}
|
||||
BOOST_CHECK_EQUAL(list.size(), 1U);
|
||||
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
|
||||
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
|
||||
wallet->SetupLegacyScriptPubKeyMan();
|
||||
wallet->SetMinVersion(FEATURE_LATEST);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
||||
CTxDestination dest;
|
||||
std::string error;
|
||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error));
|
||||
}
|
||||
|
||||
// Explicit calculation which is used to test the wallet constant
|
||||
// We get the same virtual size due to rounding(weight/4) for both use_max_sig values
|
||||
static size_t CalculateNestedKeyhashInputSize(bool use_max_sig)
|
||||
{
|
||||
// Generate ephemeral valid pubkey
|
||||
CKey key;
|
||||
key.MakeNewKey(true);
|
||||
CPubKey pubkey = key.GetPubKey();
|
||||
|
||||
// Generate pubkey hash
|
||||
uint160 key_hash(Hash160(pubkey));
|
||||
|
||||
// Create inner-script to enter into keystore. Key hash can't be 0...
|
||||
CScript inner_script = CScript() << OP_0 << std::vector<unsigned char>(key_hash.begin(), key_hash.end());
|
||||
|
||||
// Create outer P2SH script for the output
|
||||
uint160 script_id(Hash160(inner_script));
|
||||
CScript script_pubkey = CScript() << OP_HASH160 << std::vector<unsigned char>(script_id.begin(), script_id.end()) << OP_EQUAL;
|
||||
|
||||
// Add inner-script to key store and key to watchonly
|
||||
FillableSigningProvider keystore;
|
||||
keystore.AddCScript(inner_script);
|
||||
keystore.AddKeyPubKey(key, pubkey);
|
||||
|
||||
// Fill in dummy signatures for fee calculation.
|
||||
SignatureData sig_data;
|
||||
|
||||
if (!ProduceSignature(keystore, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, script_pubkey, sig_data)) {
|
||||
// We're hand-feeding it correct arguments; shouldn't happen
|
||||
assert(false);
|
||||
}
|
||||
|
||||
CTxIn tx_in;
|
||||
UpdateInput(tx_in, sig_data);
|
||||
return (size_t)GetVirtualTransactionInputSize(tx_in);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(false), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
|
||||
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(true), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
|
||||
}
|
||||
|
||||
bool malformed_descriptor(std::ios_base::failure e)
|
||||
{
|
||||
std::string s(e.what());
|
||||
return s.find("Missing checksum") != std::string::npos;
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
|
||||
{
|
||||
std::vector<unsigned char> malformed_record;
|
||||
CVectorWriter vw(0, 0, malformed_record, 0);
|
||||
vw << std::string("notadescriptor");
|
||||
vw << (uint64_t)0;
|
||||
vw << (int32_t)0;
|
||||
vw << (int32_t)0;
|
||||
vw << (int32_t)1;
|
||||
|
||||
VectorReader vr(0, 0, malformed_record, 0);
|
||||
WalletDescriptor w_desc;
|
||||
BOOST_CHECK_EXCEPTION(vr >> w_desc, std::ios_base::failure, malformed_descriptor);
|
||||
}
|
||||
|
||||
//! Test CWallet::Create() and its behavior handling potential race
|
||||
//! conditions if it's called the same time an incoming transaction shows up in
|
||||
//! the mempool or a new block.
|
||||
//!
|
||||
//! It isn't possible to verify there aren't race condition in every case, so
|
||||
//! this test just checks two specific cases and ensures that timing of
|
||||
//! notifications in these cases doesn't prevent the wallet from detecting
|
||||
//! transactions.
|
||||
//!
|
||||
//! In the first case, block and mempool transactions are created before the
|
||||
//! wallet is loaded, but notifications about these transactions are delayed
|
||||
//! until after it is loaded. The notifications are superfluous in this case, so
|
||||
//! the test verifies the transactions are detected before they arrive.
|
||||
//!
|
||||
//! In the second case, block and mempool transactions are created after the
|
||||
//! wallet rescan and notifications are immediately synced, to verify the wallet
|
||||
//! must already have a handler in place for them, and there's no gap after
|
||||
//! rescanning where new transactions in new blocks could be lost.
|
||||
BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
|
||||
{
|
||||
gArgs.ForceSetArg("-unsafesqlitesync", "1");
|
||||
gArgs.ForceSetArg("-dandelion", "0");
|
||||
// Create new wallet with known key and unload it.
|
||||
auto wallet = TestLoadWallet(m_node.chain.get());
|
||||
CKey key;
|
||||
key.MakeNewKey(true);
|
||||
AddKey(*wallet, key);
|
||||
TestUnloadWallet(std::move(wallet));
|
||||
|
||||
|
||||
// Add log hook to detect AddToWallet events from rescans, blockConnected,
|
||||
// and transactionAddedToMempool notifications
|
||||
int addtx_count = 0;
|
||||
DebugLogHelper addtx_counter("[default wallet] AddToWallet", [&](const std::string* s) {
|
||||
if (s) ++addtx_count;
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
bool rescan_completed = false;
|
||||
DebugLogHelper rescan_check("[default wallet] Rescan completed", [&](const std::string* s) {
|
||||
if (s) rescan_completed = true;
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
// Block the queue to prevent the wallet receiving blockConnected and
|
||||
// transactionAddedToMempool notifications, and create block and mempool
|
||||
// transactions paying to the wallet
|
||||
std::promise<void> promise;
|
||||
CallFunctionInValidationInterfaceQueue([&promise] {
|
||||
promise.get_future().wait();
|
||||
});
|
||||
std::string error;
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
|
||||
|
||||
|
||||
// Reload wallet and make sure new transactions are detected despite events
|
||||
// being blocked
|
||||
wallet = TestLoadWallet(m_node.chain.get());
|
||||
BOOST_CHECK(rescan_completed);
|
||||
BOOST_CHECK_EQUAL(addtx_count, 2);
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U);
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetHash()), 1U);
|
||||
}
|
||||
|
||||
|
||||
// Unblock notification queue and make sure stale blockConnected and
|
||||
// transactionAddedToMempool events are processed
|
||||
promise.set_value();
|
||||
SyncWithValidationInterfaceQueue();
|
||||
BOOST_CHECK_EQUAL(addtx_count, 4);
|
||||
|
||||
|
||||
TestUnloadWallet(std::move(wallet));
|
||||
|
||||
|
||||
// Load wallet again, this time creating new block and mempool transactions
|
||||
// paying to the wallet as the wallet finishes loading and syncing the
|
||||
// queue so the events have to be handled immediately. Releasing the wallet
|
||||
// lock during the sync is a little artificial but is needed to avoid a
|
||||
// deadlock during the sync and simulates a new block notification happening
|
||||
// as soon as possible.
|
||||
addtx_count = 0;
|
||||
auto handler = HandleLoadWallet([&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet, cs_wallets) {
|
||||
BOOST_CHECK(rescan_completed);
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
block_tx = TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
|
||||
LEAVE_CRITICAL_SECTION(cs_wallets);
|
||||
LEAVE_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
|
||||
SyncWithValidationInterfaceQueue();
|
||||
ENTER_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
|
||||
ENTER_CRITICAL_SECTION(cs_wallets);
|
||||
});
|
||||
wallet = TestLoadWallet(m_node.chain.get());
|
||||
BOOST_CHECK_EQUAL(addtx_count, 4);
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U);
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetHash()), 1U);
|
||||
}
|
||||
|
||||
|
||||
TestUnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
|
||||
{
|
||||
auto wallet = TestLoadWallet(nullptr);
|
||||
BOOST_CHECK(wallet);
|
||||
UnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
|
||||
{
|
||||
gArgs.ForceSetArg("-unsafesqlitesync", "1");
|
||||
auto wallet = TestLoadWallet(m_node.chain.get());
|
||||
CKey key;
|
||||
key.MakeNewKey(true);
|
||||
AddKey(*wallet, key);
|
||||
|
||||
std::string error;
|
||||
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
|
||||
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
|
||||
CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
|
||||
SyncWithValidationInterfaceQueue();
|
||||
|
||||
{
|
||||
auto block_hash = block_tx.GetHash();
|
||||
auto prev_hash = m_coinbase_txns[0]->GetHash();
|
||||
|
||||
LOCK(wallet->cs_wallet);
|
||||
BOOST_CHECK(wallet->HasWalletSpend(prev_hash));
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
|
||||
|
||||
std::vector<uint256> vHashIn{ block_hash }, vHashOut;
|
||||
BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
|
||||
|
||||
BOOST_CHECK(!wallet->HasWalletSpend(prev_hash));
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);
|
||||
}
|
||||
|
||||
TestUnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
29
src/wallet/test/walletdb_tests.cpp
Normal file
29
src/wallet/test/walletdb_tests.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2012-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <test/util/setup_common.h>
|
||||
#include <clientversion.h>
|
||||
#include <streams.h>
|
||||
#include <uint256.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(walletdb_tests, BasicTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(walletdb_readkeyvalue)
|
||||
{
|
||||
/**
|
||||
* When ReadKeyValue() reads from either a "key" or "wkey" it first reads the CDataStream steam into a
|
||||
* CPrivKey or CWalletKey respectively and then reads a hash of the pubkey and privkey into a uint256.
|
||||
* Wallets from 0.8 or before do not store the pubkey/privkey hash, trying to read the hash from old
|
||||
* wallets throws an exception, for backwards compatibility this read is wrapped in a try block to
|
||||
* silently fail. The test here makes sure the type of exception thrown from CDataStream::read()
|
||||
* matches the type we expect, otherwise we need to update the "key"/"wkey" exception type caught.
|
||||
*/
|
||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||
uint256 dummy;
|
||||
BOOST_CHECK_THROW(ssValue >> dummy, std::ios_base::failure);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
Loading…
Reference in a new issue