wallet: don't witness viewing-key-only notes (fixes an abort on the next block)
A note found through an incoming viewing key only (z_importviewingkey)
has no full viewing key behind it. UpdateSaplingNullifierNoteMapWithTx
skips such notes, so they never get a nullifier. VerifyAndSetInitialWitness
treated "no nullifier" as "witness needs reseeding" on every call: it
cleared the note's witness, re-read the block from disk, reseeded it,
and then dereferenced the nullifier that never came. dragonxd aborted
in the txnotify thread (boost::optional "is_initialized" assert) on the
first block confirming a note to such an address, and again after every
restart, before the block index was flushed.
Such a note can't be spent, so it needs no witness. Skip it there; if
its spending key is imported later, it has a full viewing key and the
existing no-nullifier rule seeds it then. SaplingWitnessMinimumHeight
now takes the optional nullifier. A note without one is never extended
by BuildWitnessCache (its pending-note gate requires one), so it must
not lower the rebuild's start height. Every call site that worked before
passes a set nullifier and behaves as before.
The deref came in with the 2020 witness rework (6c48397cf). The new
gtest ViewingKeyOnlyNoteIsNotWitnessed fails on the old code (the note
reaches the reseed path).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+17
-7
@@ -1262,9 +1262,11 @@ void ClearSingleNoteWitnessCache(NoteData* nd)
|
||||
nd->witnessRootValidated = false;
|
||||
}
|
||||
|
||||
int CWallet::SaplingWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight)
|
||||
// A note without a nullifier is never extended by BuildWitnessCache (its pending-note gate requires one),
|
||||
// so it doesn't pull the rebuild's start height down.
|
||||
int CWallet::SaplingWitnessMinimumHeight(const boost::optional<uint256>& nullifier, int nWitnessHeight, int nMinimumHeight)
|
||||
{
|
||||
if (GetSaplingSpendDepth(nullifier) <= WITNESS_CACHE_SIZE) {
|
||||
if (nullifier && GetSaplingSpendDepth(*nullifier) <= WITNESS_CACHE_SIZE) {
|
||||
nMinimumHeight = min(nWitnessHeight, nMinimumHeight);
|
||||
}
|
||||
return nMinimumHeight;
|
||||
@@ -1296,6 +1298,14 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
||||
uint256 blockRoot;
|
||||
uint256 witnessRoot;
|
||||
|
||||
//A note found with an incoming viewing key only (z_importviewingkey) can't be spent, so it needs
|
||||
//no witness, and it never gets a nullifier: deriving one takes the full viewing key, which
|
||||
//UpdateSaplingNullifierNoteMapWithTx skips without. Seeding it here meant clearing and reseeding
|
||||
//it from disk on every call (the no-nullifier rule below), then aborting on the missing nullifier
|
||||
//the first time it was confirmed. If its spending key is imported later, the rule below seeds it.
|
||||
if (!HaveSaplingFullViewingKey(nd->ivk))
|
||||
continue;
|
||||
|
||||
if (!nd->nullifier)
|
||||
::ClearSingleNoteWitnessCache(nd);
|
||||
|
||||
@@ -1307,7 +1317,7 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
||||
|
||||
//Skip Validation when witness root has been validated
|
||||
if (nd->witnessRootValidated) {
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1319,12 +1329,12 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
||||
CBlockIndex* whIndex = chainActive[nd->witnessHeight];
|
||||
if (whIndex == NULL) {
|
||||
//witnessHeight strictly above the active chain (transient catch-up): cannot validate yet
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
continue;
|
||||
}
|
||||
if (nd->witnesses.front().root() == whIndex->hashFinalSaplingRoot) {
|
||||
nd->witnessRootValidated = true;
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
continue;
|
||||
}
|
||||
//root mismatch on the active chain -> desynced; fall through to rebuild below
|
||||
@@ -1336,7 +1346,7 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
||||
blockRoot = pblockindex->hashFinalSaplingRoot;
|
||||
if (witnessRoot == blockRoot) {
|
||||
nd->witnessRootValidated = true;
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1388,7 +1398,7 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
||||
}
|
||||
nd->witnessHeight = pblockindex->GetHeight();
|
||||
UpdateSaplingNullifierNoteMapWithTx(wtxItem.second);
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user