Bitcoin Core  0.20.99
P2P Digital Currency
salvage.cpp
Go to the documentation of this file.
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2020 The Bitcoin Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #include <fs.h>
7 #include <streams.h>
8 #include <util/translation.h>
9 #include <wallet/salvage.h>
10 #include <wallet/wallet.h>
11 #include <wallet/walletdb.h>
12 
13 /* End of headers, beginning of key/value data */
14 static const char *HEADER_END = "HEADER=END";
15 /* End of key/value data */
16 static const char *DATA_END = "DATA=END";
17 typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
18 
19 static bool KeyFilter(const std::string& type)
20 {
21  return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
22 }
23 
24 bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
25 {
26  DatabaseOptions options;
27  DatabaseStatus status;
28  options.require_existing = true;
29  options.verify = false;
30  std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
31  if (!database) return false;
32 
33  std::string filename;
34  std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
35 
36  if (!env->Open(error)) {
37  return false;
38  }
39 
40  // Recovery procedure:
41  // move wallet file to walletfilename.timestamp.bak
42  // Call Salvage with fAggressive=true to
43  // get as much data as possible.
44  // Rewrite salvaged data to fresh wallet file
45  // Set -rescan so any missing transactions will be
46  // found.
47  int64_t now = GetTime();
48  std::string newFilename = strprintf("%s.%d.bak", filename, now);
49 
50  int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
51  newFilename.c_str(), DB_AUTO_COMMIT);
52  if (result != 0)
53  {
54  error = strprintf(Untranslated("Failed to rename %s to %s"), filename, newFilename);
55  return false;
56  }
57 
64  std::vector<KeyValPair> salvagedData;
65 
66  std::stringstream strDump;
67 
68  Db db(env->dbenv.get(), 0);
69  result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE);
70  if (result == DB_VERIFY_BAD) {
71  warnings.push_back(Untranslated("Salvage: Database salvage found errors, all data may not be recoverable."));
72  }
73  if (result != 0 && result != DB_VERIFY_BAD) {
74  error = strprintf(Untranslated("Salvage: Database salvage failed with result %d."), result);
75  return false;
76  }
77 
78  // Format of bdb dump is ascii lines:
79  // header lines...
80  // HEADER=END
81  // hexadecimal key
82  // hexadecimal value
83  // ... repeated
84  // DATA=END
85 
86  std::string strLine;
87  while (!strDump.eof() && strLine != HEADER_END)
88  getline(strDump, strLine); // Skip past header
89 
90  std::string keyHex, valueHex;
91  while (!strDump.eof() && keyHex != DATA_END) {
92  getline(strDump, keyHex);
93  if (keyHex != DATA_END) {
94  if (strDump.eof())
95  break;
96  getline(strDump, valueHex);
97  if (valueHex == DATA_END) {
98  warnings.push_back(Untranslated("Salvage: WARNING: Number of keys in data does not match number of values."));
99  break;
100  }
101  salvagedData.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
102  }
103  }
104 
105  bool fSuccess;
106  if (keyHex != DATA_END) {
107  warnings.push_back(Untranslated("Salvage: WARNING: Unexpected end of file while reading salvage output."));
108  fSuccess = false;
109  } else {
110  fSuccess = (result == 0);
111  }
112 
113  if (salvagedData.empty())
114  {
115  error = strprintf(Untranslated("Salvage(aggressive) found no records in %s."), newFilename);
116  return false;
117  }
118 
119  std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
120  int ret = pdbCopy->open(nullptr, // Txn pointer
121  filename.c_str(), // Filename
122  "main", // Logical db name
123  DB_BTREE, // Database type
124  DB_CREATE, // Flags
125  0);
126  if (ret > 0) {
127  error = strprintf(Untranslated("Cannot create database file %s"), filename);
128  pdbCopy->close(0);
129  return false;
130  }
131 
132  DbTxn* ptxn = env->TxnBegin();
133  CWallet dummyWallet(nullptr, "", CreateDummyWalletDatabase());
134  for (KeyValPair& row : salvagedData)
135  {
136  /* Filter for only private key type KV pairs to be added to the salvaged wallet */
137  CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
138  CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
139  std::string strType, strErr;
140  bool fReadOK;
141  {
142  // Required in LoadKeyMetadata():
143  LOCK(dummyWallet.cs_wallet);
144  fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr, KeyFilter);
145  }
146  if (!KeyFilter(strType)) {
147  continue;
148  }
149  if (!fReadOK)
150  {
151  warnings.push_back(strprintf(Untranslated("WARNING: WalletBatch::Recover skipping %s: %s"), strType, strErr));
152  continue;
153  }
154  Dbt datKey(&row.first[0], row.first.size());
155  Dbt datValue(&row.second[0], row.second.size());
156  int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
157  if (ret2 > 0)
158  fSuccess = false;
159  }
160  ptxn->commit(0);
161  pdbCopy->close(0);
162 
163  return fSuccess;
164 }
const std::chrono::seconds now
std::shared_ptr< BerkeleyEnvironment > GetWalletEnv(const fs::path &wallet_path, std::string &database_filename)
Get BerkeleyEnvironment and database filename given a wallet path.
Definition: bdb.cpp:62
Bilingual messages:
Definition: translation.h:16
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1164
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:40
std::vector< unsigned char > ParseHex(const char *psz)
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:202
std::pair< std::vector< unsigned char >, std::vector< unsigned char > > KeyValPair
Definition: salvage.cpp:17
bool verify
Definition: db.h:210
const std::string HDCHAIN
Definition: walletdb.cpp:35
static bool IsKeyType(const std::string &strType)
Definition: walletdb.cpp:685
#define LOCK(cs)
Definition: sync.h:230
bool require_existing
Definition: db.h:205
std::unique_ptr< WalletDatabase > CreateDummyWalletDatabase()
Return object for accessing dummy database with no read/write capabilities.
Definition: walletdb.cpp:1059
static const char * HEADER_END
Definition: salvage.cpp:14
static bool KeyFilter(const std::string &type)
Definition: salvage.cpp:19
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:622
RecursiveMutex cs_wallet
Definition: wallet.h:733
bool RecoverDatabaseFile(const fs::path &file_path, bilingual_str &error, std::vector< bilingual_str > &warnings)
Definition: salvage.cpp:24
static bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey, CDataStream &ssValue, CWalletScanState &wss, std::string &strType, std::string &strErr, const KeyFilterFn &filter_fn=nullptr) EXCLUSIVE_LOCKS_REQUIRED(pwallet -> cs_wallet)
Definition: walletdb.cpp:268
int64_t GetTime()
Return system time (or mocked time, if set)
Definition: time.cpp:23
DatabaseStatus
Definition: db.h:213
static const int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Definition: clientversion.h:38
bool error(const char *fmt, const Args &... args)
Definition: system.h:52
std::unique_ptr< WalletDatabase > MakeDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: walletdb.cpp:999
static const char * DATA_END
Definition: salvage.cpp:16