|
|
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
|
|
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
|
|
|
// Copyright (c) 2019-2020 The Hush developers
|
|
|
|
// Distributed under the GPLv3 software license, see the accompanying
|
|
|
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
* Copyright © 2014-2019 The SuperNET Developers. *
|
|
|
|
* *
|
|
|
|
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
|
|
|
|
* the top-level directory of this distribution for the individual copyright *
|
|
|
|
* holder information and the developer policies on copyright and licensing. *
|
|
|
|
* *
|
|
|
|
* Unless otherwise agreed in a custom licensing agreement, no part of the *
|
|
|
|
* SuperNET software, including this file may be copied, modified, propagated *
|
|
|
|
* or distributed except according to the terms contained in the LICENSE file *
|
|
|
|
* *
|
|
|
|
* Removal or modification of this copyright notice is prohibited. *
|
|
|
|
* *
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
#include "wallet/walletdb.h"
|
|
|
|
|
|
|
|
#include "consensus/validation.h"
|
|
|
|
#include "key_io.h"
|
|
|
|
#include "main.h"
|
|
|
|
#include "protocol.h"
|
|
|
|
#include "serialize.h"
|
|
|
|
#include "sync.h"
|
Split up util.cpp/h
Split up util.cpp/h into:
- string utilities (hex, base32, base64): no internal dependencies, no dependency on boost (apart from foreach)
- money utilities (parsesmoney, formatmoney)
- time utilities (gettime*, sleep, format date):
- and the rest (logging, argument parsing, config file parsing)
The latter is basically the environment and OS handling,
and is stripped of all utility functions, so we may want to
rename it to something else than util.cpp/h for clarity (Matt suggested
osinterface).
Breaks dependency of sha256.cpp on all the things pulled in by util.
10 years ago
|
|
|
#include "util.h"
|
|
|
|
#include "utiltime.h"
|
|
|
|
#include "wallet/wallet.h"
|
|
|
|
#include "zcash/Proof.hpp"
|
|
|
|
#include "komodo_defs.h"
|
|
|
|
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include <boost/foreach.hpp>
|
|
|
|
#include <boost/scoped_ptr.hpp>
|
Split up util.cpp/h
Split up util.cpp/h into:
- string utilities (hex, base32, base64): no internal dependencies, no dependency on boost (apart from foreach)
- money utilities (parsesmoney, formatmoney)
- time utilities (gettime*, sleep, format date):
- and the rest (logging, argument parsing, config file parsing)
The latter is basically the environment and OS handling,
and is stripped of all utility functions, so we may want to
rename it to something else than util.cpp/h for clarity (Matt suggested
osinterface).
Breaks dependency of sha256.cpp on all the things pulled in by util.
10 years ago
|
|
|
#include <boost/thread.hpp>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
static uint64_t nAccountingEntryNumber = 0;
|
|
|
|
static list<uint256> deadTxns;
|
|
|
|
extern CBlockIndex *komodo_blockindex(uint256 hash);
|
|
|
|
|
|
|
|
//
|
|
|
|
// CWalletDB
|
|
|
|
//
|
|
|
|
|
|
|
|
bool CWalletDB::WriteName(const string& strAddress, const string& strName)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(make_pair(string("name"), strAddress), strName);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::EraseName(const string& strAddress)
|
|
|
|
{
|
|
|
|
// This should only be used for sending addresses, never for receiving addresses,
|
|
|
|
// receiving addresses must always have an address book entry if they're not change return.
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Erase(make_pair(string("name"), strAddress));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(make_pair(string("purpose"), strAddress), strPurpose);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::ErasePurpose(const string& strPurpose)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Erase(make_pair(string("purpose"), strPurpose));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteTx(uint256 hash, const CWalletTx& wtx)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("tx"), hash), wtx);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::EraseTx(uint256 hash)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Erase(std::make_pair(std::string("tx"), hash));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
|
|
|
|
if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
|
|
|
|
keyMeta, false))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// hash pubkey/privkey to accelerate wallet load
|
|
|
|
std::vector<unsigned char> vchKey;
|
|
|
|
vchKey.reserve(vchPubKey.size() + vchPrivKey.size());
|
|
|
|
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
|
|
|
|
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());
|
|
|
|
|
|
|
|
return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
|
|
|
|
const std::vector<unsigned char>& vchCryptedSecret,
|
|
|
|
const CKeyMetadata &keyMeta)
|
|
|
|
{
|
|
|
|
const bool fEraseUnencryptedKey = true;
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
|
|
|
|
if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
|
|
|
|
keyMeta))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false))
|
|
|
|
return false;
|
|
|
|
if (fEraseUnencryptedKey)
|
|
|
|
{
|
|
|
|
Erase(std::make_pair(std::string("key"), vchPubKey));
|
|
|
|
Erase(std::make_pair(std::string("wkey"), vchPubKey));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteCryptedSaplingZKey(
|
|
|
|
const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
|
|
|
const std::vector<unsigned char>& vchCryptedSecret,
|
|
|
|
const CKeyMetadata &keyMeta)
|
|
|
|
{
|
|
|
|
const bool fEraseUnencryptedKey = true;
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
auto ivk = extfvk.fvk.in_viewing_key();
|
|
|
|
|
|
|
|
if (!Write(std::make_pair(std::string("sapzkeymeta"), ivk), keyMeta))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!Write(std::make_pair(std::string("csapzkey"), ivk), std::make_pair(extfvk, vchCryptedSecret), false))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (fEraseUnencryptedKey)
|
|
|
|
{
|
|
|
|
Erase(std::make_pair(std::string("sapzkey"), ivk));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
|
|
|
|
const libzcash::SaplingExtendedSpendingKey &key,
|
|
|
|
const CKeyMetadata &keyMeta)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
|
|
|
|
if (!Write(std::make_pair(std::string("sapzkeymeta"), ivk), keyMeta))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return Write(std::make_pair(std::string("sapzkey"), ivk), key, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteSaplingPaymentAddress(
|
|
|
|
const libzcash::SaplingPaymentAddress &addr,
|
|
|
|
const libzcash::SaplingIncomingViewingKey &ivk)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
|
|
|
|
return Write(std::make_pair(std::string("sapzaddr"), addr), ivk, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteWatchOnly(const CScript &dest)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::EraseWatchOnly(const CScript &dest)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::string("bestblock"), locator);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
|
|
|
|
{
|
|
|
|
return Read(std::string("bestblock"), locator);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::string("orderposnext"), nOrderPosNext);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::string("defaultkey"), vchPubKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteWitnessCacheSize(int64_t nWitnessCacheSize)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::string("witnesscachesize"), nWitnessCacheSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
|
|
|
|
{
|
|
|
|
return Read(std::make_pair(std::string("pool"), nPool), keypool);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("pool"), nPool), keypool);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::ErasePool(int64_t nPool)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Erase(std::make_pair(std::string("pool"), nPool));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteMinVersion(int nVersion)
|
|
|
|
{
|
|
|
|
return Write(std::string("minversion"), nVersion);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
|
|
|
|
{
|
|
|
|
account.SetNull();
|
|
|
|
return Read(make_pair(string("acc"), strAccount), account);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
|
|
|
|
{
|
|
|
|
return Write(make_pair(string("acc"), strAccount), account);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
|
|
|
|
{
|
|
|
|
return Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
|
|
|
|
{
|
|
|
|
return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
|
|
|
|
}
|
|
|
|
|
|
|
|
CAmount CWalletDB::GetAccountCreditDebit(const string& strAccount)
|
|
|
|
{
|
|
|
|
list<CAccountingEntry> entries;
|
|
|
|
ListAccountCreditDebit(strAccount, entries);
|
|
|
|
|
|
|
|
CAmount nCreditDebit = 0;
|
|
|
|
BOOST_FOREACH (const CAccountingEntry& entry, entries)
|
|
|
|
nCreditDebit += entry.nCreditDebit;
|
|
|
|
|
|
|
|
return nCreditDebit;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
|
|
|
|
{
|
|
|
|
bool fAllAccounts = (strAccount == "*");
|
|
|
|
|
|
|
|
Dbc* pcursor = GetCursor();
|
|
|
|
if (!pcursor)
|
|
|
|
throw runtime_error("CWalletDB::ListAccountCreditDebit(): cannot create DB cursor");
|
|
|
|
unsigned int fFlags = DB_SET_RANGE;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Read next record
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
|
|
if (fFlags == DB_SET_RANGE)
|
|
|
|
ssKey << std::make_pair(std::string("acentry"), std::make_pair((fAllAccounts ? string("") : strAccount), uint64_t(0)));
|
|
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
|
|
int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
|
|
|
|
fFlags = DB_NEXT;
|
|
|
|
if (ret == DB_NOTFOUND)
|
|
|
|
break;
|
|
|
|
else if (ret != 0)
|
|
|
|
{
|
|
|
|
pcursor->close();
|
|
|
|
throw runtime_error("CWalletDB::ListAccountCreditDebit(): error scanning DB");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unserialize
|
|
|
|
string strType;
|
|
|
|
ssKey >> strType;
|
|
|
|
if (strType != "acentry")
|
|
|
|
break;
|
|
|
|
CAccountingEntry acentry;
|
|
|
|
ssKey >> acentry.strAccount;
|
|
|
|
if (!fAllAccounts && acentry.strAccount != strAccount)
|
|
|
|
break;
|
|
|
|
|
|
|
|
ssValue >> acentry;
|
|
|
|
ssKey >> acentry.nEntryNo;
|
|
|
|
entries.push_back(acentry);
|
|
|
|
}
|
|
|
|
|
|
|
|
pcursor->close();
|
|
|
|
}
|
|
|
|
|
|
|
|
DBErrors CWalletDB::ReorderTransactions(CWallet* pwallet)
|
|
|
|
{
|
|
|
|
LOCK(pwallet->cs_wallet);
|
|
|
|
// Old wallets didn't have any defined order for transactions
|
|
|
|
// Probably a bad idea to change the output of this
|
|
|
|
|
|
|
|
// First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
|
|
|
|
typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
|
|
|
|
typedef multimap<int64_t, TxPair > TxItems;
|
|
|
|
TxItems txByTime;
|
|
|
|
|
|
|
|
for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
|
|
|
|
{
|
|
|
|
CWalletTx* wtx = &((*it).second);
|
|
|
|
txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
|
|
|
|
}
|
|
|
|
list<CAccountingEntry> acentries;
|
|
|
|
ListAccountCreditDebit("", acentries);
|
|
|
|
BOOST_FOREACH(CAccountingEntry& entry, acentries)
|
|
|
|
{
|
|
|
|
txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t& nOrderPosNext = pwallet->nOrderPosNext;
|
|
|
|
nOrderPosNext = 0;
|
|
|
|
std::vector<int64_t> nOrderPosOffsets;
|
|
|
|
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
|
|
|
|
{
|
|
|
|
CWalletTx *const pwtx = (*it).second.first;
|
|
|
|
CAccountingEntry *const pacentry = (*it).second.second;
|
|
|
|
int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
|
|
|
|
|
|
|
|
if (nOrderPos == -1)
|
|
|
|
{
|
|
|
|
nOrderPos = nOrderPosNext++;
|
|
|
|
nOrderPosOffsets.push_back(nOrderPos);
|
|
|
|
|
|
|
|
if (pwtx)
|
|
|
|
{
|
|
|
|
if (!WriteTx(pwtx->GetHash(), *pwtx))
|
|
|
|
return DB_LOAD_FAIL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
|
|
|
|
return DB_LOAD_FAIL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int64_t nOrderPosOff = 0;
|
|
|
|
BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets)
|
|
|
|
{
|
|
|
|
if (nOrderPos >= nOffsetStart)
|
|
|
|
++nOrderPosOff;
|
|
|
|
}
|
|
|
|
nOrderPos += nOrderPosOff;
|
|
|
|
nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
|
|
|
|
|
|
|
|
if (!nOrderPosOff)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Since we're changing the order, write it back
|
|
|
|
if (pwtx)
|
|
|
|
{
|
|
|
|
if (!WriteTx(pwtx->GetHash(), *pwtx))
|
|
|
|
return DB_LOAD_FAIL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
|
|
|
|
return DB_LOAD_FAIL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WriteOrderPosNext(nOrderPosNext);
|
|
|
|
|
|
|
|
return DB_LOAD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
class CWalletScanState {
|
|
|
|
public:
|
|
|
|
unsigned int nKeys;
|
|
|
|
unsigned int nCKeys;
|
|
|
|
unsigned int nKeyMeta;
|
|
|
|
unsigned int nZKeys;
|
|
|
|
unsigned int nCZKeys;
|
|
|
|
unsigned int nZKeyMeta;
|
|
|
|
unsigned int nSapZAddrs;
|
|
|
|
bool fIsEncrypted;
|
|
|
|
bool fAnyUnordered;
|
|
|
|
int nFileVersion;
|
|
|
|
vector<uint256> vWalletUpgrade;
|
|
|
|
|
|
|
|
CWalletScanState() {
|
|
|
|
nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = nSapZAddrs = 0;
|
|
|
|
fIsEncrypted = false;
|
|
|
|
fAnyUnordered = false;
|
|
|
|
nFileVersion = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
bool
|
|
|
|
ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
|
|
|
CWalletScanState &wss, string& strType, string& strErr)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
// Unserialize
|
|
|
|
// Taking advantage of the fact that pair serialization
|
|
|
|
// is just the two items serialized one after the other
|
|
|
|
ssKey >> strType;
|
|
|
|
if (strType == "name")
|
|
|
|
{
|
|
|
|
string strAddress;
|
|
|
|
ssKey >> strAddress;
|
|
|
|
ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name;
|
|
|
|
}
|
|
|
|
else if (strType == "purpose")
|
|
|
|
{
|
|
|
|
string strAddress;
|
|
|
|
ssKey >> strAddress;
|
|
|
|
ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose;
|
|
|
|
}
|
|
|
|
else if (strType == "tx")
|
|
|
|
{
|
|
|
|
uint256 hash;
|
|
|
|
ssKey >> hash;
|
|
|
|
CWalletTx wtx;
|
|
|
|
ssValue >> wtx;
|
|
|
|
CValidationState state;
|
|
|
|
auto verifier = libzcash::ProofVerifier::Strict();
|
|
|
|
// ac_public chains set at height like KMD and ZEX, will force a rescan if we dont ignore this error: bad-txns-acpublic-chain
|
|
|
|
// there cannot be any ztx in the wallet on ac_public chains that started from block 1, so this wont affect those.
|
|
|
|
// ac_private chains fail this check for notary nodes, need exception. Triggers full rescan without it.
|
|
|
|
if ( !(CheckTransaction(0,wtx, state, verifier, 0, 0) && (wtx.GetHash() == hash) && state.IsValid()) && (state.GetRejectReason() != "bad-txns-acpublic-chain" && state.GetRejectReason() != "bad-txns-acprivacy-chain" && state.GetRejectReason() != "bad-txns-stakingtx") )
|
|
|
|
{
|
|
|
|
//fprintf(stderr, "tx failed: %s rejectreason.%s\n", wtx.GetHash().GetHex().c_str(), state.GetRejectReason().c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Undo serialize changes in 31600
|
|
|
|
if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
|
|
|
|
{
|
|
|
|
if (!ssValue.empty())
|
|
|
|
{
|
|
|
|
char fTmp;
|
|
|
|
char fUnused;
|
|
|
|
ssValue >> fTmp >> fUnused >> wtx.strFromAccount;
|
|
|
|
strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s",
|
|
|
|
wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount, hash.ToString());
|
|
|
|
wtx.fTimeReceivedIsTxTime = fTmp;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString());
|
|
|
|
wtx.fTimeReceivedIsTxTime = 0;
|
|
|
|
}
|
|
|
|
wss.vWalletUpgrade.push_back(hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wtx.nOrderPos == -1)
|
|
|
|
wss.fAnyUnordered = true;
|
|
|
|
|
|
|
|
pwallet->AddToWallet(wtx, true, NULL);
|
|
|
|
}
|
|
|
|
else if (strType == "acentry")
|
|
|
|
{
|
|
|
|
string strAccount;
|
|
|
|
ssKey >> strAccount;
|
|
|
|
uint64_t nNumber;
|
|
|
|
ssKey >> nNumber;
|
|
|
|
if (nNumber > nAccountingEntryNumber)
|
|
|
|
nAccountingEntryNumber = nNumber;
|
|
|
|
|
|
|
|
if (!wss.fAnyUnordered)
|
|
|
|
{
|
|
|
|
CAccountingEntry acentry;
|
|
|
|
ssValue >> acentry;
|
|
|
|
if (acentry.nOrderPos == -1)
|
|
|
|
wss.fAnyUnordered = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strType == "watchs")
|
|
|
|
{
|
|
|
|
CScript script;
|
|
|
|
ssKey >> *(CScriptBase*)(&script);
|
|
|
|
char fYes;
|
|
|
|
ssValue >> fYes;
|
|
|
|
if (fYes == '1')
|
|
|
|
pwallet->LoadWatchOnly(script);
|
|
|
|
|
|
|
|
// Watch-only addresses have no birthday information for now,
|
|
|
|
// so set the wallet birthday to the beginning of time.
|
|
|
|
pwallet->nTimeFirstKey = 1;
|
|
|
|
}
|
|
|
|
else if (strType == "vkey")
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
libzcash::SproutViewingKey vk;
|
|
|
|
ssKey >> vk;
|
|
|
|
char fYes;
|
|
|
|
ssValue >> fYes;
|
|
|
|
if (fYes == '1')
|
|
|
|
pwallet->LoadSproutViewingKey(vk);
|
|
|
|
|
|
|
|
Viewing keys have no birthday information for now,
|
|
|
|
so set the wallet birthday to the beginning of time.
|
|
|
|
pwallet->nTimeFirstKey = 1;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
else if (strType == "zkey")
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
libzcash::SproutPaymentAddress addr;
|
|
|
|
ssKey >> addr;
|
|
|
|
libzcash::SproutSpendingKey key;
|
|
|
|
ssValue >> key;
|
|
|
|
|
|
|
|
//if (!pwallet->LoadZKey(key))
|
|
|
|
//{
|
|
|
|
// strErr = "Error reading wallet database: LoadZKey failed";
|
|
|
|
// return false;
|
|
|
|
//}
|
|
|
|
|
|
|
|
wss.nZKeys++;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
else if (strType == "sapzkey")
|
|
|
|
{
|
|
|
|
libzcash::SaplingIncomingViewingKey ivk;
|
|
|
|
ssKey >> ivk;
|
|
|
|
libzcash::SaplingExtendedSpendingKey key;
|
|
|
|
ssValue >> key;
|
|
|
|
|
|
|
|
if (!pwallet->LoadSaplingZKey(key))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadSaplingZKey failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//add checks for integrity
|
|
|
|
wss.nZKeys++;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (strType == "key" || strType == "wkey")
|
|
|
|
{
|
|
|
|
CPubKey vchPubKey;
|
|
|
|
ssKey >> vchPubKey;
|
|
|
|
if (!vchPubKey.IsValid())
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: CPubKey corrupt";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
CKey key;
|
|
|
|
CPrivKey pkey;
|
|
|
|
uint256 hash;
|
|
|
|
|
|
|
|
if (strType == "key")
|
|
|
|
{
|
|
|
|
wss.nKeys++;
|
|
|
|
ssValue >> pkey;
|
|
|
|
} else {
|
|
|
|
CWalletKey wkey;
|
|
|
|
ssValue >> wkey;
|
|
|
|
pkey = wkey.vchPrivKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Old wallets store keys as "key" [pubkey] => [privkey]
|
|
|
|
// ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key
|
|
|
|
// using EC operations as a checksum.
|
|
|
|
// Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while
|
|
|
|
// remaining backwards-compatible.
|
|
|
|
try
|
|
|
|
{
|
|
|
|
ssValue >> hash;
|
|
|
|
}
|
|
|
|
catch (...) {}
|
|
|
|
|
|
|
|
bool fSkipCheck = false;
|
|
|
|
|
|
|
|
if (!hash.IsNull())
|
|
|
|
{
|
|
|
|
// hash pubkey/privkey to accelerate wallet load
|
|
|
|
std::vector<unsigned char> vchKey;
|
|
|
|
vchKey.reserve(vchPubKey.size() + pkey.size());
|
|
|
|
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
|
|
|
|
vchKey.insert(vchKey.end(), pkey.begin(), pkey.end());
|
|
|
|
|
|
|
|
if (Hash(vchKey.begin(), vchKey.end()) != hash)
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fSkipCheck = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!key.Load(pkey, vchPubKey, fSkipCheck))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: CPrivKey corrupt";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!pwallet->LoadKey(key, vchPubKey))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadKey failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strType == "mkey")
|
|
|
|
{
|
|
|
|
unsigned int nID;
|
|
|
|
ssKey >> nID;
|
|
|
|
CMasterKey kMasterKey;
|
|
|
|
ssValue >> kMasterKey;
|
|
|
|
if(pwallet->mapMasterKeys.count(nID) != 0)
|
|
|
|
{
|
|
|
|
strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
pwallet->mapMasterKeys[nID] = kMasterKey;
|
|
|
|
if (pwallet->nMasterKeyMaxID < nID)
|
|
|
|
pwallet->nMasterKeyMaxID = nID;
|
|
|
|
}
|
|
|
|
else if (strType == "ckey")
|
|
|
|
{
|
|
|
|
vector<unsigned char> vchPubKey;
|
|
|
|
ssKey >> vchPubKey;
|
|
|
|
vector<unsigned char> vchPrivKey;
|
|
|
|
ssValue >> vchPrivKey;
|
|
|
|
wss.nCKeys++;
|
|
|
|
|
|
|
|
if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadCryptedKey failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
wss.fIsEncrypted = true;
|
|
|
|
}
|
|
|
|
else if (strType == "czkey")
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
libzcash::SproutPaymentAddress addr;
|
|
|
|
ssKey >> addr;
|
|
|
|
// Deserialization of a pair is just one item after another
|
|
|
|
uint256 rkValue;
|
|
|
|
ssValue >> rkValue;
|
|
|
|
libzcash::ReceivingKey rk(rkValue);
|
|
|
|
vector<unsigned char> vchCryptedSecret;
|
|
|
|
ssValue >> vchCryptedSecret;
|
|
|
|
wss.nCKeys++;
|
|
|
|
|
|
|
|
if (!pwallet->LoadCryptedZKey(addr, rk, vchCryptedSecret))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadCryptedZKey failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
wss.fIsEncrypted = true;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
else if (strType == "csapzkey")
|
|
|
|
{
|
|
|
|
libzcash::SaplingIncomingViewingKey ivk;
|
|
|
|
ssKey >> ivk;
|
|
|
|
libzcash::SaplingExtendedFullViewingKey extfvk;
|
|
|
|
ssValue >> extfvk;
|
|
|
|
vector<unsigned char> vchCryptedSecret;
|
|
|
|
ssValue >> vchCryptedSecret;
|
|
|
|
wss.nCKeys++;
|
|
|
|
|
|
|
|
if (!pwallet->LoadCryptedSaplingZKey(extfvk, vchCryptedSecret))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadCryptedSaplingZKey failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
wss.fIsEncrypted = true;
|
|
|
|
}
|
|
|
|
else if (strType == "keymeta")
|
|
|
|
{
|
|
|
|
CPubKey vchPubKey;
|
|
|
|
ssKey >> vchPubKey;
|
|
|
|
CKeyMetadata keyMeta;
|
|
|
|
ssValue >> keyMeta;
|
|
|
|
wss.nKeyMeta++;
|
|
|
|
|
|
|
|
pwallet->LoadKeyMetadata(vchPubKey, keyMeta);
|
|
|
|
|
|
|
|
// find earliest key creation time, as wallet birthday
|
|
|
|
if (!pwallet->nTimeFirstKey ||
|
|
|
|
(keyMeta.nCreateTime < pwallet->nTimeFirstKey))
|
|
|
|
pwallet->nTimeFirstKey = keyMeta.nCreateTime;
|
|
|
|
}
|
|
|
|
else if (strType == "zkeymeta")
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
libzcash::SproutPaymentAddress addr;
|
|
|
|
ssKey >> addr;
|
|
|
|
CKeyMetadata keyMeta;
|
|
|
|
ssValue >> keyMeta;
|
|
|
|
wss.nZKeyMeta++;
|
|
|
|
*/
|
|
|
|
|
|
|
|
// pwallet->LoadZKeyMetadata(addr, keyMeta);
|
|
|
|
|
|
|
|
// ignore earliest key creation time as taddr will exist before any zaddr
|
|
|
|
}
|
|
|
|
else if (strType == "sapzkeymeta")
|
|
|
|
{
|
|
|
|
libzcash::SaplingIncomingViewingKey ivk;
|
|
|
|
ssKey >> ivk;
|
|
|
|
CKeyMetadata keyMeta;
|
|
|
|
ssValue >> keyMeta;
|
|
|
|
|
|
|
|
wss.nZKeyMeta++;
|
|
|
|
|
|
|
|
pwallet->LoadSaplingZKeyMetadata(ivk, keyMeta);
|
|
|
|
}
|
|
|
|
else if (strType == "sapzaddr")
|
|
|
|
{
|
|
|
|
libzcash::SaplingPaymentAddress addr;
|
|
|
|
ssKey >> addr;
|
|
|
|
libzcash::SaplingIncomingViewingKey ivk;
|
|
|
|
ssValue >> ivk;
|
|
|
|
|
|
|
|
wss.nSapZAddrs++;
|
|
|
|
|
|
|
|
if (!pwallet->LoadSaplingPaymentAddress(addr, ivk))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadSaplingPaymentAddress failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strType == "defaultkey")
|
|
|
|
{
|
|
|
|
ssValue >> pwallet->vchDefaultKey;
|
|
|
|
}
|
|
|
|
else if (strType == "pool")
|
|
|
|
{
|
|
|
|
int64_t nIndex;
|
|
|
|
ssKey >> nIndex;
|
|
|
|
CKeyPool keypool;
|
|
|
|
ssValue >> keypool;
|
|
|
|
pwallet->setKeyPool.insert(nIndex);
|
|
|
|
|
|
|
|
// If no metadata exists yet, create a default with the pool key's
|
|
|
|
// creation time. Note that this may be overwritten by actually
|
|
|
|
// stored metadata for that key later, which is fine.
|
|
|
|
CKeyID keyid = keypool.vchPubKey.GetID();
|
|
|
|
if (pwallet->mapKeyMetadata.count(keyid) == 0)
|
|
|
|
pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
|
|
|
|
}
|
|
|
|
else if (strType == "version")
|
|
|
|
{
|
|
|
|
ssValue >> wss.nFileVersion;
|
|
|
|
if (wss.nFileVersion == 10300)
|
|
|
|
wss.nFileVersion = 300;
|
|
|
|
}
|
|
|
|
else if (strType == "cscript")
|
|
|
|
{
|
|
|
|
uint160 hash;
|
|
|
|
ssKey >> hash;
|
|
|
|
CScript script;
|
|
|
|
ssValue >> *(CScriptBase*)(&script);
|
|
|
|
if (!pwallet->LoadCScript(script))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadCScript failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strType == "orderposnext")
|
|
|
|
{
|
|
|
|
ssValue >> pwallet->nOrderPosNext;
|
|
|
|
}
|
|
|
|
else if (strType == "destdata")
|
|
|
|
{
|
|
|
|
std::string strAddress, strKey, strValue;
|
|
|
|
ssKey >> strAddress;
|
|
|
|
ssKey >> strKey;
|
|
|
|
ssValue >> strValue;
|
|
|
|
if (!pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadDestData failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strType == "witnesscachesize")
|
|
|
|
{
|
|
|
|
ssValue >> pwallet->nWitnessCacheSize;
|
|
|
|
}
|
|
|
|
else if (strType == "hdseed")
|
|
|
|
{
|
|
|
|
uint256 seedFp;
|
|
|
|
RawHDSeed rawSeed;
|
|
|
|
ssKey >> seedFp;
|
|
|
|
ssValue >> rawSeed;
|
|
|
|
HDSeed seed(rawSeed);
|
|
|
|
|
|
|
|
if (seed.Fingerprint() != seedFp)
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: HDSeed corrupt";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pwallet->LoadHDSeed(seed))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadHDSeed failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strType == "chdseed")
|
|
|
|
{
|
|
|
|
uint256 seedFp;
|
|
|
|
vector<unsigned char> vchCryptedSecret;
|
|
|
|
ssKey >> seedFp;
|
|
|
|
ssValue >> vchCryptedSecret;
|
|
|
|
if (!pwallet->LoadCryptedHDSeed(seedFp, vchCryptedSecret))
|
|
|
|
{
|
|
|
|
strErr = "Error reading wallet database: LoadCryptedHDSeed failed";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
wss.fIsEncrypted = true;
|
|
|
|
}
|
|
|
|
else if (strType == "hdchain")
|
|
|
|
{
|
|
|
|
CHDChain chain;
|
|
|
|
ssValue >> chain;
|
|
|
|
pwallet->SetHDChain(chain, true);
|
|
|
|
}
|
|
|
|
} catch (...)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool IsKeyType(string strType)
|
|
|
|
{
|
|
|
|
return (strType == "key" || strType == "wkey" ||
|
|
|
|
strType == "hdseed" || strType == "chdseed" ||
|
|
|
|
strType == "zkey" || strType == "czkey" ||
|
|
|
|
strType == "sapzkey" || strType == "csapzkey" ||
|
|
|
|
strType == "vkey" ||
|
|
|
|
strType == "mkey" || strType == "ckey");
|
|
|
|
}
|
|
|
|
|
|
|
|
DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
|
|
|
|
{
|
|
|
|
pwallet->vchDefaultKey = CPubKey();
|
|
|
|
CWalletScanState wss;
|
|
|
|
bool fNoncriticalErrors = false;
|
|
|
|
DBErrors result = DB_LOAD_OK;
|
|
|
|
|
|
|
|
try {
|
|
|
|
LOCK(pwallet->cs_wallet);
|
|
|
|
int nMinVersion = 0;
|
|
|
|
if (Read((string)"minversion", nMinVersion))
|
|
|
|
{
|
|
|
|
if (nMinVersion > CLIENT_VERSION)
|
|
|
|
return DB_TOO_NEW;
|
|
|
|
pwallet->LoadMinVersion(nMinVersion);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get cursor
|
|
|
|
Dbc* pcursor = GetCursor();
|
|
|
|
if (!pcursor)
|
|
|
|
{
|
|
|
|
LogPrintf("Error getting wallet database cursor\n");
|
|
|
|
return DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Read next record
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
|
|
int ret = ReadAtCursor(pcursor, ssKey, ssValue);
|
|
|
|
if (ret == DB_NOTFOUND)
|
|
|
|
break;
|
|
|
|
else if (ret != 0)
|
|
|
|
{
|
|
|
|
LogPrintf("Error reading next record from wallet database\n");
|
|
|
|
return DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to be tolerant of single corrupt records:
|
|
|
|
string strType, strErr;
|
|
|
|
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
|
|
|
|
{
|
|
|
|
// losing keys is considered a catastrophic error, anything else
|
|
|
|
// we assume the user can live with:
|
|
|
|
if (IsKeyType(strType))
|
|
|
|
result = DB_CORRUPT;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Leave other errors alone, if we try to fix them we might make things worse.
|
|
|
|
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
|
|
|
|
// set rescan for any error that is not vin-empty on staking chains.
|
|
|
|
if ( deadTxns.empty() && strType == "tx")
|
|
|
|
SoftSetBoolArg("-rescan", true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!strErr.empty())
|
|
|
|
LogPrintf("%s\n", strErr);
|
|
|
|
}
|
|
|
|
pcursor->close();
|
|
|
|
}
|
|
|
|
catch (const boost::thread_interrupted&) {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
result = DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !deadTxns.empty() )
|
|
|
|
{
|
|
|
|
// staking chains with vin-empty error is a failed staking tx.
|
|
|
|
// we remove then re add the tx here to stop needing a full rescan, which does not actually fix the problem.
|
|
|
|
int32_t reAdded = 0;
|
|
|
|
BOOST_FOREACH (uint256& hash, deadTxns)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Removing possible orphaned staking transaction from wallet.%s\n", hash.ToString().c_str());
|
|
|
|
if (!EraseTx(hash))
|
|
|
|
fprintf(stderr, "could not delete tx.%s\n",hash.ToString().c_str());
|
|
|
|
uint256 blockhash; CTransaction tx; CBlockIndex* pindex;
|
|
|
|
if ( GetTransaction(hash,tx,blockhash,false) && (pindex= komodo_blockindex(blockhash)) != 0 && chainActive.Contains(pindex) )
|
|
|
|
{
|
|
|
|
CWalletTx wtx(pwallet,tx);
|
|
|
|
pwallet->AddToWallet(wtx, true, NULL);
|
|
|
|
reAdded++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fprintf(stderr, "Cleared %li orphaned staking transactions from wallet. Readded %i real transactions.\n",deadTxns.size(),reAdded);
|
|
|
|
fNoncriticalErrors = false;
|
|
|
|
deadTxns.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fNoncriticalErrors && result == DB_LOAD_OK)
|
|
|
|
result = DB_NONCRITICAL_ERROR;
|
|
|
|
|
|
|
|
// Any wallet corruption at all: skip any rewriting or
|
|
|
|
// upgrading, we don't want to make it worse.
|
|
|
|
if (result != DB_LOAD_OK)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
LogPrintf("nFileVersion = %d\n", wss.nFileVersion);
|
|
|
|
|
|
|
|
LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n",
|
|
|
|
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys);
|
|
|
|
|
|
|
|
LogPrintf("ZKeys: %u plaintext, %u encrypted, %u w/metadata, %u total\n",
|
|
|
|
wss.nZKeys, wss.nCZKeys, wss.nZKeyMeta, wss.nZKeys + wss.nCZKeys);
|
|
|
|
|
|
|
|
// nTimeFirstKey is only reliable if all keys have metadata
|
|
|
|
if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta)
|
|
|
|
pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value'
|
|
|
|
|
|
|
|
BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade)
|
|
|
|
WriteTx(hash, pwallet->mapWallet[hash]);
|
|
|
|
|
|
|
|
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
|
|
|
|
if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
|
|
|
|
return DB_NEED_REWRITE;
|
|
|
|
|
|
|
|
if (wss.nFileVersion < CLIENT_VERSION) // Update
|
|
|
|
WriteVersion(CLIENT_VERSION);
|
|
|
|
|
|
|
|
if (wss.fAnyUnordered)
|
|
|
|
result = ReorderTransactions(pwallet);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vector<CWalletTx>& vWtx)
|
|
|
|
{
|
|
|
|
pwallet->vchDefaultKey = CPubKey();
|
|
|
|
bool fNoncriticalErrors = false;
|
|
|
|
DBErrors result = DB_LOAD_OK;
|
|
|
|
|
|
|
|
try {
|
|
|
|
LOCK(pwallet->cs_wallet);
|
|
|
|
int nMinVersion = 0;
|
|
|
|
if (Read((string)"minversion", nMinVersion))
|
|
|
|
{
|
|
|
|
if (nMinVersion > CLIENT_VERSION)
|
|
|
|
return DB_TOO_NEW;
|
|
|
|
pwallet->LoadMinVersion(nMinVersion);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get cursor
|
|
|
|
Dbc* pcursor = GetCursor();
|
|
|
|
if (!pcursor)
|
|
|
|
{
|
|
|
|
LogPrintf("Error getting wallet database cursor\n");
|
|
|
|
return DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Read next record
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
|
|
int ret = ReadAtCursor(pcursor, ssKey, ssValue);
|
|
|
|
if (ret == DB_NOTFOUND)
|
|
|
|
break;
|
|
|
|
else if (ret != 0)
|
|
|
|
{
|
|
|
|
LogPrintf("Error reading next record from wallet database\n");
|
|
|
|
return DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
string strType;
|
|
|
|
ssKey >> strType;
|
|
|
|
if (strType == "tx") {
|
|
|
|
uint256 hash;
|
|
|
|
ssKey >> hash;
|
|
|
|
|
|
|
|
std::vector<unsigned char> txData(ssValue.begin(), ssValue.end());
|
|
|
|
try {
|
|
|
|
CWalletTx wtx;
|
|
|
|
ssValue >> wtx;
|
|
|
|
vWtx.push_back(wtx);
|
|
|
|
} catch (...) {
|
|
|
|
// Decode failure likely due to Sapling v4 transaction format change
|
|
|
|
// between 2.0.0 and 2.0.1. As user is requesting deletion, log the
|
|
|
|
// transaction entry and then mark it for deletion anyway.
|
|
|
|
LogPrintf("Failed to decode wallet transaction; logging it here before deletion:\n");
|
|
|
|
LogPrintf("txid: %s\n%s\n", hash.GetHex(), HexStr(txData));
|
|
|
|
}
|
|
|
|
|
|
|
|
vTxHash.push_back(hash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pcursor->close();
|
|
|
|
}
|
|
|
|
catch (const boost::thread_interrupted&) {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
result = DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fNoncriticalErrors && result == DB_LOAD_OK)
|
|
|
|
result = DB_NONCRITICAL_ERROR;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector<CWalletTx>& vWtx)
|
|
|
|
{
|
|
|
|
// build list of wallet TXs
|
|
|
|
vector<uint256> vTxHash;
|
|
|
|
DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx);
|
|
|
|
if (err != DB_LOAD_OK)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
// erase each wallet TX
|
|
|
|
BOOST_FOREACH (uint256& hash, vTxHash) {
|
|
|
|
if (!EraseTx(hash))
|
|
|
|
return DB_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return DB_LOAD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ThreadFlushWalletDB(const string& strFile)
|
|
|
|
{
|
|
|
|
// Make this thread recognisable as the wallet flushing thread
|
|
|
|
RenameThread("hush-wallet");
|
|
|
|
|
|
|
|
static bool fOneThread;
|
|
|
|
if (fOneThread)
|
|
|
|
return;
|
|
|
|
fOneThread = true;
|
|
|
|
if (!GetBoolArg("-flushwallet", true))
|
|
|
|
return;
|
|
|
|
|
|
|
|
unsigned int nLastSeen = nWalletDBUpdated;
|
|
|
|
unsigned int nLastFlushed = nWalletDBUpdated;
|
|
|
|
int64_t nLastWalletUpdate = GetTime();
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
MilliSleep(500);
|
|
|
|
|
|
|
|
if (nLastSeen != nWalletDBUpdated)
|
|
|
|
{
|
|
|
|
nLastSeen = nWalletDBUpdated;
|
|
|
|
nLastWalletUpdate = GetTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
|
|
|
|
{
|
|
|
|
TRY_LOCK(bitdb.cs_db,lockDb);
|
|
|
|
if (lockDb)
|
|
|
|
{
|
|
|
|
// Don't do this if any databases are in use
|
|
|
|
int nRefCount = 0;
|
|
|
|
map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
|
|
|
|
while (mi != bitdb.mapFileUseCount.end())
|
|
|
|
{
|
|
|
|
nRefCount += (*mi).second;
|
|
|
|
mi++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nRefCount == 0)
|
|
|
|
{
|
|
|
|
boost::this_thread::interruption_point();
|
|
|
|
map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
|
|
|
|
if (mi != bitdb.mapFileUseCount.end())
|
|
|
|
{
|
|
|
|
LogPrint("db", "Flushing wallet.dat\n");
|
|
|
|
nLastFlushed = nWalletDBUpdated;
|
|
|
|
int64_t nStart = GetTimeMillis();
|
|
|
|
|
|
|
|
// Flush wallet.dat so it's self contained
|
|
|
|
bitdb.CloseDb(strFile);
|
|
|
|
bitdb.CheckpointLSN(strFile);
|
|
|
|
|
|
|
|
bitdb.mapFileUseCount.erase(mi++);
|
|
|
|
LogPrint("db", "Flushed wallet.dat %dms\n", GetTimeMillis() - nStart);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackupWallet(const CWallet& wallet, const string& strDest)
|
|
|
|
{
|
|
|
|
if (!wallet.fFileBacked)
|
|
|
|
return false;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
LOCK(bitdb.cs_db);
|
|
|
|
if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0)
|
|
|
|
{
|
|
|
|
// Flush log data to the dat file
|
|
|
|
bitdb.CloseDb(wallet.strWalletFile);
|
|
|
|
bitdb.CheckpointLSN(wallet.strWalletFile);
|
|
|
|
bitdb.mapFileUseCount.erase(wallet.strWalletFile);
|
|
|
|
|
|
|
|
// Copy wallet.dat
|
|
|
|
boost::filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile;
|
|
|
|
boost::filesystem::path pathDest(strDest);
|
|
|
|
if (boost::filesystem::is_directory(pathDest))
|
|
|
|
pathDest /= wallet.strWalletFile;
|
|
|
|
|
|
|
|
try {
|
|
|
|
boost::filesystem::copy_file(pathSrc, pathDest, boost::filesystem::copy_option::overwrite_if_exists);
|
|
|
|
LogPrintf("copied wallet.dat to %s\n", pathDest.string());
|
|
|
|
return true;
|
|
|
|
} catch (const boost::filesystem::filesystem_error& e) {
|
|
|
|
LogPrintf("error copying wallet.dat to %s - %s\n", pathDest.string(), e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MilliSleep(100);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::Compact(CDBEnv& dbenv, const std::string& strFile)
|
|
|
|
{
|
|
|
|
bool fSuccess = dbenv.Compact(strFile);
|
|
|
|
return fSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Try to (very carefully!) recover wallet.dat if there is a problem.
|
|
|
|
//
|
|
|
|
bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys)
|
|
|
|
{
|
|
|
|
// Recovery procedure:
|
|
|
|
// move wallet.dat to wallet.timestamp.bak
|
|
|
|
// Call Salvage with fAggressive=true to
|
|
|
|
// get as much data as possible.
|
|
|
|
// Rewrite salvaged data to wallet.dat
|
|
|
|
// Set -rescan so any missing transactions will be
|
|
|
|
// found.
|
|
|
|
int64_t now = GetTime();
|
|
|
|
std::string newFilename = strprintf("wallet.%d.bak", now);
|
|
|
|
|
|
|
|
int result = dbenv.dbenv->dbrename(NULL, filename.c_str(), NULL,
|
|
|
|
newFilename.c_str(), DB_AUTO_COMMIT);
|
|
|
|
if (result == 0)
|
|
|
|
LogPrintf("Renamed %s to %s\n", filename, newFilename);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<CDBEnv::KeyValPair> salvagedData;
|
|
|
|
bool fSuccess = dbenv.Salvage(newFilename, true, salvagedData);
|
|
|
|
if (salvagedData.empty())
|
|
|
|
{
|
|
|
|
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
|
|
|
|
|
|
|
|
boost::scoped_ptr<Db> pdbCopy(new Db(dbenv.dbenv, 0));
|
|
|
|
int ret = pdbCopy->open(NULL, // Txn pointer
|
|
|
|
filename.c_str(), // Filename
|
|
|
|
"main", // Logical db name
|
|
|
|
DB_BTREE, // Database type
|
|
|
|
DB_CREATE, // Flags
|
|
|
|
0);
|
|
|
|
if (ret > 0)
|
|
|
|
{
|
|
|
|
LogPrintf("Cannot create database file %s\n", filename);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
CWallet dummyWallet;
|
|
|
|
CWalletScanState wss;
|
|
|
|
|
|
|
|
DbTxn* ptxn = dbenv.TxnBegin();
|
|
|
|
BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData)
|
|
|
|
{
|
|
|
|
if (fOnlyKeys)
|
|
|
|
{
|
|
|
|
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
|
|
|
|
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
|
|
|
|
string strType, strErr;
|
|
|
|
bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
|
|
|
|
wss, strType, strErr);
|
|
|
|
if (!IsKeyType(strType))
|
|
|
|
continue;
|
|
|
|
if (!fReadOK)
|
|
|
|
{
|
|
|
|
LogPrintf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType, strErr);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Dbt datKey(&row.first[0], row.first.size());
|
|
|
|
Dbt datValue(&row.second[0], row.second.size());
|
|
|
|
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
|
|
|
|
if (ret2 > 0)
|
|
|
|
fSuccess = false;
|
|
|
|
}
|
|
|
|
ptxn->commit(0);
|
|
|
|
pdbCopy->close(0);
|
|
|
|
|
|
|
|
return fSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename)
|
|
|
|
{
|
|
|
|
return CWalletDB::Recover(dbenv, filename, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::EraseDestData(const std::string &address, const std::string &key)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CWalletDB::WriteHDSeed(const HDSeed& seed)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("hdseed"), seed.Fingerprint()), seed.RawSeed());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::make_pair(std::string("chdseed"), seedFp), vchCryptedSecret);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CWalletDB::WriteHDChain(const CHDChain& chain)
|
|
|
|
{
|
|
|
|
nWalletDBUpdated++;
|
|
|
|
return Write(std::string("hdchain"), chain);
|
|
|
|
}
|