PIVX Core  5.6.99
P2P Digital Currency
wallet_shielded_balances_tests.cpp
Go to the documentation of this file.
1 // Copyright (c) 2021 The PIVX Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or https://www.opensource.org/licenses/mit-license.php.
4 
6 
7 #include "consensus/merkle.h"
8 #include "primitives/block.h"
9 #include "random.h"
10 #include "sapling/note.h"
11 #include "sapling/noteencryption.h"
13 #include "test/librust/utiltest.h"
14 #include "wallet/wallet.h"
15 
16 #include <boost/filesystem.hpp>
17 #include <boost/test/unit_test.hpp>
18 
19 CAmount fee = COIN; // Hardcoded fee
20 
21 BOOST_FIXTURE_TEST_SUITE(wallet_shielded_balances_tests, WalletTestingSetup)
22 
23 void setupWallet(CWallet& wallet)
24 {
25  wallet.SetMinVersion(FEATURE_SAPLING);
26  wallet.SetupSPKM(false);
27 }
28 
29 // Find and set notes data in the tx + add any missing ivk to the wallet's keystore.
31 {
32  Optional<mapSaplingNoteData_t> saplingNoteData{nullopt};
33  wallet->FindNotesDataAndAddMissingIVKToKeystore(*wtx.tx, saplingNoteData);
34  assert(static_cast<bool>(saplingNoteData));
35  wtx.SetSaplingNoteData(*saplingNoteData);
36  BOOST_CHECK(wallet->AddToWallet(wtx));
37  // Updated tx
38  return wallet->mapWallet.at(wtx.GetHash());
39 }
40 
45  const std::vector<ShieldedDestination>& vDest,
46  CWallet* wallet,
47  const Consensus::Params& consensusParams)
48 {
49 
50  // Dummy wallet, used to generate the dummy transparent input key and sign it in the transaction builder
51  CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
52  dummyWallet.SetMinVersion(FEATURE_SAPLING);
53  dummyWallet.SetupSPKM(false, true);
54  LOCK(dummyWallet.cs_wallet);
55 
56  // Create a transaction shielding balance to 'vDest' and load it to the wallet.
57  CWalletTx wtx = GetValidSaplingReceive(consensusParams, dummyWallet, inputAmount, vDest, wallet);
58 
59  // Updated tx after load it to the wallet
60  CWalletTx& wtxUpdated = SetWalletNotesData(wallet, wtx);
61  // Check tx credit now
62  BOOST_CHECK_EQUAL(wtxUpdated.GetCredit(ISMINE_ALL), inputAmount);
64  return wtxUpdated;
65 }
66 
68  CWallet& wallet, const Consensus::Params& consensusParams,
70 {
71  // Create a transaction shielding balance to 'sendTo' and load it to the wallet.
72  BOOST_CHECK(wallet.GetSaplingExtendedSpendingKey(sendTo, extskOut));
73  std::vector<ShieldedDestination> vDest;
74  vDest.push_back({extskOut, amount});
75  return AddShieldedBalanceToWallet(amount, vDest, &wallet, consensusParams);
76 }
77 
80  const uint256 anchor;
82 };
83 
89 {
90  // Get note
91  SaplingNoteData nd = wtx.mapSaplingNoteData.at(sapPoint);
92  assert(nd.IsMyNote());
93  const auto& ivk = *(nd.ivk);
95  wtx.tx->sapData->vShieldedOutput[sapPoint.n].encCiphertext,
96  ivk,
97  wtx.tx->sapData->vShieldedOutput[sapPoint.n].ephemeralKey,
98  wtx.tx->sapData->vShieldedOutput[sapPoint.n].cmu);
99  assert(static_cast<bool>(maybe_pt));
100  Optional<libzcash::SaplingNotePlaintext> notePlainText = maybe_pt.get();
101  libzcash::SaplingNote note = notePlainText->note(ivk).get();
102 
103  // Append note to the tree
104  auto commitment = note.cmu().get();
105  SaplingMerkleTree tree;
106  tree.append(commitment);
107  auto anchor = tree.root();
108  auto witness = tree.witness();
109 
110  // Update wtx credit chain data
111  // Pretend we mined the tx by adding a fake witness and nullifier to be able to spend it.
112  wtx.mapSaplingNoteData[sapPoint].witnesses.push_front(tree.witness());
113  wtx.mapSaplingNoteData[sapPoint].witnessHeight = 1;
116  return {note, anchor, witness};
117 }
118 
129 BOOST_AUTO_TEST_CASE(GetShieldedSimpleCachedCreditAndDebit)
130 {
131 
135 
136  auto consensusParams = Params().GetConsensus();
137 
138  // Main wallet
139  CWallet &wallet = m_wallet;
140  LOCK2(cs_main, wallet.cs_wallet);
141  setupWallet(wallet);
142 
143  // First generate a shielded address
145  CAmount firstCredit = COIN * 10;
146 
147  // Add shielded balance.
149  CWalletTx& wtxUpdated = AddShieldedBalanceToWallet(pa, firstCredit, wallet, consensusParams, extskOut);
150 
154 
155  // Update transaction and wallet internal state to be able to spend it.
156  SaplingOutPoint sapPoint {wtxUpdated.GetHash(), 0};
157  SaplingSpendValues sapSpendValues = UpdateWalletInternalNotesData(wtxUpdated, sapPoint, wallet);
158 
159  // Debit value
160  CAmount firstDebit = COIN * 5;
161  CAmount firstDebitShieldedChange = firstDebit - fee;
162 
163  // Create the spending transaction
164  auto builder = TransactionBuilder(consensusParams, &wallet);
165  builder.SetFee(fee);
166  builder.AddSaplingSpend(
167  extskOut.expsk,
168  sapSpendValues.note,
169  sapSpendValues.anchor,
170  sapSpendValues.witness);
171 
172  // Send to transparent address
173  builder.AddTransparentOutput(CreateDummyDestinationScript(),
174  firstDebit);
175 
176  CTransaction tx = builder.Build().GetTxOrThrow();
177  // add tx to wallet and update it.
178  wallet.AddToWallet({&wallet, MakeTransactionRef(tx)});
179  CWalletTx& wtxDebit = wallet.mapWallet.at(tx.GetHash());
180  // Update tx notes data (shielded change need it)
181  CWalletTx& wtxDebitUpdated = SetWalletNotesData(&wallet, wtxDebit);
182 
183  // The debit need to be the entire first note value
184  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetDebit(ISMINE_ALL), firstCredit);
186  // The credit should be only the change.
187  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetCredit(ISMINE_ALL), firstDebitShieldedChange);
189 
190  // Checks that the only shielded output of this tx is change.
192  SaplingOutPoint(wtxDebitUpdated.GetHash(), 0), pa));
193 }
194 
196 {
197  HDSeed seed;
199  return m.DefaultAddress();
200 }
201 
203  const SaplingSpendValues& sapSpendValues, libzcash::SaplingPaymentAddress dest,
204  const CAmount& destAmount, const Consensus::Params& consensus)
205 {
206  // Create the spending transaction
207  auto builder = TransactionBuilder(consensus, &wallet);
208  builder.SetFee(fee);
209  builder.AddSaplingSpend(
210  extskOut.expsk,
211  sapSpendValues.note,
212  sapSpendValues.anchor,
213  sapSpendValues.witness);
214 
215  // Send to shielded address
216  builder.AddSaplingOutput(
217  extskOut.expsk.ovk,
218  dest,
219  destAmount,
220  {}
221  );
222 
223  CTransaction tx = builder.Build().GetTxOrThrow();
224  // add tx to wallet and update it.
225  wallet.AddToWallet({&wallet, MakeTransactionRef(tx)});
226  CWalletTx& wtx = wallet.mapWallet.at(tx.GetHash());
227  // Update tx notes data and return the updated wtx.
228  return SetWalletNotesData(&wallet, wtx);
229 }
230 
234 BOOST_AUTO_TEST_CASE(VerifyShieldedToRemoteShieldedCachedBalance)
235 {
236  auto consensusParams = Params().GetConsensus();
237 
238  // Main wallet
239  CWallet &wallet = m_wallet;
240  LOCK2(cs_main, wallet.cs_wallet);
241  setupWallet(wallet);
242 
243  // First generate a shielded address
245  CAmount firstCredit = COIN * 20;
246 
247  // Add shielded balance.
249  CWalletTx& wtxUpdated = AddShieldedBalanceToWallet(pa, firstCredit, wallet, consensusParams, extskOut);
250 
251  // Update transaction and wallet internal state to be able to spend it.
252  SaplingOutPoint sapPoint {wtxUpdated.GetHash(), 0};
253  SaplingSpendValues sapSpendValues = UpdateWalletInternalNotesData(wtxUpdated, sapPoint, wallet);
254 
255  // Remote destination values
257  CAmount destAmount = COIN * 8;
258 
259  // Create the spending transaction and load it to the wallet
260  CWalletTx& wtxDebitUpdated = buildTxAndLoadToWallet(wallet,
261  extskOut,
262  sapSpendValues,
263  destShieldedAddress,
264  destAmount,
265  consensusParams);
266 
267  // Validate results
268  CAmount expectedShieldedChange = firstCredit - destAmount - fee;
269 
270  // The debit need to be the entire first note value
271  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetDebit(ISMINE_ALL), firstCredit);
273  // The credit should be only the change.
274  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetCredit(ISMINE_ALL), expectedShieldedChange);
276  // Plus, change should be same and be cached as well
277  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetShieldedChange(), expectedShieldedChange);
278  BOOST_CHECK(wtxDebitUpdated.fShieldedChangeCached);
279 }
280 
281 struct FakeBlock
282 {
285 };
286 
288 {
289  FakeBlock fakeBlock;
291  fakeBlock.block.vtx.emplace_back(wtx.tx);
292  fakeBlock.block.hashMerkleRoot = BlockMerkleRoot(fakeBlock.block);
293  for (const OutputDescription& out : wtx.tx->sapData->vShieldedOutput) {
294  currentTree.append(out.cmu);
295  }
296  fakeBlock.block.hashFinalSaplingRoot = currentTree.root();
297  fakeBlock.pindex = new CBlockIndex(fakeBlock.block);
298  mapBlockIndex.insert(std::make_pair(fakeBlock.block.GetHash(), fakeBlock.pindex));
299  fakeBlock.pindex->phashBlock = &mapBlockIndex.find(fakeBlock.block.GetHash())->first;
300  chainActive.SetTip(fakeBlock.pindex);
302  WITH_LOCK(wallet.cs_wallet, wallet.SetLastBlockProcessed(fakeBlock.pindex));
303  wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeBlock.pindex->nHeight, fakeBlock.pindex->GetBlockHash(), 0);
304  return fakeBlock;
305 }
306 
314 BOOST_AUTO_TEST_CASE(GetShieldedAvailableCredit)
315 {
316  auto consensusParams = Params().GetConsensus();
317 
318  // Main wallet
319  CWallet &wallet = m_wallet;
320  LOCK2(cs_main, wallet.cs_wallet);
321  setupWallet(wallet);
322 
323  // 1) generate a shielded address and send 20 PIV in two shielded outputs
325  CAmount credit = COIN * 20;
326 
327  // Add two equal shielded outputs.
329  BOOST_CHECK(wallet.GetSaplingExtendedSpendingKey(pa, extskOut));
330 
331  std::vector<ShieldedDestination> vDest;
332  vDest.push_back({extskOut, credit / 2});
333  vDest.push_back({extskOut, credit / 2});
334  CWalletTx& wtxUpdated = AddShieldedBalanceToWallet(credit, vDest, &wallet, consensusParams);
335 
336  // Available credit ISMINE_SPENDABLE must be 0
337  // Available credit ISMINE_SHIELDED_SPENDABLE must be 'credit' and be cached.
339  BOOST_CHECK_EQUAL(wtxUpdated.GetShieldedAvailableCredit(), credit);
341 
342  // 2) Confirm the tx
343  SaplingMerkleTree tree;
344  FakeBlock fakeBlock = SimpleFakeMine(wtxUpdated, tree, wallet);
345  // Simulate receiving a new block and updating the witnesses/nullifiers
346  wallet.IncrementNoteWitnesses(fakeBlock.pindex, &fakeBlock.block, tree);
348  wtxUpdated = wallet.mapWallet.at(wtxUpdated.GetHash());
349 
350  // 3) Now can spend one output and recalculate the shielded credit.
351  std::vector<SaplingNoteEntry> saplingEntries;
353  wallet.GetSaplingScriptPubKeyMan()->GetFilteredNotes(saplingEntries,
354  opPa,
355  0);
356 
357  std::vector<SaplingOutPoint> ops = {saplingEntries[0].op};
358  uint256 anchor;
359  std::vector<Optional<SaplingWitness>> witnesses;
360  wallet.GetSaplingScriptPubKeyMan()->GetSaplingNoteWitnesses(ops, witnesses, anchor);
361  SaplingSpendValues sapSpendValues{saplingEntries[0].note, anchor, *witnesses[0]};
362 
363  // Remote destination values
365  CAmount change = COIN * 1;
366  CAmount destAmount = credit / 2 - fee - change; // one note - fee
367 
368  // Create the spending transaction and load it to the wallet
369  CWalletTx& wtxDebitUpdated = buildTxAndLoadToWallet(wallet,
370  extskOut,
371  sapSpendValues,
372  destShieldedAddress,
373  destAmount,
374  consensusParams);
375 
376  // Check previous credit tx balance being the same and then force a recalculation
377  BOOST_CHECK_EQUAL(wtxUpdated.GetShieldedAvailableCredit(), credit);
378  BOOST_CHECK_EQUAL(wtxUpdated.GetShieldedAvailableCredit(false), credit / 2);
379  BOOST_CHECK_EQUAL(wtxUpdated.GetShieldedChange(), 0);
380 
381  // Now check the debit tx
382  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetDebit(ISMINE_SPENDABLE_SHIELDED), credit / 2);
383  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetShieldedChange(), change);
384  BOOST_CHECK_EQUAL(wtxDebitUpdated.GetCredit(ISMINE_SPENDABLE_SHIELDED), change);
385 }
386 
int64_t CAmount
Amount in PIV (Can be negative)
Definition: amount.h:13
const CChainParams & Params()
Return the currently selected parameters.
uint32_t n
Definition: transaction.h:36
static std::unique_ptr< BerkeleyDatabase > CreateDummy()
Return object for accessing dummy database with no read/write capabilities.
Definition: db.h:132
bool GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymentAddress &addr, libzcash::SaplingExtendedSpendingKey &extskOut) const
Definition: keystore.cpp:240
static const int32_t CURRENT_VERSION
Definition: block.h:26
uint256 hashFinalSaplingRoot
Definition: block.h:34
int32_t nVersion
Definition: block.h:27
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
uint256 GetBlockHash() const
Definition: chain.h:215
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:151
const uint256 * phashBlock
pointer to the hash of the block, if any. memory is owned by this CBlockIndex
Definition: chain.h:142
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
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:244
const uint256 & GetHash() const
Definition: transaction.h:301
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
RecursiveMutex cs_wallet
Definition: wallet.h:720
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
SaplingScriptPubKeyMan * GetSaplingScriptPubKeyMan() const
Definition: wallet.h:712
libzcash::SaplingPaymentAddress GenerateNewSaplingZKey(std::string label="")
Generates new Sapling key.
Definition: wallet.cpp:4710
A transaction with a bunch of additional info that only the owner cares about.
Definition: wallet.h:325
CTransactionRef tx
Definition: wallet.h:364
CAmount GetShieldedChange() const
Definition: wallet.cpp:4939
Confirmation m_confirm
Definition: wallet.h:394
@ CREDIT
Definition: wallet.h:349
@ AVAILABLE_CREDIT
Definition: wallet.h:349
@ DEBIT
Definition: wallet.h:349
void SetSaplingNoteData(mapSaplingNoteData_t &noteData)
Definition: wallet.cpp:4852
bool fShieldedChangeCached
Definition: wallet.h:358
mapSaplingNoteData_t mapSaplingNoteData
Definition: wallet.h:336
CAmount GetShieldedAvailableCredit(bool fUseCache=true) const
Definition: wallet.cpp:4954
const uint256 & GetHash() const
Definition: wallet.h:561
Definition: zip32.h:20
A shielded output to a transaction.
uint256 cmu
The u-coordinate of the note commitment for the output note.
Optional< libzcash::SaplingIncomingViewingKey > ivk
An outpoint - a combination of a transaction hash and an index n into its sapling output description ...
Definition: transaction.h:82
bool IsNoteSaplingChange(const std::set< std::pair< libzcash::PaymentAddress, uint256 >> &nullifierSet, const libzcash::PaymentAddress &address, const SaplingOutPoint &entry) const
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 UpdateSaplingNullifierNoteMapWithTx(CWalletTx &wtx)
Update mapSaplingNullifiersToNotes, computing the nullifier from a cached witness if necessary.
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 ...
IncrementalWitness< Depth, Hash > witness() const
Optional< uint256 > cmu() const
Definition: note.cpp:29
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()
bool IsAmountCached(AmountType type, const isminefilter &filter) const
Definition: wallet.cpp:1574
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 AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose=true)
Definition: wallet.cpp:901
CAmount GetDebit(const isminefilter &filter) const
filter decides which addresses will count towards the debit
Definition: wallet.cpp:1580
bool FindNotesDataAndAddMissingIVKToKeystore(const CTransaction &tx, Optional< mapSaplingNoteData_t > &saplingNoteData)
Definition: wallet.cpp:1026
CAmount GetCredit(const isminefilter &filter, bool recalculate=false) const
Definition: wallet.cpp:1616
bool SetupSPKM(bool newKeypool=true, bool memOnly=false)
Generates hd wallet //.
Definition: wallet.cpp:133
CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter &filter=ISMINE_SPENDABLE) const
Definition: wallet.cpp:1647
@ ISMINE_ALL
Definition: ismine.h:35
@ ISMINE_SPENDABLE_SHIELDED
Indicates that we have the spending key of a shielded spend/output.
Definition: ismine.h:30
@ 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_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
Parameters that influence chain consensus.
Definition: params.h:171
libzcash::SaplingExpandedSpendingKey expsk
Definition: zip32.h:92
static SaplingExtendedSpendingKey Master(const HDSeed &seed)
Definition: zip32.cpp:117
#define LOCK2(cs1, cs2)
Definition: sync.h:221
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:247
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
CScript CreateDummyDestinationScript()
Generates a dummy destination script.
Definition: utiltest.cpp:112
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
FakeBlock SimpleFakeMine(CWalletTx &wtx, SaplingMerkleTree &currentTree, CWallet &wallet)
CWalletTx & SetWalletNotesData(CWallet *wallet, CWalletTx &wtx)
BOOST_AUTO_TEST_CASE(GetShieldedSimpleCachedCreditAndDebit)
Validates: 1) CWalletTx getCredit for shielded credit.
SaplingSpendValues UpdateWalletInternalNotesData(CWalletTx &wtx, const SaplingOutPoint &sapPoint, CWallet &wallet)
Update the wallet internally as if the wallet would had received a valid block containing wtx.
void setupWallet(CWallet &wallet)
CWalletTx & AddShieldedBalanceToWallet(CAmount inputAmount, const std::vector< ShieldedDestination > &vDest, CWallet *wallet, const Consensus::Params &consensusParams)
Creates and send a tx with an input of 'inputAmount' to 'vDest'.
CWalletTx & buildTxAndLoadToWallet(CWallet &wallet, const libzcash::SaplingExtendedSpendingKey &extskOut, const SaplingSpendValues &sapSpendValues, libzcash::SaplingPaymentAddress dest, const CAmount &destAmount, const Consensus::Params &consensus)
libzcash::SaplingPaymentAddress getNewDummyShieldedAddress()