mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-30 02:15:54 +02:00
Merge #11281: Avoid permanent cs_main/cs_wallet lock during RescanFromTime
7f81250Mention that other RPC calls report keys as "imported" while txns are still missing (Jonas Schnelli)ccd8ef6Reduce cs_main lock in ReadBlockFromDisk, only read GetBlockPos under the lock (Jonas Schnelli)bc356b4Make sure WalletRescanReserver has successfully reserved the rescan (Jonas Schnelli)dbf8556Add RAII wallet rescan reserver (Jonas Schnelli)8d0b610Avoid pemanent cs_main/cs_wallet lock during wallet rescans (Jonas Schnelli) Pull request description: Right now, we are holding `cs_main`/`cs_wallet` during the whole rescan process (which can take a couple of hours). This was probably only done because of laziness and it is an important show-stopper for #11200 (GUI rescan abort). Tree-SHA512: 0fc3f82d0ee9b2f013e6bacba8d59f7334306660cd676cd64c47bb305c4cb7c7a36219d6a6f76023b74e5fe87f3ab9fc7fd2439e939f71aef653fddb0a1e23b1
This commit is contained in:
@@ -169,7 +169,9 @@ void TestGUI()
|
||||
}
|
||||
{
|
||||
LOCK(cs_main);
|
||||
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
|
||||
}
|
||||
wallet.SetBroadcastTransactions(true);
|
||||
|
||||
|
||||
@@ -1121,7 +1121,13 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus:
|
||||
|
||||
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
|
||||
{
|
||||
if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), consensusParams))
|
||||
CDiskBlockPos blockPos;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
blockPos = pindex->GetBlockPos();
|
||||
}
|
||||
|
||||
if (!ReadBlockFromDisk(block, blockPos, consensusParams))
|
||||
return false;
|
||||
if (block.GetHash() != pindex->GetBlockHash())
|
||||
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
|
||||
|
||||
@@ -86,7 +86,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
||||
"1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
|
||||
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
||||
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true.\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
|
||||
"may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
|
||||
"\nExamples:\n"
|
||||
"\nDump a private key\n"
|
||||
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
|
||||
@@ -101,61 +102,65 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
||||
);
|
||||
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
std::string strSecret = request.params[0].get_str();
|
||||
std::string strLabel = "";
|
||||
if (!request.params[1].isNull())
|
||||
strLabel = request.params[1].get_str();
|
||||
|
||||
// Whether to perform rescan after import
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
bool fRescan = true;
|
||||
if (!request.params[2].isNull())
|
||||
fRescan = request.params[2].get_bool();
|
||||
|
||||
if (fRescan && fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
||||
|
||||
CBitcoinSecret vchSecret;
|
||||
bool fGood = vchSecret.SetString(strSecret);
|
||||
|
||||
if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
|
||||
|
||||
CKey key = vchSecret.GetKey();
|
||||
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
|
||||
|
||||
CPubKey pubkey = key.GetPubKey();
|
||||
assert(key.VerifyPubKey(pubkey));
|
||||
CKeyID vchAddress = pubkey.GetID();
|
||||
{
|
||||
pwallet->MarkDirty();
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
// We don't know which corresponding address will be used; label them all
|
||||
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
|
||||
pwallet->SetAddressBook(dest, strLabel, "receive");
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
std::string strSecret = request.params[0].get_str();
|
||||
std::string strLabel = "";
|
||||
if (!request.params[1].isNull())
|
||||
strLabel = request.params[1].get_str();
|
||||
|
||||
// Whether to perform rescan after import
|
||||
if (!request.params[2].isNull())
|
||||
fRescan = request.params[2].get_bool();
|
||||
|
||||
if (fRescan && fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
||||
|
||||
if (fRescan && !reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
// Don't throw error in case a key is already there
|
||||
if (pwallet->HaveKey(vchAddress)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
|
||||
|
||||
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
||||
}
|
||||
pwallet->LearnAllRelatedScripts(pubkey);
|
||||
|
||||
// whenever a key is imported, we need to scan the whole chain
|
||||
pwallet->UpdateTimeFirstKey(1);
|
||||
|
||||
if (fRescan) {
|
||||
pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
|
||||
CBitcoinSecret vchSecret;
|
||||
bool fGood = vchSecret.SetString(strSecret);
|
||||
|
||||
if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
|
||||
|
||||
CKey key = vchSecret.GetKey();
|
||||
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
|
||||
|
||||
CPubKey pubkey = key.GetPubKey();
|
||||
assert(key.VerifyPubKey(pubkey));
|
||||
CKeyID vchAddress = pubkey.GetID();
|
||||
{
|
||||
pwallet->MarkDirty();
|
||||
// We don't know which corresponding address will be used; label them all
|
||||
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
|
||||
pwallet->SetAddressBook(dest, strLabel, "receive");
|
||||
}
|
||||
|
||||
// Don't throw error in case a key is already there
|
||||
if (pwallet->HaveKey(vchAddress)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
// whenever a key is imported, we need to scan the whole chain
|
||||
pwallet->UpdateTimeFirstKey(1);
|
||||
pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
|
||||
|
||||
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
||||
}
|
||||
pwallet->LearnAllRelatedScripts(pubkey);
|
||||
}
|
||||
}
|
||||
if (fRescan) {
|
||||
pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
}
|
||||
@@ -237,7 +242,8 @@ UniValue importaddress(const JSONRPCRequest& request)
|
||||
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
||||
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
|
||||
"4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true.\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
|
||||
"may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
|
||||
"If you have the full public key, you should call importpubkey instead of this.\n"
|
||||
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
|
||||
"as change, and not show up in many RPCs.\n"
|
||||
@@ -263,29 +269,35 @@ UniValue importaddress(const JSONRPCRequest& request)
|
||||
if (fRescan && fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
||||
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
if (fRescan && !reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
// Whether to import a p2sh version, too
|
||||
bool fP2SH = false;
|
||||
if (!request.params[3].isNull())
|
||||
fP2SH = request.params[3].get_bool();
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
{
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
CTxDestination dest = DecodeDestination(request.params[0].get_str());
|
||||
if (IsValidDestination(dest)) {
|
||||
if (fP2SH) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
|
||||
CTxDestination dest = DecodeDestination(request.params[0].get_str());
|
||||
if (IsValidDestination(dest)) {
|
||||
if (fP2SH) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
|
||||
}
|
||||
ImportAddress(pwallet, dest, strLabel);
|
||||
} else if (IsHex(request.params[0].get_str())) {
|
||||
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
|
||||
ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
|
||||
}
|
||||
ImportAddress(pwallet, dest, strLabel);
|
||||
} else if (IsHex(request.params[0].get_str())) {
|
||||
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
|
||||
ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
|
||||
}
|
||||
|
||||
if (fRescan)
|
||||
{
|
||||
pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
|
||||
pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
|
||||
pwallet->ReacceptWalletTransactions();
|
||||
}
|
||||
|
||||
@@ -406,7 +418,8 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
||||
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
|
||||
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
||||
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true.\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
|
||||
"may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
|
||||
"\nExamples:\n"
|
||||
"\nImport a public key with rescan\n"
|
||||
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
|
||||
@@ -429,6 +442,11 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
||||
if (fRescan && fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
||||
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
if (fRescan && !reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
if (!IsHex(request.params[0].get_str()))
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
||||
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
|
||||
@@ -436,17 +454,18 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
||||
if (!pubKey.IsFullyValid())
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
{
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
|
||||
ImportAddress(pwallet, dest, strLabel);
|
||||
for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
|
||||
ImportAddress(pwallet, dest, strLabel);
|
||||
}
|
||||
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
|
||||
pwallet->LearnAllRelatedScripts(pubKey);
|
||||
}
|
||||
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
|
||||
pwallet->LearnAllRelatedScripts(pubKey);
|
||||
|
||||
if (fRescan)
|
||||
{
|
||||
pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
|
||||
pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
|
||||
pwallet->ReacceptWalletTransactions();
|
||||
}
|
||||
|
||||
@@ -479,91 +498,98 @@ UniValue importwallet(const JSONRPCRequest& request)
|
||||
if (fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
std::ifstream file;
|
||||
file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
||||
|
||||
int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
|
||||
|
||||
bool fGood = true;
|
||||
|
||||
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
|
||||
file.seekg(0, file.beg);
|
||||
|
||||
pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
|
||||
while (file.good()) {
|
||||
pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
std::vector<std::string> vstr;
|
||||
boost::split(vstr, line, boost::is_any_of(" "));
|
||||
if (vstr.size() < 2)
|
||||
continue;
|
||||
CBitcoinSecret vchSecret;
|
||||
if (vchSecret.SetString(vstr[0])) {
|
||||
CKey key = vchSecret.GetKey();
|
||||
CPubKey pubkey = key.GetPubKey();
|
||||
assert(key.VerifyPubKey(pubkey));
|
||||
CKeyID keyid = pubkey.GetID();
|
||||
if (pwallet->HaveKey(keyid)) {
|
||||
LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
|
||||
continue;
|
||||
}
|
||||
int64_t nTime = DecodeDumpTime(vstr[1]);
|
||||
std::string strLabel;
|
||||
bool fLabel = true;
|
||||
for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
|
||||
if (boost::algorithm::starts_with(vstr[nStr], "#"))
|
||||
break;
|
||||
if (vstr[nStr] == "change=1")
|
||||
fLabel = false;
|
||||
if (vstr[nStr] == "reserve=1")
|
||||
fLabel = false;
|
||||
if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
|
||||
strLabel = DecodeDumpString(vstr[nStr].substr(6));
|
||||
fLabel = true;
|
||||
}
|
||||
}
|
||||
LogPrintf("Importing %s...\n", EncodeDestination(keyid));
|
||||
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
||||
fGood = false;
|
||||
continue;
|
||||
}
|
||||
pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
|
||||
if (fLabel)
|
||||
pwallet->SetAddressBook(keyid, strLabel, "receive");
|
||||
nTimeBegin = std::min(nTimeBegin, nTime);
|
||||
} else if(IsHex(vstr[0])) {
|
||||
std::vector<unsigned char> vData(ParseHex(vstr[0]));
|
||||
CScript script = CScript(vData.begin(), vData.end());
|
||||
if (pwallet->HaveCScript(script)) {
|
||||
LogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
|
||||
continue;
|
||||
}
|
||||
if(!pwallet->AddCScript(script)) {
|
||||
LogPrintf("Error importing script %s\n", vstr[0]);
|
||||
fGood = false;
|
||||
continue;
|
||||
}
|
||||
int64_t birth_time = DecodeDumpTime(vstr[1]);
|
||||
if (birth_time > 0) {
|
||||
pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time;
|
||||
nTimeBegin = std::min(nTimeBegin, birth_time);
|
||||
}
|
||||
}
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
if (!reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
file.close();
|
||||
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
|
||||
pwallet->UpdateTimeFirstKey(nTimeBegin);
|
||||
pwallet->RescanFromTime(nTimeBegin, false /* update */);
|
||||
|
||||
int64_t nTimeBegin = 0;
|
||||
bool fGood = true;
|
||||
{
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
std::ifstream file;
|
||||
file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
||||
}
|
||||
nTimeBegin = chainActive.Tip()->GetBlockTime();
|
||||
|
||||
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
|
||||
file.seekg(0, file.beg);
|
||||
|
||||
pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
|
||||
while (file.good()) {
|
||||
pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
std::vector<std::string> vstr;
|
||||
boost::split(vstr, line, boost::is_any_of(" "));
|
||||
if (vstr.size() < 2)
|
||||
continue;
|
||||
CBitcoinSecret vchSecret;
|
||||
if (vchSecret.SetString(vstr[0])) {
|
||||
CKey key = vchSecret.GetKey();
|
||||
CPubKey pubkey = key.GetPubKey();
|
||||
assert(key.VerifyPubKey(pubkey));
|
||||
CKeyID keyid = pubkey.GetID();
|
||||
if (pwallet->HaveKey(keyid)) {
|
||||
LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
|
||||
continue;
|
||||
}
|
||||
int64_t nTime = DecodeDumpTime(vstr[1]);
|
||||
std::string strLabel;
|
||||
bool fLabel = true;
|
||||
for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
|
||||
if (boost::algorithm::starts_with(vstr[nStr], "#"))
|
||||
break;
|
||||
if (vstr[nStr] == "change=1")
|
||||
fLabel = false;
|
||||
if (vstr[nStr] == "reserve=1")
|
||||
fLabel = false;
|
||||
if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
|
||||
strLabel = DecodeDumpString(vstr[nStr].substr(6));
|
||||
fLabel = true;
|
||||
}
|
||||
}
|
||||
LogPrintf("Importing %s...\n", EncodeDestination(keyid));
|
||||
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
||||
fGood = false;
|
||||
continue;
|
||||
}
|
||||
pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
|
||||
if (fLabel)
|
||||
pwallet->SetAddressBook(keyid, strLabel, "receive");
|
||||
nTimeBegin = std::min(nTimeBegin, nTime);
|
||||
} else if(IsHex(vstr[0])) {
|
||||
std::vector<unsigned char> vData(ParseHex(vstr[0]));
|
||||
CScript script = CScript(vData.begin(), vData.end());
|
||||
if (pwallet->HaveCScript(script)) {
|
||||
LogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
|
||||
continue;
|
||||
}
|
||||
if(!pwallet->AddCScript(script)) {
|
||||
LogPrintf("Error importing script %s\n", vstr[0]);
|
||||
fGood = false;
|
||||
continue;
|
||||
}
|
||||
int64_t birth_time = DecodeDumpTime(vstr[1]);
|
||||
if (birth_time > 0) {
|
||||
pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time;
|
||||
nTimeBegin = std::min(nTimeBegin, birth_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
|
||||
pwallet->UpdateTimeFirstKey(nTimeBegin);
|
||||
}
|
||||
pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */);
|
||||
pwallet->MarkDirty();
|
||||
|
||||
if (!fGood)
|
||||
@@ -685,7 +711,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
|
||||
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
|
||||
file << "\n";
|
||||
|
||||
// add the base58check encoded extended master if the wallet uses HD
|
||||
// add the base58check encoded extended master if the wallet uses HD
|
||||
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
|
||||
if (!masterKeyID.IsNull())
|
||||
{
|
||||
@@ -1110,6 +1136,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
||||
" {\n"
|
||||
" \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
|
||||
" }\n"
|
||||
"\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
|
||||
"may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n"
|
||||
"\nExamples:\n" +
|
||||
HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
|
||||
"{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
|
||||
@@ -1135,49 +1163,55 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
||||
}
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
// Verify all timestamps are present before importing any keys.
|
||||
const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
|
||||
for (const UniValue& data : requests.getValues()) {
|
||||
GetImportTimestamp(data, now);
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
if (fRescan && !reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
int64_t now = 0;
|
||||
bool fRunScan = false;
|
||||
const int64_t minimumTimestamp = 1;
|
||||
int64_t nLowestTimestamp = 0;
|
||||
|
||||
if (fRescan && chainActive.Tip()) {
|
||||
nLowestTimestamp = chainActive.Tip()->GetBlockTime();
|
||||
} else {
|
||||
fRescan = false;
|
||||
}
|
||||
|
||||
UniValue response(UniValue::VARR);
|
||||
{
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
for (const UniValue& data : requests.getValues()) {
|
||||
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
|
||||
const UniValue result = ProcessImport(pwallet, data, timestamp);
|
||||
response.push_back(result);
|
||||
|
||||
if (!fRescan) {
|
||||
continue;
|
||||
// Verify all timestamps are present before importing any keys.
|
||||
now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
|
||||
for (const UniValue& data : requests.getValues()) {
|
||||
GetImportTimestamp(data, now);
|
||||
}
|
||||
|
||||
// If at least one request was successful then allow rescan.
|
||||
if (result["success"].get_bool()) {
|
||||
fRunScan = true;
|
||||
const int64_t minimumTimestamp = 1;
|
||||
|
||||
if (fRescan && chainActive.Tip()) {
|
||||
nLowestTimestamp = chainActive.Tip()->GetBlockTime();
|
||||
} else {
|
||||
fRescan = false;
|
||||
}
|
||||
|
||||
// Get the lowest timestamp.
|
||||
if (timestamp < nLowestTimestamp) {
|
||||
nLowestTimestamp = timestamp;
|
||||
for (const UniValue& data : requests.getValues()) {
|
||||
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
|
||||
const UniValue result = ProcessImport(pwallet, data, timestamp);
|
||||
response.push_back(result);
|
||||
|
||||
if (!fRescan) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If at least one request was successful then allow rescan.
|
||||
if (result["success"].get_bool()) {
|
||||
fRunScan = true;
|
||||
}
|
||||
|
||||
// Get the lowest timestamp.
|
||||
if (timestamp < nLowestTimestamp) {
|
||||
nLowestTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fRescan && fRunScan && requests.size()) {
|
||||
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, true /* update */);
|
||||
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
|
||||
pwallet->ReacceptWalletTransactions();
|
||||
|
||||
if (scannedTime > nLowestTimestamp) {
|
||||
|
||||
@@ -3416,30 +3416,41 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||
);
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
||||
CBlockIndex *pindexStart = chainActive.Genesis();
|
||||
CBlockIndex *pindexStop = nullptr;
|
||||
if (!request.params[0].isNull()) {
|
||||
pindexStart = chainActive[request.params[0].get_int()];
|
||||
if (!pindexStart) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
|
||||
}
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
if (!reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
pindexStop = chainActive[request.params[1].get_int()];
|
||||
if (!pindexStop) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
|
||||
CBlockIndex *pindexStart = nullptr;
|
||||
CBlockIndex *pindexStop = nullptr;
|
||||
CBlockIndex *pChainTip = nullptr;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
pindexStart = chainActive.Genesis();
|
||||
pChainTip = chainActive.Tip();
|
||||
|
||||
if (!request.params[0].isNull()) {
|
||||
pindexStart = chainActive[request.params[0].get_int()];
|
||||
if (!pindexStart) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
|
||||
}
|
||||
}
|
||||
else if (pindexStop->nHeight < pindexStart->nHeight) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
pindexStop = chainActive[request.params[1].get_int()];
|
||||
if (!pindexStop) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
|
||||
}
|
||||
else if (pindexStop->nHeight < pindexStart->nHeight) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can't rescan beyond non-pruned blocks, stop and throw an error
|
||||
if (fPruneMode) {
|
||||
CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip();
|
||||
LOCK(cs_main);
|
||||
CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
|
||||
while (block && block->nHeight >= pindexStart->nHeight) {
|
||||
if (!(block->nStatus & BLOCK_HAVE_DATA)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
|
||||
@@ -3448,18 +3459,17 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||
}
|
||||
}
|
||||
|
||||
CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true);
|
||||
CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true);
|
||||
if (!stopBlock) {
|
||||
if (pwallet->IsAbortingRescan()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
|
||||
}
|
||||
// if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
|
||||
stopBlock = pindexStop ? pindexStop : chainActive.Tip();
|
||||
stopBlock = pindexStop ? pindexStop : pChainTip;
|
||||
}
|
||||
else {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
||||
}
|
||||
|
||||
UniValue response(UniValue::VOBJ);
|
||||
response.pushKV("start_height", pindexStart->nHeight);
|
||||
response.pushKV("stop_height", stopBlock->nHeight);
|
||||
|
||||
@@ -384,7 +384,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||
{
|
||||
CWallet wallet;
|
||||
AddKey(wallet, coinbaseKey);
|
||||
BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr));
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
|
||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
|
||||
}
|
||||
|
||||
@@ -397,7 +399,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||
{
|
||||
CWallet wallet;
|
||||
AddKey(wallet, coinbaseKey);
|
||||
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr));
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
|
||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
|
||||
}
|
||||
|
||||
@@ -608,7 +612,9 @@ public:
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
AddKey(*wallet, coinbaseKey);
|
||||
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr);
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
|
||||
}
|
||||
|
||||
~ListCoinsTestingSetup()
|
||||
|
||||
@@ -1612,19 +1612,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
|
||||
* @return Earliest timestamp that could be successfully scanned from. Timestamp
|
||||
* returned will be higher than startTime if relevant blocks could not be read.
|
||||
*/
|
||||
int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
|
||||
int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
// Find starting block. May be null if nCreateTime is greater than the
|
||||
// highest blockchain timestamp, in which case there is nothing that needs
|
||||
// to be scanned.
|
||||
CBlockIndex* const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
|
||||
LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
|
||||
CBlockIndex* startBlock = nullptr;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
|
||||
LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
|
||||
}
|
||||
|
||||
if (startBlock) {
|
||||
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update);
|
||||
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
|
||||
if (failedBlock) {
|
||||
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
|
||||
}
|
||||
@@ -1643,12 +1644,17 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
|
||||
*
|
||||
* If pindexStop is not a nullptr, the scan will stop at the block-index
|
||||
* defined by pindexStop
|
||||
*
|
||||
* Caller needs to make sure pindexStop (and the optional pindexStart) are on
|
||||
* the main chain after to the addition of any new keys you want to detect
|
||||
* transactions for.
|
||||
*/
|
||||
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate)
|
||||
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
|
||||
{
|
||||
int64_t nNow = GetTime();
|
||||
const CChainParams& chainParams = Params();
|
||||
|
||||
assert(reserver.isReserved());
|
||||
if (pindexStop) {
|
||||
assert(pindexStop->nHeight >= pindexStart->nHeight);
|
||||
}
|
||||
@@ -1656,24 +1662,42 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
|
||||
CBlockIndex* pindex = pindexStart;
|
||||
CBlockIndex* ret = nullptr;
|
||||
{
|
||||
LOCK2(cs_main, cs_wallet);
|
||||
fAbortRescan = false;
|
||||
fScanningWallet = true;
|
||||
|
||||
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
|
||||
double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
|
||||
double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
|
||||
CBlockIndex* tip = nullptr;
|
||||
double dProgressStart;
|
||||
double dProgressTip;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
tip = chainActive.Tip();
|
||||
dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
|
||||
dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
|
||||
}
|
||||
while (pindex && !fAbortRescan)
|
||||
{
|
||||
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
|
||||
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
|
||||
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
|
||||
double gvp = 0;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
gvp = GuessVerificationProgress(chainParams.TxData(), pindex);
|
||||
}
|
||||
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
|
||||
}
|
||||
if (GetTime() >= nNow + 60) {
|
||||
nNow = GetTime();
|
||||
LOCK(cs_main);
|
||||
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
|
||||
}
|
||||
|
||||
CBlock block;
|
||||
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
|
||||
LOCK2(cs_main, cs_wallet);
|
||||
if (pindex && !chainActive.Contains(pindex)) {
|
||||
// Abort scan if current block is no longer active, to prevent
|
||||
// marking transactions as coming from the wrong block.
|
||||
ret = pindex;
|
||||
break;
|
||||
}
|
||||
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
||||
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
|
||||
}
|
||||
@@ -1683,14 +1707,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
|
||||
if (pindex == pindexStop) {
|
||||
break;
|
||||
}
|
||||
pindex = chainActive.Next(pindex);
|
||||
{
|
||||
LOCK(cs_main);
|
||||
pindex = chainActive.Next(pindex);
|
||||
if (tip != chainActive.Tip()) {
|
||||
tip = chainActive.Tip();
|
||||
// in case the tip has changed, update progress max
|
||||
dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pindex && fAbortRescan) {
|
||||
LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
|
||||
}
|
||||
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
|
||||
|
||||
fScanningWallet = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -4001,7 +4031,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
|
||||
}
|
||||
|
||||
nStart = GetTimeMillis();
|
||||
walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true);
|
||||
{
|
||||
WalletRescanReserver reserver(walletInstance);
|
||||
if (!reserver.reserve()) {
|
||||
InitError(_("Failed to rescan the wallet during initialization"));
|
||||
return nullptr;
|
||||
}
|
||||
walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
|
||||
}
|
||||
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
|
||||
walletInstance->SetBestChain(chainActive.GetLocator());
|
||||
walletInstance->dbw->IncrementUpdateCounter();
|
||||
|
||||
@@ -659,6 +659,7 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime
|
||||
/**
|
||||
* A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
|
||||
* and provides the ability to create new transactions.
|
||||
@@ -668,7 +669,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
|
||||
private:
|
||||
static std::atomic<bool> fFlushScheduled;
|
||||
std::atomic<bool> fAbortRescan;
|
||||
std::atomic<bool> fScanningWallet;
|
||||
std::atomic<bool> fScanningWallet; //controlled by WalletRescanReserver
|
||||
std::mutex mutexScanning;
|
||||
friend class WalletRescanReserver;
|
||||
|
||||
|
||||
/**
|
||||
* Select a set of coins such that nValueRet >= nTargetValue and at least
|
||||
@@ -945,8 +949,8 @@ public:
|
||||
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override;
|
||||
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
|
||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
|
||||
int64_t RescanFromTime(int64_t startTime, bool update);
|
||||
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false);
|
||||
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
|
||||
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false);
|
||||
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
|
||||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
|
||||
@@ -1263,4 +1267,39 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType);
|
||||
/** Get all destinations (potentially) supported by the wallet for the given key. */
|
||||
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
|
||||
|
||||
/** RAII object to check and reserve a wallet rescan */
|
||||
class WalletRescanReserver
|
||||
{
|
||||
private:
|
||||
CWalletRef m_wallet;
|
||||
bool m_could_reserve;
|
||||
public:
|
||||
explicit WalletRescanReserver(CWalletRef w) : m_wallet(w), m_could_reserve(false) {}
|
||||
|
||||
bool reserve()
|
||||
{
|
||||
assert(!m_could_reserve);
|
||||
std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
|
||||
if (m_wallet->fScanningWallet) {
|
||||
return false;
|
||||
}
|
||||
m_wallet->fScanningWallet = true;
|
||||
m_could_reserve = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isReserved() const
|
||||
{
|
||||
return (m_could_reserve && m_wallet->fScanningWallet);
|
||||
}
|
||||
|
||||
~WalletRescanReserver()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
|
||||
if (m_could_reserve) {
|
||||
m_wallet->fScanningWallet = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_WALLET_H
|
||||
|
||||
Reference in New Issue
Block a user