Line data Source code
1 : // Copyright (c) 2016-2020 The ZCash developers
2 : // Copyright (c) 2020-2021 The PIVX Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "wallet/test/wallet_test_fixture.h"
7 :
8 : #include <sodium.h>
9 :
10 : #include "chainparams.h"
11 : #include "key_io.h"
12 : #include "validation.h"
13 : #include "optional.h"
14 : #include "primitives/block.h"
15 : #include "random.h"
16 : #include "sapling/transaction_builder.h"
17 : #include "test/librust/utiltest.h"
18 : #include "wallet/wallet.h"
19 : #include "consensus/merkle.h"
20 : #include "sapling/note.h"
21 : #include "sapling/noteencryption.h"
22 :
23 : #include <boost/filesystem.hpp>
24 :
25 : #include <boost/test/unit_test.hpp>
26 :
27 13 : void setupWallet(CWallet& wallet)
28 : {
29 13 : wallet.SetMinVersion(FEATURE_SAPLING);
30 13 : wallet.SetupSPKM(false);
31 13 : }
32 :
33 118 : std::vector<SaplingOutPoint> SetSaplingNoteData(CWalletTx& wtx) {
34 118 : mapSaplingNoteData_t saplingNoteData;
35 118 : SaplingOutPoint saplingOutPoint = {wtx.GetHash(), 0};
36 236 : SaplingNoteData saplingNd;
37 : // set this as internal note (with non-nullopt ivk)
38 236 : saplingNd.ivk = libzcash::SaplingIncomingViewingKey();
39 118 : saplingNoteData[saplingOutPoint] = saplingNd;
40 118 : wtx.SetSaplingNoteData(saplingNoteData);
41 118 : std::vector<SaplingOutPoint> saplingNotes {saplingOutPoint};
42 236 : return saplingNotes;
43 : }
44 :
45 114 : SaplingOutPoint CreateValidBlock(CWallet& wallet,
46 : libzcash::SaplingExtendedSpendingKey& sk,
47 : const CBlockIndex& index,
48 : CBlock& block,
49 : SaplingMerkleTree& saplingTree)
50 : {
51 114 : CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
52 114 : wallet, sk, 0, true);
53 228 : auto saplingNotes = SetSaplingNoteData(wtx);
54 114 : wallet.LoadToWallet(wtx);
55 :
56 114 : block.vtx.emplace_back(wtx.tx);
57 114 : wallet.IncrementNoteWitnesses(&index, &block, saplingTree);
58 :
59 228 : return saplingNotes[0];
60 : }
61 :
62 241 : uint256 GetWitnessesAndAnchors(CWallet& wallet,
63 : const std::vector<SaplingOutPoint>& saplingNotes,
64 : std::vector<Optional<SaplingWitness>>& saplingWitnesses)
65 : {
66 241 : saplingWitnesses.clear();
67 241 : uint256 saplingAnchor;
68 241 : wallet.GetSaplingScriptPubKeyMan()->GetSaplingNoteWitnesses(saplingNotes, saplingWitnesses, saplingAnchor);
69 241 : return saplingAnchor;
70 : }
71 :
72 : BOOST_FIXTURE_TEST_SUITE(sapling_wallet_tests, WalletRegTestingSetup)
73 :
74 2 : BOOST_AUTO_TEST_CASE(SetSaplingNoteAddrsInCWalletTx) {
75 1 : auto consensusParams = Params().GetConsensus();
76 :
77 1 : CWallet& wallet = m_wallet;
78 2 : LOCK(wallet.cs_wallet);
79 1 : setupWallet(wallet);
80 :
81 1 : auto sk = GetTestMasterSaplingSpendingKey();
82 1 : auto expsk = sk.expsk;
83 1 : auto fvk = expsk.full_viewing_key();
84 1 : auto ivk = fvk.in_viewing_key();
85 1 : auto pk = sk.DefaultAddress();
86 :
87 2 : libzcash::SaplingNote note(pk, 50000000);
88 2 : auto cm = note.cmu().get();
89 2 : SaplingMerkleTree tree;
90 1 : tree.append(cm);
91 1 : auto anchor = tree.root();
92 2 : auto witness = tree.witness();
93 :
94 2 : Optional<uint256> nf = note.nullifier(fvk, witness.position());
95 2 : BOOST_CHECK(nf != boost::none);
96 1 : uint256 nullifier = nf.get();
97 :
98 2 : auto builder = TransactionBuilder(consensusParams);
99 1 : builder.AddSaplingSpend(expsk, note, anchor, witness);
100 1 : builder.AddSaplingOutput(fvk.ovk, pk, 40000000, {});
101 1 : builder.SetFee(10000000);
102 2 : auto tx = builder.Build().GetTxOrThrow();
103 :
104 1 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
105 :
106 1 : BOOST_CHECK_EQUAL(0, wtx.mapSaplingNoteData.size());
107 2 : mapSaplingNoteData_t noteData;
108 :
109 1 : SaplingOutPoint op {wtx.GetHash(), 0};
110 2 : SaplingNoteData nd;
111 1 : nd.nullifier = nullifier;
112 1 : nd.ivk = ivk;
113 1 : nd.witnesses.push_front(witness);
114 1 : nd.witnessHeight = 123;
115 2 : noteData.insert(std::make_pair(op, nd));
116 :
117 1 : wtx.SetSaplingNoteData(noteData);
118 2 : BOOST_CHECK(noteData == wtx.mapSaplingNoteData);
119 :
120 : // Test individual fields in case equality operator is defined/changed.
121 2 : BOOST_CHECK(wtx.mapSaplingNoteData[op].IsMyNote());
122 2 : BOOST_CHECK(ivk == *(wtx.mapSaplingNoteData[op].ivk));
123 2 : BOOST_CHECK(nullifier == wtx.mapSaplingNoteData[op].nullifier);
124 2 : BOOST_CHECK(nd.witnessHeight == wtx.mapSaplingNoteData[op].witnessHeight);
125 2 : BOOST_CHECK(witness == wtx.mapSaplingNoteData[op].witnesses.front());
126 1 : }
127 :
128 : // Cannot add note data for an index which does not exist in tx.vShieldedOutput
129 2 : BOOST_AUTO_TEST_CASE(SetInvalidSaplingNoteDataInCWalletTx) {
130 1 : CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
131 1 : BOOST_CHECK_EQUAL(0, wtx.mapSaplingNoteData.size());
132 :
133 2 : mapSaplingNoteData_t noteData;
134 2 : SaplingOutPoint op {uint256(), 1};
135 2 : SaplingNoteData nd;
136 2 : noteData.insert(std::make_pair(op, nd));
137 :
138 2 : BOOST_CHECK_THROW(wtx.SetSaplingNoteData(noteData), std::logic_error);
139 1 : }
140 :
141 2 : BOOST_AUTO_TEST_CASE(FindMySaplingNotes)
142 : {
143 1 : auto consensusParams = Params().GetConsensus();
144 :
145 1 : CWallet& wallet = m_wallet;
146 2 : LOCK(wallet.cs_wallet);
147 1 : wallet.SetupSPKM(false);
148 :
149 : // Generate dummy Sapling address
150 1 : auto sk = GetTestMasterSaplingSpendingKey();
151 1 : auto expsk = sk.expsk;
152 1 : auto extfvk = sk.ToXFVK();
153 1 : auto pa = sk.DefaultAddress();
154 :
155 2 : auto testNote = GetTestSaplingNote(pa, 50000000);
156 :
157 : // Generate transaction
158 2 : auto builder = TransactionBuilder(consensusParams);
159 1 : builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
160 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000000, {});
161 1 : builder.SetFee(10000000);
162 2 : auto tx = builder.Build().GetTxOrThrow();
163 :
164 : // No Sapling notes can be found in tx which does not belong to the wallet
165 1 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
166 2 : BOOST_CHECK(!wallet.HaveSaplingSpendingKey(extfvk));
167 3 : auto noteMap = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
168 1 : BOOST_CHECK_EQUAL(0, noteMap.size());
169 :
170 : // Add spending key to wallet, so Sapling notes can be found
171 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
172 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
173 2 : noteMap = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
174 1 : BOOST_CHECK_EQUAL(2, noteMap.size());
175 1 : }
176 :
177 : // Generate note A and spend to create note B, from which we spend to create two conflicting transactions
178 2 : BOOST_AUTO_TEST_CASE(GetConflictedSaplingNotes)
179 : {
180 1 : auto consensusParams = Params().GetConsensus();
181 :
182 1 : CWallet& wallet = m_wallet;
183 3 : LOCK2(cs_main, wallet.cs_wallet);
184 1 : setupWallet(wallet);
185 :
186 : // Generate Sapling address
187 1 : auto sk = GetTestMasterSaplingSpendingKey();
188 1 : auto expsk = sk.expsk;
189 1 : auto extfvk = sk.ToXFVK();
190 1 : auto ivk = extfvk.fvk.in_viewing_key();
191 1 : auto pk = sk.DefaultAddress();
192 :
193 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
194 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
195 :
196 : // Generate note A
197 2 : libzcash::SaplingNote note(pk, 50000000);
198 2 : auto cm = note.cmu().get();
199 2 : SaplingMerkleTree saplingTree;
200 1 : saplingTree.append(cm);
201 1 : auto anchor = saplingTree.root();
202 2 : auto witness = saplingTree.witness();
203 :
204 : // Generate tx to create output note B
205 2 : auto builder = TransactionBuilder(consensusParams);
206 1 : builder.AddSaplingSpend(expsk, note, anchor, witness);
207 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000000, {});
208 1 : builder.SetFee(10000000);
209 2 : auto tx = builder.Build().GetTxOrThrow();
210 2 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
211 :
212 : // Fake-mine the transaction
213 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
214 1 : CBlock block;
215 3 : block.hashPrevBlock = chainActive[0]->GetBlockHash();
216 1 : block.vtx.emplace_back(wtx.tx);
217 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
218 1 : const auto& blockHash = block.GetHash();
219 2 : CBlockIndex fakeIndex {block};
220 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
221 1 : fakeIndex.phashBlock = &((*mi).first);
222 1 : chainActive.SetTip(&fakeIndex);
223 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
224 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
225 :
226 : // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
227 3 : auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
228 2 : BOOST_CHECK(saplingNoteData.size() > 0);
229 1 : wtx.SetSaplingNoteData(saplingNoteData);
230 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
231 2 : BOOST_CHECK(wallet.LoadToWallet(wtx));
232 :
233 : // Simulate receiving new block and ChainTip signal
234 1 : wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
235 1 : wallet.GetSaplingScriptPubKeyMan()->UpdateSaplingNullifierNoteMapForBlock(&block);
236 :
237 : // Retrieve the updated wtx from wallet
238 1 : const uint256& hash = wtx.GetHash();
239 1 : wtx = wallet.mapWallet.at(hash);
240 :
241 : // Decrypt output note B
242 1 : auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
243 1 : wtx.tx->sapData->vShieldedOutput[0].encCiphertext,
244 : ivk,
245 1 : wtx.tx->sapData->vShieldedOutput[0].ephemeralKey,
246 2 : wtx.tx->sapData->vShieldedOutput[0].cmu);
247 2 : BOOST_CHECK(static_cast<bool>(maybe_pt) == true);
248 2 : auto maybe_note = maybe_pt.get().note(ivk);
249 2 : BOOST_CHECK(static_cast<bool>(maybe_note) == true);
250 2 : auto note2 = maybe_note.get();
251 :
252 1 : SaplingOutPoint sop0(wtx.GetHash(), 0);
253 2 : auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front();
254 2 : auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position());
255 2 : BOOST_CHECK(static_cast<bool>(maybe_nf) == true);
256 :
257 1 : anchor = saplingTree.root();
258 :
259 : // Create transaction to spend note B
260 2 : auto builder2 = TransactionBuilder(consensusParams);
261 1 : builder2.SetFee(10000000);
262 1 : builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
263 1 : builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 20000000, {});
264 2 : auto tx2 = builder2.Build().GetTxOrThrow();
265 :
266 : // Create conflicting transaction which also spends note B
267 2 : auto builder3 = TransactionBuilder(consensusParams);
268 1 : builder3.SetFee(10000000);
269 1 : builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
270 1 : builder3.AddSaplingOutput(extfvk.fvk.ovk, pk, 19999000, {});
271 2 : auto tx3 = builder3.Build().GetTxOrThrow();
272 :
273 2 : CWalletTx wtx2 {&wallet, MakeTransactionRef(tx2)};
274 1 : CWalletTx wtx3 {&wallet, MakeTransactionRef(tx3)};
275 :
276 1 : const auto& hash2 = wtx2.GetHash();
277 1 : const auto& hash3 = wtx3.GetHash();
278 :
279 : // No conflicts for no spends (wtx is currently the only transaction in the wallet)
280 1 : BOOST_CHECK_EQUAL(0, wallet.GetConflicts(hash2).size());
281 1 : BOOST_CHECK_EQUAL(0, wallet.GetConflicts(hash3).size());
282 :
283 : // No conflicts for one spend
284 2 : BOOST_CHECK(wallet.LoadToWallet(wtx2));
285 1 : BOOST_CHECK_EQUAL(0, wallet.GetConflicts(hash2).size());
286 :
287 : // Conflicts for two spends
288 2 : BOOST_CHECK(wallet.LoadToWallet(wtx3));
289 2 : auto c3 = wallet.GetConflicts(hash2);
290 1 : BOOST_CHECK_EQUAL(2, c3.size());
291 3 : BOOST_CHECK(std::set<uint256>({hash2, hash3}) == c3);
292 :
293 : // Tear down
294 1 : chainActive.SetTip(nullptr);
295 1 : mapBlockIndex.erase(blockHash);
296 1 : }
297 :
298 2 : BOOST_AUTO_TEST_CASE(SaplingNullifierIsSpent)
299 : {
300 1 : auto consensusParams = Params().GetConsensus();
301 :
302 1 : CWallet& wallet = m_wallet;
303 3 : LOCK2(cs_main, wallet.cs_wallet);
304 1 : setupWallet(wallet);
305 :
306 : // Generate dummy Sapling address
307 1 : auto sk = GetTestMasterSaplingSpendingKey();
308 1 : auto expsk = sk.expsk;
309 1 : auto extfvk = sk.ToXFVK();
310 1 : auto pa = sk.DefaultAddress();
311 :
312 2 : auto testNote = GetTestSaplingNote(pa, 50000000);
313 :
314 : // Generate transaction
315 2 : auto builder = TransactionBuilder(consensusParams);
316 1 : builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
317 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 2500000, {});
318 1 : builder.SetFee(10000000);
319 2 : auto tx = builder.Build().GetTxOrThrow();
320 :
321 1 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
322 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
323 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
324 :
325 : // Manually compute the nullifier based on the known position
326 3 : auto nf = testNote.note.nullifier(extfvk.fvk, testNote.tree.witness().position());
327 2 : BOOST_CHECK(nf);
328 1 : uint256 nullifier = nf.get();
329 :
330 : // Verify note has not been spent
331 2 : BOOST_CHECK(!wallet.GetSaplingScriptPubKeyMan()->IsSaplingSpent(nullifier));
332 :
333 : // Fake-mine the transaction
334 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
335 2 : CBlock block;
336 2 : block.hashPrevBlock = chainActive[0]->GetBlockHash();
337 1 : block.vtx.emplace_back(wtx.tx);
338 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
339 1 : const auto& blockHash = block.GetHash();
340 2 : CBlockIndex fakeIndex {block};
341 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
342 1 : fakeIndex.phashBlock = &((*mi).first);
343 1 : chainActive.SetTip(&fakeIndex);
344 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
345 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
346 :
347 2 : wallet.BlockConnected(std::make_shared<CBlock>(block), mi->second);
348 :
349 : // Verify note has been spent
350 2 : BOOST_CHECK(wallet.GetSaplingScriptPubKeyMan()->IsSaplingSpent(nullifier));
351 :
352 : // Tear down
353 1 : chainActive.SetTip(nullptr);
354 1 : mapBlockIndex.erase(blockHash);
355 1 : }
356 :
357 2 : BOOST_AUTO_TEST_CASE(NavigateFromSaplingNullifierToNote)
358 : {
359 1 : auto consensusParams = Params().GetConsensus();
360 :
361 1 : CWallet& wallet = m_wallet;
362 3 : LOCK2(cs_main, wallet.cs_wallet);
363 1 : setupWallet(wallet);
364 :
365 : // Generate dummy Sapling address
366 1 : auto sk = GetTestMasterSaplingSpendingKey();
367 1 : auto expsk = sk.expsk;
368 1 : auto extfvk = sk.ToXFVK();
369 1 : auto pa = sk.DefaultAddress();
370 :
371 2 : auto testNote = GetTestSaplingNote(pa, 50000000);
372 :
373 : // Generate transaction
374 2 : auto builder = TransactionBuilder(consensusParams);
375 1 : builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
376 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000000, {});
377 1 : builder.SetFee(10000000);
378 2 : auto tx = builder.Build().GetTxOrThrow();
379 :
380 1 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
381 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
382 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
383 :
384 : // Manually compute the nullifier based on the expected position
385 3 : auto nf = testNote.note.nullifier(extfvk.fvk, testNote.tree.witness().position());
386 2 : BOOST_CHECK(nf);
387 1 : uint256 nullifier = nf.get();
388 :
389 : // Verify dummy note is unspent
390 2 : BOOST_CHECK(!wallet.GetSaplingScriptPubKeyMan()->IsSaplingSpent(nullifier));
391 :
392 : // Fake-mine the transaction
393 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
394 2 : CBlock block;
395 2 : block.hashPrevBlock = chainActive[0]->GetBlockHash();
396 1 : block.vtx.emplace_back(wtx.tx);
397 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
398 1 : const auto& blockHash = block.GetHash();
399 2 : CBlockIndex fakeIndex {block};
400 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
401 1 : fakeIndex.phashBlock = &((*mi).first);
402 1 : chainActive.SetTip(&fakeIndex);
403 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
404 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
405 :
406 : // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
407 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
408 3 : auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
409 2 : BOOST_CHECK(saplingNoteData.size() > 0);
410 1 : wtx.SetSaplingNoteData(saplingNoteData);
411 1 : wallet.LoadToWallet(wtx);
412 :
413 : // Verify dummy note is now spent, as AddToWallet invokes AddToSpends()
414 1 : wallet.SetLastBlockProcessed(mi->second);
415 2 : BOOST_CHECK(wallet.GetSaplingScriptPubKeyMan()->IsSaplingSpent(nullifier));
416 :
417 : // Test invariant: no witnesses means no nullifier.
418 1 : BOOST_CHECK_EQUAL(0, wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes.size());
419 3 : for (mapSaplingNoteData_t::value_type &item : wtx.mapSaplingNoteData) {
420 4 : SaplingNoteData nd = item.second;
421 4 : BOOST_CHECK(nd.witnesses.empty());
422 4 : BOOST_CHECK(!nd.nullifier);
423 : }
424 :
425 : // Simulate receiving new block and ChainTip signal
426 1 : wallet.IncrementNoteWitnesses(&fakeIndex, &block, testNote.tree);
427 1 : wallet.GetSaplingScriptPubKeyMan()->UpdateSaplingNullifierNoteMapForBlock(&block);
428 :
429 : // Retrieve the updated wtx from wallet
430 1 : const uint256& hash = wtx.GetHash();
431 1 : wtx = wallet.mapWallet.at(hash);
432 :
433 : // Verify Sapling nullifiers map to SaplingOutPoints
434 1 : BOOST_CHECK_EQUAL(2, wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes.size());
435 3 : for (mapSaplingNoteData_t::value_type &item : wtx.mapSaplingNoteData) {
436 2 : SaplingOutPoint op = item.first;
437 4 : SaplingNoteData nd = item.second;
438 4 : BOOST_CHECK(hash == op.hash);
439 2 : BOOST_CHECK_EQUAL(1, nd.witnesses.size());
440 4 : BOOST_CHECK(nd.nullifier);
441 2 : auto nf = nd.nullifier.get();
442 4 : BOOST_CHECK_EQUAL(1, wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes.count(nf));
443 4 : BOOST_CHECK(op.hash == wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes[nf].hash);
444 2 : BOOST_CHECK_EQUAL(op.n, wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes[nf].n);
445 : }
446 :
447 : // Tear down
448 1 : chainActive.SetTip(nullptr);
449 1 : mapBlockIndex.erase(blockHash);
450 1 : }
451 :
452 : // Create note A, spend A to create note B, spend and verify note B is from me.
453 2 : BOOST_AUTO_TEST_CASE(SpentSaplingNoteIsFromMe)
454 : {
455 1 : auto consensusParams = Params().GetConsensus();
456 :
457 1 : CWallet& wallet = m_wallet;
458 3 : LOCK2(cs_main, wallet.cs_wallet);
459 1 : setupWallet(wallet);
460 :
461 : // Generate Sapling address
462 1 : auto sk = GetTestMasterSaplingSpendingKey();
463 1 : auto expsk = sk.expsk;
464 1 : auto extfvk = sk.ToXFVK();
465 1 : auto ivk = extfvk.fvk.in_viewing_key();
466 1 : auto pk = sk.DefaultAddress();
467 :
468 : // Generate Sapling note A
469 2 : libzcash::SaplingNote note(pk, 50000000);
470 2 : auto cm = note.cmu().get();
471 2 : SaplingMerkleTree saplingTree;
472 1 : saplingTree.append(cm);
473 1 : auto anchor = saplingTree.root();
474 2 : auto witness = saplingTree.witness();
475 :
476 : // Generate transaction, which sends funds to note B
477 2 : auto builder = TransactionBuilder(consensusParams);
478 1 : builder.AddSaplingSpend(expsk, note, anchor, witness);
479 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000000, {});
480 1 : builder.SetFee(10000000);
481 2 : auto tx = builder.Build().GetTxOrThrow();
482 :
483 2 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
484 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
485 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
486 :
487 : // Fake-mine the transaction
488 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
489 1 : CBlock block;
490 2 : block.hashPrevBlock = chainActive[0]->GetBlockHash();
491 1 : block.vtx.emplace_back(wtx.tx);
492 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
493 1 : const auto& blockHash = block.GetHash();
494 2 : CBlockIndex fakeIndex {block};
495 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
496 1 : fakeIndex.phashBlock = &((*mi).first);
497 1 : chainActive.SetTip(&fakeIndex);
498 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
499 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
500 :
501 3 : auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
502 2 : BOOST_CHECK(saplingNoteData.size() > 0);
503 1 : wtx.SetSaplingNoteData(saplingNoteData);
504 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
505 1 : wallet.LoadToWallet(wtx);
506 :
507 : // Simulate receiving new block and ChainTip signal.
508 : // This triggers calculation of nullifiers for notes belonging to this wallet
509 : // in the output descriptions of wtx.
510 1 : wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
511 1 : wallet.GetSaplingScriptPubKeyMan()->UpdateSaplingNullifierNoteMapForBlock(&block);
512 1 : wallet.SetLastBlockProcessed(mi->second);
513 :
514 : // Retrieve the updated wtx from wallet
515 1 : wtx = wallet.mapWallet.at(wtx.GetHash());
516 :
517 : // The test wallet never received the fake note which is being spent, so there
518 : // is no mapping from nullifier to notedata stored in mapSaplingNullifiersToNotes.
519 : // Therefore the wallet does not know the tx belongs to the wallet.
520 2 : BOOST_CHECK(!wallet.IsFromMe(wtx.tx));
521 :
522 : // Manually compute the nullifier and check map entry does not exist
523 1 : auto nf = note.nullifier(extfvk.fvk, witness.position());
524 2 : BOOST_CHECK(nf);
525 3 : BOOST_CHECK(!wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes.count(nf.get()));
526 :
527 : // Decrypt note B
528 1 : auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
529 1 : wtx.tx->sapData->vShieldedOutput[0].encCiphertext,
530 : ivk,
531 1 : wtx.tx->sapData->vShieldedOutput[0].ephemeralKey,
532 2 : wtx.tx->sapData->vShieldedOutput[0].cmu);
533 1 : BOOST_CHECK_EQUAL(static_cast<bool>(maybe_pt), true);
534 2 : auto maybe_note = maybe_pt.get().note(ivk);
535 1 : BOOST_CHECK_EQUAL(static_cast<bool>(maybe_note), true);
536 2 : auto note2 = maybe_note.get();
537 :
538 : // Get witness to retrieve position of note B we want to spend
539 1 : SaplingOutPoint sop0(wtx.GetHash(), 0);
540 2 : auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front();
541 2 : auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position());
542 1 : BOOST_CHECK_EQUAL(static_cast<bool>(maybe_nf), true);
543 1 : auto nullifier2 = maybe_nf.get();
544 :
545 : // NOTE: Not updating the anchor results in a core dump. Shouldn't builder just return error?
546 : // *** Error in `./zcash-gtest': double free or corruption (out): 0x00007ffd8755d990 ***
547 1 : anchor = saplingTree.root();
548 :
549 : // Create transaction to spend note B
550 2 : auto builder2 = TransactionBuilder(consensusParams);
551 1 : builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
552 1 : builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500000, {});
553 1 : builder2.SetFee(10000000);
554 2 : auto tx2 = builder2.Build().GetTxOrThrow();
555 1 : BOOST_CHECK_EQUAL(tx2.vin.size(), 0);
556 1 : BOOST_CHECK_EQUAL(tx2.vout.size(), 0);
557 1 : BOOST_CHECK_EQUAL(tx2.sapData->vShieldedSpend.size(), 1);
558 1 : BOOST_CHECK_EQUAL(tx2.sapData->vShieldedOutput.size(), 2);
559 1 : BOOST_CHECK_EQUAL(tx2.sapData->valueBalance, 10000000);
560 :
561 2 : CWalletTx wtx2 {&wallet, MakeTransactionRef(tx2)};
562 :
563 : // Fake-mine this tx into the next block
564 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
565 1 : CBlock block2;
566 1 : block2.vtx.emplace_back(wtx2.tx);
567 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
568 1 : block2.hashPrevBlock = blockHash;
569 1 : auto blockHash2 = block2.GetHash();
570 2 : CBlockIndex fakeIndex2 {block2};
571 1 : BlockMap::iterator mi2 = mapBlockIndex.emplace(blockHash2, &fakeIndex2).first;
572 1 : fakeIndex2.phashBlock = &((*mi2).first);
573 1 : fakeIndex2.nHeight = 1;
574 1 : chainActive.SetTip(&fakeIndex2);
575 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex2));
576 1 : BOOST_CHECK_EQUAL(1, chainActive.Height());
577 :
578 3 : auto saplingNoteData2 = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx2.tx).first;
579 2 : BOOST_CHECK(saplingNoteData2.size() > 0);
580 1 : wtx2.SetSaplingNoteData(saplingNoteData2);
581 1 : wtx2.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex2.nHeight, block2.GetHash(), 0);
582 1 : wallet.LoadToWallet(wtx2);
583 1 : wallet.SetLastBlockProcessed(mi2->second);
584 :
585 : // Verify note B is spent. AddToWallet invokes AddToSpends which updates mapTxSaplingNullifiers
586 2 : BOOST_CHECK(wallet.GetSaplingScriptPubKeyMan()->IsSaplingSpent(nullifier2));
587 :
588 : // Verify note B belongs to wallet.
589 2 : BOOST_CHECK(wallet.IsFromMe(wtx2.tx));
590 3 : BOOST_CHECK(wallet.GetSaplingScriptPubKeyMan()->mapSaplingNullifiersToNotes.count(nullifier2));
591 :
592 : // Tear down
593 1 : chainActive.SetTip(nullptr);
594 1 : mapBlockIndex.erase(blockHash);
595 1 : mapBlockIndex.erase(blockHash2);
596 1 : }
597 :
598 2 : BOOST_AUTO_TEST_CASE(CachedWitnessesEmptyChain)
599 : {
600 2 : auto consensusParams = Params().GetConsensus();
601 :
602 1 : CWallet& wallet = m_wallet;
603 1 : {
604 1 : LOCK(wallet.cs_wallet);
605 1 : setupWallet(wallet);
606 : }
607 :
608 1 : auto sk = GetTestMasterSaplingSpendingKey();
609 1 : CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(), wallet, sk, 10, true);
610 :
611 2 : std::vector<SaplingOutPoint> saplingNotes = SetSaplingNoteData(wtx);
612 2 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
613 :
614 1 : ::GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
615 :
616 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
617 :
618 1 : wallet.LoadToWallet(wtx);
619 :
620 1 : ::GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
621 :
622 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
623 :
624 1 : CBlock block;
625 1 : block.vtx.emplace_back(wtx.tx);
626 2 : CBlockIndex index(block);
627 2 : SaplingMerkleTree saplingTree;
628 1 : wallet.IncrementNoteWitnesses(&index, &block, saplingTree);
629 :
630 1 : ::GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
631 :
632 2 : BOOST_CHECK((bool) saplingWitnesses[0]);
633 :
634 : // Until zcash#1302 is implemented, this should trigger an assertion
635 2 : BOOST_CHECK_THROW(wallet.DecrementNoteWitnesses(&index), std::runtime_error);
636 1 : }
637 :
638 2 : BOOST_AUTO_TEST_CASE(CachedWitnessesChainTip)
639 : {
640 2 : auto consensusParams = Params().GetConsensus();
641 :
642 1 : libzcash::SaplingExtendedSpendingKey sk = GetTestMasterSaplingSpendingKey();
643 1 : CWallet& wallet = m_wallet;
644 1 : {
645 1 : LOCK(wallet.cs_wallet);
646 1 : setupWallet(wallet);
647 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
648 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(sk.ToXFVK()));
649 : }
650 :
651 1 : uint256 anchors1;
652 1 : CBlock block1;
653 2 : SaplingMerkleTree saplingTree;
654 :
655 1 : {
656 : // First block (case tested in _empty_chain)
657 1 : CBlockIndex index1(block1);
658 1 : index1.nHeight = 1;
659 1 : auto output = CreateValidBlock(wallet, sk, index1, block1, saplingTree);
660 :
661 : // Called to fetch anchor
662 2 : std::vector<SaplingOutPoint> saplingNotes {output};
663 1 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
664 :
665 1 : anchors1 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
666 : }
667 :
668 1 : {
669 : // Second transaction
670 1 : CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
671 1 : wallet, sk, 50, true);
672 :
673 2 : std::vector<SaplingOutPoint> saplingNotes = SetSaplingNoteData(wtx);
674 1 : wallet.LoadToWallet(wtx);
675 :
676 2 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
677 1 : GetWitnessesAndAnchors(wallet , saplingNotes, saplingWitnesses);
678 :
679 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
680 :
681 : // Second block
682 1 : CBlock block2;
683 1 : block2.hashPrevBlock = block1.GetHash();
684 1 : block2.vtx.emplace_back(wtx.tx);
685 2 : CBlockIndex index2(block2);
686 1 : index2.nHeight = 2;
687 2 : SaplingMerkleTree saplingTree2 {saplingTree};
688 1 : wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree2);
689 :
690 1 : auto anchors2 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
691 2 : BOOST_CHECK((bool) saplingWitnesses[0]);
692 2 : BOOST_CHECK(anchors1 != anchors2);
693 :
694 : // Decrementing should give us the previous anchor
695 1 : wallet.DecrementNoteWitnesses(&index2);
696 1 : auto anchors3 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
697 :
698 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
699 : // Should not equal first anchor because none of these notes had witnesses
700 2 : BOOST_CHECK(anchors1 != anchors3);
701 :
702 : // Re-incrementing with the same block should give the same result
703 1 : wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree);
704 1 : auto anchors4 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
705 2 : BOOST_CHECK((bool) saplingWitnesses[0]);
706 2 : BOOST_CHECK(anchors2 == anchors4);
707 :
708 : // Incrementing with the same block again should not change the cache
709 1 : wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree);
710 2 : std::vector<Optional<SaplingWitness>> saplingWitnesses5;
711 :
712 1 : auto anchors5 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses5);
713 2 : BOOST_CHECK(saplingWitnesses == saplingWitnesses5);
714 2 : BOOST_CHECK(anchors4 == anchors5);
715 : }
716 1 : }
717 :
718 2 : BOOST_AUTO_TEST_CASE(CachedWitnessesDecrementFirst)
719 : {
720 1 : auto consensusParams = Params().GetConsensus();
721 1 : libzcash::SaplingExtendedSpendingKey sk = GetTestMasterSaplingSpendingKey();
722 1 : CWallet& wallet = m_wallet;
723 1 : {
724 1 : LOCK(wallet.cs_wallet);
725 1 : setupWallet(wallet);
726 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
727 : }
728 :
729 2 : SaplingMerkleTree saplingTree;
730 1 : {
731 : // First block (case tested in _empty_chain)
732 1 : CBlock block1;
733 2 : CBlockIndex index1(block1);
734 1 : index1.nHeight = 1;
735 1 : CreateValidBlock(wallet, sk, index1, block1, saplingTree);
736 : }
737 :
738 1 : uint256 anchors2;
739 2 : CBlock block2;
740 2 : CBlockIndex index2(block2);
741 :
742 1 : {
743 : // Second block (case tested in _chain_tip)
744 1 : index2.nHeight = 2;
745 1 : auto output = CreateValidBlock(wallet, sk, index2, block2, saplingTree);
746 :
747 : // Called to fetch anchor
748 1 : std::vector<SaplingOutPoint> saplingNotes {output};
749 1 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
750 1 : anchors2 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
751 : }
752 :
753 1 : {
754 : // Third transaction - never mined
755 1 : CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
756 1 : wallet, sk, 20, true);
757 2 : std::vector<SaplingOutPoint> saplingNotes = SetSaplingNoteData(wtx);
758 1 : wallet.LoadToWallet(wtx);
759 :
760 2 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
761 :
762 1 : auto anchors3 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
763 :
764 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
765 :
766 : // Decrementing (before the transaction has ever seen an increment)
767 : // should give us the previous anchor
768 1 : wallet.DecrementNoteWitnesses(&index2);
769 :
770 1 : auto anchors4 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
771 :
772 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
773 : // Should not equal second anchor because none of these notes had witnesses
774 2 : BOOST_CHECK(anchors2 != anchors4);
775 :
776 : // Re-incrementing with the same block should give the same result
777 1 : wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree);
778 :
779 1 : auto anchors5 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
780 :
781 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
782 2 : BOOST_CHECK(anchors3 == anchors5);
783 : }
784 1 : }
785 :
786 2 : BOOST_AUTO_TEST_CASE(CachedWitnessesCleanIndex)
787 : {
788 2 : auto consensusParams = Params().GetConsensus();
789 :
790 1 : libzcash::SaplingExtendedSpendingKey sk = GetTestMasterSaplingSpendingKey();
791 1 : CWallet& wallet = m_wallet;
792 1 : {
793 1 : LOCK(wallet.cs_wallet);
794 1 : setupWallet(wallet);
795 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
796 : }
797 :
798 2 : std::vector<CBlock> blocks;
799 0 : std::vector<CBlockIndex> indices;
800 1 : std::vector<SaplingOutPoint> saplingNotes;
801 1 : std::vector<uint256> saplingAnchors;
802 2 : SaplingMerkleTree saplingTree;
803 2 : SaplingMerkleTree saplingRiTree = saplingTree;
804 2 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
805 :
806 : // Generate a chain
807 1 : size_t numBlocks = WITNESS_CACHE_SIZE + 10;
808 1 : blocks.resize(numBlocks);
809 1 : indices.resize(numBlocks);
810 112 : for (size_t i = 0; i < numBlocks; i++) {
811 111 : indices[i].nHeight = i;
812 111 : auto oldSaplingRoot = saplingTree.root();
813 111 : auto outpts = CreateValidBlock(wallet, sk, indices[i], blocks[i], saplingTree);
814 222 : BOOST_CHECK(oldSaplingRoot != saplingTree.root());
815 111 : saplingNotes.push_back(outpts);
816 :
817 111 : auto anchor = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
818 6327 : for (size_t j = 0; j <= i; j++) {
819 12432 : BOOST_CHECK((bool) saplingWitnesses[j]);
820 : }
821 111 : saplingAnchors.push_back(anchor);
822 : }
823 :
824 : // Now pretend we are reindexing: the chain is cleared, and each block is
825 : // used to increment witnesses again.
826 112 : for (size_t i = 0; i < numBlocks; i++) {
827 222 : SaplingMerkleTree saplingRiPrevTree {saplingRiTree};
828 111 : wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), saplingRiTree);
829 :
830 111 : auto anchors = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
831 12432 : for (size_t j = 0; j < numBlocks; j++) {
832 24642 : BOOST_CHECK((bool) saplingWitnesses[j]);
833 : }
834 : // Should equal final anchor because witness cache unaffected
835 222 : BOOST_CHECK(saplingAnchors.back() == anchors);
836 :
837 111 : if ((i == 5) || (i == 50)) {
838 : // Pretend a reorg happened that was recorded in the block files
839 2 : {
840 2 : wallet.DecrementNoteWitnesses(&(indices[i]));
841 :
842 2 : auto anchors = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
843 224 : for (size_t j = 0; j < numBlocks; j++) {
844 444 : BOOST_CHECK((bool) saplingWitnesses[j]);
845 : }
846 : // Should equal final anchor because witness cache unaffected
847 4 : BOOST_CHECK(saplingAnchors.back() == anchors);
848 : }
849 :
850 2 : {
851 2 : wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), saplingRiPrevTree);
852 2 : auto anchors = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
853 224 : for (size_t j = 0; j < numBlocks; j++) {
854 444 : BOOST_CHECK((bool) saplingWitnesses[j]);
855 : }
856 : // Should equal final anchor because witness cache unaffected
857 4 : BOOST_CHECK(saplingAnchors.back() == anchors);
858 : }
859 : }
860 : }
861 1 : }
862 :
863 2 : BOOST_AUTO_TEST_CASE(ClearNoteWitnessCache)
864 : {
865 2 : auto consensusParams = Params().GetConsensus();
866 :
867 1 : libzcash::SaplingExtendedSpendingKey sk = GetTestMasterSaplingSpendingKey();
868 1 : CWallet& wallet = m_wallet;
869 1 : {
870 1 : LOCK(wallet.cs_wallet);
871 1 : setupWallet(wallet);
872 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
873 : }
874 :
875 1 : CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
876 1 : wallet, sk, 10, true);
877 1 : auto hash = wtx.GetHash();
878 2 : auto saplingNotes = SetSaplingNoteData(wtx);
879 :
880 : // Pretend we mined the tx by adding a fake witness
881 2 : SaplingMerkleTree saplingTree;
882 2 : wtx.mapSaplingNoteData[saplingNotes[0]].witnesses.push_front(saplingTree.witness());
883 1 : wtx.mapSaplingNoteData[saplingNotes[0]].witnessHeight = 1;
884 1 : wallet.GetSaplingScriptPubKeyMan()->nWitnessCacheSize = 1;
885 :
886 1 : wallet.LoadToWallet(wtx);
887 :
888 : // SetSaplingNoteData() only created a single Sapling output
889 : // which is in the wallet, so we add a second SaplingOutPoint here to
890 : // exercise the "note not in wallet" case.
891 1 : saplingNotes.emplace_back(wtx.GetHash(), 1);
892 1 : BOOST_CHECK_EQUAL(saplingNotes.size(), 2);
893 :
894 2 : std::vector<Optional<SaplingWitness>> saplingWitnesses;
895 :
896 : // Before clearing, we should have a witness for one note
897 1 : GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
898 2 : BOOST_CHECK((bool) saplingWitnesses[0]);
899 2 : BOOST_CHECK(!(bool) saplingWitnesses[1]);
900 1 : BOOST_CHECK_EQUAL(1, wallet.mapWallet.at(hash).mapSaplingNoteData[saplingNotes[0]].witnessHeight);
901 1 : BOOST_CHECK_EQUAL(1, wallet.GetSaplingScriptPubKeyMan()->nWitnessCacheSize);
902 :
903 : // After clearing, we should not have a witness for either note
904 1 : wallet.GetSaplingScriptPubKeyMan()->ClearNoteWitnessCache();
905 1 : GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
906 2 : BOOST_CHECK(!(bool) saplingWitnesses[0]);
907 2 : BOOST_CHECK(!(bool) saplingWitnesses[1]);
908 1 : BOOST_CHECK_EQUAL(-1, wallet.mapWallet.at(hash).mapSaplingNoteData[saplingNotes[0]].witnessHeight);
909 1 : BOOST_CHECK_EQUAL(0, wallet.GetSaplingScriptPubKeyMan()->nWitnessCacheSize);
910 1 : }
911 :
912 2 : BOOST_AUTO_TEST_CASE(UpdatedSaplingNoteData)
913 : {
914 1 : auto consensusParams = Params().GetConsensus();
915 :
916 1 : CWallet& wallet = m_wallet;
917 : // Need to lock cs_main for now due the lock ordering. future: revamp all of this function to only lock where is needed.
918 3 : LOCK2(cs_main, wallet.cs_wallet);
919 1 : setupWallet(wallet);
920 :
921 1 : auto m = GetTestMasterSaplingSpendingKey();
922 :
923 : // Generate dummy Sapling address
924 1 : auto sk = m.Derive(0);
925 1 : auto expsk = sk.expsk;
926 1 : auto extfvk = sk.ToXFVK();
927 1 : auto pa = sk.DefaultAddress();
928 :
929 : // Generate dummy recipient Sapling address
930 1 : auto sk2 = m.Derive(1);
931 1 : auto extfvk2 = sk2.ToXFVK();
932 1 : auto pa2 = sk2.DefaultAddress();
933 :
934 2 : auto testNote = GetTestSaplingNote(pa, 50000000);
935 :
936 : // Generate transaction
937 2 : auto builder = TransactionBuilder(consensusParams);
938 1 : builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
939 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pa2, 25000000, {});
940 1 : builder.SetFee(10000000);
941 2 : auto tx = builder.Build().GetTxOrThrow();
942 :
943 : // Wallet contains extfvk1 but not extfvk2
944 2 : CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
945 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
946 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
947 2 : BOOST_CHECK(!wallet.HaveSaplingSpendingKey(extfvk2));
948 :
949 : // Fake-mine the transaction
950 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
951 1 : CBlock block;
952 1 : block.vtx.emplace_back(wtx.tx);
953 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
954 1 : const auto& blockHash = block.GetHash();
955 2 : CBlockIndex fakeIndex {block};
956 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
957 1 : fakeIndex.phashBlock = &((*mi).first);
958 1 : chainActive.SetTip(&fakeIndex);
959 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
960 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
961 :
962 : // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
963 3 : auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
964 2 : BOOST_CHECK(saplingNoteData.size() == 1); // wallet only has key for change output
965 1 : wtx.SetSaplingNoteData(saplingNoteData);
966 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
967 1 : wallet.LoadToWallet(wtx);
968 :
969 : // Simulate receiving new block and ChainTip signal
970 1 : wallet.IncrementNoteWitnesses(&fakeIndex, &block, testNote.tree);
971 1 : wallet.GetSaplingScriptPubKeyMan()->UpdateSaplingNullifierNoteMapForBlock(&block);
972 :
973 : // Retrieve the updated wtx from wallet
974 1 : const uint256& hash = wtx.GetHash();
975 1 : wtx = wallet.mapWallet.at(hash);
976 :
977 : // Now lets add key extfvk2 so wallet can find the payment note sent to pa2
978 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk2));
979 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk2));
980 2 : CWalletTx wtx2 = wtx;
981 3 : auto saplingNoteData2 = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx2.tx).first;
982 2 : BOOST_CHECK(saplingNoteData2.size() == 2);
983 1 : wtx2.SetSaplingNoteData(saplingNoteData2);
984 :
985 : // The payment note has not been witnessed yet, so let's fake the witness.
986 1 : SaplingOutPoint sop0(wtx2.GetHash(), 0);
987 1 : SaplingOutPoint sop1(wtx2.GetHash(), 1);
988 2 : wtx2.mapSaplingNoteData[sop0].witnesses.push_front(testNote.tree.witness());
989 1 : wtx2.mapSaplingNoteData[sop0].witnessHeight = 0;
990 :
991 : // The txs are different as wtx is aware of just the change output,
992 : // whereas wtx2 is aware of both payment and change outputs.
993 2 : BOOST_CHECK(wtx.mapSaplingNoteData != wtx2.mapSaplingNoteData);
994 1 : BOOST_CHECK_EQUAL(1, wtx.mapSaplingNoteData.size());
995 1 : BOOST_CHECK_EQUAL(1, wtx.mapSaplingNoteData[sop1].witnesses.size()); // wtx has witness for change
996 :
997 1 : BOOST_CHECK_EQUAL(2, wtx2.mapSaplingNoteData.size());
998 1 : BOOST_CHECK_EQUAL(1, wtx2.mapSaplingNoteData[sop0].witnesses.size()); // wtx2 has fake witness for payment output
999 1 : BOOST_CHECK_EQUAL(0, wtx2.mapSaplingNoteData[sop1].witnesses.size()); // wtx2 never had incrementnotewitness called
1000 :
1001 : // After updating, they should be the same
1002 2 : BOOST_CHECK(wallet.GetSaplingScriptPubKeyMan()->UpdatedNoteData(wtx2, wtx));
1003 :
1004 : // We can't do this:
1005 : // BOOST_CHECK_EQUAL(wtx.mapSaplingNoteData, wtx2.mapSaplingNoteData);
1006 : // because nullifiers (if part of == comparator) have not all been computed
1007 : // Also note that mapwallet[hash] is not updated with the updated wtx.
1008 : // wtx = wallet.mapWallet[hash];
1009 :
1010 1 : BOOST_CHECK_EQUAL(2, wtx.mapSaplingNoteData.size());
1011 1 : BOOST_CHECK_EQUAL(2, wtx2.mapSaplingNoteData.size());
1012 : // wtx copied over the fake witness from wtx2 for the payment output
1013 2 : BOOST_CHECK(wtx.mapSaplingNoteData[sop0].witnesses.front() == wtx2.mapSaplingNoteData[sop0].witnesses.front());
1014 : // wtx2 never had its change output witnessed even though it has been in wtx
1015 1 : BOOST_CHECK_EQUAL(0, wtx2.mapSaplingNoteData[sop1].witnesses.size());
1016 2 : BOOST_CHECK(wtx.mapSaplingNoteData[sop1].witnesses.front() == testNote.tree.witness());
1017 :
1018 : // Tear down
1019 1 : chainActive.SetTip(nullptr);
1020 1 : mapBlockIndex.erase(blockHash);
1021 1 : }
1022 :
1023 2 : BOOST_AUTO_TEST_CASE(MarkAffectedSaplingTransactionsDirty)
1024 : {
1025 1 : auto consensusParams = Params().GetConsensus();
1026 :
1027 1 : CWallet& wallet = m_wallet;
1028 3 : LOCK2(cs_main, wallet.cs_wallet);
1029 1 : setupWallet(wallet);
1030 :
1031 : // Generate Sapling address
1032 1 : auto sk = GetTestMasterSaplingSpendingKey();
1033 1 : auto expsk = sk.expsk;
1034 1 : auto extfvk = sk.ToXFVK();
1035 1 : auto ivk = extfvk.fvk.in_viewing_key();
1036 1 : auto pk = sk.DefaultAddress();
1037 :
1038 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
1039 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
1040 :
1041 : // Set up transparent address
1042 2 : CBasicKeyStore keystore;
1043 2 : CKey tsk = AddTestCKeyToKeyStore(keystore);
1044 2 : auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
1045 :
1046 : // Generate shielding tx from transparent to Sapling
1047 : // 0.5 t-PIV in, 0.4 z-PIV out, 0.1 t-PIV fee
1048 2 : auto builder = TransactionBuilder(consensusParams, &keystore);
1049 1 : builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000000);
1050 1 : builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 40000000, {});
1051 1 : builder.SetFee(10000000);
1052 2 : auto tx1 = builder.Build().GetTxOrThrow();
1053 :
1054 1 : BOOST_CHECK_EQUAL(tx1.vin.size(), 1);
1055 1 : BOOST_CHECK_EQUAL(tx1.vout.size(), 0);
1056 1 : BOOST_CHECK_EQUAL(tx1.sapData->vShieldedSpend.size(), 0);
1057 1 : BOOST_CHECK_EQUAL(tx1.sapData->vShieldedOutput.size(), 1);
1058 1 : BOOST_CHECK_EQUAL(tx1.sapData->valueBalance, -40000000);
1059 :
1060 1 : CWalletTx wtx {&wallet, MakeTransactionRef(tx1)};
1061 :
1062 : // Fake-mine the transaction
1063 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
1064 2 : SaplingMerkleTree saplingTree;
1065 2 : CBlock block;
1066 1 : block.vtx.emplace_back(wtx.tx);
1067 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
1068 1 : const auto& blockHash = block.GetHash();
1069 2 : CBlockIndex fakeIndex {block};
1070 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
1071 1 : fakeIndex.phashBlock = &((*mi).first);
1072 1 : chainActive.SetTip(&fakeIndex);
1073 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
1074 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
1075 :
1076 : // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
1077 3 : auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
1078 2 : BOOST_CHECK(saplingNoteData.size() > 0);
1079 1 : wtx.SetSaplingNoteData(saplingNoteData);
1080 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
1081 1 : wallet.LoadToWallet(wtx);
1082 :
1083 : // Simulate receiving new block and ChainTip signal
1084 1 : wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
1085 1 : wallet.GetSaplingScriptPubKeyMan()->UpdateSaplingNullifierNoteMapForBlock(&block);
1086 :
1087 : // Retrieve the updated wtx from wallet
1088 1 : const uint256& hash = wtx.GetHash();
1089 1 : wtx = wallet.mapWallet.at(hash);
1090 :
1091 : // Prepare to spend the note that was just created
1092 1 : auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
1093 2 : tx1.sapData->vShieldedOutput[0].encCiphertext, ivk, tx1.sapData->vShieldedOutput[0].ephemeralKey, tx1.sapData->vShieldedOutput[0].cmu);
1094 2 : BOOST_CHECK(static_cast<bool>(maybe_pt));
1095 2 : auto maybe_note = maybe_pt.get().note(ivk);
1096 2 : BOOST_CHECK(static_cast<bool>(maybe_note));
1097 2 : auto note = maybe_note.get();
1098 1 : auto anchor = saplingTree.root();
1099 2 : auto witness = saplingTree.witness();
1100 :
1101 : // Create a Sapling-only transaction
1102 : // 0.4 z-PIV in, 0.25 z-PIV out, 0.1 t-PIV fee, 0.05 z-PIV change
1103 2 : auto builder2 = TransactionBuilder(consensusParams);
1104 1 : builder2.AddSaplingSpend(expsk, note, anchor, witness);
1105 1 : builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000000, {});
1106 1 : builder2.SetFee(10000000);
1107 2 : auto tx2 = builder2.Build().GetTxOrThrow();
1108 :
1109 1 : BOOST_CHECK_EQUAL(tx2.vin.size(), 0);
1110 1 : BOOST_CHECK_EQUAL(tx2.vout.size(), 0);
1111 1 : BOOST_CHECK_EQUAL(tx2.sapData->vShieldedSpend.size(), 1);
1112 1 : BOOST_CHECK_EQUAL(tx2.sapData->vShieldedOutput.size(), 2);
1113 1 : BOOST_CHECK_EQUAL(tx2.sapData->valueBalance, 10000000);
1114 :
1115 1 : CWalletTx wtx2 {&wallet, MakeTransactionRef(tx2)};
1116 :
1117 1 : wallet.MarkAffectedTransactionsDirty(*wtx.tx);
1118 :
1119 : // After getting a cached value, the first tx should be clean
1120 1 : wallet.mapWallet.at(hash).GetDebit(ISMINE_SPENDABLE);
1121 2 : BOOST_CHECK(wallet.mapWallet.at(hash).IsAmountCached(CWalletTx::AmountType::DEBIT, ISMINE_SPENDABLE));
1122 :
1123 : // After adding the note spend, the first tx should be dirty
1124 1 : wallet.LoadToWallet(wtx2);
1125 1 : wallet.MarkAffectedTransactionsDirty(*wtx2.tx);
1126 2 : BOOST_CHECK(!wallet.mapWallet.at(hash).IsAmountCached(CWalletTx::AmountType::DEBIT, ISMINE_SPENDABLE));
1127 :
1128 : // Tear down
1129 1 : chainActive.SetTip(nullptr);
1130 1 : mapBlockIndex.erase(blockHash);
1131 1 : }
1132 :
1133 2 : BOOST_AUTO_TEST_CASE(GetNotes)
1134 : {
1135 1 : auto consensusParams = Params().GetConsensus();
1136 :
1137 1 : CWallet& wallet = m_wallet;
1138 1 : libzcash::SaplingPaymentAddress pk;
1139 1 : uint256 blockHash;
1140 2 : std::vector<SaplingOutPoint> saplingOutpoints;
1141 1 : {
1142 2 : LOCK2(cs_main, wallet.cs_wallet);
1143 1 : setupWallet(wallet);
1144 :
1145 : // Generate Sapling address
1146 1 : auto sk = GetTestMasterSaplingSpendingKey();
1147 1 : auto extfvk = sk.ToXFVK();
1148 1 : pk = sk.DefaultAddress();
1149 :
1150 2 : BOOST_CHECK(wallet.AddSaplingZKey(sk));
1151 2 : BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
1152 :
1153 : // Set up transparent address
1154 2 : CBasicKeyStore keystore;
1155 2 : CKey tsk = AddTestCKeyToKeyStore(keystore);
1156 2 : auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
1157 :
1158 : // Generate shielding tx from transparent to Sapling (five 1 PIV notes)
1159 2 : auto builder = TransactionBuilder(consensusParams, &keystore);
1160 1 : builder.AddTransparentInput(COutPoint(), scriptPubKey, 510000000);
1161 6 : for (int i=0; i<5; i++) builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 100000000, {});
1162 1 : builder.SetFee(10000000);
1163 2 : auto tx1 = builder.Build().GetTxOrThrow();
1164 :
1165 1 : CWalletTx wtx {&wallet, MakeTransactionRef(tx1)};
1166 :
1167 : // Fake-mine the transaction
1168 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
1169 2 : SaplingMerkleTree saplingTree;
1170 2 : CBlock block;
1171 1 : block.vtx.emplace_back(wtx.tx);
1172 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
1173 1 : blockHash = block.GetHash();
1174 2 : CBlockIndex fakeIndex {block};
1175 1 : BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
1176 1 : fakeIndex.phashBlock = &((*mi).first);
1177 1 : chainActive.SetTip(&fakeIndex);
1178 3 : BOOST_CHECK(chainActive.Contains(&fakeIndex));
1179 1 : BOOST_CHECK_EQUAL(0, chainActive.Height());
1180 :
1181 : // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
1182 3 : auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
1183 2 : BOOST_CHECK(saplingNoteData.size() > 0);
1184 1 : wtx.SetSaplingNoteData(saplingNoteData);
1185 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
1186 1 : wallet.LoadToWallet(wtx);
1187 :
1188 : // Simulate receiving new block and ChainTip signal
1189 1 : wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
1190 1 : wallet.GetSaplingScriptPubKeyMan()->UpdateSaplingNullifierNoteMapForBlock(&block);
1191 1 : wallet.SetLastBlockProcessed(mi->second);
1192 :
1193 1 : const uint256& txid = wtx.GetHash();
1194 6 : for (int i=0; i<5; i++) saplingOutpoints.emplace_back(txid, i);
1195 : }
1196 :
1197 : // Check GetFilteredNotes
1198 2 : std::vector<SaplingNoteEntry> entries;
1199 2 : Optional<libzcash::SaplingPaymentAddress> address = pk;
1200 1 : wallet.GetSaplingScriptPubKeyMan()->GetFilteredNotes(entries, address, 0, true, false);
1201 6 : for (int i=0; i<5; i++) {
1202 15 : BOOST_CHECK(entries[i].op == saplingOutpoints[i]);
1203 10 : BOOST_CHECK(entries[i].address == pk);
1204 5 : BOOST_CHECK_EQUAL(entries[i].confirmations, 1);
1205 : }
1206 :
1207 : // Check GetNotes
1208 2 : std::vector<SaplingNoteEntry> entries2;
1209 1 : wallet.GetSaplingScriptPubKeyMan()->GetNotes(saplingOutpoints, entries2);
1210 6 : for (int i=0; i<5; i++) {
1211 15 : BOOST_CHECK(entries2[i].op == entries[i].op);
1212 10 : BOOST_CHECK(entries2[i].address == entries[i].address);
1213 15 : BOOST_CHECK(entries2[i].note.d == entries[i].note.d);
1214 5 : BOOST_CHECK_EQUAL(entries2[i].note.pk_d, entries[i].note.pk_d);
1215 5 : BOOST_CHECK_EQUAL(entries2[i].note.r, entries[i].note.r);
1216 15 : BOOST_CHECK(entries2[i].memo == entries[i].memo);
1217 5 : BOOST_CHECK_EQUAL(entries2[i].confirmations, entries[i].confirmations);
1218 : }
1219 :
1220 : // Tear down
1221 2 : LOCK(cs_main);
1222 1 : chainActive.SetTip(nullptr);
1223 1 : mapBlockIndex.erase(blockHash);
1224 1 : }
1225 :
1226 : // TODO: Back port WriteWitnessCache & SetBestChainIgnoresTxsWithoutShieldedData test cases.
1227 :
1228 : BOOST_AUTO_TEST_SUITE_END()
|