test: Remove legacy wallet unit tests

This commit is contained in:
Ava Chow 2024-01-05 18:41:32 -05:00
parent d9ac9dbd8e
commit f94f9399ac
4 changed files with 19 additions and 469 deletions

View file

@ -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

View file

@ -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";

View file

@ -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

View file

@ -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