Skip to content

Commit e6f7158

Browse files
committed
Wallet SaplingMan: Restructure IncrementNoteWitnesses process
Reworked the Sapling witnesses increment and cache workflow, so it does not loop over the entire wallet txs map indiscriminately. From: ``` 1) for-each tx in wallet : call CopyPreviousWitnesses. 2) for-each tx in block: a) for-each tx in wallet : call AppendNoteCommitment(). b) if tx is mine : call WitnessNoteIfMine. 3) for-each tx in wallet : call UpdateWitnessHeights. ``` To: ``` 1) for-each tx in block: a) gather note commitments in vecComm. b) witness note if ours. 2) for-each shield tx in wallet: a) copy the previous witness. b) append vecComm notes commitment. c) Update witness last processed height. ```
1 parent bd2fd1c commit e6f7158

File tree

1 file changed

+91
-61
lines changed

1 file changed

+91
-61
lines changed

src/sapling/saplingscriptpubkeyman.cpp

Lines changed: 91 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -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

189185
template<typename NoteDataMap>
@@ -203,48 +199,82 @@ void UpdateWitnessHeights(NoteDataMap& noteDataMap, int indexHeight, int64_t nWi
203199
}
204200

205201
void 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

Comments
 (0)