auto witness = tree.witness();
// Create a Sapling-only transaction
- // 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 ZEC change
+ // 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);
ASSERT_TRUE(builder2.AddSaplingSpend(xsk, note, anchor, witness));
// Check that trying to add a different anchor fails
EXPECT_EQ(tx2.vout.size(), 0);
EXPECT_EQ(tx2.vjoinsplit.size(), 0);
EXPECT_EQ(tx2.vShieldedSpend.size(), 1);
- EXPECT_EQ(tx2.vShieldedOutput.size(), 1);
- EXPECT_EQ(tx2.valueBalance, 15000);
+ EXPECT_EQ(tx2.vShieldedOutput.size(), 2);
+ EXPECT_EQ(tx2.valueBalance, 10000);
EXPECT_TRUE(ContextualCheckTransaction(tx2, state, 3, 0));
EXPECT_EQ(state.GetRejectReason(), "");
EXPECT_FALSE(builder.AddTransparentOutput(taddr, 50));
}
+TEST(TransactionBuilder, RejectsInvalidTransparentChangeAddress)
+{
+ auto consensusParams = Params().GetConsensus();
+
+ // Default CTxDestination type is an invalid address
+ CTxDestination taddr;
+ auto builder = TransactionBuilder(consensusParams, 1);
+ EXPECT_FALSE(builder.SendChangeTo(taddr));
+}
+
TEST(TransactionBuilder, FailsWithNegativeChange)
{
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
+
+TEST(TransactionBuilder, ChangeOutput)
+{
+ SelectParams(CBaseChainParams::REGTEST);
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
+ auto consensusParams = Params().GetConsensus();
+
+ // Generate dummy Sapling address
+ auto sk = libzcash::SaplingSpendingKey::random();
+ auto xsk = sk.expanded_spending_key();
+ auto pk = sk.default_address();
+
+ // Generate dummy Sapling note
+ libzcash::SaplingNote note(pk, 25000);
+ auto cm = note.cm().value();
+ ZCSaplingIncrementalMerkleTree tree;
+ tree.append(cm);
+ auto anchor = tree.root();
+ auto witness = tree.witness();
+
+ // Generate change Sapling address
+ auto sk2 = libzcash::SaplingSpendingKey::random();
+ auto fvkOut = sk2.full_viewing_key();
+ auto zChangeAddr = sk2.default_address();
+
+ // Set up dummy transparent address
+ CBasicKeyStore keystore;
+ CKey tsk = DecodeSecret(tSecretRegtest);
+ keystore.AddKey(tsk);
+ auto tkeyid = tsk.GetPubKey().GetID();
+ auto scriptPubKey = GetScriptForDestination(tkeyid);
+ CTxDestination taddr = tkeyid;
+
+ // No change address and no Sapling spends
+ {
+ auto builder = TransactionBuilder(consensusParams, 1, &keystore);
+ builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
+ EXPECT_FALSE(static_cast<bool>(builder.Build()));
+ }
+
+ // Change to the same address as the first Sapling spend
+ {
+ auto builder = TransactionBuilder(consensusParams, 1, &keystore);
+ builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
+ ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness));
+ auto maybe_tx = builder.Build();
+ ASSERT_EQ(static_cast<bool>(maybe_tx), true);
+ auto tx = maybe_tx.get();
+
+ EXPECT_EQ(tx.vin.size(), 1);
+ EXPECT_EQ(tx.vout.size(), 0);
+ EXPECT_EQ(tx.vjoinsplit.size(), 0);
+ EXPECT_EQ(tx.vShieldedSpend.size(), 1);
+ EXPECT_EQ(tx.vShieldedOutput.size(), 1);
+ EXPECT_EQ(tx.valueBalance, -15000);
+ }
+
+ // Change to a Sapling address
+ {
+ auto builder = TransactionBuilder(consensusParams, 1, &keystore);
+ builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
+ builder.SendChangeTo(zChangeAddr, fvkOut);
+ auto maybe_tx = builder.Build();
+ ASSERT_EQ(static_cast<bool>(maybe_tx), true);
+ auto tx = maybe_tx.get();
+
+ 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, -15000);
+ }
+
+ // Change to a transparent address
+ {
+ auto builder = TransactionBuilder(consensusParams, 1, &keystore);
+ builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
+ ASSERT_TRUE(builder.SendChangeTo(taddr));
+ auto maybe_tx = builder.Build();
+ ASSERT_EQ(static_cast<bool>(maybe_tx), true);
+ auto tx = maybe_tx.get();
+
+ EXPECT_EQ(tx.vin.size(), 1);
+ EXPECT_EQ(tx.vout.size(), 1);
+ EXPECT_EQ(tx.vjoinsplit.size(), 0);
+ EXPECT_EQ(tx.vShieldedSpend.size(), 0);
+ EXPECT_EQ(tx.vShieldedOutput.size(), 0);
+ EXPECT_EQ(tx.valueBalance, 0);
+ EXPECT_EQ(tx.vout[0].nValue, 15000);
+ }
+
+ // Revert to default
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
+}
return true;
}
+void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut)
+{
+ zChangeAddr = std::make_pair(fvkOut, changeAddr);
+ tChangeAddr = boost::none;
+}
+
+bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
+{
+ if (!IsValidDestination(changeAddr)) {
+ return false;
+ }
+
+ tChangeAddr = changeAddr;
+ zChangeAddr = boost::none;
+}
+
boost::optional<CTransaction> TransactionBuilder::Build()
{
// Fixed fee
return boost::none;
}
- // TODO: Create change output (currently, the change is added to the fee)
+ //
+ // Change output
+ //
+
+ if (change > 0) {
+ // Send change to the specified change address. If no change address
+ // was set, send change to the first Sapling address given as input.
+ if (zChangeAddr) {
+ AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change, {});
+ } else if (tChangeAddr) {
+ // tChangeAddr has already been validated.
+ assert(AddTransparentOutput(tChangeAddr.value(), change));
+ } else if (!spends.empty()) {
+ auto fvk = spends[0].xsk.full_viewing_key();
+ auto note = spends[0].note;
+ libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
+ AddSaplingOutput(fvk, changeAddr, change, {});
+ } else {
+ return boost::none;
+ }
+ }
//
// Sapling spends and outputs