mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
test: Remove legacy wallet unit tests
This commit is contained in:
parent
d9ac9dbd8e
commit
f94f9399ac
4 changed files with 19 additions and 469 deletions
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
#include <addresstype.h>
|
#include <addresstype.h>
|
||||||
#include <bench/bench.h>
|
#include <bench/bench.h>
|
||||||
#include <bitcoin-build-config.h> // IWYU pragma: keep
|
|
||||||
#include <key.h>
|
#include <key.h>
|
||||||
#include <key_io.h>
|
#include <key_io.h>
|
||||||
#include <script/descriptor.h>
|
#include <script/descriptor.h>
|
||||||
|
@ -26,7 +25,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace wallet {
|
namespace wallet {
|
||||||
static void WalletIsMine(benchmark::Bench& bench, bool legacy_wallet, int num_combo = 0)
|
static void WalletIsMine(benchmark::Bench& bench, int num_combo = 0)
|
||||||
{
|
{
|
||||||
const auto test_setup = MakeNoLogFileContext<TestingSetup>();
|
const auto test_setup = MakeNoLogFileContext<TestingSetup>();
|
||||||
|
|
||||||
|
@ -36,16 +35,13 @@ static void WalletIsMine(benchmark::Bench& bench, bool legacy_wallet, int num_co
|
||||||
|
|
||||||
// Setup the wallet
|
// Setup the wallet
|
||||||
// Loading the wallet will also create it
|
// Loading the wallet will also create it
|
||||||
uint64_t create_flags = 0;
|
uint64_t create_flags = WALLET_FLAG_DESCRIPTORS;
|
||||||
if (!legacy_wallet) {
|
|
||||||
create_flags = WALLET_FLAG_DESCRIPTORS;
|
|
||||||
}
|
|
||||||
auto database = CreateMockableWalletDatabase();
|
auto database = CreateMockableWalletDatabase();
|
||||||
auto wallet = TestLoadWallet(std::move(database), context, create_flags);
|
auto wallet = TestLoadWallet(std::move(database), context, create_flags);
|
||||||
|
|
||||||
// For a descriptor wallet, fill with num_combo combo descriptors with random keys
|
// For a descriptor wallet, fill with num_combo combo descriptors with random keys
|
||||||
// This benchmarks a non-HD wallet migrated to descriptors
|
// This benchmarks a non-HD wallet migrated to descriptors
|
||||||
if (!legacy_wallet && num_combo > 0) {
|
if (num_combo > 0) {
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
for (int i = 0; i < num_combo; ++i) {
|
for (int i = 0; i < num_combo; ++i) {
|
||||||
CKey key;
|
CKey key;
|
||||||
|
@ -70,13 +66,8 @@ static void WalletIsMine(benchmark::Bench& bench, bool legacy_wallet, int num_co
|
||||||
TestUnloadWallet(std::move(wallet));
|
TestUnloadWallet(std::move(wallet));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_BDB
|
static void WalletIsMineDescriptors(benchmark::Bench& bench) { WalletIsMine(bench); }
|
||||||
static void WalletIsMineLegacy(benchmark::Bench& bench) { WalletIsMine(bench, /*legacy_wallet=*/true); }
|
static void WalletIsMineMigratedDescriptors(benchmark::Bench& bench) { WalletIsMine(bench, /*num_combo=*/2000); }
|
||||||
BENCHMARK(WalletIsMineLegacy, benchmark::PriorityLevel::LOW);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void WalletIsMineDescriptors(benchmark::Bench& bench) { WalletIsMine(bench, /*legacy_wallet=*/false); }
|
|
||||||
static void WalletIsMineMigratedDescriptors(benchmark::Bench& bench) { WalletIsMine(bench, /*legacy_wallet=*/false, /*num_combo=*/2000); }
|
|
||||||
BENCHMARK(WalletIsMineDescriptors, benchmark::PriorityLevel::LOW);
|
BENCHMARK(WalletIsMineDescriptors, benchmark::PriorityLevel::LOW);
|
||||||
BENCHMARK(WalletIsMineMigratedDescriptors, benchmark::PriorityLevel::LOW);
|
BENCHMARK(WalletIsMineMigratedDescriptors, benchmark::PriorityLevel::LOW);
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
|
@ -2,17 +2,12 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <bitcoin-build-config.h> // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
#include <test/util/setup_common.h>
|
#include <test/util/setup_common.h>
|
||||||
#include <util/check.h>
|
#include <util/check.h>
|
||||||
#include <util/fs.h>
|
#include <util/fs.h>
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
#ifdef USE_BDB
|
|
||||||
#include <wallet/bdb.h>
|
|
||||||
#endif
|
|
||||||
#include <wallet/sqlite.h>
|
#include <wallet/sqlite.h>
|
||||||
#include <wallet/migrate.h>
|
#include <wallet/migrate.h>
|
||||||
#include <wallet/test/util.h>
|
#include <wallet/test/util.h>
|
||||||
|
@ -60,82 +55,13 @@ static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix,
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
|
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
|
||||||
|
|
||||||
#ifdef USE_BDB
|
|
||||||
static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, fs::path& database_filename)
|
|
||||||
{
|
|
||||||
fs::path data_file = BDBDataFile(path);
|
|
||||||
database_filename = data_file.filename();
|
|
||||||
return GetBerkeleyEnv(data_file.parent_path(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(getwalletenv_file)
|
|
||||||
{
|
|
||||||
fs::path test_name = "test_name.dat";
|
|
||||||
const fs::path datadir = m_args.GetDataDirNet();
|
|
||||||
fs::path file_path = datadir / test_name;
|
|
||||||
std::ofstream f{file_path};
|
|
||||||
f.close();
|
|
||||||
|
|
||||||
fs::path 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)
|
|
||||||
{
|
|
||||||
fs::path expected_name = "wallet.dat";
|
|
||||||
const fs::path datadir = m_args.GetDataDirNet();
|
|
||||||
|
|
||||||
fs::path 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 = m_args.GetDataDirNet() / "1";
|
|
||||||
fs::path datadir_2 = m_args.GetDataDirNet() / "2";
|
|
||||||
fs::path 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";
|
|
||||||
fs::path 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);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
|
static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
|
||||||
{
|
{
|
||||||
std::vector<std::unique_ptr<WalletDatabase>> dbs;
|
std::vector<std::unique_ptr<WalletDatabase>> dbs;
|
||||||
DatabaseOptions options;
|
DatabaseOptions options;
|
||||||
DatabaseStatus status;
|
DatabaseStatus status;
|
||||||
bilingual_str error;
|
bilingual_str error;
|
||||||
#ifdef USE_BDB
|
// Unable to test BerkeleyRO since we cannot create a new BDB database to open
|
||||||
dbs.emplace_back(MakeBerkeleyDatabase(path_root / "bdb", options, status, error));
|
|
||||||
// Needs BDB to make the DB to read
|
|
||||||
dbs.emplace_back(std::make_unique<BerkeleyRODatabase>(BDBDataFile(path_root / "bdb"), /*open=*/false));
|
|
||||||
#endif
|
|
||||||
dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
|
dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
|
||||||
dbs.emplace_back(CreateMockableWalletDatabase());
|
dbs.emplace_back(CreateMockableWalletDatabase());
|
||||||
return dbs;
|
return dbs;
|
||||||
|
@ -148,15 +74,10 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
|
||||||
std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
|
std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
|
||||||
|
|
||||||
std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
|
std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
|
||||||
if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
|
// Write elements to it
|
||||||
// For BerkeleyRO, open the file now. This must happen after BDB has written to the file
|
for (unsigned int i = 0; i < 10; i++) {
|
||||||
database->Open();
|
for (const auto& prefix : prefixes) {
|
||||||
} else {
|
BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
|
||||||
// Write elements to it if not berkeleyro
|
|
||||||
for (unsigned int i = 0; i < 10; i++) {
|
|
||||||
for (const auto& prefix : prefixes) {
|
|
||||||
BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,14 +127,9 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
|
||||||
for (const auto& database : TestDatabases(m_path_root)) {
|
for (const auto& database : TestDatabases(m_path_root)) {
|
||||||
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
|
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
|
||||||
|
|
||||||
if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
|
// Write elements to it if not berkeleyro
|
||||||
// For BerkeleyRO, open the file now. This must happen after BDB has written to the file
|
for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
|
||||||
database->Open();
|
batch->Write(std::span{k}, std::span{v});
|
||||||
} else {
|
|
||||||
// Write elements to it if not berkeleyro
|
|
||||||
for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
|
|
||||||
batch->Write(std::span{k}, std::span{v});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
|
CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
|
||||||
|
@ -231,10 +147,6 @@ BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
|
||||||
// To simulate the behavior, record overwrites are disallowed, and the test verifies
|
// To simulate the behavior, record overwrites are disallowed, and the test verifies
|
||||||
// that the database remains active after failing to store an existing record.
|
// that the database remains active after failing to store an existing record.
|
||||||
for (const auto& database : TestDatabases(m_path_root)) {
|
for (const auto& database : TestDatabases(m_path_root)) {
|
||||||
if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
|
|
||||||
// Skip this test if BerkeleyRO
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Write original record
|
// Write original record
|
||||||
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
|
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
|
||||||
std::string key = "key";
|
std::string key = "key";
|
||||||
|
|
|
@ -195,141 +195,6 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
|
|
||||||
{
|
|
||||||
// Cap last block file size, and mine new block in a new block file.
|
|
||||||
CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
|
|
||||||
WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
|
|
||||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
|
||||||
CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
|
|
||||||
|
|
||||||
// Prune the older block file.
|
|
||||||
int file_number;
|
|
||||||
{
|
|
||||||
LOCK(cs_main);
|
|
||||||
file_number = oldTip->GetBlockPos().nFile;
|
|
||||||
Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
|
|
||||||
}
|
|
||||||
m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
{
|
|
||||||
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
|
|
||||||
wallet->SetupLegacyScriptPubKeyMan();
|
|
||||||
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
|
|
||||||
WalletContext context;
|
|
||||||
context.args = &m_args;
|
|
||||||
AddWallet(context, 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 = GenerateRandomKey();
|
|
||||||
key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey())));
|
|
||||||
key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1);
|
|
||||||
key.pushKV("internal", UniValue(true));
|
|
||||||
keys.push_back(std::move(key));
|
|
||||||
JSONRPCRequest request;
|
|
||||||
request.context = &context;
|
|
||||||
request.params.setArray();
|
|
||||||
request.params.push_back(std::move(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 bitcoind log for details) and could be dealt with by "
|
|
||||||
"downloading and rescanning the relevant blocks (see -reindex option and rescanblockchain "
|
|
||||||
"RPC).\"}},{\"success\":true}]",
|
|
||||||
0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW));
|
|
||||||
RemoveWallet(context, wallet, /* load_on_start= */ 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 = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return 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 = fs::PathToString(m_args.GetDataDirNet() / "wallet.backup");
|
|
||||||
|
|
||||||
// Import key into wallet and call dumpwallet to create backup file.
|
|
||||||
{
|
|
||||||
WalletContext context;
|
|
||||||
context.args = &m_args;
|
|
||||||
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
|
|
||||||
{
|
|
||||||
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(context, wallet);
|
|
||||||
LOCK(Assert(m_node.chainman)->GetMutex());
|
|
||||||
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
|
||||||
}
|
|
||||||
JSONRPCRequest request;
|
|
||||||
request.context = &context;
|
|
||||||
request.params.setArray();
|
|
||||||
request.params.push_back(backup_file);
|
|
||||||
|
|
||||||
wallet::dumpwallet().HandleRequest(request);
|
|
||||||
RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
|
|
||||||
// were scanned, and no prior blocks were scanned.
|
|
||||||
{
|
|
||||||
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
|
|
||||||
LOCK(wallet->cs_wallet);
|
|
||||||
wallet->SetupLegacyScriptPubKeyMan();
|
|
||||||
|
|
||||||
WalletContext context;
|
|
||||||
context.args = &m_args;
|
|
||||||
JSONRPCRequest request;
|
|
||||||
request.context = &context;
|
|
||||||
request.params.setArray();
|
|
||||||
request.params.push_back(backup_file);
|
|
||||||
AddWallet(context, wallet);
|
|
||||||
LOCK(Assert(m_node.chainman)->GetMutex());
|
|
||||||
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
|
|
||||||
wallet::importwallet().HandleRequest(request);
|
|
||||||
RemoveWallet(context, wallet, /* load_on_start= */ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This test verifies that wallet settings can be added and removed
|
// This test verifies that wallet settings can be added and removed
|
||||||
// concurrently, ensuring no race conditions occur during either process.
|
// concurrently, ensuring no race conditions occur during either process.
|
||||||
BOOST_FIXTURE_TEST_CASE(write_wallet_settings_concurrently, TestingSetup)
|
BOOST_FIXTURE_TEST_CASE(write_wallet_settings_concurrently, TestingSetup)
|
||||||
|
@ -495,87 +360,6 @@ BOOST_FIXTURE_TEST_CASE(LoadReceiveRequests, TestingSetup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 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)
|
|
||||||
{
|
|
||||||
assert(pubkey.size() >= 1);
|
|
||||||
std::vector<unsigned char> pubkey_raw;
|
|
||||||
pubkey_raw.push_back(pubkey[0]);
|
|
||||||
pubkey_raw.insert(pubkey_raw.end(), pubkey.size() - 1, 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
|
class ListCoinsTestingSetup : public TestChain100Setup
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -718,22 +502,12 @@ BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, ListCoinsTest)
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||||
{
|
{
|
||||||
{
|
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
|
||||||
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
|
LOCK(wallet->cs_wallet);
|
||||||
wallet->SetupLegacyScriptPubKeyMan();
|
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||||
wallet->SetMinVersion(FEATURE_LATEST);
|
wallet->SetMinVersion(FEATURE_LATEST);
|
||||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
|
||||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
|
|
||||||
LOCK(wallet->cs_wallet);
|
|
||||||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
|
||||||
wallet->SetMinVersion(FEATURE_LATEST);
|
|
||||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
|
||||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit calculation which is used to test the wallet constant
|
// Explicit calculation which is used to test the wallet constant
|
||||||
|
|
|
@ -84,132 +84,5 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasAnyRecordOfType(WalletDatabase& db, const std::string& key)
|
|
||||||
{
|
|
||||||
std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(false);
|
|
||||||
BOOST_CHECK(batch);
|
|
||||||
std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor();
|
|
||||||
BOOST_CHECK(cursor);
|
|
||||||
while (true) {
|
|
||||||
DataStream ssKey{};
|
|
||||||
DataStream ssValue{};
|
|
||||||
DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
|
|
||||||
assert(status != DatabaseCursor::Status::FAIL);
|
|
||||||
if (status == DatabaseCursor::Status::DONE) break;
|
|
||||||
std::string type;
|
|
||||||
ssKey >> type;
|
|
||||||
if (type == key) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
SerializeData MakeSerializeData(const Args&... args)
|
|
||||||
{
|
|
||||||
DataStream s{};
|
|
||||||
SerializeMany(s, args...);
|
|
||||||
return {s.begin(), s.end()};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(wallet_load_ckey, TestingSetup)
|
|
||||||
{
|
|
||||||
SerializeData ckey_record_key;
|
|
||||||
SerializeData ckey_record_value;
|
|
||||||
MockableData records;
|
|
||||||
|
|
||||||
{
|
|
||||||
// Context setup.
|
|
||||||
// Create and encrypt legacy wallet
|
|
||||||
std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase()));
|
|
||||||
LOCK(wallet->cs_wallet);
|
|
||||||
auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan();
|
|
||||||
BOOST_CHECK(legacy_spkm->SetupGeneration(true));
|
|
||||||
|
|
||||||
// Retrieve a key
|
|
||||||
CTxDestination dest = *Assert(legacy_spkm->GetNewDestination(OutputType::LEGACY));
|
|
||||||
CKeyID key_id = GetKeyForDestination(*legacy_spkm, dest);
|
|
||||||
CKey first_key;
|
|
||||||
BOOST_CHECK(legacy_spkm->GetKey(key_id, first_key));
|
|
||||||
|
|
||||||
// Encrypt the wallet
|
|
||||||
BOOST_CHECK(wallet->EncryptWallet("encrypt"));
|
|
||||||
wallet->Flush();
|
|
||||||
|
|
||||||
// Store a copy of all the records
|
|
||||||
records = GetMockableDatabase(*wallet).m_records;
|
|
||||||
|
|
||||||
// Get the record for the retrieved key
|
|
||||||
ckey_record_key = MakeSerializeData(DBKeys::CRYPTED_KEY, first_key.GetPubKey());
|
|
||||||
ckey_record_value = records.at(ckey_record_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// First test case:
|
|
||||||
// Erase all the crypted keys from db and unlock the wallet.
|
|
||||||
// The wallet will only re-write the crypted keys to db if any checksum is missing at load time.
|
|
||||||
// So, if any 'ckey' record re-appears on db, then the checksums were not properly calculated, and we are re-writing
|
|
||||||
// the records every time that 'CWallet::Unlock' gets called, which is not good.
|
|
||||||
|
|
||||||
// Load the wallet and check that is encrypted
|
|
||||||
std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
|
|
||||||
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
|
|
||||||
BOOST_CHECK(wallet->IsCrypted());
|
|
||||||
BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
|
|
||||||
|
|
||||||
// Now delete all records and check that the 'Unlock' function doesn't re-write them
|
|
||||||
BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
|
|
||||||
BOOST_CHECK(!HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
|
|
||||||
BOOST_CHECK(wallet->Unlock("encrypt"));
|
|
||||||
BOOST_CHECK(!HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Second test case:
|
|
||||||
// Verify that loading up a 'ckey' with no checksum triggers a complete re-write of the crypted keys.
|
|
||||||
|
|
||||||
// Cut off the 32 byte checksum from a ckey record
|
|
||||||
records[ckey_record_key].resize(ckey_record_value.size() - 32);
|
|
||||||
|
|
||||||
// Load the wallet and check that is encrypted
|
|
||||||
std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
|
|
||||||
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
|
|
||||||
BOOST_CHECK(wallet->IsCrypted());
|
|
||||||
BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
|
|
||||||
|
|
||||||
// Now delete all ckey records and check that the 'Unlock' function re-writes them
|
|
||||||
// (this is because the wallet, at load time, found a ckey record with no checksum)
|
|
||||||
BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
|
|
||||||
BOOST_CHECK(!HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
|
|
||||||
BOOST_CHECK(wallet->Unlock("encrypt"));
|
|
||||||
BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Third test case:
|
|
||||||
// Verify that loading up a 'ckey' with an invalid checksum throws an error.
|
|
||||||
|
|
||||||
// Cut off the 32 byte checksum from a ckey record
|
|
||||||
records[ckey_record_key].resize(ckey_record_value.size() - 32);
|
|
||||||
// Fill in the checksum space with 0s
|
|
||||||
records[ckey_record_key].resize(ckey_record_value.size());
|
|
||||||
|
|
||||||
std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
|
|
||||||
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Fourth test case:
|
|
||||||
// Verify that loading up a 'ckey' with an invalid pubkey throws an error
|
|
||||||
CPubKey invalid_key;
|
|
||||||
BOOST_CHECK(!invalid_key.IsValid());
|
|
||||||
SerializeData key = MakeSerializeData(DBKeys::CRYPTED_KEY, invalid_key);
|
|
||||||
records[key] = ckey_record_value;
|
|
||||||
|
|
||||||
std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
|
|
||||||
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
Loading…
Add table
Reference in a new issue