wallet: -checknotecache, a diagnostic for the note plaintext cache
With -checknotecache=1 (debug help), GetFilteredNotes decrypts every note it returns again. If the cached plaintext differs from the fresh decryption, it logs the note and replaces the cache entry with the fresh value. It costs the full decryption the cache exists to avoid, so it is off by default; it is there for validating the cache on a real wallet, or for diagnosing a suspected cache problem in the field. CheckNoteCacheRepairsADifference: by default a tampered cache entry is returned as is (the cache is trusted); with the option it is repaired and the fresh value is returned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+27
-3
@@ -5823,6 +5823,25 @@ void CWallet::WarmSaplingNoteCache()
|
||||
nBad ? strprintf("; %u could not be decrypted (-debug=zrpcunsafe lists them)", nBad) : "");
|
||||
}
|
||||
|
||||
// -checknotecache: decrypts a cached note again, and logs and repairs any difference. A diagnostic for
|
||||
// the cache; it costs the full decryption it exists to avoid.
|
||||
static void CheckSaplingNoteCache(const SaplingOutPoint& op, SaplingNoteData& nd, const OutputDescription& output)
|
||||
{
|
||||
auto fresh = SaplingNotePlaintext::decrypt_with_note(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm);
|
||||
if (!fresh) {
|
||||
LogPrintf("%s: note %s:%u has a cached plaintext but no longer decrypts\n", __func__, op.hash.ToString(), op.n);
|
||||
return;
|
||||
}
|
||||
const SaplingNoteCache& cached = *nd.cachedPlaintext;
|
||||
const libzcash::SaplingNote& note = fresh->second;
|
||||
if (!(cached.note.d == note.d && cached.note.pk_d == note.pk_d && cached.note.r == note.r &&
|
||||
cached.note.value() == note.value() && cached.memo == fresh->first.memo())) {
|
||||
LogPrintf("%s: cached plaintext of note %s:%u differs from a fresh decryption; using the fresh one\n",
|
||||
__func__, op.hash.ToString(), op.n);
|
||||
nd.cachedPlaintext = SaplingNoteCache{ note, fresh->first.memo() };
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces tx_height() on the wallet's note paths. tx_height() re-read the tx from disk through
|
||||
// GetTransaction, once per wallet tx (or per returned note) under LOCK2, and on a -txindex=0 node it
|
||||
// never found a fully shielded tx, so those notes were silently skipped. The depth already locates
|
||||
@@ -5878,7 +5897,8 @@ void CWallet::GetFilteredNotes(
|
||||
// Pass 1: the notes that pass every filter not needing the plaintext, in wallet order.
|
||||
struct Candidate {
|
||||
const SaplingOutPoint* op;
|
||||
const SaplingNoteData* nd;
|
||||
SaplingNoteData* nd;
|
||||
const OutputDescription* output;
|
||||
int nDepth;
|
||||
};
|
||||
std::vector<Candidate> candidates;
|
||||
@@ -5934,9 +5954,10 @@ void CWallet::GetFilteredNotes(
|
||||
}
|
||||
|
||||
assert(op.n < wtx.vShieldedOutput.size());
|
||||
candidates.push_back(Candidate { &op, &nd, nDepth });
|
||||
const OutputDescription* output = &wtx.vShieldedOutput[op.n];
|
||||
candidates.push_back(Candidate { &op, &nd, output, nDepth });
|
||||
if (!nd.cachedPlaintext)
|
||||
uncached.push_back(SaplingNoteToDecrypt { &nd, &wtx.vShieldedOutput[op.n] });
|
||||
uncached.push_back(SaplingNoteToDecrypt { &nd, output });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5945,10 +5966,13 @@ void CWallet::GetFilteredNotes(
|
||||
const unsigned int nFilled = uncached.empty() ? 0 : FillSaplingNoteCache(uncached);
|
||||
|
||||
// Pass 2: the filters that need the note's address.
|
||||
const bool fCheckCache = GetBoolArg("-checknotecache", false);
|
||||
saplingEntries.reserve(saplingEntries.size() + candidates.size());
|
||||
for (const Candidate& c : candidates) {
|
||||
// A wallet note that doesn't decrypt under its own ivk is corrupt; as before, that's fatal here.
|
||||
assert(static_cast<bool>(c.nd->cachedPlaintext));
|
||||
if (fCheckCache)
|
||||
CheckSaplingNoteCache(*c.op, *c.nd, *c.output);
|
||||
const SaplingNoteCache& plaintext = *c.nd->cachedPlaintext;
|
||||
const libzcash::SaplingPaymentAddress pa(plaintext.note.d, plaintext.note.pk_d);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user