PIVX Core  5.6.99
P2P Digital Currency
sapling_wallet_tests.cpp
Go to the documentation of this file.
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 
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"
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 void setupWallet(CWallet& wallet)
28 {
30  wallet.SetupSPKM(false);
31 }
32 
33 std::vector<SaplingOutPoint> SetSaplingNoteData(CWalletTx& wtx) {
34  mapSaplingNoteData_t saplingNoteData;
35  SaplingOutPoint saplingOutPoint = {wtx.GetHash(), 0};
36  SaplingNoteData saplingNd;
37  // set this as internal note (with non-nullopt ivk)
39  saplingNoteData[saplingOutPoint] = saplingNd;
40  wtx.SetSaplingNoteData(saplingNoteData);
41  std::vector<SaplingOutPoint> saplingNotes {saplingOutPoint};
42  return saplingNotes;
43 }
44 
47  const CBlockIndex& index,
48  CBlock& block,
49  SaplingMerkleTree& saplingTree)
50 {
51  CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
52  wallet, sk, 0, true);
53  auto saplingNotes = SetSaplingNoteData(wtx);
54  wallet.LoadToWallet(wtx);
55 
56  block.vtx.emplace_back(wtx.tx);
57  wallet.IncrementNoteWitnesses(&index, &block, saplingTree);
58 
59  return saplingNotes[0];
60 }
61 
63  const std::vector<SaplingOutPoint>& saplingNotes,
64  std::vector<Optional<SaplingWitness>>& saplingWitnesses)
65 {
66  saplingWitnesses.clear();
67  uint256 saplingAnchor;
68  wallet.GetSaplingScriptPubKeyMan()->GetSaplingNoteWitnesses(saplingNotes, saplingWitnesses, saplingAnchor);
69  return saplingAnchor;
70 }
71 
73 
74 BOOST_AUTO_TEST_CASE(SetSaplingNoteAddrsInCWalletTx) {
75  auto consensusParams = Params().GetConsensus();
76 
77  CWallet& wallet = m_wallet;
78  LOCK(wallet.cs_wallet);
79  setupWallet(wallet);
80 
82  auto expsk = sk.expsk;
83  auto fvk = expsk.full_viewing_key();
84  auto ivk = fvk.in_viewing_key();
85  auto pk = sk.DefaultAddress();
86 
87  libzcash::SaplingNote note(pk, 50000000);
88  auto cm = note.cmu().get();
89  SaplingMerkleTree tree;
90  tree.append(cm);
91  auto anchor = tree.root();
92  auto witness = tree.witness();
93 
94  Optional<uint256> nf = note.nullifier(fvk, witness.position());
95  BOOST_CHECK(nf != boost::none);
96  uint256 nullifier = nf.get();
97 
98  auto builder = TransactionBuilder(consensusParams);
99  builder.AddSaplingSpend(expsk, note, anchor, witness);
100  builder.AddSaplingOutput(fvk.ovk, pk, 40000000, {});
101  builder.SetFee(10000000);
102  auto tx = builder.Build().GetTxOrThrow();
103 
104  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
105 
106  BOOST_CHECK_EQUAL(0, wtx.mapSaplingNoteData.size());
107  mapSaplingNoteData_t noteData;
108 
109  SaplingOutPoint op {wtx.GetHash(), 0};
110  SaplingNoteData nd;
111  nd.nullifier = nullifier;
112  nd.ivk = ivk;
113  nd.witnesses.push_front(witness);
114  nd.witnessHeight = 123;
115  noteData.insert(std::make_pair(op, nd));
116 
117  wtx.SetSaplingNoteData(noteData);
118  BOOST_CHECK(noteData == wtx.mapSaplingNoteData);
119 
120  // Test individual fields in case equality operator is defined/changed.
121  BOOST_CHECK(wtx.mapSaplingNoteData[op].IsMyNote());
122  BOOST_CHECK(ivk == *(wtx.mapSaplingNoteData[op].ivk));
123  BOOST_CHECK(nullifier == wtx.mapSaplingNoteData[op].nullifier);
124  BOOST_CHECK(nd.witnessHeight == wtx.mapSaplingNoteData[op].witnessHeight);
125  BOOST_CHECK(witness == wtx.mapSaplingNoteData[op].witnesses.front());
126 }
127 
128 // Cannot add note data for an index which does not exist in tx.vShieldedOutput
129 BOOST_AUTO_TEST_CASE(SetInvalidSaplingNoteDataInCWalletTx) {
130  CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
131  BOOST_CHECK_EQUAL(0, wtx.mapSaplingNoteData.size());
132 
133  mapSaplingNoteData_t noteData;
134  SaplingOutPoint op {uint256(), 1};
135  SaplingNoteData nd;
136  noteData.insert(std::make_pair(op, nd));
137 
138  BOOST_CHECK_THROW(wtx.SetSaplingNoteData(noteData), std::logic_error);
139 }
140 
141 BOOST_AUTO_TEST_CASE(FindMySaplingNotes)
142 {
143  auto consensusParams = Params().GetConsensus();
144 
145  CWallet& wallet = m_wallet;
146  LOCK(wallet.cs_wallet);
147  wallet.SetupSPKM(false);
148 
149  // Generate dummy Sapling address
151  auto expsk = sk.expsk;
152  auto extfvk = sk.ToXFVK();
153  auto pa = sk.DefaultAddress();
154 
155  auto testNote = GetTestSaplingNote(pa, 50000000);
156 
157  // Generate transaction
158  auto builder = TransactionBuilder(consensusParams);
159  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
160  builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000000, {});
161  builder.SetFee(10000000);
162  auto tx = builder.Build().GetTxOrThrow();
163 
164  // No Sapling notes can be found in tx which does not belong to the wallet
165  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
166  BOOST_CHECK(!wallet.HaveSaplingSpendingKey(extfvk));
167  auto noteMap = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
168  BOOST_CHECK_EQUAL(0, noteMap.size());
169 
170  // Add spending key to wallet, so Sapling notes can be found
171  BOOST_CHECK(wallet.AddSaplingZKey(sk));
172  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
173  noteMap = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
174  BOOST_CHECK_EQUAL(2, noteMap.size());
175 }
176 
177 // Generate note A and spend to create note B, from which we spend to create two conflicting transactions
178 BOOST_AUTO_TEST_CASE(GetConflictedSaplingNotes)
179 {
180  auto consensusParams = Params().GetConsensus();
181 
182  CWallet& wallet = m_wallet;
183  LOCK2(cs_main, wallet.cs_wallet);
184  setupWallet(wallet);
185 
186  // Generate Sapling address
188  auto expsk = sk.expsk;
189  auto extfvk = sk.ToXFVK();
190  auto ivk = extfvk.fvk.in_viewing_key();
191  auto pk = sk.DefaultAddress();
192 
193  BOOST_CHECK(wallet.AddSaplingZKey(sk));
194  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
195 
196  // Generate note A
197  libzcash::SaplingNote note(pk, 50000000);
198  auto cm = note.cmu().get();
199  SaplingMerkleTree saplingTree;
200  saplingTree.append(cm);
201  auto anchor = saplingTree.root();
202  auto witness = saplingTree.witness();
203 
204  // Generate tx to create output note B
205  auto builder = TransactionBuilder(consensusParams);
206  builder.AddSaplingSpend(expsk, note, anchor, witness);
207  builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000000, {});
208  builder.SetFee(10000000);
209  auto tx = builder.Build().GetTxOrThrow();
210  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
211 
212  // Fake-mine the transaction
214  CBlock block;
215  block.hashPrevBlock = chainActive[0]->GetBlockHash();
216  block.vtx.emplace_back(wtx.tx);
217  block.hashMerkleRoot = BlockMerkleRoot(block);
218  const auto& blockHash = block.GetHash();
219  CBlockIndex fakeIndex {block};
220  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
221  fakeIndex.phashBlock = &((*mi).first);
222  chainActive.SetTip(&fakeIndex);
223  BOOST_CHECK(chainActive.Contains(&fakeIndex));
225 
226  // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
227  auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
228  BOOST_CHECK(saplingNoteData.size() > 0);
229  wtx.SetSaplingNoteData(saplingNoteData);
230  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
231  BOOST_CHECK(wallet.LoadToWallet(wtx));
232 
233  // Simulate receiving new block and ChainTip signal
234  wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
236 
237  // Retrieve the updated wtx from wallet
238  const uint256& hash = wtx.GetHash();
239  wtx = wallet.mapWallet.at(hash);
240 
241  // Decrypt output note B
243  wtx.tx->sapData->vShieldedOutput[0].encCiphertext,
244  ivk,
245  wtx.tx->sapData->vShieldedOutput[0].ephemeralKey,
246  wtx.tx->sapData->vShieldedOutput[0].cmu);
247  BOOST_CHECK(static_cast<bool>(maybe_pt) == true);
248  auto maybe_note = maybe_pt.get().note(ivk);
249  BOOST_CHECK(static_cast<bool>(maybe_note) == true);
250  auto note2 = maybe_note.get();
251 
252  SaplingOutPoint sop0(wtx.GetHash(), 0);
253  auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front();
254  auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position());
255  BOOST_CHECK(static_cast<bool>(maybe_nf) == true);
256 
257  anchor = saplingTree.root();
258 
259  // Create transaction to spend note B
260  auto builder2 = TransactionBuilder(consensusParams);
261  builder2.SetFee(10000000);
262  builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
263  builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 20000000, {});
264  auto tx2 = builder2.Build().GetTxOrThrow();
265 
266  // Create conflicting transaction which also spends note B
267  auto builder3 = TransactionBuilder(consensusParams);
268  builder3.SetFee(10000000);
269  builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
270  builder3.AddSaplingOutput(extfvk.fvk.ovk, pk, 19999000, {});
271  auto tx3 = builder3.Build().GetTxOrThrow();
272 
273  CWalletTx wtx2 {&wallet, MakeTransactionRef(tx2)};
274  CWalletTx wtx3 {&wallet, MakeTransactionRef(tx3)};
275 
276  const auto& hash2 = wtx2.GetHash();
277  const auto& hash3 = wtx3.GetHash();
278 
279  // No conflicts for no spends (wtx is currently the only transaction in the wallet)
280  BOOST_CHECK_EQUAL(0, wallet.GetConflicts(hash2).size());
281  BOOST_CHECK_EQUAL(0, wallet.GetConflicts(hash3).size());
282 
283  // No conflicts for one spend
284  BOOST_CHECK(wallet.LoadToWallet(wtx2));
285  BOOST_CHECK_EQUAL(0, wallet.GetConflicts(hash2).size());
286 
287  // Conflicts for two spends
288  BOOST_CHECK(wallet.LoadToWallet(wtx3));
289  auto c3 = wallet.GetConflicts(hash2);
290  BOOST_CHECK_EQUAL(2, c3.size());
291  BOOST_CHECK(std::set<uint256>({hash2, hash3}) == c3);
292 
293  // Tear down
294  chainActive.SetTip(nullptr);
295  mapBlockIndex.erase(blockHash);
296 }
297 
298 BOOST_AUTO_TEST_CASE(SaplingNullifierIsSpent)
299 {
300  auto consensusParams = Params().GetConsensus();
301 
302  CWallet& wallet = m_wallet;
303  LOCK2(cs_main, wallet.cs_wallet);
304  setupWallet(wallet);
305 
306  // Generate dummy Sapling address
308  auto expsk = sk.expsk;
309  auto extfvk = sk.ToXFVK();
310  auto pa = sk.DefaultAddress();
311 
312  auto testNote = GetTestSaplingNote(pa, 50000000);
313 
314  // Generate transaction
315  auto builder = TransactionBuilder(consensusParams);
316  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
317  builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 2500000, {});
318  builder.SetFee(10000000);
319  auto tx = builder.Build().GetTxOrThrow();
320 
321  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
322  BOOST_CHECK(wallet.AddSaplingZKey(sk));
323  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
324 
325  // Manually compute the nullifier based on the known position
326  auto nf = testNote.note.nullifier(extfvk.fvk, testNote.tree.witness().position());
327  BOOST_CHECK(nf);
328  uint256 nullifier = nf.get();
329 
330  // Verify note has not been spent
332 
333  // Fake-mine the transaction
335  CBlock block;
336  block.hashPrevBlock = chainActive[0]->GetBlockHash();
337  block.vtx.emplace_back(wtx.tx);
338  block.hashMerkleRoot = BlockMerkleRoot(block);
339  const auto& blockHash = block.GetHash();
340  CBlockIndex fakeIndex {block};
341  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
342  fakeIndex.phashBlock = &((*mi).first);
343  chainActive.SetTip(&fakeIndex);
344  BOOST_CHECK(chainActive.Contains(&fakeIndex));
346 
347  wallet.BlockConnected(std::make_shared<CBlock>(block), mi->second);
348 
349  // Verify note has been spent
351 
352  // Tear down
353  chainActive.SetTip(nullptr);
354  mapBlockIndex.erase(blockHash);
355 }
356 
357 BOOST_AUTO_TEST_CASE(NavigateFromSaplingNullifierToNote)
358 {
359  auto consensusParams = Params().GetConsensus();
360 
361  CWallet& wallet = m_wallet;
362  LOCK2(cs_main, wallet.cs_wallet);
363  setupWallet(wallet);
364 
365  // Generate dummy Sapling address
367  auto expsk = sk.expsk;
368  auto extfvk = sk.ToXFVK();
369  auto pa = sk.DefaultAddress();
370 
371  auto testNote = GetTestSaplingNote(pa, 50000000);
372 
373  // Generate transaction
374  auto builder = TransactionBuilder(consensusParams);
375  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
376  builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000000, {});
377  builder.SetFee(10000000);
378  auto tx = builder.Build().GetTxOrThrow();
379 
380  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
381  BOOST_CHECK(wallet.AddSaplingZKey(sk));
382  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
383 
384  // Manually compute the nullifier based on the expected position
385  auto nf = testNote.note.nullifier(extfvk.fvk, testNote.tree.witness().position());
386  BOOST_CHECK(nf);
387  uint256 nullifier = nf.get();
388 
389  // Verify dummy note is unspent
391 
392  // Fake-mine the transaction
394  CBlock block;
395  block.hashPrevBlock = chainActive[0]->GetBlockHash();
396  block.vtx.emplace_back(wtx.tx);
397  block.hashMerkleRoot = BlockMerkleRoot(block);
398  const auto& blockHash = block.GetHash();
399  CBlockIndex fakeIndex {block};
400  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
401  fakeIndex.phashBlock = &((*mi).first);
402  chainActive.SetTip(&fakeIndex);
403  BOOST_CHECK(chainActive.Contains(&fakeIndex));
405 
406  // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
407  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
408  auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
409  BOOST_CHECK(saplingNoteData.size() > 0);
410  wtx.SetSaplingNoteData(saplingNoteData);
411  wallet.LoadToWallet(wtx);
412 
413  // Verify dummy note is now spent, as AddToWallet invokes AddToSpends()
414  wallet.SetLastBlockProcessed(mi->second);
416 
417  // Test invariant: no witnesses means no nullifier.
419  for (mapSaplingNoteData_t::value_type &item : wtx.mapSaplingNoteData) {
420  SaplingNoteData nd = item.second;
421  BOOST_CHECK(nd.witnesses.empty());
422  BOOST_CHECK(!nd.nullifier);
423  }
424 
425  // Simulate receiving new block and ChainTip signal
426  wallet.IncrementNoteWitnesses(&fakeIndex, &block, testNote.tree);
428 
429  // Retrieve the updated wtx from wallet
430  const uint256& hash = wtx.GetHash();
431  wtx = wallet.mapWallet.at(hash);
432 
433  // Verify Sapling nullifiers map to SaplingOutPoints
435  for (mapSaplingNoteData_t::value_type &item : wtx.mapSaplingNoteData) {
436  SaplingOutPoint op = item.first;
437  SaplingNoteData nd = item.second;
438  BOOST_CHECK(hash == op.hash);
439  BOOST_CHECK_EQUAL(1, nd.witnesses.size());
441  auto nf = nd.nullifier.get();
445  }
446 
447  // Tear down
448  chainActive.SetTip(nullptr);
449  mapBlockIndex.erase(blockHash);
450 }
451 
452 // Create note A, spend A to create note B, spend and verify note B is from me.
453 BOOST_AUTO_TEST_CASE(SpentSaplingNoteIsFromMe)
454 {
455  auto consensusParams = Params().GetConsensus();
456 
457  CWallet& wallet = m_wallet;
458  LOCK2(cs_main, wallet.cs_wallet);
459  setupWallet(wallet);
460 
461  // Generate Sapling address
463  auto expsk = sk.expsk;
464  auto extfvk = sk.ToXFVK();
465  auto ivk = extfvk.fvk.in_viewing_key();
466  auto pk = sk.DefaultAddress();
467 
468  // Generate Sapling note A
469  libzcash::SaplingNote note(pk, 50000000);
470  auto cm = note.cmu().get();
471  SaplingMerkleTree saplingTree;
472  saplingTree.append(cm);
473  auto anchor = saplingTree.root();
474  auto witness = saplingTree.witness();
475 
476  // Generate transaction, which sends funds to note B
477  auto builder = TransactionBuilder(consensusParams);
478  builder.AddSaplingSpend(expsk, note, anchor, witness);
479  builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000000, {});
480  builder.SetFee(10000000);
481  auto tx = builder.Build().GetTxOrThrow();
482 
483  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
484  BOOST_CHECK(wallet.AddSaplingZKey(sk));
485  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
486 
487  // Fake-mine the transaction
489  CBlock block;
490  block.hashPrevBlock = chainActive[0]->GetBlockHash();
491  block.vtx.emplace_back(wtx.tx);
492  block.hashMerkleRoot = BlockMerkleRoot(block);
493  const auto& blockHash = block.GetHash();
494  CBlockIndex fakeIndex {block};
495  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
496  fakeIndex.phashBlock = &((*mi).first);
497  chainActive.SetTip(&fakeIndex);
498  BOOST_CHECK(chainActive.Contains(&fakeIndex));
500 
501  auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
502  BOOST_CHECK(saplingNoteData.size() > 0);
503  wtx.SetSaplingNoteData(saplingNoteData);
504  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
505  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  wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
512  wallet.SetLastBlockProcessed(mi->second);
513 
514  // Retrieve the updated wtx from wallet
515  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  BOOST_CHECK(!wallet.IsFromMe(wtx.tx));
521 
522  // Manually compute the nullifier and check map entry does not exist
523  auto nf = note.nullifier(extfvk.fvk, witness.position());
524  BOOST_CHECK(nf);
526 
527  // Decrypt note B
529  wtx.tx->sapData->vShieldedOutput[0].encCiphertext,
530  ivk,
531  wtx.tx->sapData->vShieldedOutput[0].ephemeralKey,
532  wtx.tx->sapData->vShieldedOutput[0].cmu);
533  BOOST_CHECK_EQUAL(static_cast<bool>(maybe_pt), true);
534  auto maybe_note = maybe_pt.get().note(ivk);
535  BOOST_CHECK_EQUAL(static_cast<bool>(maybe_note), true);
536  auto note2 = maybe_note.get();
537 
538  // Get witness to retrieve position of note B we want to spend
539  SaplingOutPoint sop0(wtx.GetHash(), 0);
540  auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front();
541  auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position());
542  BOOST_CHECK_EQUAL(static_cast<bool>(maybe_nf), true);
543  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  anchor = saplingTree.root();
548 
549  // Create transaction to spend note B
550  auto builder2 = TransactionBuilder(consensusParams);
551  builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
552  builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500000, {});
553  builder2.SetFee(10000000);
554  auto tx2 = builder2.Build().GetTxOrThrow();
555  BOOST_CHECK_EQUAL(tx2.vin.size(), 0);
556  BOOST_CHECK_EQUAL(tx2.vout.size(), 0);
557  BOOST_CHECK_EQUAL(tx2.sapData->vShieldedSpend.size(), 1);
558  BOOST_CHECK_EQUAL(tx2.sapData->vShieldedOutput.size(), 2);
559  BOOST_CHECK_EQUAL(tx2.sapData->valueBalance, 10000000);
560 
561  CWalletTx wtx2 {&wallet, MakeTransactionRef(tx2)};
562 
563  // Fake-mine this tx into the next block
565  CBlock block2;
566  block2.vtx.emplace_back(wtx2.tx);
567  block.hashMerkleRoot = BlockMerkleRoot(block);
568  block2.hashPrevBlock = blockHash;
569  auto blockHash2 = block2.GetHash();
570  CBlockIndex fakeIndex2 {block2};
571  BlockMap::iterator mi2 = mapBlockIndex.emplace(blockHash2, &fakeIndex2).first;
572  fakeIndex2.phashBlock = &((*mi2).first);
573  fakeIndex2.nHeight = 1;
574  chainActive.SetTip(&fakeIndex2);
575  BOOST_CHECK(chainActive.Contains(&fakeIndex2));
577 
578  auto saplingNoteData2 = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx2.tx).first;
579  BOOST_CHECK(saplingNoteData2.size() > 0);
580  wtx2.SetSaplingNoteData(saplingNoteData2);
581  wtx2.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex2.nHeight, block2.GetHash(), 0);
582  wallet.LoadToWallet(wtx2);
583  wallet.SetLastBlockProcessed(mi2->second);
584 
585  // Verify note B is spent. AddToWallet invokes AddToSpends which updates mapTxSaplingNullifiers
587 
588  // Verify note B belongs to wallet.
589  BOOST_CHECK(wallet.IsFromMe(wtx2.tx));
591 
592  // Tear down
593  chainActive.SetTip(nullptr);
594  mapBlockIndex.erase(blockHash);
595  mapBlockIndex.erase(blockHash2);
596 }
597 
598 BOOST_AUTO_TEST_CASE(CachedWitnessesEmptyChain)
599 {
600  auto consensusParams = Params().GetConsensus();
601 
602  CWallet& wallet = m_wallet;
603  {
604  LOCK(wallet.cs_wallet);
605  setupWallet(wallet);
606  }
607 
609  CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(), wallet, sk, 10, true);
610 
611  std::vector<SaplingOutPoint> saplingNotes = SetSaplingNoteData(wtx);
612  std::vector<Optional<SaplingWitness>> saplingWitnesses;
613 
614  ::GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
615 
616  BOOST_CHECK(!(bool) saplingWitnesses[0]);
617 
618  wallet.LoadToWallet(wtx);
619 
620  ::GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
621 
622  BOOST_CHECK(!(bool) saplingWitnesses[0]);
623 
624  CBlock block;
625  block.vtx.emplace_back(wtx.tx);
626  CBlockIndex index(block);
627  SaplingMerkleTree saplingTree;
628  wallet.IncrementNoteWitnesses(&index, &block, saplingTree);
629 
630  ::GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
631 
632  BOOST_CHECK((bool) saplingWitnesses[0]);
633 
634  // Until zcash#1302 is implemented, this should trigger an assertion
635  BOOST_CHECK_THROW(wallet.DecrementNoteWitnesses(&index), std::runtime_error);
636 }
637 
638 BOOST_AUTO_TEST_CASE(CachedWitnessesChainTip)
639 {
640  auto consensusParams = Params().GetConsensus();
641 
643  CWallet& wallet = m_wallet;
644  {
645  LOCK(wallet.cs_wallet);
646  setupWallet(wallet);
647  BOOST_CHECK(wallet.AddSaplingZKey(sk));
649  }
650 
651  uint256 anchors1;
652  CBlock block1;
653  SaplingMerkleTree saplingTree;
654 
655  {
656  // First block (case tested in _empty_chain)
657  CBlockIndex index1(block1);
658  index1.nHeight = 1;
659  auto output = CreateValidBlock(wallet, sk, index1, block1, saplingTree);
660 
661  // Called to fetch anchor
662  std::vector<SaplingOutPoint> saplingNotes {output};
663  std::vector<Optional<SaplingWitness>> saplingWitnesses;
664 
665  anchors1 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
666  }
667 
668  {
669  // Second transaction
670  CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
671  wallet, sk, 50, true);
672 
673  std::vector<SaplingOutPoint> saplingNotes = SetSaplingNoteData(wtx);
674  wallet.LoadToWallet(wtx);
675 
676  std::vector<Optional<SaplingWitness>> saplingWitnesses;
677  GetWitnessesAndAnchors(wallet , saplingNotes, saplingWitnesses);
678 
679  BOOST_CHECK(!(bool) saplingWitnesses[0]);
680 
681  // Second block
682  CBlock block2;
683  block2.hashPrevBlock = block1.GetHash();
684  block2.vtx.emplace_back(wtx.tx);
685  CBlockIndex index2(block2);
686  index2.nHeight = 2;
687  SaplingMerkleTree saplingTree2 {saplingTree};
688  wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree2);
689 
690  auto anchors2 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
691  BOOST_CHECK((bool) saplingWitnesses[0]);
692  BOOST_CHECK(anchors1 != anchors2);
693 
694  // Decrementing should give us the previous anchor
695  wallet.DecrementNoteWitnesses(&index2);
696  auto anchors3 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
697 
698  BOOST_CHECK(!(bool) saplingWitnesses[0]);
699  // Should not equal first anchor because none of these notes had witnesses
700  BOOST_CHECK(anchors1 != anchors3);
701 
702  // Re-incrementing with the same block should give the same result
703  wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree);
704  auto anchors4 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
705  BOOST_CHECK((bool) saplingWitnesses[0]);
706  BOOST_CHECK(anchors2 == anchors4);
707 
708  // Incrementing with the same block again should not change the cache
709  wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree);
710  std::vector<Optional<SaplingWitness>> saplingWitnesses5;
711 
712  auto anchors5 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses5);
713  BOOST_CHECK(saplingWitnesses == saplingWitnesses5);
714  BOOST_CHECK(anchors4 == anchors5);
715  }
716 }
717 
718 BOOST_AUTO_TEST_CASE(CachedWitnessesDecrementFirst)
719 {
720  auto consensusParams = Params().GetConsensus();
722  CWallet& wallet = m_wallet;
723  {
724  LOCK(wallet.cs_wallet);
725  setupWallet(wallet);
726  BOOST_CHECK(wallet.AddSaplingZKey(sk));
727  }
728 
729  SaplingMerkleTree saplingTree;
730  {
731  // First block (case tested in _empty_chain)
732  CBlock block1;
733  CBlockIndex index1(block1);
734  index1.nHeight = 1;
735  CreateValidBlock(wallet, sk, index1, block1, saplingTree);
736  }
737 
738  uint256 anchors2;
739  CBlock block2;
740  CBlockIndex index2(block2);
741 
742  {
743  // Second block (case tested in _chain_tip)
744  index2.nHeight = 2;
745  auto output = CreateValidBlock(wallet, sk, index2, block2, saplingTree);
746 
747  // Called to fetch anchor
748  std::vector<SaplingOutPoint> saplingNotes {output};
749  std::vector<Optional<SaplingWitness>> saplingWitnesses;
750  anchors2 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
751  }
752 
753  {
754  // Third transaction - never mined
755  CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
756  wallet, sk, 20, true);
757  std::vector<SaplingOutPoint> saplingNotes = SetSaplingNoteData(wtx);
758  wallet.LoadToWallet(wtx);
759 
760  std::vector<Optional<SaplingWitness>> saplingWitnesses;
761 
762  auto anchors3 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
763 
764  BOOST_CHECK(!(bool) saplingWitnesses[0]);
765 
766  // Decrementing (before the transaction has ever seen an increment)
767  // should give us the previous anchor
768  wallet.DecrementNoteWitnesses(&index2);
769 
770  auto anchors4 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
771 
772  BOOST_CHECK(!(bool) saplingWitnesses[0]);
773  // Should not equal second anchor because none of these notes had witnesses
774  BOOST_CHECK(anchors2 != anchors4);
775 
776  // Re-incrementing with the same block should give the same result
777  wallet.IncrementNoteWitnesses(&index2, &block2, saplingTree);
778 
779  auto anchors5 = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
780 
781  BOOST_CHECK(!(bool) saplingWitnesses[0]);
782  BOOST_CHECK(anchors3 == anchors5);
783  }
784 }
785 
786 BOOST_AUTO_TEST_CASE(CachedWitnessesCleanIndex)
787 {
788  auto consensusParams = Params().GetConsensus();
789 
791  CWallet& wallet = m_wallet;
792  {
793  LOCK(wallet.cs_wallet);
794  setupWallet(wallet);
795  BOOST_CHECK(wallet.AddSaplingZKey(sk));
796  }
797 
798  std::vector<CBlock> blocks;
799  std::vector<CBlockIndex> indices;
800  std::vector<SaplingOutPoint> saplingNotes;
801  std::vector<uint256> saplingAnchors;
802  SaplingMerkleTree saplingTree;
803  SaplingMerkleTree saplingRiTree = saplingTree;
804  std::vector<Optional<SaplingWitness>> saplingWitnesses;
805 
806  // Generate a chain
807  size_t numBlocks = WITNESS_CACHE_SIZE + 10;
808  blocks.resize(numBlocks);
809  indices.resize(numBlocks);
810  for (size_t i = 0; i < numBlocks; i++) {
811  indices[i].nHeight = i;
812  auto oldSaplingRoot = saplingTree.root();
813  auto outpts = CreateValidBlock(wallet, sk, indices[i], blocks[i], saplingTree);
814  BOOST_CHECK(oldSaplingRoot != saplingTree.root());
815  saplingNotes.push_back(outpts);
816 
817  auto anchor = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
818  for (size_t j = 0; j <= i; j++) {
819  BOOST_CHECK((bool) saplingWitnesses[j]);
820  }
821  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  for (size_t i = 0; i < numBlocks; i++) {
827  SaplingMerkleTree saplingRiPrevTree {saplingRiTree};
828  wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), saplingRiTree);
829 
830  auto anchors = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
831  for (size_t j = 0; j < numBlocks; j++) {
832  BOOST_CHECK((bool) saplingWitnesses[j]);
833  }
834  // Should equal final anchor because witness cache unaffected
835  BOOST_CHECK(saplingAnchors.back() == anchors);
836 
837  if ((i == 5) || (i == 50)) {
838  // Pretend a reorg happened that was recorded in the block files
839  {
840  wallet.DecrementNoteWitnesses(&(indices[i]));
841 
842  auto anchors = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
843  for (size_t j = 0; j < numBlocks; j++) {
844  BOOST_CHECK((bool) saplingWitnesses[j]);
845  }
846  // Should equal final anchor because witness cache unaffected
847  BOOST_CHECK(saplingAnchors.back() == anchors);
848  }
849 
850  {
851  wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), saplingRiPrevTree);
852  auto anchors = GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
853  for (size_t j = 0; j < numBlocks; j++) {
854  BOOST_CHECK((bool) saplingWitnesses[j]);
855  }
856  // Should equal final anchor because witness cache unaffected
857  BOOST_CHECK(saplingAnchors.back() == anchors);
858  }
859  }
860  }
861 }
862 
863 BOOST_AUTO_TEST_CASE(ClearNoteWitnessCache)
864 {
865  auto consensusParams = Params().GetConsensus();
866 
868  CWallet& wallet = m_wallet;
869  {
870  LOCK(wallet.cs_wallet);
871  setupWallet(wallet);
872  BOOST_CHECK(wallet.AddSaplingZKey(sk));
873  }
874 
875  CWalletTx wtx = GetValidSaplingReceive(Params().GetConsensus(),
876  wallet, sk, 10, true);
877  auto hash = wtx.GetHash();
878  auto saplingNotes = SetSaplingNoteData(wtx);
879 
880  // Pretend we mined the tx by adding a fake witness
881  SaplingMerkleTree saplingTree;
882  wtx.mapSaplingNoteData[saplingNotes[0]].witnesses.push_front(saplingTree.witness());
883  wtx.mapSaplingNoteData[saplingNotes[0]].witnessHeight = 1;
885 
886  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  saplingNotes.emplace_back(wtx.GetHash(), 1);
892  BOOST_CHECK_EQUAL(saplingNotes.size(), 2);
893 
894  std::vector<Optional<SaplingWitness>> saplingWitnesses;
895 
896  // Before clearing, we should have a witness for one note
897  GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
898  BOOST_CHECK((bool) saplingWitnesses[0]);
899  BOOST_CHECK(!(bool) saplingWitnesses[1]);
900  BOOST_CHECK_EQUAL(1, wallet.mapWallet.at(hash).mapSaplingNoteData[saplingNotes[0]].witnessHeight);
902 
903  // After clearing, we should not have a witness for either note
905  GetWitnessesAndAnchors(wallet, saplingNotes, saplingWitnesses);
906  BOOST_CHECK(!(bool) saplingWitnesses[0]);
907  BOOST_CHECK(!(bool) saplingWitnesses[1]);
908  BOOST_CHECK_EQUAL(-1, wallet.mapWallet.at(hash).mapSaplingNoteData[saplingNotes[0]].witnessHeight);
910 }
911 
912 BOOST_AUTO_TEST_CASE(UpdatedSaplingNoteData)
913 {
914  auto consensusParams = Params().GetConsensus();
915 
916  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  LOCK2(cs_main, wallet.cs_wallet);
919  setupWallet(wallet);
920 
922 
923  // Generate dummy Sapling address
924  auto sk = m.Derive(0);
925  auto expsk = sk.expsk;
926  auto extfvk = sk.ToXFVK();
927  auto pa = sk.DefaultAddress();
928 
929  // Generate dummy recipient Sapling address
930  auto sk2 = m.Derive(1);
931  auto extfvk2 = sk2.ToXFVK();
932  auto pa2 = sk2.DefaultAddress();
933 
934  auto testNote = GetTestSaplingNote(pa, 50000000);
935 
936  // Generate transaction
937  auto builder = TransactionBuilder(consensusParams);
938  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
939  builder.AddSaplingOutput(extfvk.fvk.ovk, pa2, 25000000, {});
940  builder.SetFee(10000000);
941  auto tx = builder.Build().GetTxOrThrow();
942 
943  // Wallet contains extfvk1 but not extfvk2
944  CWalletTx wtx {&wallet, MakeTransactionRef(tx)};
945  BOOST_CHECK(wallet.AddSaplingZKey(sk));
946  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
947  BOOST_CHECK(!wallet.HaveSaplingSpendingKey(extfvk2));
948 
949  // Fake-mine the transaction
951  CBlock block;
952  block.vtx.emplace_back(wtx.tx);
953  block.hashMerkleRoot = BlockMerkleRoot(block);
954  const auto& blockHash = block.GetHash();
955  CBlockIndex fakeIndex {block};
956  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
957  fakeIndex.phashBlock = &((*mi).first);
958  chainActive.SetTip(&fakeIndex);
959  BOOST_CHECK(chainActive.Contains(&fakeIndex));
961 
962  // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
963  auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
964  BOOST_CHECK(saplingNoteData.size() == 1); // wallet only has key for change output
965  wtx.SetSaplingNoteData(saplingNoteData);
966  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
967  wallet.LoadToWallet(wtx);
968 
969  // Simulate receiving new block and ChainTip signal
970  wallet.IncrementNoteWitnesses(&fakeIndex, &block, testNote.tree);
972 
973  // Retrieve the updated wtx from wallet
974  const uint256& hash = wtx.GetHash();
975  wtx = wallet.mapWallet.at(hash);
976 
977  // Now lets add key extfvk2 so wallet can find the payment note sent to pa2
978  BOOST_CHECK(wallet.AddSaplingZKey(sk2));
979  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk2));
980  CWalletTx wtx2 = wtx;
981  auto saplingNoteData2 = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx2.tx).first;
982  BOOST_CHECK(saplingNoteData2.size() == 2);
983  wtx2.SetSaplingNoteData(saplingNoteData2);
984 
985  // The payment note has not been witnessed yet, so let's fake the witness.
986  SaplingOutPoint sop0(wtx2.GetHash(), 0);
987  SaplingOutPoint sop1(wtx2.GetHash(), 1);
988  wtx2.mapSaplingNoteData[sop0].witnesses.push_front(testNote.tree.witness());
989  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  BOOST_CHECK(wtx.mapSaplingNoteData != wtx2.mapSaplingNoteData);
994  BOOST_CHECK_EQUAL(1, wtx.mapSaplingNoteData.size());
995  BOOST_CHECK_EQUAL(1, wtx.mapSaplingNoteData[sop1].witnesses.size()); // wtx has witness for change
996 
997  BOOST_CHECK_EQUAL(2, wtx2.mapSaplingNoteData.size());
998  BOOST_CHECK_EQUAL(1, wtx2.mapSaplingNoteData[sop0].witnesses.size()); // wtx2 has fake witness for payment output
999  BOOST_CHECK_EQUAL(0, wtx2.mapSaplingNoteData[sop1].witnesses.size()); // wtx2 never had incrementnotewitness called
1000 
1001  // After updating, they should be the same
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  BOOST_CHECK_EQUAL(2, wtx.mapSaplingNoteData.size());
1011  BOOST_CHECK_EQUAL(2, wtx2.mapSaplingNoteData.size());
1012  // wtx copied over the fake witness from wtx2 for the payment output
1013  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  BOOST_CHECK_EQUAL(0, wtx2.mapSaplingNoteData[sop1].witnesses.size());
1016  BOOST_CHECK(wtx.mapSaplingNoteData[sop1].witnesses.front() == testNote.tree.witness());
1017 
1018  // Tear down
1019  chainActive.SetTip(nullptr);
1020  mapBlockIndex.erase(blockHash);
1021 }
1022 
1023 BOOST_AUTO_TEST_CASE(MarkAffectedSaplingTransactionsDirty)
1024 {
1025  auto consensusParams = Params().GetConsensus();
1026 
1027  CWallet& wallet = m_wallet;
1028  LOCK2(cs_main, wallet.cs_wallet);
1029  setupWallet(wallet);
1030 
1031  // Generate Sapling address
1032  auto sk = GetTestMasterSaplingSpendingKey();
1033  auto expsk = sk.expsk;
1034  auto extfvk = sk.ToXFVK();
1035  auto ivk = extfvk.fvk.in_viewing_key();
1036  auto pk = sk.DefaultAddress();
1037 
1038  BOOST_CHECK(wallet.AddSaplingZKey(sk));
1039  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
1040 
1041  // Set up transparent address
1042  CBasicKeyStore keystore;
1043  CKey tsk = AddTestCKeyToKeyStore(keystore);
1044  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  auto builder = TransactionBuilder(consensusParams, &keystore);
1049  builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000000);
1050  builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 40000000, {});
1051  builder.SetFee(10000000);
1052  auto tx1 = builder.Build().GetTxOrThrow();
1053 
1054  BOOST_CHECK_EQUAL(tx1.vin.size(), 1);
1055  BOOST_CHECK_EQUAL(tx1.vout.size(), 0);
1056  BOOST_CHECK_EQUAL(tx1.sapData->vShieldedSpend.size(), 0);
1057  BOOST_CHECK_EQUAL(tx1.sapData->vShieldedOutput.size(), 1);
1058  BOOST_CHECK_EQUAL(tx1.sapData->valueBalance, -40000000);
1059 
1060  CWalletTx wtx {&wallet, MakeTransactionRef(tx1)};
1061 
1062  // Fake-mine the transaction
1064  SaplingMerkleTree saplingTree;
1065  CBlock block;
1066  block.vtx.emplace_back(wtx.tx);
1067  block.hashMerkleRoot = BlockMerkleRoot(block);
1068  const auto& blockHash = block.GetHash();
1069  CBlockIndex fakeIndex {block};
1070  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
1071  fakeIndex.phashBlock = &((*mi).first);
1072  chainActive.SetTip(&fakeIndex);
1073  BOOST_CHECK(chainActive.Contains(&fakeIndex));
1075 
1076  // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
1077  auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
1078  BOOST_CHECK(saplingNoteData.size() > 0);
1079  wtx.SetSaplingNoteData(saplingNoteData);
1080  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
1081  wallet.LoadToWallet(wtx);
1082 
1083  // Simulate receiving new block and ChainTip signal
1084  wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
1086 
1087  // Retrieve the updated wtx from wallet
1088  const uint256& hash = wtx.GetHash();
1089  wtx = wallet.mapWallet.at(hash);
1090 
1091  // Prepare to spend the note that was just created
1093  tx1.sapData->vShieldedOutput[0].encCiphertext, ivk, tx1.sapData->vShieldedOutput[0].ephemeralKey, tx1.sapData->vShieldedOutput[0].cmu);
1094  BOOST_CHECK(static_cast<bool>(maybe_pt));
1095  auto maybe_note = maybe_pt.get().note(ivk);
1096  BOOST_CHECK(static_cast<bool>(maybe_note));
1097  auto note = maybe_note.get();
1098  auto anchor = saplingTree.root();
1099  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  auto builder2 = TransactionBuilder(consensusParams);
1104  builder2.AddSaplingSpend(expsk, note, anchor, witness);
1105  builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000000, {});
1106  builder2.SetFee(10000000);
1107  auto tx2 = builder2.Build().GetTxOrThrow();
1108 
1109  BOOST_CHECK_EQUAL(tx2.vin.size(), 0);
1110  BOOST_CHECK_EQUAL(tx2.vout.size(), 0);
1111  BOOST_CHECK_EQUAL(tx2.sapData->vShieldedSpend.size(), 1);
1112  BOOST_CHECK_EQUAL(tx2.sapData->vShieldedOutput.size(), 2);
1113  BOOST_CHECK_EQUAL(tx2.sapData->valueBalance, 10000000);
1114 
1115  CWalletTx wtx2 {&wallet, MakeTransactionRef(tx2)};
1116 
1117  wallet.MarkAffectedTransactionsDirty(*wtx.tx);
1118 
1119  // After getting a cached value, the first tx should be clean
1120  wallet.mapWallet.at(hash).GetDebit(ISMINE_SPENDABLE);
1121  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  wallet.LoadToWallet(wtx2);
1125  wallet.MarkAffectedTransactionsDirty(*wtx2.tx);
1126  BOOST_CHECK(!wallet.mapWallet.at(hash).IsAmountCached(CWalletTx::AmountType::DEBIT, ISMINE_SPENDABLE));
1127 
1128  // Tear down
1129  chainActive.SetTip(nullptr);
1130  mapBlockIndex.erase(blockHash);
1131 }
1132 
1134 {
1135  auto consensusParams = Params().GetConsensus();
1136 
1137  CWallet& wallet = m_wallet;
1139  uint256 blockHash;
1140  std::vector<SaplingOutPoint> saplingOutpoints;
1141  {
1142  LOCK2(cs_main, wallet.cs_wallet);
1143  setupWallet(wallet);
1144 
1145  // Generate Sapling address
1146  auto sk = GetTestMasterSaplingSpendingKey();
1147  auto extfvk = sk.ToXFVK();
1148  pk = sk.DefaultAddress();
1149 
1150  BOOST_CHECK(wallet.AddSaplingZKey(sk));
1151  BOOST_CHECK(wallet.HaveSaplingSpendingKey(extfvk));
1152 
1153  // Set up transparent address
1154  CBasicKeyStore keystore;
1155  CKey tsk = AddTestCKeyToKeyStore(keystore);
1156  auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
1157 
1158  // Generate shielding tx from transparent to Sapling (five 1 PIV notes)
1159  auto builder = TransactionBuilder(consensusParams, &keystore);
1160  builder.AddTransparentInput(COutPoint(), scriptPubKey, 510000000);
1161  for (int i=0; i<5; i++) builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 100000000, {});
1162  builder.SetFee(10000000);
1163  auto tx1 = builder.Build().GetTxOrThrow();
1164 
1165  CWalletTx wtx {&wallet, MakeTransactionRef(tx1)};
1166 
1167  // Fake-mine the transaction
1169  SaplingMerkleTree saplingTree;
1170  CBlock block;
1171  block.vtx.emplace_back(wtx.tx);
1172  block.hashMerkleRoot = BlockMerkleRoot(block);
1173  blockHash = block.GetHash();
1174  CBlockIndex fakeIndex {block};
1175  BlockMap::iterator mi = mapBlockIndex.emplace(blockHash, &fakeIndex).first;
1176  fakeIndex.phashBlock = &((*mi).first);
1177  chainActive.SetTip(&fakeIndex);
1178  BOOST_CHECK(chainActive.Contains(&fakeIndex));
1180 
1181  // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
1182  auto saplingNoteData = wallet.GetSaplingScriptPubKeyMan()->FindMySaplingNotes(*wtx.tx).first;
1183  BOOST_CHECK(saplingNoteData.size() > 0);
1184  wtx.SetSaplingNoteData(saplingNoteData);
1185  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex.nHeight, block.GetHash(), 0);
1186  wallet.LoadToWallet(wtx);
1187 
1188  // Simulate receiving new block and ChainTip signal
1189  wallet.IncrementNoteWitnesses(&fakeIndex, &block, saplingTree);
1191  wallet.SetLastBlockProcessed(mi->second);
1192 
1193  const uint256& txid = wtx.GetHash();
1194  for (int i=0; i<5; i++) saplingOutpoints.emplace_back(txid, i);
1195  }
1196 
1197  // Check GetFilteredNotes
1198  std::vector<SaplingNoteEntry> entries;
1200  wallet.GetSaplingScriptPubKeyMan()->GetFilteredNotes(entries, address, 0, true, false);
1201  for (int i=0; i<5; i++) {
1202  BOOST_CHECK(entries[i].op == saplingOutpoints[i]);
1203  BOOST_CHECK(entries[i].address == pk);
1204  BOOST_CHECK_EQUAL(entries[i].confirmations, 1);
1205  }
1206 
1207  // Check GetNotes
1208  std::vector<SaplingNoteEntry> entries2;
1209  wallet.GetSaplingScriptPubKeyMan()->GetNotes(saplingOutpoints, entries2);
1210  for (int i=0; i<5; i++) {
1211  BOOST_CHECK(entries2[i].op == entries[i].op);
1212  BOOST_CHECK(entries2[i].address == entries[i].address);
1213  BOOST_CHECK(entries2[i].note.d == entries[i].note.d);
1214  BOOST_CHECK_EQUAL(entries2[i].note.pk_d, entries[i].note.pk_d);
1215  BOOST_CHECK_EQUAL(entries2[i].note.r, entries[i].note.r);
1216  BOOST_CHECK(entries2[i].memo == entries[i].memo);
1217  BOOST_CHECK_EQUAL(entries2[i].confirmations, entries[i].confirmations);
1218  }
1219 
1220  // Tear down
1221  LOCK(cs_main);
1222  chainActive.SetTip(nullptr);
1223  mapBlockIndex.erase(blockHash);
1224 }
1225 
1226 // TODO: Back port WriteWitnessCache & SetBestChainIgnoresTxsWithoutShieldedData test cases.
1227 
const CChainParams & Params()
Return the currently selected parameters.
uint256 hash
Definition: transaction.h:35
uint32_t n
Definition: transaction.h:36
Basic key store, that keeps keys in an address->secret map.
Definition: keystore.h:99
uint256 hashPrevBlock
Definition: block.h:28
uint256 hashMerkleRoot
Definition: block.h:29
uint256 GetHash() const
Definition: block.cpp:15
Definition: block.h:80
std::vector< CTransactionRef > vtx
Definition: block.h:83
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:139
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:151
int Height() const
Return the maximal height in the chain.
Definition: chain.h:450
void SetTip(CBlockIndex *pindex)
Set/initialize a chain with a given tip.
Definition: chain.cpp:14
bool Contains(const CBlockIndex *pindex) const
Efficiently check whether a block is present in this chain.
Definition: chain.h:435
const Consensus::Params & GetConsensus() const
Definition: chainparams.h:72
bool HaveSaplingSpendingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk) const override
An encapsulated private key.
Definition: key.h:30
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:186
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:72
CKeyID GetID() const
Get the KeyID of this public key (hash of its serialization)
Definition: pubkey.h:167
A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,...
Definition: wallet.h:577
std::map< uint256, CWalletTx > mapWallet
Definition: wallet.h:766
void IncrementNoteWitnesses(const CBlockIndex *pindex, const CBlock *pblock, SaplingMerkleTree &saplingTree)
pindex is the new tip being connected.
Definition: wallet.cpp:4720
void DecrementNoteWitnesses(const CBlockIndex *pindex)
pindex is the old tip being disconnected.
Definition: wallet.cpp:4724
RecursiveMutex cs_wallet
Definition: wallet.h:720
bool AddSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key)
Adds Sapling spending key to the store, and saves it to disk.
Definition: wallet.cpp:4728
void SetLastBlockProcessed(const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
Set last block processed height, currently only use in unit test.
Definition: wallet.h:698
bool IsFromMe(const CTransactionRef &tx) const
should probably be renamed to IsRelevantToMe
Definition: wallet.cpp:4625
SaplingScriptPubKeyMan * GetSaplingScriptPubKeyMan() const
Definition: wallet.h:712
A transaction with a bunch of additional info that only the owner cares about.
Definition: wallet.h:325
CTransactionRef tx
Definition: wallet.h:364
void SetSaplingNoteData(mapSaplingNoteData_t &noteData)
Definition: wallet.cpp:4852
mapSaplingNoteData_t mapSaplingNoteData
Definition: wallet.h:336
const uint256 & GetHash() const
Definition: wallet.h:561
Optional< uint256 > nullifier
Cached note nullifier.
std::list< SaplingWitness > witnesses
Optional< libzcash::SaplingIncomingViewingKey > ivk
int witnessHeight
Block height corresponding to the most current witness.
An outpoint - a combination of a transaction hash and an index n into its sapling output description ...
Definition: transaction.h:82
bool UpdatedNoteData(const CWalletTx &wtxIn, CWalletTx &wtx)
Update note data if is needed.
bool IsSaplingSpent(const SaplingOutPoint &op) const
std::map< uint256, SaplingOutPoint > mapSaplingNullifiersToNotes
The reverse mapping of nullifiers to notes.
void GetNotes(const std::vector< SaplingOutPoint > &saplingOutpoints, std::vector< SaplingNoteEntry > &saplingEntriesRet) const
Find notes for the outpoints.
std::pair< mapSaplingNoteData_t, SaplingIncomingViewingKeyMap > FindMySaplingNotes(const CTransaction &tx) const
Finds all output notes in the given tx that have been sent to a SaplingPaymentAddress in this wallet.
void GetSaplingNoteWitnesses(const std::vector< SaplingOutPoint > &notes, std::vector< Optional< SaplingWitness >> &witnesses, uint256 &final_anchor) const
Return all of the witnesses for the input notes.
void GetFilteredNotes(std::vector< SaplingNoteEntry > &saplingEntries, Optional< libzcash::SaplingPaymentAddress > &address, int minDepth=1, bool ignoreSpent=true, bool requireSpendingKey=true, bool ignoreLocked=true) const
Find notes in the wallet filtered by payment address, min depth and ability to spend and if the notes...
void UpdateSaplingNullifierNoteMapForBlock(const CBlock *pblock)
Iterate over transactions in a block and update the cached Sapling nullifiers for transactions which ...
void ClearNoteWitnessCache()
Clear every notesData from every wallet tx and reset the witness cache size.
IncrementalWitness< Depth, Hash > witness() const
Optional< uint256 > cmu() const
Definition: note.cpp:29
Optional< uint256 > nullifier(const SaplingFullViewingKey &vk, const uint64_t position) const
Definition: note.cpp:47
static Optional< SaplingNotePlaintext > decrypt(const SaplingEncCiphertext &ciphertext, const uint256 &ivk, const uint256 &epk, const uint256 &cmu)
Definition: note.cpp:115
Sapling functions.
Definition: address.h:30
256-bit opaque blob.
Definition: uint256.h:138
BOOST_AUTO_TEST_SUITE_END()
std::set< uint256 > GetConflicts(const uint256 &txid) const
Get wallet transactions that conflict with given transaction (spend same outputs)
Definition: wallet.cpp:618
bool SetMinVersion(enum WalletFeature, WalletBatch *batch_in=nullptr, bool fExplicit=false)
signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVe...
Definition: wallet.cpp:580
bool LoadToWallet(CWalletTx &wtxIn)
Definition: wallet.cpp:983
bool SetupSPKM(bool newKeypool=true, bool memOnly=false)
Generates hd wallet //.
Definition: wallet.cpp:133
void BlockConnected(const std::shared_ptr< const CBlock > &pblock, const CBlockIndex *pindex) override
Notifies listeners of a block being connected.
Definition: wallet.cpp:1319
void MarkAffectedTransactionsDirty(const CTransaction &tx)
Definition: wallet.cpp:1408
@ ISMINE_SPENDABLE
Definition: ismine.h:22
@ LOCK
Definition: lockunlock.h:16
uint256 BlockMerkleRoot(const CBlock &block, bool *mutated)
Definition: merkle.cpp:154
RecursiveMutex cs_main
Global state.
Definition: validation.cpp:80
#define BOOST_CHECK_THROW(stmt, excMatch)
Definition: object.cpp:19
#define BOOST_FIXTURE_TEST_SUITE(a, b)
Definition: object.cpp:14
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
boost::optional< T > Optional
Substitute for C++17 std::optional.
Definition: optional.h:12
BOOST_AUTO_TEST_CASE(SetSaplingNoteAddrsInCWalletTx)
void setupWallet(CWallet &wallet)
SaplingOutPoint CreateValidBlock(CWallet &wallet, libzcash::SaplingExtendedSpendingKey &sk, const CBlockIndex &index, CBlock &block, SaplingMerkleTree &saplingTree)
uint256 GetWitnessesAndAnchors(CWallet &wallet, const std::vector< SaplingOutPoint > &saplingNotes, std::vector< Optional< SaplingWitness >> &saplingWitnesses)
std::vector< SaplingOutPoint > SetSaplingNoteData(CWalletTx &wtx)
std::map< SaplingOutPoint, SaplingNoteData > mapSaplingNoteData_t
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a PIVX scriptPubKey for the given CTxDestination.
Definition: standard.cpp:278
SaplingExtendedFullViewingKey ToXFVK() const
Definition: zip32.cpp:150
#define LOCK2(cs1, cs2)
Definition: sync.h:221
CKey AddTestCKeyToKeyStore(CBasicKeyStore &keyStore, bool genNewKey)
Definition: utiltest.cpp:30
TestSaplingNote GetTestSaplingNote(const libzcash::SaplingPaymentAddress &pa, CAmount value)
Generate a dummy SaplingNote and a SaplingMerkleTree with that note's commitment.
Definition: utiltest.cpp:41
CWalletTx GetValidSaplingReceive(const Consensus::Params &consensusParams, CBasicKeyStore &keyStoreFrom, std::vector< TransparentInput > vIn, std::vector< ShieldedDestination > vDest, const CWallet *pwalletIn)
One or many inputs from keyStoreFrom, one or many shielded outputs to pwalletIn (if not nullptr).
Definition: utiltest.cpp:50
libzcash::SaplingExtendedSpendingKey GetTestMasterSaplingSpendingKey()
Definition: utiltest.cpp:16
BlockMap mapBlockIndex
Definition: validation.cpp:82
CChain chainActive
The currently-connected chain of blocks (protected by cs_main).
Definition: validation.cpp:84
@ FEATURE_SAPLING
Definition: wallet.h:118