@@ -137,53 +137,49 @@ void CopyPreviousWitnesses(NoteDataMap& noteDataMap, int indexHeight, int64_t nW
137137 }
138138}
139139
140- template <typename NoteDataMap>
141- void AppendNoteCommitment (NoteDataMap& noteDataMap, int indexHeight, int64_t nWitnessCacheSize, const uint256& note_commitment)
140+ void AppendNoteCommitment (SaplingNoteData* nd, int indexHeight, int64_t nWitnessCacheSize, const uint256& note_commitment)
142141{
143- for (auto & item : noteDataMap) {
144- auto * nd = &(item.second );
145- // skip externally sent notes
146- if (!nd->IsMyNote ()) continue ;
147- if (nd->witnessHeight < indexHeight && nd->witnesses .size () > 0 ) {
148- // Check the validity of the cache
149- // See comment in CopyPreviousWitnesses about validity.
150- assert (nWitnessCacheSize >= (int64_t ) nd->witnesses .size ());
151- nd->witnesses .front ().append (note_commitment);
152- }
142+ // skip externally sent notes
143+ if (!nd->IsMyNote ()) return ;
144+ // No empty witnesses can reach here. Before any append, the note must be already witnessed.
145+ if (nd->witnessHeight < indexHeight && nd->witnesses .size () > 0 ) {
146+ // Check the validity of the cache
147+ // See comment in CopyPreviousWitnesses about validity.
148+ assert (nWitnessCacheSize >= (int64_t ) nd->witnesses .size ());
149+ nd->witnesses .front ().append (note_commitment);
153150 }
154151}
155152
156- template <typename OutPoint, typename NoteData, typename Witness>
157- void WitnessNoteIfMine (std::map<OutPoint, NoteData>& noteDataMap, int indexHeight, int64_t nWitnessCacheSize, const OutPoint& key, const Witness& witness)
153+ template <typename Witness>
154+ void WitnessNoteIfMine (SaplingNoteData* nd,
155+ int indexHeight,
156+ int64_t nWitnessCacheSize,
157+ const Witness& witness)
158158{
159- auto ndIt = noteDataMap.find (key);
160- if (ndIt != noteDataMap.end ()) {
161- auto * nd = &ndIt->second ;
162- // skip externally sent and already witnessed notes
163- if (!nd->IsMyNote () || nd->witnessHeight >= indexHeight) return ;
164- if (nd->witnesses .size () > 0 ) {
165- // We think this can happen because we write out the
166- // witness cache state after every block increment or
167- // decrement, but the block index itself is written in
168- // batches. So if the node crashes in between these two
169- // operations, it is possible for IncrementNoteWitnesses
170- // to be called again on previously-cached blocks. This
171- // doesn't affect existing cached notes because of the
172- // NoteData::witnessHeight checks. See #1378 for details.
173- LogPrintf (" Inconsistent witness cache state found for %s\n - Cache size: %d\n - Top (height %d): %s\n - New (height %d): %s\n " ,
174- key.ToString (), nd->witnesses .size (),
175- nd->witnessHeight ,
176- nd->witnesses .front ().root ().GetHex (),
177- indexHeight,
178- witness.root ().GetHex ());
179- nd->witnesses .clear ();
180- }
181- nd->witnesses .push_front (witness);
182- // Set height to one less than pindex so it gets incremented
183- nd->witnessHeight = indexHeight - 1 ;
184- // Check the validity of the cache
185- assert (nWitnessCacheSize >= (int64_t ) nd->witnesses .size ());
159+ assert (nd);
160+ // skip externally sent and already witnessed notes
161+ if (!nd->IsMyNote () || nd->witnessHeight >= indexHeight) return ;
162+ if (!nd->witnesses .empty ()) {
163+ // We think this can happen because we write out the
164+ // witness cache state after every block increment or
165+ // decrement, but the block index itself is written in
166+ // batches. So if the node crashes in between these two
167+ // operations, it is possible for IncrementNoteWitnesses
168+ // to be called again on previously-cached blocks. This
169+ // doesn't affect existing cached notes because of the
170+ // NoteData::witnessHeight checks. See #1378 for details.
171+ LogPrintf (" Inconsistent witness cache state found\n - Cache size: %d\n - Top (height %d): %s\n - New (height %d): %s\n " ,
172+ nd->witnesses .size (), nd->witnessHeight ,
173+ nd->witnesses .front ().root ().GetHex (),
174+ indexHeight,
175+ witness.root ().GetHex ());
176+ nd->witnesses .clear ();
186177 }
178+ nd->witnesses .push_front (witness);
179+ // Set height to one less than pindex so it gets incremented
180+ nd->witnessHeight = indexHeight - 1 ;
181+ // Check the validity of the cache
182+ assert (nWitnessCacheSize >= (int64_t ) nd->witnesses .size ());
187183}
188184
189185template <typename NoteDataMap>
@@ -203,48 +199,82 @@ void UpdateWitnessHeights(NoteDataMap& noteDataMap, int indexHeight, int64_t nWi
203199}
204200
205201void SaplingScriptPubKeyMan::IncrementNoteWitnesses (const CBlockIndex* pindex,
206- const CBlock* pblock,
207- SaplingMerkleTree& saplingTree )
202+ const CBlock* pblock,
203+ SaplingMerkleTree& saplingTreeRes )
208204{
209205 LOCK (wallet->cs_wallet );
210206 int chainHeight = pindex->nHeight ;
211- for (std::pair<const uint256, CWalletTx>& wtxItem : wallet->mapWallet ) {
212- ::CopyPreviousWitnesses (wtxItem.second.mapSaplingNoteData, chainHeight, nWitnessCacheSize);
213- }
214207
208+ // Set the update cache flag.
209+ int64_t prevWitCacheSize = nWitnessCacheSize;
215210 if (nWitnessCacheSize < WITNESS_CACHE_SIZE) {
216211 nWitnessCacheSize += 1 ;
217212 nWitnessCacheNeedsUpdate = true ;
218213 }
219214
215+ // 1) Loop over the block txs and gather the note commitments ordered.
216+ // If the wtx is from this wallet, witness it and append the following block note commitments on top.
217+ std::vector<uint256> noteCommitments;
218+ std::vector<std::pair<CWalletTx*, SaplingNoteData*>> inBlockArrivingNotes;
220219 for (const auto & tx : pblock->vtx ) {
221220 if (!tx->IsShieldedTx ()) continue ;
222221
223- const uint256& hash = tx->GetHash ();
224- bool txIsOurs = wallet->mapWallet .count (hash);
222+ const auto & hash = tx->GetHash ();
223+ auto it = wallet->mapWallet .find (hash);
224+ bool txIsOurs = it != wallet->mapWallet .end ();
225225
226- // Sapling
227226 for (uint32_t i = 0 ; i < tx->sapData ->vShieldedOutput .size (); i++) {
228- const uint256& note_commitment = tx->sapData ->vShieldedOutput [i].cmu ;
229- saplingTree.append (note_commitment);
230-
231- // Increment existing witnesses
232- for (std::pair<const uint256, CWalletTx>& wtxItem : wallet->mapWallet ) {
233- ::AppendNoteCommitment (wtxItem.second.mapSaplingNoteData, chainHeight, nWitnessCacheSize, note_commitment);
227+ const auto & cmu = tx->sapData ->vShieldedOutput [i].cmu ;
228+ noteCommitments.emplace_back (cmu);
229+
230+ // Append note commitment to the in-block wallet's notes.
231+ // This is processed here because we already looked for the wtx on
232+ // the WitnessNoteIfMine call and only need to append the follow-up block notes,
233+ // not every block note (check below).
234+ for (auto & item : inBlockArrivingNotes) {
235+ ::AppendNoteCommitment (item.second, chainHeight, nWitnessCacheSize, cmu);
234236 }
235237
236- // If this is our note, witness it
238+ // If tx is from this wallet, try to witness the note for the first time (if exists).
239+ // And add it to the in-block arriving txs.
240+ saplingTreeRes.append (cmu);
237241 if (txIsOurs) {
238- SaplingOutPoint outPoint {hash, i};
239- ::WitnessNoteIfMine (wallet->mapWallet.at(hash).mapSaplingNoteData, chainHeight, nWitnessCacheSize, outPoint, saplingTree.witness());
242+ CWalletTx* wtx = &it->second ;
243+ auto ndIt = wtx->mapSaplingNoteData .find ({hash, i});
244+ if (ndIt != wtx->mapSaplingNoteData .end ()) {
245+ SaplingNoteData* nd = &ndIt->second ;
246+ ::WitnessNoteIfMine (nd, chainHeight, nWitnessCacheSize, saplingTreeRes.witness());
247+ inBlockArrivingNotes.emplace_back (std::make_pair (wtx, nd));
248+ }
240249 }
241250 }
251+ }
242252
253+ // 2) Mark already sync wtx, so we don't process them again.
254+ for (auto & item : inBlockArrivingNotes) {
255+ ::UpdateWitnessHeights (item.first->mapSaplingNoteData, chainHeight, nWitnessCacheSize);
243256 }
244257
245- // Update witness heights
246- for (std::pair<const uint256, CWalletTx>& wtxItem : wallet->mapWallet ) {
247- ::UpdateWitnessHeights (wtxItem.second.mapSaplingNoteData, chainHeight, nWitnessCacheSize);
258+ // 3) Loop over the shield txs in the wallet's map (excluding the wtx arriving in this block) and for each tx:
259+ // a) Copy the previous witness.
260+ // b) Append all new notes commitments
261+ // c) Update witness last processed height
262+ for (auto & it : wallet->mapWallet ) {
263+ CWalletTx& wtx = it.second ;
264+ if (!wtx.mapSaplingNoteData .empty ()) {
265+ // Create copy of the previous witness (verifying pre-arriving block witness cache size)
266+ ::CopyPreviousWitnesses (wtx.mapSaplingNoteData, chainHeight, prevWitCacheSize);
267+
268+ // Append new notes commitments.
269+ for (auto & noteComm : noteCommitments) {
270+ for (auto & item : wtx.mapSaplingNoteData ) {
271+ AppendNoteCommitment (&(item.second ), chainHeight, nWitnessCacheSize, noteComm);
272+ }
273+ }
274+
275+ // Set last processed height.
276+ ::UpdateWitnessHeights (wtx.mapSaplingNoteData, chainHeight, nWitnessCacheSize);
277+ }
248278 }
249279
250280 // For performance reasons, we write out the witness cache in
0 commit comments