#include "key_io.h"
#include "main.h"
#include "pubkey.h"
+#include "rpc/protocol.h"
#include "transaction_builder.h"
#include "zcash/Address.hpp"
// 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);
- ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note, anchor, witness));
+ builder2.AddSaplingSpend(expsk, note, anchor, witness);
// Check that trying to add a different anchor fails
- ASSERT_FALSE(builder2.AddSaplingSpend(expsk, note, uint256(), witness));
+ // TODO: the following check can be split out in to another test
+ ASSERT_THROW(builder2.AddSaplingSpend(expsk, note, uint256(), witness), UniValue);
builder2.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx2 = builder2.Build().GetTxOrThrow();
// Default CTxDestination type is an invalid address
CTxDestination taddr;
auto builder = TransactionBuilder(consensusParams, 1);
- EXPECT_FALSE(builder.AddTransparentOutput(taddr, 50));
+ ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
}
TEST(TransactionBuilder, RejectsInvalidTransparentChangeAddress)
// Default CTxDestination type is an invalid address
CTxDestination taddr;
auto builder = TransactionBuilder(consensusParams, 1);
- EXPECT_FALSE(builder.SendChangeTo(taddr));
+ ASSERT_THROW(builder.SendChangeTo(taddr), UniValue);
}
TEST(TransactionBuilder, FailsWithNegativeChange)
// Fail if there is only a transparent output
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
builder = TransactionBuilder(consensusParams, 1, &keystore);
- EXPECT_TRUE(builder.AddTransparentOutput(taddr, 50000));
+ builder.AddTransparentOutput(taddr, 50000);
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
// Fails if there is insufficient input
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in
- EXPECT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
// Succeeds if there is sufficient input
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1);
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
- ASSERT_TRUE(builder.SendChangeTo(taddr));
+ builder.SendChangeTo(taddr);
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1);
// Default fee
{
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
// Configured fee
{
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
builder.SetFee(20000);
auto tx = builder.Build().GetTxOrThrow();
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
}
-bool TransactionBuilder::AddSaplingSpend(
+void TransactionBuilder::AddSaplingSpend(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
}
// Consistency check: all anchors must equal the first one
- if (!spends.empty()) {
- if (spends[0].anchor != anchor) {
- return false;
- }
+ if (spends.size() > 0 && spends[0].anchor != anchor) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Anchor does not match previously-added Sapling spends.");
}
spends.emplace_back(expsk, note, anchor, witness);
mtx.valueBalance += note.value();
- return true;
}
void TransactionBuilder::AddSaplingOutput(
tIns.emplace_back(scriptPubKey, value);
}
-bool TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value)
+void TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value)
{
if (!IsValidDestination(to)) {
- return false;
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
}
CScript scriptPubKey = GetScriptForDestination(to);
CTxOut out(value, scriptPubKey);
mtx.vout.push_back(out);
- return true;
}
void TransactionBuilder::SetFee(CAmount fee)
tChangeAddr = boost::none;
}
-bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
+void TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
{
if (!IsValidDestination(changeAddr)) {
- return false;
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid change address, not a valid taddr.");
}
tChangeAddr = changeAddr;
zChangeAddr = boost::none;
-
- return true;
}
TransactionBuilderResult TransactionBuilder::Build()
AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change);
} else if (tChangeAddr) {
// tChangeAddr has already been validated.
- assert(AddTransparentOutput(tChangeAddr.value(), change));
+ AddTransparentOutput(tChangeAddr.value(), change);
} else if (!spends.empty()) {
auto fvk = spends[0].expsk.full_viewing_key();
auto note = spends[0].note;
void SetFee(CAmount fee);
- // Returns false if the anchor does not match the anchor used by
+ // Throws if the anchor does not match the anchor used by
// previously-added Sapling spends.
- bool AddSaplingSpend(
+ void AddSaplingSpend(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
// Assumes that the value correctly corresponds to the provided UTXO.
void AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value);
- bool AddTransparentOutput(CTxDestination& to, CAmount value);
+ void AddTransparentOutput(CTxDestination& to, CAmount value);
void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk);
- bool SendChangeTo(CTxDestination& changeAddr);
+ void SendChangeTo(CTxDestination& changeAddr);
TransactionBuilderResult Build();
};
if (!witnesses[i]) {
throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note");
}
- assert(builder_.AddSaplingSpend(expsks[i], saplingNotes[i], anchor, witnesses[i].get()));
+ builder_.AddSaplingSpend(expsks[i], saplingNotes[i], anchor, witnesses[i].get());
}
if (isToTaddr_) {
- if (!builder_.AddTransparentOutput(toTaddr_, sendAmount)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
- }
+ builder_.AddTransparentOutput(toTaddr_, sendAmount);
} else {
std::string zaddr = std::get<0>(recipient_);
std::string memo = std::get<1>(recipient_);
}
CTxDestination changeAddr = vchPubKey.GetID();
- assert(builder_.SendChangeTo(changeAddr));
+ builder_.SendChangeTo(changeAddr);
}
// Select Sapling notes
if (!witnesses[i]) {
throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note");
}
- assert(builder_.AddSaplingSpend(expsk, notes[i], anchor, witnesses[i].get()));
+ builder_.AddSaplingSpend(expsk, notes[i], anchor, witnesses[i].get());
}
// Add Sapling outputs
auto amount = std::get<1>(r);
auto address = DecodeDestination(outputAddress);
- if (!builder_.AddTransparentOutput(address, amount)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
- }
+ builder_.AddTransparentOutput(address, amount);
}
// Build the transaction
uint256 nullifier = nf.get();
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
auto tx = builder.Build().GetTxOrThrow();
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
// Generate tx to create output note B
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 35000, {});
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
- ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness));
+ builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(fvk.ovk, pk, 20000, {});
auto tx2 = builder2.Build().GetTxOrThrow();
// Create conflicting transaction which also spends note B
auto builder3 = TransactionBuilder(consensusParams, 2);
- ASSERT_TRUE(builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness));
+ builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder3.AddSaplingOutput(fvk.ovk, pk, 19999, {});
auto tx3 = builder3.Build().GetTxOrThrow();
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
// Generate transaction, which sends funds to note B
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
- ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness));
+ builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(fvk.ovk, pk, 12500, {});
auto tx2 = builder2.Build().GetTxOrThrow();
EXPECT_EQ(tx2.vin.size(), 0);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
- ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
+ builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk2, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
// 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);
- ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note, anchor, witness));
+ builder2.AddSaplingSpend(expsk, note, anchor, witness);
builder2.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto tx2 = builder2.Build().GetTxOrThrow();