Keep track of spent notes, and detect and report conflicts

This commit is contained in:
Jack Grigg
2016-08-24 15:51:40 +12:00
parent c3a7307a69
commit 0f1060478f
3 changed files with 188 additions and 7 deletions

View File

@@ -3,6 +3,7 @@
#include "base58.h"
#include "chainparams.h"
#include "main.h"
#include "random.h"
#include "wallet/wallet.h"
#include "zcash/JoinSplit.hpp"
@@ -78,6 +79,55 @@ libzcash::Note GetNote(const libzcash::SpendingKey& sk,
return note_pt.note(sk.address());
}
CWalletTx GetValidSpend(const libzcash::SpendingKey& sk,
const libzcash::Note& note, CAmount value) {
CMutableTransaction mtx;
mtx.vout.resize(2);
mtx.vout[0].nValue = value;
mtx.vout[1].nValue = 0;
// Generate an ephemeral keypair.
uint256 joinSplitPubKey;
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey);
mtx.joinSplitPubKey = joinSplitPubKey;
// Fake tree for the unused witness
ZCIncrementalMerkleTree tree;
boost::array<libzcash::JSInput, 2> inputs = {
libzcash::JSInput(tree.witness(), note, sk),
libzcash::JSInput() // dummy input
};
boost::array<libzcash::JSOutput, 2> outputs = {
libzcash::JSOutput(), // dummy output
libzcash::JSOutput() // dummy output
};
boost::array<libzcash::Note, 2> output_notes;
// Prepare JoinSplits
uint256 rt;
JSDescription jsdesc {*params, mtx.joinSplitPubKey, rt,
inputs, outputs, 0, value, false};
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
// Add the signature
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey
) == 0);
CTransaction tx {mtx};
CWalletTx wtx {NULL, tx};
return wtx;
}
TEST(wallet_tests, set_note_addrs_in_cwallettx) {
auto sk = libzcash::SpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
@@ -125,3 +175,74 @@ TEST(wallet_tests, find_note_in_tx) {
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}
TEST(wallet_tests, get_conflicted_notes) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto wtx2 = GetValidSpend(sk, note, 5);
auto wtx3 = GetValidSpend(sk, note, 10);
auto hash2 = wtx2.GetTxid();
auto hash3 = wtx3.GetTxid();
// No conflicts for no spends
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
wallet.AddToWallet(wtx, true, NULL);
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// No conflicts for one spend
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// Conflicts for two spends
wallet.AddToWallet(wtx3, true, NULL);
auto c3 = wallet.GetConflicts(hash2);
EXPECT_EQ(2, c3.size());
EXPECT_EQ(std::set<uint256>({hash2, hash3}), c3);
}
TEST(wallet_tests, nullifier_is_spent) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
EXPECT_FALSE(wallet.IsSpent(nullifier));
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSpent(nullifier));
auto wtx2 = GetValidSpend(sk, note, 5);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_FALSE(wallet.IsSpent(nullifier));
// Fake-mine the transaction
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx2);
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
wtx2.SetMerkleBranch(block);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_TRUE(wallet.IsSpent(nullifier));
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
}