Bitcoin ABC  0.29.2
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 <config.h>
7 #include <fs.h>
8 #include <streams.h>
9 #include <util/translation.h>
10 #include <wallet/salvage.h>
11 #include <wallet/wallet.h>
12 #include <wallet/walletdb.h>
13 
14 /* End of headers, beginning of key/value data */
15 static const char *HEADER_END = "HEADER=END";
16 /* End of key/value data */
17 static const char *DATA_END = "DATA=END";
18 typedef std::pair<std::vector<uint8_t>, std::vector<uint8_t>> KeyValPair;
19 
20 static bool KeyFilter(const std::string &type) {
21  return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
22 }
23 
25  std::vector<bilingual_str> &warnings) {
26  DatabaseOptions options;
27  DatabaseStatus status;
28  options.require_existing = true;
29  options.verify = false;
30  std::unique_ptr<WalletDatabase> database =
31  MakeDatabase(file_path, options, status, error);
32  if (!database) {
33  return false;
34  }
35 
36  std::string filename;
37  std::shared_ptr<BerkeleyEnvironment> env =
38  GetWalletEnv(file_path, filename);
39 
40  if (!env->Open(error)) {
41  return false;
42  }
43 
44  // Recovery procedure:
45  // move wallet file to walletfilename.timestamp.bak
46  // Call Salvage with fAggressive=true to
47  // get as much data as possible.
48  // Rewrite salvaged data to fresh wallet file
49  // Set -rescan so any missing transactions will be
50  // found.
51  int64_t now = GetTime();
52  std::string newFilename = strprintf("%s.%d.bak", filename, now);
53 
54  int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
55  newFilename.c_str(), DB_AUTO_COMMIT);
56  if (result != 0) {
57  error = strprintf(Untranslated("Failed to rename %s to %s"), filename,
58  newFilename);
59  return false;
60  }
61 
69  std::vector<KeyValPair> salvagedData;
70 
71  std::stringstream strDump;
72 
73  Db db(env->dbenv.get(), 0);
74  result = db.verify(newFilename.c_str(), nullptr, &strDump,
75  DB_SALVAGE | DB_AGGRESSIVE);
76  if (result == DB_VERIFY_BAD) {
77  warnings.push_back(
78  Untranslated("Salvage: Database salvage found errors, all data may "
79  "not be recoverable."));
80  }
81  if (result != 0 && result != DB_VERIFY_BAD) {
82  error = strprintf(
83  Untranslated("Salvage: Database salvage failed with result %d."),
84  result);
85  return false;
86  }
87 
88  // Format of bdb dump is ascii lines:
89  // header lines...
90  // HEADER=END
91  // hexadecimal key
92  // hexadecimal value
93  // ... repeated
94  // DATA=END
95 
96  std::string strLine;
97  while (!strDump.eof() && strLine != HEADER_END) {
98  getline(strDump, strLine); // Skip past header
99  }
100 
101  std::string keyHex, valueHex;
102  while (!strDump.eof() && keyHex != DATA_END) {
103  getline(strDump, keyHex);
104  if (keyHex != DATA_END) {
105  if (strDump.eof()) {
106  break;
107  }
108  getline(strDump, valueHex);
109  if (valueHex == DATA_END) {
110  warnings.push_back(
111  Untranslated("Salvage: WARNING: Number of keys in data "
112  "does not match number of values."));
113  break;
114  }
115  salvagedData.push_back(
116  make_pair(ParseHex(keyHex), ParseHex(valueHex)));
117  }
118  }
119 
120  bool fSuccess;
121  if (keyHex != DATA_END) {
122  warnings.push_back(Untranslated("Salvage: WARNING: Unexpected end of "
123  "file while reading salvage output."));
124  fSuccess = false;
125  } else {
126  fSuccess = (result == 0);
127  }
128 
129  if (salvagedData.empty()) {
130  error = strprintf(
131  Untranslated("Salvage(aggressive) found no records in %s."),
132  newFilename);
133  return false;
134  }
135 
136  std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
137  int ret = pdbCopy->open(nullptr, // Txn pointer
138  filename.c_str(), // Filename
139  "main", // Logical db name
140  DB_BTREE, // Database type
141  DB_CREATE, // Flags
142  0);
143  if (ret > 0) {
144  error =
145  strprintf(Untranslated("Cannot create database file %s"), filename);
146  pdbCopy->close(0);
147  return false;
148  }
149 
150  DbTxn *ptxn = env->TxnBegin();
151  CWallet dummyWallet(nullptr, "", CreateDummyWalletDatabase());
152  for (KeyValPair &row : salvagedData) {
153  /* Filter for only private key type KV pairs to be added to the salvaged
154  * wallet */
155  CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
156  CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
157  std::string strType, strErr;
158  bool fReadOK;
159  {
160  // Required in LoadKeyMetadata():
161  LOCK(dummyWallet.cs_wallet);
162  fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType,
163  strErr, KeyFilter);
164  }
165  if (!KeyFilter(strType)) {
166  continue;
167  }
168  if (!fReadOK) {
169  warnings.push_back(strprintf(
170  Untranslated("WARNING: WalletBatch::Recover skipping %s: %s"),
171  strType, strErr));
172  continue;
173  }
174  Dbt datKey(row.first.data(), row.first.size());
175  Dbt datValue(row.second.data(), row.second.size());
176  int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
177  if (ret2 > 0) {
178  fSuccess = false;
179  }
180  }
181  ptxn->commit(0);
182  pdbCopy->close(0);
183 
184  return fSuccess;
185 }
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:78
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:177
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:253
RecursiveMutex cs_wallet
Definition: wallet.h:388
static bool IsKeyType(const std::string &strType)
Definition: walletdb.cpp:769
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:30
static constexpr int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Definition: clientversion.h:38
const std::string HDCHAIN
Definition: walletdb.cpp:35
static const char * HEADER_END
Definition: salvage.cpp:15
static const char * DATA_END
Definition: salvage.cpp:17
std::pair< std::vector< uint8_t >, std::vector< uint8_t > > KeyValPair
Definition: salvage.cpp:18
bool RecoverDatabaseFile(const fs::path &file_path, bilingual_str &error, std::vector< bilingual_str > &warnings)
Definition: salvage.cpp:24
static bool KeyFilter(const std::string &type)
Definition: salvage.cpp:20
CAddrDb db
Definition: main.cpp:35
@ SER_DISK
Definition: serialize.h:153
std::vector< uint8_t > ParseHex(const char *psz)
bool verify
Definition: db.h:226
bool require_existing
Definition: db.h:222
Bilingual messages:
Definition: translation.h:17
#define LOCK(cs)
Definition: sync.h:306
bool error(const char *fmt, const Args &...args)
Definition: system.h:45
int64_t GetTime()
Definition: time.cpp:109
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1202
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:36
DatabaseStatus
Definition: db.h:229
std::unique_ptr< WalletDatabase > MakeDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: walletdb.cpp:1120
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:303
std::unique_ptr< WalletDatabase > CreateDummyWalletDatabase()
Return object for accessing dummy database with no read/write capabilities.
Definition: walletdb.cpp:1170