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:
7 files changed
+193
-59
No files matched your search
@@ -169,7 +169,9 @@ void TestGUI()
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true);
|
WalletRescanReserver reserver(&wallet);
|
||||||
|
reserver.reserve();
|
||||||
|
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
|
||||||
}
|
}
|
||||||
wallet.SetBroadcastTransactions(true);
|
wallet.SetBroadcastTransactions(true);
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -1121,7 +1121,13 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus:
|
|||||||
|
|
||||||
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
|
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;
|
return false;
|
||||||
if (block.GetHash() != pindex->GetBlockHash())
|
if (block.GetHash() != pindex->GetBlockHash())
|
||||||
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
|
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
|
||||||
|
|||||||
+62
-28
@@ -86,7 +86,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
|||||||
"1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
|
"1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
|
||||||
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
||||||
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\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"
|
"\nExamples:\n"
|
||||||
"\nDump a private key\n"
|
"\nDump a private key\n"
|
||||||
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
|
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
|
||||||
@@ -101,6 +102,9 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
WalletRescanReserver reserver(pwallet);
|
||||||
|
bool fRescan = true;
|
||||||
|
{
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
@@ -111,13 +115,16 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
|||||||
strLabel = request.params[1].get_str();
|
strLabel = request.params[1].get_str();
|
||||||
|
|
||||||
// Whether to perform rescan after import
|
// Whether to perform rescan after import
|
||||||
bool fRescan = true;
|
|
||||||
if (!request.params[2].isNull())
|
if (!request.params[2].isNull())
|
||||||
fRescan = request.params[2].get_bool();
|
fRescan = request.params[2].get_bool();
|
||||||
|
|
||||||
if (fRescan && fPruneMode)
|
if (fRescan && fPruneMode)
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
CBitcoinSecret vchSecret;
|
CBitcoinSecret vchSecret;
|
||||||
bool fGood = vchSecret.SetString(strSecret);
|
bool fGood = vchSecret.SetString(strSecret);
|
||||||
|
|
||||||
@@ -131,7 +138,6 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
|||||||
CKeyID vchAddress = pubkey.GetID();
|
CKeyID vchAddress = pubkey.GetID();
|
||||||
{
|
{
|
||||||
pwallet->MarkDirty();
|
pwallet->MarkDirty();
|
||||||
|
|
||||||
// We don't know which corresponding address will be used; label them all
|
// We don't know which corresponding address will be used; label them all
|
||||||
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
|
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
|
||||||
pwallet->SetAddressBook(dest, strLabel, "receive");
|
pwallet->SetAddressBook(dest, strLabel, "receive");
|
||||||
@@ -142,20 +148,19 @@ UniValue importprivkey(const JSONRPCRequest& request)
|
|||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// whenever a key is imported, we need to scan the whole chain
|
||||||
|
pwallet->UpdateTimeFirstKey(1);
|
||||||
pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
|
pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
|
||||||
|
|
||||||
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
||||||
}
|
}
|
||||||
pwallet->LearnAllRelatedScripts(pubkey);
|
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 */);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (fRescan) {
|
||||||
|
pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
|
||||||
|
}
|
||||||
|
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
@@ -237,7 +242,8 @@ UniValue importaddress(const JSONRPCRequest& request)
|
|||||||
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
||||||
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\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"
|
"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"
|
"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"
|
"\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"
|
"as change, and not show up in many RPCs.\n"
|
||||||
@@ -263,11 +269,17 @@ UniValue importaddress(const JSONRPCRequest& request)
|
|||||||
if (fRescan && fPruneMode)
|
if (fRescan && fPruneMode)
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
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
|
// Whether to import a p2sh version, too
|
||||||
bool fP2SH = false;
|
bool fP2SH = false;
|
||||||
if (!request.params[3].isNull())
|
if (!request.params[3].isNull())
|
||||||
fP2SH = request.params[3].get_bool();
|
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());
|
CTxDestination dest = DecodeDestination(request.params[0].get_str());
|
||||||
@@ -282,10 +294,10 @@ UniValue importaddress(const JSONRPCRequest& request)
|
|||||||
} else {
|
} else {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (fRescan)
|
if (fRescan)
|
||||||
{
|
{
|
||||||
pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
|
pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
|
||||||
pwallet->ReacceptWalletTransactions();
|
pwallet->ReacceptWalletTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +418,8 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
|||||||
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
|
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
|
||||||
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
"2. \"label\" (string, optional, default=\"\") An optional label\n"
|
||||||
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\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"
|
"\nExamples:\n"
|
||||||
"\nImport a public key with rescan\n"
|
"\nImport a public key with rescan\n"
|
||||||
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
|
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
|
||||||
@@ -429,6 +442,11 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
|||||||
if (fRescan && fPruneMode)
|
if (fRescan && fPruneMode)
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
|
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()))
|
if (!IsHex(request.params[0].get_str()))
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
||||||
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
|
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
|
||||||
@@ -436,6 +454,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
|||||||
if (!pubKey.IsFullyValid())
|
if (!pubKey.IsFullyValid())
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
|
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)) {
|
for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
|
||||||
@@ -443,10 +462,10 @@ UniValue importpubkey(const JSONRPCRequest& request)
|
|||||||
}
|
}
|
||||||
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
|
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
|
||||||
pwallet->LearnAllRelatedScripts(pubKey);
|
pwallet->LearnAllRelatedScripts(pubKey);
|
||||||
|
}
|
||||||
if (fRescan)
|
if (fRescan)
|
||||||
{
|
{
|
||||||
pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
|
pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
|
||||||
pwallet->ReacceptWalletTransactions();
|
pwallet->ReacceptWalletTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,18 +498,24 @@ UniValue importwallet(const JSONRPCRequest& request)
|
|||||||
if (fPruneMode)
|
if (fPruneMode)
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
|
||||||
|
|
||||||
|
WalletRescanReserver reserver(pwallet);
|
||||||
|
if (!reserver.reserve()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t nTimeBegin = 0;
|
||||||
|
bool fGood = true;
|
||||||
|
{
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
std::ifstream file;
|
std::ifstream file;
|
||||||
file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
|
file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
|
||||||
if (!file.is_open())
|
if (!file.is_open()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
||||||
|
}
|
||||||
int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
|
nTimeBegin = chainActive.Tip()->GetBlockTime();
|
||||||
|
|
||||||
bool fGood = true;
|
|
||||||
|
|
||||||
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
|
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
|
||||||
file.seekg(0, file.beg);
|
file.seekg(0, file.beg);
|
||||||
@@ -563,7 +588,8 @@ UniValue importwallet(const JSONRPCRequest& request)
|
|||||||
file.close();
|
file.close();
|
||||||
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
|
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
|
||||||
pwallet->UpdateTimeFirstKey(nTimeBegin);
|
pwallet->UpdateTimeFirstKey(nTimeBegin);
|
||||||
pwallet->RescanFromTime(nTimeBegin, false /* update */);
|
}
|
||||||
|
pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */);
|
||||||
pwallet->MarkDirty();
|
pwallet->MarkDirty();
|
||||||
|
|
||||||
if (!fGood)
|
if (!fGood)
|
||||||
@@ -1110,6 +1136,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
|||||||
" {\n"
|
" {\n"
|
||||||
" \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
|
" \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
|
||||||
" }\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" +
|
"\nExamples:\n" +
|
||||||
HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
|
HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
|
||||||
"{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
|
"{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
|
||||||
@@ -1135,18 +1163,26 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
int64_t nLowestTimestamp = 0;
|
||||||
|
UniValue response(UniValue::VARR);
|
||||||
|
{
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
// Verify all timestamps are present before importing any keys.
|
// Verify all timestamps are present before importing any keys.
|
||||||
const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
|
now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
|
||||||
for (const UniValue& data : requests.getValues()) {
|
for (const UniValue& data : requests.getValues()) {
|
||||||
GetImportTimestamp(data, now);
|
GetImportTimestamp(data, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fRunScan = false;
|
|
||||||
const int64_t minimumTimestamp = 1;
|
const int64_t minimumTimestamp = 1;
|
||||||
int64_t nLowestTimestamp = 0;
|
|
||||||
|
|
||||||
if (fRescan && chainActive.Tip()) {
|
if (fRescan && chainActive.Tip()) {
|
||||||
nLowestTimestamp = chainActive.Tip()->GetBlockTime();
|
nLowestTimestamp = chainActive.Tip()->GetBlockTime();
|
||||||
@@ -1154,8 +1190,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
|||||||
fRescan = false;
|
fRescan = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue response(UniValue::VARR);
|
|
||||||
|
|
||||||
for (const UniValue& data : requests.getValues()) {
|
for (const UniValue& data : requests.getValues()) {
|
||||||
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
|
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
|
||||||
const UniValue result = ProcessImport(pwallet, data, timestamp);
|
const UniValue result = ProcessImport(pwallet, data, timestamp);
|
||||||
@@ -1175,9 +1209,9 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
|||||||
nLowestTimestamp = timestamp;
|
nLowestTimestamp = timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (fRescan && fRunScan && requests.size()) {
|
if (fRescan && fRunScan && requests.size()) {
|
||||||
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, true /* update */);
|
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
|
||||||
pwallet->ReacceptWalletTransactions();
|
pwallet->ReacceptWalletTransactions();
|
||||||
|
|
||||||
if (scannedTime > nLowestTimestamp) {
|
if (scannedTime > nLowestTimestamp) {
|
||||||
|
|||||||
@@ -3416,10 +3416,19 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
WalletRescanReserver reserver(pwallet);
|
||||||
|
if (!reserver.reserve()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||||
|
}
|
||||||
|
|
||||||
CBlockIndex *pindexStart = chainActive.Genesis();
|
CBlockIndex *pindexStart = nullptr;
|
||||||
CBlockIndex *pindexStop = nullptr;
|
CBlockIndex *pindexStop = nullptr;
|
||||||
|
CBlockIndex *pChainTip = nullptr;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
pindexStart = chainActive.Genesis();
|
||||||
|
pChainTip = chainActive.Tip();
|
||||||
|
|
||||||
if (!request.params[0].isNull()) {
|
if (!request.params[0].isNull()) {
|
||||||
pindexStart = chainActive[request.params[0].get_int()];
|
pindexStart = chainActive[request.params[0].get_int()];
|
||||||
if (!pindexStart) {
|
if (!pindexStart) {
|
||||||
@@ -3436,10 +3445,12 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
|
|||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
|
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
|
// We can't rescan beyond non-pruned blocks, stop and throw an error
|
||||||
if (fPruneMode) {
|
if (fPruneMode) {
|
||||||
CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip();
|
LOCK(cs_main);
|
||||||
|
CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
|
||||||
while (block && block->nHeight >= pindexStart->nHeight) {
|
while (block && block->nHeight >= pindexStart->nHeight) {
|
||||||
if (!(block->nStatus & BLOCK_HAVE_DATA)) {
|
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.");
|
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 (!stopBlock) {
|
||||||
if (pwallet->IsAbortingRescan()) {
|
if (pwallet->IsAbortingRescan()) {
|
||||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
|
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
|
||||||
}
|
}
|
||||||
// if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
|
// if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
|
||||||
stopBlock = pindexStop ? pindexStop : chainActive.Tip();
|
stopBlock = pindexStop ? pindexStop : pChainTip;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue response(UniValue::VOBJ);
|
UniValue response(UniValue::VOBJ);
|
||||||
response.pushKV("start_height", pindexStart->nHeight);
|
response.pushKV("start_height", pindexStart->nHeight);
|
||||||
response.pushKV("stop_height", stopBlock->nHeight);
|
response.pushKV("stop_height", stopBlock->nHeight);
|
||||||
|
|||||||
@@ -384,7 +384,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||||||
{
|
{
|
||||||
CWallet wallet;
|
CWallet wallet;
|
||||||
AddKey(wallet, coinbaseKey);
|
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);
|
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +399,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||||||
{
|
{
|
||||||
CWallet wallet;
|
CWallet wallet;
|
||||||
AddKey(wallet, coinbaseKey);
|
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);
|
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +612,9 @@ public:
|
|||||||
bool firstRun;
|
bool firstRun;
|
||||||
wallet->LoadWallet(firstRun);
|
wallet->LoadWallet(firstRun);
|
||||||
AddKey(*wallet, coinbaseKey);
|
AddKey(*wallet, coinbaseKey);
|
||||||
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr);
|
WalletRescanReserver reserver(wallet.get());
|
||||||
|
reserver.reserve();
|
||||||
|
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
~ListCoinsTestingSetup()
|
~ListCoinsTestingSetup()
|
||||||
|
|||||||
+54
-17
@@ -1612,19 +1612,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
|
|||||||
* @return Earliest timestamp that could be successfully scanned from. Timestamp
|
* @return Earliest timestamp that could be successfully scanned from. Timestamp
|
||||||
* returned will be higher than startTime if relevant blocks could not be read.
|
* 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
|
// Find starting block. May be null if nCreateTime is greater than the
|
||||||
// highest blockchain timestamp, in which case there is nothing that needs
|
// highest blockchain timestamp, in which case there is nothing that needs
|
||||||
// to be scanned.
|
// to be scanned.
|
||||||
CBlockIndex* const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
|
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);
|
LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (startBlock) {
|
if (startBlock) {
|
||||||
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update);
|
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
|
||||||
if (failedBlock) {
|
if (failedBlock) {
|
||||||
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
|
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
|
* If pindexStop is not a nullptr, the scan will stop at the block-index
|
||||||
* defined by pindexStop
|
* 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();
|
int64_t nNow = GetTime();
|
||||||
const CChainParams& chainParams = Params();
|
const CChainParams& chainParams = Params();
|
||||||
|
|
||||||
|
assert(reserver.isReserved());
|
||||||
if (pindexStop) {
|
if (pindexStop) {
|
||||||
assert(pindexStop->nHeight >= pindexStart->nHeight);
|
assert(pindexStop->nHeight >= pindexStart->nHeight);
|
||||||
}
|
}
|
||||||
@@ -1656,24 +1662,42 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
|
|||||||
CBlockIndex* pindex = pindexStart;
|
CBlockIndex* pindex = pindexStart;
|
||||||
CBlockIndex* ret = nullptr;
|
CBlockIndex* ret = nullptr;
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, cs_wallet);
|
|
||||||
fAbortRescan = false;
|
fAbortRescan = false;
|
||||||
fScanningWallet = true;
|
|
||||||
|
|
||||||
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
|
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
|
||||||
double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
|
CBlockIndex* tip = nullptr;
|
||||||
double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
|
double dProgressStart;
|
||||||
|
double dProgressTip;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
tip = chainActive.Tip();
|
||||||
|
dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
|
||||||
|
dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
|
||||||
|
}
|
||||||
while (pindex && !fAbortRescan)
|
while (pindex && !fAbortRescan)
|
||||||
{
|
{
|
||||||
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
|
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))));
|
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) {
|
if (GetTime() >= nNow + 60) {
|
||||||
nNow = GetTime();
|
nNow = GetTime();
|
||||||
|
LOCK(cs_main);
|
||||||
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
|
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
|
||||||
}
|
}
|
||||||
|
|
||||||
CBlock block;
|
CBlock block;
|
||||||
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
|
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) {
|
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
||||||
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
|
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
|
||||||
}
|
}
|
||||||
@@ -1683,14 +1707,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
|
|||||||
if (pindex == pindexStop) {
|
if (pindex == pindexStop) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
pindex = chainActive.Next(pindex);
|
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) {
|
if (pindex && fAbortRescan) {
|
||||||
LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
|
LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
|
||||||
}
|
}
|
||||||
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
|
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
|
||||||
|
|
||||||
fScanningWallet = false;
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -4001,7 +4031,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
|
|||||||
}
|
}
|
||||||
|
|
||||||
nStart = GetTimeMillis();
|
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);
|
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
|
||||||
walletInstance->SetBestChain(chainActive.GetLocator());
|
walletInstance->SetBestChain(chainActive.GetLocator());
|
||||||
walletInstance->dbw->IncrementUpdateCounter();
|
walletInstance->dbw->IncrementUpdateCounter();
|
||||||
|
|||||||
+42
-3
@@ -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,
|
* 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.
|
* and provides the ability to create new transactions.
|
||||||
@@ -668,7 +669,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
|
|||||||
private:
|
private:
|
||||||
static std::atomic<bool> fFlushScheduled;
|
static std::atomic<bool> fFlushScheduled;
|
||||||
std::atomic<bool> fAbortRescan;
|
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
|
* 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 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;
|
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
|
||||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
|
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
|
||||||
int64_t RescanFromTime(int64_t startTime, bool update);
|
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
|
||||||
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false);
|
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false);
|
||||||
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
|
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
|
||||||
void ReacceptWalletTransactions();
|
void ReacceptWalletTransactions();
|
||||||
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
|
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. */
|
/** Get all destinations (potentially) supported by the wallet for the given key. */
|
||||||
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& 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
|
#endif // BITCOIN_WALLET_WALLET_H
|
||||||
Reference in new issue
Block a user