TEST(wallet_tests, WriteWitnessCache) { TestWallet wallet; MockWalletDB walletdb; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); auto wtx = GetValidReceive(sk, 10, true); wallet.AddToWallet(wtx, true, NULL); // TxnBegin fails EXPECT_CALL(walletdb, TxnBegin()) .WillOnce(Return(false)); wallet.WriteWitnessCache(walletdb); EXPECT_CALL(walletdb, TxnBegin()) .WillRepeatedly(Return(true)); // WriteTx fails EXPECT_CALL(walletdb, WriteTx(wtx.GetHash(), wtx)) .WillOnce(Return(false)); EXPECT_CALL(walletdb, TxnAbort()) .Times(1); wallet.WriteWitnessCache(walletdb); // WriteTx throws EXPECT_CALL(walletdb, WriteTx(wtx.GetHash(), wtx)) .WillOnce(ThrowLogicError()); EXPECT_CALL(walletdb, TxnAbort()) .Times(1); wallet.WriteWitnessCache(walletdb); EXPECT_CALL(walletdb, WriteTx(wtx.GetHash(), wtx)) .WillRepeatedly(Return(true)); // WriteWitnessCacheSize fails EXPECT_CALL(walletdb, WriteWitnessCacheSize(0)) .WillOnce(Return(false)); EXPECT_CALL(walletdb, TxnAbort()) .Times(1); wallet.WriteWitnessCache(walletdb); // WriteWitnessCacheSize throws EXPECT_CALL(walletdb, WriteWitnessCacheSize(0)) .WillOnce(ThrowLogicError()); EXPECT_CALL(walletdb, TxnAbort()) .Times(1); wallet.WriteWitnessCache(walletdb); EXPECT_CALL(walletdb, WriteWitnessCacheSize(0)) .WillRepeatedly(Return(true)); // TxCommit fails EXPECT_CALL(walletdb, TxnCommit()) .WillOnce(Return(false)); wallet.WriteWitnessCache(walletdb); EXPECT_CALL(walletdb, TxnCommit()) .WillRepeatedly(Return(true)); // Everything succeeds wallet.WriteWitnessCache(walletdb); }
TEST(wallet_tests, UpdatedNoteData) { TestWallet wallet; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 0); auto note2 = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); auto nullifier2 = note2.nullifier(sk); auto wtx2 = wtx; // First pretend we added the tx to the wallet and // we don't have the key for the second note mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 0}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); // Pretend we mined the tx by adding a fake witness ZCIncrementalMerkleTree tree; wtx.mapNoteData[jsoutpt].witnesses.push_front(tree.witness()); wtx.mapNoteData[jsoutpt].witnessHeight = 100; // Now pretend we added the key for the second note, and // the tx was "added" to the wallet again to update it. // This happens via the 'z_importkey' RPC method. JSOutPoint jsoutpt2 {wtx2.GetHash(), 0, 1}; CNoteData nd2 {sk.address(), nullifier2}; noteData[jsoutpt2] = nd2; wtx2.SetNoteData(noteData); // The txs should initially be different EXPECT_NE(wtx.mapNoteData, wtx2.mapNoteData); EXPECT_EQ(1, wtx.mapNoteData[jsoutpt].witnesses.size()); EXPECT_EQ(100, wtx.mapNoteData[jsoutpt].witnessHeight); // After updating, they should be the same EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx)); EXPECT_EQ(wtx.mapNoteData, wtx2.mapNoteData); EXPECT_EQ(1, wtx.mapNoteData[jsoutpt].witnesses.size()); EXPECT_EQ(100, wtx.mapNoteData[jsoutpt].witnessHeight); // TODO: The new note should get witnessed (but maybe not here) (#1350) }
TEST(wallet_tests, UpdateNullifierNoteMap) { TestWallet wallet; uint256 r {GetRandHash()}; CKeyingMaterial vMasterKey (r.begin(), r.end()); auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); ASSERT_TRUE(wallet.EncryptKeys(vMasterKey)); auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); // Pretend that we called FindMyNotes while the wallet was locked mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address()}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); EXPECT_EQ(0, wallet.mapNullifiersToNotes.count(nullifier)); EXPECT_FALSE(wallet.UpdateNullifierNoteMap()); ASSERT_TRUE(wallet.Unlock(vMasterKey)); EXPECT_TRUE(wallet.UpdateNullifierNoteMap()); EXPECT_EQ(1, wallet.mapNullifiersToNotes.count(nullifier)); EXPECT_EQ(wtx.GetHash(), wallet.mapNullifiersToNotes[nullifier].hash); EXPECT_EQ(0, wallet.mapNullifiersToNotes[nullifier].js); EXPECT_EQ(1, wallet.mapNullifiersToNotes[nullifier].n); }
TEST(wallet_tests, MarkAffectedTransactionsDirty) { TestWallet wallet; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); auto wtx = GetValidReceive(sk, 10, true); auto hash = wtx.GetHash(); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); auto wtx2 = GetValidSpend(sk, note, 5); mapNoteData_t noteData; JSOutPoint jsoutpt {hash, 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); wallet.MarkAffectedTransactionsDirty(wtx); // After getting a cached value, the first tx should be clean wallet.mapWallet[hash].GetDebit(ISMINE_ALL); EXPECT_TRUE(wallet.mapWallet[hash].fDebitCached); // After adding the note spend, the first tx should be dirty wallet.AddToWallet(wtx2, true, NULL); wallet.MarkAffectedTransactionsDirty(wtx2); EXPECT_FALSE(wallet.mapWallet[hash].fDebitCached); }
TEST(wallet_tests, FindMyNotesInEncryptedWallet) { TestWallet wallet; uint256 r {GetRandHash()}; CKeyingMaterial vMasterKey (r.begin(), r.end()); auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); ASSERT_TRUE(wallet.EncryptKeys(vMasterKey)); auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); auto noteMap = wallet.FindMyNotes(wtx); EXPECT_EQ(2, noteMap.size()); JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; EXPECT_EQ(1, noteMap.count(jsoutpt)); EXPECT_NE(nd, noteMap[jsoutpt]); ASSERT_TRUE(wallet.Unlock(vMasterKey)); noteMap = wallet.FindMyNotes(wtx); EXPECT_EQ(2, noteMap.size()); EXPECT_EQ(1, noteMap.count(jsoutpt)); EXPECT_EQ(nd, noteMap[jsoutpt]); }
TEST(wallet_tests, cached_witnesses_empty_chain) { TestWallet wallet; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 0); auto note2 = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); auto nullifier2 = note2.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 0}; JSOutPoint jsoutpt2 {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; CNoteData nd2 {sk.address(), nullifier2}; noteData[jsoutpt] = nd; noteData[jsoutpt2] = nd2; wtx.SetNoteData(noteData); std::vector<JSOutPoint> notes {jsoutpt, jsoutpt2}; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; uint256 anchor; wallet.GetNoteWitnesses(notes, witnesses, anchor); EXPECT_FALSE((bool) witnesses[0]); EXPECT_FALSE((bool) witnesses[1]); wallet.AddToWallet(wtx, true, NULL); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor); EXPECT_FALSE((bool) witnesses[0]); EXPECT_FALSE((bool) witnesses[1]); CBlock block; block.vtx.push_back(wtx); CBlockIndex index(block); ZCIncrementalMerkleTree tree; wallet.IncrementNoteWitnesses(&index, &block, tree); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor); EXPECT_TRUE((bool) witnesses[0]); EXPECT_TRUE((bool) witnesses[1]); // Until #1302 is implemented, this should triggger an assertion EXPECT_DEATH(wallet.DecrementNoteWitnesses(&index), "Assertion `nWitnessCacheSize > 0' failed."); }
TEST(wallet_tests, ClearNoteWitnessCache) { TestWallet wallet; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); auto wtx = GetValidReceive(sk, 10, true); auto hash = wtx.GetHash(); auto note = GetNote(sk, wtx, 0, 0); auto nullifier = note.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 0}; JSOutPoint jsoutpt2 {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); // Pretend we mined the tx by adding a fake witness ZCIncrementalMerkleTree tree; wtx.mapNoteData[jsoutpt].witnesses.push_front(tree.witness()); wtx.mapNoteData[jsoutpt].witnessHeight = 1; wallet.nWitnessCacheSize = 1; wallet.AddToWallet(wtx, true, NULL); std::vector<JSOutPoint> notes {jsoutpt, jsoutpt2}; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; uint256 anchor2; // Before clearing, we should have a witness for one note wallet.GetNoteWitnesses(notes, witnesses, anchor2); EXPECT_TRUE((bool) witnesses[0]); EXPECT_FALSE((bool) witnesses[1]); EXPECT_EQ(1, wallet.mapWallet[hash].mapNoteData[jsoutpt].witnessHeight); EXPECT_EQ(1, wallet.nWitnessCacheSize); // After clearing, we should not have a witness for either note wallet.ClearNoteWitnessCache(); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor2); EXPECT_FALSE((bool) witnesses[0]); EXPECT_FALSE((bool) witnesses[1]); EXPECT_EQ(-1, wallet.mapWallet[hash].mapNoteData[jsoutpt].witnessHeight); EXPECT_EQ(0, wallet.nWitnessCacheSize); }
TEST(wallet_tests, cached_witnesses_chain_tip) { TestWallet wallet; uint256 anchor1; CBlock block1; ZCIncrementalMerkleTree tree; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); { // First transaction (case tested in _empty_chain) auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); std::vector<JSOutPoint> notes {jsoutpt}; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; // First block (case tested in _empty_chain) block1.vtx.push_back(wtx); wallet.IncrementNoteWitnesses(NULL, &block1, tree); // Called to fetch anchor wallet.GetNoteWitnesses(notes, witnesses, anchor1); } { // Second transaction auto wtx = GetValidReceive(sk, 50, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); std::vector<JSOutPoint> notes {jsoutpt}; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; uint256 anchor2; wallet.GetNoteWitnesses(notes, witnesses, anchor2); EXPECT_FALSE((bool) witnesses[0]); // Second block CBlock block2; block2.hashPrevBlock = block1.GetHash(); block2.vtx.push_back(wtx); ZCIncrementalMerkleTree tree2 {tree}; wallet.IncrementNoteWitnesses(NULL, &block2, tree2); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor2); EXPECT_TRUE((bool) witnesses[0]); EXPECT_NE(anchor1, anchor2); // Decrementing should give us the previous anchor uint256 anchor3; wallet.DecrementNoteWitnesses(); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor3); EXPECT_FALSE((bool) witnesses[0]); // Should not equal first anchor because none of these notes had witnesses EXPECT_NE(anchor1, anchor3); // Re-incrementing with the same block should give the same result uint256 anchor4; wallet.IncrementNoteWitnesses(NULL, &block2, tree); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor4); EXPECT_TRUE((bool) witnesses[0]); EXPECT_EQ(anchor2, anchor4); } }
TEST(wallet_tests, CachedWitnessesDecrementFirst) { TestWallet wallet; uint256 anchor2; CBlock block2; CBlockIndex index2(block2); ZCIncrementalMerkleTree tree; auto sk = libzcash::SpendingKey::random(); wallet.AddSpendingKey(sk); { // First transaction (case tested in _empty_chain) auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); // First block (case tested in _empty_chain) CBlock block1; block1.vtx.push_back(wtx); CBlockIndex index1(block1); index1.nHeight = 1; wallet.IncrementNoteWitnesses(&index1, &block1, tree); } { // Second transaction (case tested in _chain_tip) auto wtx = GetValidReceive(sk, 50, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); std::vector<JSOutPoint> notes {jsoutpt}; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; // Second block (case tested in _chain_tip) block2.vtx.push_back(wtx); index2.nHeight = 2; wallet.IncrementNoteWitnesses(&index2, &block2, tree); // Called to fetch anchor wallet.GetNoteWitnesses(notes, witnesses, anchor2); } { // Third transaction - never mined auto wtx = GetValidReceive(sk, 20, true); auto note = GetNote(sk, wtx, 0, 1); auto nullifier = note.nullifier(sk); mapNoteData_t noteData; JSOutPoint jsoutpt {wtx.GetHash(), 0, 1}; CNoteData nd {sk.address(), nullifier}; noteData[jsoutpt] = nd; wtx.SetNoteData(noteData); wallet.AddToWallet(wtx, true, NULL); std::vector<JSOutPoint> notes {jsoutpt}; std::vector<boost::optional<ZCIncrementalWitness>> witnesses; uint256 anchor3; wallet.GetNoteWitnesses(notes, witnesses, anchor3); EXPECT_FALSE((bool) witnesses[0]); // Decrementing (before the transaction has ever seen an increment) // should give us the previous anchor uint256 anchor4; wallet.DecrementNoteWitnesses(&index2); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor4); EXPECT_FALSE((bool) witnesses[0]); // Should not equal second anchor because none of these notes had witnesses EXPECT_NE(anchor2, anchor4); // Re-incrementing with the same block should give the same result uint256 anchor5; wallet.IncrementNoteWitnesses(&index2, &block2, tree); witnesses.clear(); wallet.GetNoteWitnesses(notes, witnesses, anchor5); EXPECT_FALSE((bool) witnesses[0]); EXPECT_EQ(anchor3, anchor5); } }