Bitcoin Core  27.99.0
P2P Digital Currency
walletload_tests.cpp
Go to the documentation of this file.
1 // Copyright (c) 2022 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or https://www.opensource.org/licenses/mit-license.php.
4 
5 #include <wallet/test/util.h>
6 #include <wallet/wallet.h>
7 #include <test/util/logging.h>
9 
10 #include <boost/test/unit_test.hpp>
11 
12 namespace wallet {
13 
14 BOOST_AUTO_TEST_SUITE(walletload_tests)
15 
16 class DummyDescriptor final : public Descriptor {
17 private:
18  std::string desc;
19 public:
20  explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {};
21  ~DummyDescriptor() = default;
22 
23  std::string ToString(bool compat_format) const override { return desc; }
24  std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; }
25 
26  bool IsRange() const override { return false; }
27  bool IsSolvable() const override { return false; }
28  bool IsSingleType() const override { return true; }
29  bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; }
30  bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; }
31  bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };
32  bool ExpandFromCache(int pos, const DescriptorCache& read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { return false; }
33  void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {}
34  std::optional<int64_t> ScriptSize() const override { return {}; }
35  std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
36  std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
37  void GetPubKeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const override {}
38 };
39 
40 BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)
41 {
42  std::unique_ptr<WalletDatabase> database = CreateMockableWalletDatabase();
43  {
44  // Write unknown active descriptor
45  WalletBatch batch(*database, false);
46  std::string unknown_desc = "trx(tpubD6NzVbkrYhZ4Y4S7m6Y5s9GD8FqEMBy56AGphZXuagajudVZEnYyBahZMgHNCTJc2at82YX6s8JiL1Lohu5A3v1Ur76qguNH4QVQ7qYrBQx/86'/1'/0'/0/*)#8pn8tzdt";
47  WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(unknown_desc), 0, 0, 0, 0);
48  BOOST_CHECK(batch.WriteDescriptor(uint256(), wallet_descriptor));
49  BOOST_CHECK(batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(OutputType::UNKNOWN), uint256(), false));
50  }
51 
52  {
53  // Now try to load the wallet and verify the error.
54  const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
56  }
57 
58  // Test 2
59  // Now write a valid descriptor with an invalid ID.
60  // As the software produces another ID for the descriptor, the loading process must be aborted.
61  database = CreateMockableWalletDatabase();
62 
63  // Verify the error
64  bool found = false;
65  DebugLogHelper logHelper("The descriptor ID calculated by the wallet differs from the one in DB", [&](const std::string* s) {
66  found = true;
67  return false;
68  });
69 
70  {
71  // Write valid descriptor with invalid ID
72  WalletBatch batch(*database, false);
73  std::string desc = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
74  WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(desc), 0, 0, 0, 0);
75  BOOST_CHECK(batch.WriteDescriptor(uint256::ONE, wallet_descriptor));
76  }
77 
78  {
79  // Now try to load the wallet and verify the error.
80  const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
82  BOOST_CHECK(found); // The error must be logged
83  }
84 }
85 
86 bool HasAnyRecordOfType(WalletDatabase& db, const std::string& key)
87 {
88  std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(false);
89  BOOST_CHECK(batch);
90  std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor();
91  BOOST_CHECK(cursor);
92  while (true) {
93  DataStream ssKey{};
94  DataStream ssValue{};
95  DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
97  if (status == DatabaseCursor::Status::DONE) break;
98  std::string type;
99  ssKey >> type;
100  if (type == key) return true;
101  }
102  return false;
103 }
104 
105 template<typename... Args>
107 {
108  DataStream s{};
109  SerializeMany(s, args...);
110  return {s.begin(), s.end()};
111 }
112 
113 
115 {
116  SerializeData ckey_record_key;
117  SerializeData ckey_record_value;
118  MockableData records;
119 
120  {
121  // Context setup.
122  // Create and encrypt legacy wallet
123  std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase()));
124  LOCK(wallet->cs_wallet);
125  auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan();
126  BOOST_CHECK(legacy_spkm->SetupGeneration(true));
127 
128  // Retrieve a key
129  CTxDestination dest = *Assert(legacy_spkm->GetNewDestination(OutputType::LEGACY));
130  CKeyID key_id = GetKeyForDestination(*legacy_spkm, dest);
131  CKey first_key;
132  BOOST_CHECK(legacy_spkm->GetKey(key_id, first_key));
133 
134  // Encrypt the wallet
135  BOOST_CHECK(wallet->EncryptWallet("encrypt"));
136  wallet->Flush();
137 
138  // Store a copy of all the records
139  records = GetMockableDatabase(*wallet).m_records;
140 
141  // Get the record for the retrieved key
142  ckey_record_key = MakeSerializeData(DBKeys::CRYPTED_KEY, first_key.GetPubKey());
143  ckey_record_value = records.at(ckey_record_key);
144  }
145 
146  {
147  // First test case:
148  // Erase all the crypted keys from db and unlock the wallet.
149  // The wallet will only re-write the crypted keys to db if any checksum is missing at load time.
150  // So, if any 'ckey' record re-appears on db, then the checksums were not properly calculated, and we are re-writing
151  // the records every time that 'CWallet::Unlock' gets called, which is not good.
152 
153  // Load the wallet and check that is encrypted
154  std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
156  BOOST_CHECK(wallet->IsCrypted());
158 
159  // Now delete all records and check that the 'Unlock' function doesn't re-write them
160  BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
162  BOOST_CHECK(wallet->Unlock("encrypt"));
164  }
165 
166  {
167  // Second test case:
168  // Verify that loading up a 'ckey' with no checksum triggers a complete re-write of the crypted keys.
169 
170  // Cut off the 32 byte checksum from a ckey record
171  records[ckey_record_key].resize(ckey_record_value.size() - 32);
172 
173  // Load the wallet and check that is encrypted
174  std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
176  BOOST_CHECK(wallet->IsCrypted());
178 
179  // Now delete all ckey records and check that the 'Unlock' function re-writes them
180  // (this is because the wallet, at load time, found a ckey record with no checksum)
181  BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
183  BOOST_CHECK(wallet->Unlock("encrypt"));
185  }
186 
187  {
188  // Third test case:
189  // Verify that loading up a 'ckey' with an invalid checksum throws an error.
190 
191  // Cut off the 32 byte checksum from a ckey record
192  records[ckey_record_key].resize(ckey_record_value.size() - 32);
193  // Fill in the checksum space with 0s
194  records[ckey_record_key].resize(ckey_record_value.size());
195 
196  std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
198  }
199 
200  {
201  // Fourth test case:
202  // Verify that loading up a 'ckey' with an invalid pubkey throws an error
203  CPubKey invalid_key;
204  BOOST_CHECK(!invalid_key.IsValid());
206  records[key] = ckey_record_value;
207 
208  std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
210  }
211 }
212 
214 } // namespace wallet
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:131
node::NodeContext m_node
Definition: bitcoin-gui.cpp:37
ArgsManager & args
Definition: bitcoind.cpp:266
#define Assert(val)
Identity function.
Definition: check.h:77
An encapsulated private key.
Definition: key.h:33
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:188
A reference to a CKey: the Hash160 of its serialized public key.
Definition: pubkey.h:24
An encapsulated public key.
Definition: pubkey.h:34
bool IsValid() const
Definition: pubkey.h:189
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:147
Cache for single descriptor's derived extended pubkeys.
Definition: descriptor.h:19
An interface to be implemented by keystores that support signing.
256-bit opaque blob.
Definition: uint256.h:106
static const uint256 ONE
Definition: uint256.h:112
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:301
bool IsSolvable() const override
Whether this descriptor has all information about signing ignoring lack of private keys.
std::string ToString(bool compat_format) const override
Convert the descriptor back to a string, undoing parsing.
void GetPubKeys(std::set< CPubKey > &pubkeys, std::set< CExtPubKey > &ext_pubs) const override
Return all (extended) public keys for this descriptor, including any from subdescriptors.
bool ToNormalizedString(const SigningProvider &provider, std::string &out, const DescriptorCache *cache=nullptr) const override
Convert the descriptor to a normalized string.
std::optional< int64_t > ScriptSize() const override
Get the size of the scriptPubKey for this descriptor.
void ExpandPrivate(int pos, const SigningProvider &provider, FlatSigningProvider &out) const override
Expand the private key for a descriptor at a specified position, if possible.
bool ToPrivateString(const SigningProvider &provider, std::string &out) const override
Convert the descriptor to a private string.
bool Expand(int pos, const SigningProvider &provider, std::vector< CScript > &output_scripts, FlatSigningProvider &out, DescriptorCache *write_cache=nullptr) const override
Expand a descriptor at a specified position.
std::optional< int64_t > MaxSatisfactionWeight(bool) const override
Get the maximum size of a satisfaction for this descriptor, in weight units.
std::optional< OutputType > GetOutputType() const override
bool ExpandFromCache(int pos, const DescriptorCache &read_cache, std::vector< CScript > &output_scripts, FlatSigningProvider &out) const override
Expand a descriptor at a specified position using cached expansion data.
std::optional< int64_t > MaxSatisfactionElems() const override
Get the maximum size number of stack elements for satisfying this descriptor.
bool IsRange() const override
Whether the expansion of this descriptor depends on the position.
bool IsSingleType() const override
Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo)
DummyDescriptor(const std::string &descriptor)
MockableData m_records
Definition: util.h:107
Access to the wallet database.
Definition: walletdb.h:191
bool WriteDescriptor(const uint256 &desc_id, const WalletDescriptor &descriptor)
Definition: walletdb.cpp:248
bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256 &id, bool internal)
Definition: walletdb.cpp:216
An instance of this class represents one database.
Definition: db.h:124
virtual std::unique_ptr< DatabaseBatch > MakeBatch(bool flush_on_close=true)=0
Make a DatabaseBatch connected to this database.
Descriptor with some wallet metadata.
Definition: walletutil.h:85
BOOST_AUTO_TEST_SUITE(cuckoocache_tests)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
const std::string CRYPTED_KEY
Definition: walletdb.cpp:42
BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup)
bool HasAnyRecordOfType(WalletDatabase &db, const std::string &key)
MockableDatabase & GetMockableDatabase(CWallet &wallet)
Definition: util.cpp:195
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
Definition: util.cpp:190
std::map< SerializeData, SerializeData, std::less<> > MockableData
Definition: util.h:55
SerializeData MakeSerializeData(const Args &... args)
std::shared_ptr< CWallet > wallet
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
void SerializeMany(Stream &s, const Args &... args)
Support for (un)serializing many things at once.
Definition: serialize.h:1007
CKeyID GetKeyForDestination(const SigningProvider &store, const CTxDestination &dest)
Return the CKeyID of the key involved in a script (if there is a unique one).
Interface for parsed descriptor objects.
Definition: descriptor.h:98
Testing setup that configures a complete environment.
Definition: setup_common.h:83
std::unique_ptr< interfaces::Chain > chain
Definition: context.h:66
#define LOCK(cs)
Definition: sync.h:257
assert(!tx.IsCoinBase())
std::vector< std::byte, zero_after_free_allocator< std::byte > > SerializeData
Byte-vector that clears its contents before deletion.
Definition: zeroafterfree.h:49