Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sprout support to TransactionBuilder #3848

Merged
merged 4 commits into from Apr 4, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
246 changes: 210 additions & 36 deletions src/gtest/test_transaction_builder.cpp
Expand Up @@ -12,7 +12,68 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

TEST(TransactionBuilder, Invoke)
extern ZCJoinSplit* params;

// Fake an empty view
class TransactionBuilderCoinsViewDB : public CCoinsView {
public:
std::map<uint256, SproutMerkleTree> sproutTrees;

TransactionBuilderCoinsViewDB() {}

bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
auto it = sproutTrees.find(rt);
if (it != sproutTrees.end()) {
tree = it->second;
return true;
} else {
return false;
}
}

bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
return false;
}

bool GetNullifier(const uint256 &nf, ShieldedType type) const {
return false;
}

bool GetCoins(const uint256 &txid, CCoins &coins) const {
return false;
}

bool HaveCoins(const uint256 &txid) const {
return false;
}

uint256 GetBestBlock() const {
uint256 a;
return a;
}

uint256 GetBestAnchor(ShieldedType type) const {
uint256 a;
return a;
}

bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashSproutAnchor,
const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers,
CNullifiersMap saplingNullifiersMap) {
return false;
}

bool GetStats(CCoinsStats &stats) const {
return false;
}
};

TEST(TransactionBuilder, TransparentToSapling)
{
auto consensusParams = RegtestActivateSapling();

Expand All @@ -32,59 +93,172 @@ TEST(TransactionBuilder, Invoke)

// Create a shielding transaction from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
auto builder1 = TransactionBuilder(consensusParams, 1, &keystore);
builder1.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder1.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto tx1 = builder1.Build().GetTxOrThrow();

EXPECT_EQ(tx1.vin.size(), 1);
EXPECT_EQ(tx1.vout.size(), 0);
EXPECT_EQ(tx1.vjoinsplit.size(), 0);
EXPECT_EQ(tx1.vShieldedSpend.size(), 0);
EXPECT_EQ(tx1.vShieldedOutput.size(), 1);
EXPECT_EQ(tx1.valueBalance, -40000);
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto tx = builder.Build().GetTxOrThrow();

EXPECT_EQ(tx.vin.size(), 1);
EXPECT_EQ(tx.vout.size(), 0);
EXPECT_EQ(tx.vjoinsplit.size(), 0);
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
EXPECT_EQ(tx.vShieldedOutput.size(), 1);
EXPECT_EQ(tx.valueBalance, -40000);

CValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx1, state, 2, 0));
EXPECT_TRUE(ContextualCheckTransaction(tx, state, 2, 0));
EXPECT_EQ(state.GetRejectReason(), "");

// Prepare to spend the note that was just created
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey, tx1.vShieldedOutput[0].cm);
ASSERT_EQ(static_cast<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note = maybe_note.get();
SaplingMerkleTree tree;
tree.append(tx1.vShieldedOutput[0].cm);
auto anchor = tree.root();
auto witness = tree.witness();
// Revert to default
RegtestDeactivateSapling();
}

TEST(TransactionBuilder, SaplingToSapling) {
auto consensusParams = RegtestActivateSapling();

auto sk = libzcash::SaplingSpendingKey::random();
auto expsk = sk.expanded_spending_key();
auto fvk = sk.full_viewing_key();
auto pa = sk.default_address();

auto testNote = GetTestSaplingNote(pa, 40000);

// Create a Sapling-only transaction
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 z-ZEC change
auto builder2 = TransactionBuilder(consensusParams, 2);
builder2.AddSaplingSpend(expsk, note, anchor, witness);
auto builder = TransactionBuilder(consensusParams, 2);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());

// Check that trying to add a different anchor fails
// TODO: the following check can be split out in to another test
ASSERT_THROW(builder2.AddSaplingSpend(expsk, note, uint256(), witness), UniValue);
ASSERT_THROW(builder.AddSaplingSpend(expsk, testNote.note, uint256(), testNote.tree.witness()), UniValue);

builder2.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx2 = builder2.Build().GetTxOrThrow();
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();

EXPECT_EQ(tx2.vin.size(), 0);
EXPECT_EQ(tx2.vout.size(), 0);
EXPECT_EQ(tx2.vjoinsplit.size(), 0);
EXPECT_EQ(tx2.vShieldedSpend.size(), 1);
EXPECT_EQ(tx2.vShieldedOutput.size(), 2);
EXPECT_EQ(tx2.valueBalance, 10000);
EXPECT_EQ(tx.vin.size(), 0);
EXPECT_EQ(tx.vout.size(), 0);
EXPECT_EQ(tx.vjoinsplit.size(), 0);
EXPECT_EQ(tx.vShieldedSpend.size(), 1);
EXPECT_EQ(tx.vShieldedOutput.size(), 2);
EXPECT_EQ(tx.valueBalance, 10000);

EXPECT_TRUE(ContextualCheckTransaction(tx2, state, 3, 0));
CValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx, state, 3, 0));
EXPECT_EQ(state.GetRejectReason(), "");

// Revert to default
RegtestDeactivateSapling();
}

TEST(TransactionBuilder, SaplingToSprout) {
auto consensusParams = RegtestActivateSapling();

auto sk = libzcash::SaplingSpendingKey::random();
auto expsk = sk.expanded_spending_key();
auto pa = sk.default_address();

auto testNote = GetTestSaplingNote(pa, 40000);

auto sproutSk = libzcash::SproutSpendingKey::random();
auto sproutAddr = sproutSk.address();

// Create a Sapling-to-Sprout transaction (reusing the note from above)
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
// - 0.00005 Sapling-ZEC change
// - 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 2, nullptr, params);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSproutOutput(sproutAddr, 25000);
auto tx = builder.Build().GetTxOrThrow();

EXPECT_EQ(tx.vin.size(), 0);
EXPECT_EQ(tx.vout.size(), 0);
EXPECT_EQ(tx.vjoinsplit.size(), 1);
EXPECT_EQ(tx.vjoinsplit[0].vpub_old, 25000);
EXPECT_EQ(tx.vjoinsplit[0].vpub_new, 0);
EXPECT_EQ(tx.vShieldedSpend.size(), 1);
EXPECT_EQ(tx.vShieldedOutput.size(), 1);
EXPECT_EQ(tx.valueBalance, 35000);

CValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx, state, 3, 0));
EXPECT_EQ(state.GetRejectReason(), "");

// Revert to default
RegtestDeactivateSapling();
}

TEST(TransactionBuilder, SproutToSproutAndSapling) {
auto consensusParams = RegtestActivateSapling();

auto sk = libzcash::SaplingSpendingKey::random();
auto fvk = sk.full_viewing_key();
auto pa = sk.default_address();

auto sproutSk = libzcash::SproutSpendingKey::random();
auto sproutAddr = sproutSk.address();

auto wtx = GetValidSproutReceive(*params, sproutSk, 25000, true);
auto sproutNote = GetSproutNote(*params, sproutSk, wtx, 0, 1);

SproutMerkleTree sproutTree;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
sproutTree.append(wtx.vjoinsplit[0].commitments[i]);
}
SproutWitness sproutWitness = sproutTree.witness();
// Fake a view with the Sprout note in it
auto rt = sproutTree.root();
TransactionBuilderCoinsViewDB fakeDB;
fakeDB.sproutTrees.insert(std::pair<uint256, SproutMerkleTree>(rt, sproutTree));
CCoinsViewCache view(&fakeDB);

// Create a Sprout-to-[Sprout-and-Sapling] transaction
// - 0.00025 Sprout-ZEC in - 0.00006 Sprout-ZEC out
// - 0.00004 Sprout-ZEC out
// - 0.00005 Sprout-ZEC change
// - 0.00005 Sapling-ZEC out
// - 0.00005 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 2, nullptr, params, &view);
builder.SetFee(5000);
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
builder.AddSproutOutput(sproutAddr, 6000);
builder.AddSproutOutput(sproutAddr, 4000);
builder.AddSaplingOutput(fvk.ovk, pa, 5000);
auto tx = builder.Build().GetTxOrThrow();

EXPECT_EQ(tx.vin.size(), 0);
EXPECT_EQ(tx.vout.size(), 0);
// TODO: This should be doable in two JoinSplits.
// There's an inefficiency in the implementation.
EXPECT_EQ(tx.vjoinsplit.size(), 3);
EXPECT_EQ(tx.vjoinsplit[0].vpub_old, 0);
EXPECT_EQ(tx.vjoinsplit[0].vpub_new, 0);
EXPECT_EQ(tx.vjoinsplit[1].vpub_old, 0);
EXPECT_EQ(tx.vjoinsplit[1].vpub_new, 0);
EXPECT_EQ(tx.vjoinsplit[2].vpub_old, 0);
EXPECT_EQ(tx.vjoinsplit[2].vpub_new, 10000);
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
EXPECT_EQ(tx.vShieldedOutput.size(), 1);
EXPECT_EQ(tx.valueBalance, -5000);

CValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx, state, 4, 0));
EXPECT_EQ(state.GetRejectReason(), "");

// Revert to default
RegtestDeactivateSapling();
}

TEST(TransactionBuilder, ThrowsOnSproutOutputWithoutParams)
{
auto consensusParams = Params().GetConsensus();
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();

auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_THROW(builder.AddSproutOutput(addr, 10), std::runtime_error);
}

TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
{
SelectParams(CBaseChainParams::REGTEST);
Expand Down