Auto merge of #3646 - Eirik0:transaction-builder-result, r=daira

Return more information when building a transaction fails

This PR is intended to make it easier to diagnose what went wrong when building a transaction using `TransactionBuilder` fails.
This commit is contained in:
Homu 2019-01-22 10:14:27 -08:00
commit 5862b2921b
7 changed files with 119 additions and 137 deletions

View File

@ -4,6 +4,7 @@
#include "key_io.h"
#include "main.h"
#include "pubkey.h"
#include "rpc/protocol.h"
#include "transaction_builder.h"
#include "zcash/Address.hpp"
@ -39,9 +40,7 @@ TEST(TransactionBuilder, Invoke)
auto builder1 = TransactionBuilder(consensusParams, 1, &keystore);
builder1.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder1.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto maybe_tx1 = builder1.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx1), true);
auto tx1 = maybe_tx1.get();
auto tx1 = builder1.Build().GetTxOrThrow();
EXPECT_EQ(tx1.vin.size(), 1);
EXPECT_EQ(tx1.vout.size(), 0);
@ -69,14 +68,13 @@ TEST(TransactionBuilder, Invoke)
// 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 maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
auto tx2 = builder2.Build().GetTxOrThrow();
EXPECT_EQ(tx2.vin.size(), 0);
EXPECT_EQ(tx2.vout.size(), 0);
@ -110,7 +108,7 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
// 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)
@ -121,7 +119,7 @@ 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)
@ -157,22 +155,22 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1);
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
EXPECT_FALSE(static_cast<bool>(builder.Build()));
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
// 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));
EXPECT_FALSE(static_cast<bool>(builder.Build()));
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));
EXPECT_FALSE(static_cast<bool>(builder.Build()));
builder.AddSaplingSpend(expsk, note, anchor, witness);
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
// Succeeds if there is sufficient input
builder.AddTransparentInput(COutPoint(), scriptPubKey, 1);
EXPECT_TRUE(static_cast<bool>(builder.Build()));
EXPECT_TRUE(builder.Build().IsTx());
// Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
@ -216,17 +214,15 @@ TEST(TransactionBuilder, ChangeOutput)
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
EXPECT_FALSE(static_cast<bool>(builder.Build()));
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
}
// 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(expsk, note, anchor, witness));
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
builder.AddSaplingSpend(expsk, note, anchor, witness);
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1);
EXPECT_EQ(tx.vout.size(), 0);
@ -241,9 +237,7 @@ TEST(TransactionBuilder, ChangeOutput)
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1);
EXPECT_EQ(tx.vout.size(), 0);
@ -257,10 +251,8 @@ TEST(TransactionBuilder, ChangeOutput)
{
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();
builder.SendChangeTo(taddr);
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1);
EXPECT_EQ(tx.vout.size(), 1);
@ -300,11 +292,9 @@ TEST(TransactionBuilder, SetFee)
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 0);
EXPECT_EQ(tx.vout.size(), 0);
@ -317,12 +307,10 @@ TEST(TransactionBuilder, SetFee)
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 0);
EXPECT_EQ(tx.vout.size(), 0);

View File

@ -6,6 +6,7 @@
#include "main.h"
#include "pubkey.h"
#include "rpc/protocol.h"
#include "script/sign.h"
#include <boost/variant.hpp>
@ -20,6 +21,31 @@ SpendDescriptionInfo::SpendDescriptionInfo(
librustzcash_sapling_generate_r(alpha.begin());
}
TransactionBuilderResult::TransactionBuilderResult(const CTransaction& tx) : maybeTx(tx) {}
TransactionBuilderResult::TransactionBuilderResult(const std::string& error) : maybeError(error) {}
bool TransactionBuilderResult::IsTx() { return maybeTx != boost::none; }
bool TransactionBuilderResult::IsError() { return maybeError != boost::none; }
CTransaction TransactionBuilderResult::GetTxOrThrow() {
if (maybeTx) {
return maybeTx.get();
} else {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction: " + GetError());
}
}
std::string TransactionBuilderResult::GetError() {
if (maybeError) {
return maybeError.get();
} else {
// This can only happen if isTx() is true in which case we should not call getError()
throw std::runtime_error("getError() was called in TransactionBuilderResult, but the result was not initialized as an error.");
}
}
TransactionBuilder::TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
@ -28,7 +54,7 @@ TransactionBuilder::TransactionBuilder(
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
}
bool TransactionBuilder::AddSaplingSpend(
void TransactionBuilder::AddSaplingSpend(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
@ -40,15 +66,12 @@ bool TransactionBuilder::AddSaplingSpend(
}
// 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(
@ -77,16 +100,15 @@ void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKe
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)
@ -100,19 +122,17 @@ void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr
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;
}
boost::optional<CTransaction> TransactionBuilder::Build()
TransactionBuilderResult TransactionBuilder::Build()
{
//
// Consistency checks
@ -127,7 +147,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
change -= tOut.nValue;
}
if (change < 0) {
return boost::none;
return TransactionBuilderResult("Change cannot be negative");
}
//
@ -141,14 +161,14 @@ boost::optional<CTransaction> 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;
libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
AddSaplingOutput(fvk.ovk, changeAddr, change);
} else {
return boost::none;
return TransactionBuilderResult("Could not determine change address");
}
}
@ -165,7 +185,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
spend.expsk.full_viewing_key(), spend.witness.position());
if (!(cm && nf)) {
librustzcash_sapling_proving_ctx_free(ctx);
return boost::none;
return TransactionBuilderResult("Missing spend commitment or nullifier");
}
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
@ -187,7 +207,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
sdesc.rk.begin(),
sdesc.zkproof.data())) {
librustzcash_sapling_proving_ctx_free(ctx);
return boost::none;
return TransactionBuilderResult("Spend proof failed");
}
sdesc.anchor = spend.anchor;
@ -200,7 +220,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
auto cm = output.note.cm();
if (!cm) {
librustzcash_sapling_proving_ctx_free(ctx);
return boost::none;
return TransactionBuilderResult("Missing output commitment");
}
libzcash::SaplingNotePlaintext notePlaintext(output.note, output.memo);
@ -208,7 +228,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
auto res = notePlaintext.encrypt(output.note.pk_d);
if (!res) {
librustzcash_sapling_proving_ctx_free(ctx);
return boost::none;
return TransactionBuilderResult("Failed to encrypt note");
}
auto enc = res.get();
auto encryptor = enc.second;
@ -224,7 +244,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
odesc.cv.begin(),
odesc.zkproof.begin())) {
librustzcash_sapling_proving_ctx_free(ctx);
return boost::none;
return TransactionBuilderResult("Output proof failed");
}
odesc.cm = *cm;
@ -253,7 +273,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
} catch (std::logic_error ex) {
librustzcash_sapling_proving_ctx_free(ctx);
return boost::none;
return TransactionBuilderResult("Could not construct signature hash");
}
// Create Sapling spendAuth and binding signatures
@ -283,11 +303,11 @@ boost::optional<CTransaction> TransactionBuilder::Build()
tIn.scriptPubKey, sigdata, consensusBranchId);
if (!signSuccess) {
return boost::none;
return TransactionBuilderResult("Failed to sign transaction");
} else {
UpdateTransaction(mtx, nIn, sigdata);
}
}
return CTransaction(mtx);
return TransactionBuilderResult(CTransaction(mtx));
}

View File

@ -52,6 +52,20 @@ struct TransparentInputInfo {
CAmount value) : scriptPubKey(scriptPubKey), value(value) {}
};
class TransactionBuilderResult {
private:
boost::optional<CTransaction> maybeTx;
boost::optional<std::string> maybeError;
public:
TransactionBuilderResult() = delete;
TransactionBuilderResult(const CTransaction& tx);
TransactionBuilderResult(const std::string& error);
bool IsTx();
bool IsError();
CTransaction GetTxOrThrow();
std::string GetError();
};
class TransactionBuilder
{
private:
@ -74,9 +88,9 @@ public:
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,
@ -91,13 +105,13 @@ public:
// 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);
boost::optional<CTransaction> Build();
TransactionBuilderResult Build();
};
#endif /* TRANSACTION_BUILDER_H */

View File

@ -338,13 +338,11 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
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_);
@ -375,11 +373,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// Build the transaction
auto maybe_tx = builder_.Build();
if (!maybe_tx) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
}
tx_ = maybe_tx.get();
tx_ = builder_.Build().GetTxOrThrow();
// Send the transaction
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction

View File

@ -416,7 +416,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
}
CTxDestination changeAddr = vchPubKey.GetID();
assert(builder_.SendChangeTo(changeAddr));
builder_.SendChangeTo(changeAddr);
}
// Select Sapling notes
@ -445,7 +445,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
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
@ -469,17 +469,11 @@ bool AsyncRPCOperation_sendmany::main_impl() {
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
auto maybe_tx = builder_.Build();
if (!maybe_tx) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
}
tx_ = maybe_tx.get();
tx_ = builder_.Build().GetTxOrThrow();
// Send the transaction
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction

View File

@ -274,11 +274,7 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c
m_op->builder_.SendChangeTo(zaddr, ovk);
// Build the transaction
auto maybe_tx = m_op->builder_.Build();
if (!maybe_tx) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
}
m_op->tx_ = maybe_tx.get();
m_op->tx_ = m_op->builder_.Build().GetTxOrThrow();
// Send the transaction
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction

View File

@ -383,12 +383,10 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
@ -503,11 +501,9 @@ TEST(WalletTests, FindMySaplingNotes) {
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
// No Sapling notes can be found in tx which does not belong to the wallet
CWalletTx wtx {&wallet, tx};
@ -643,11 +639,9 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
// Fake-mine the transaction
@ -699,19 +693,15 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
// 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 maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
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 maybe_tx3 = builder3.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx3), true);
auto tx3 = maybe_tx3.get();
auto tx3 = builder3.Build().GetTxOrThrow();
CWalletTx wtx2 {&wallet, tx2};
CWalletTx wtx3 {&wallet, tx3};
@ -808,11 +798,9 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
ASSERT_TRUE(wallet.AddSaplingZKey(sk, pk));
@ -905,11 +893,9 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
ASSERT_TRUE(wallet.AddSaplingZKey(sk, pk));
@ -1041,11 +1027,9 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
ASSERT_TRUE(wallet.AddSaplingZKey(sk, pk));
@ -1113,11 +1097,9 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
// 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 maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
auto tx2 = builder2.Build().GetTxOrThrow();
EXPECT_EQ(tx2.vin.size(), 0);
EXPECT_EQ(tx2.vout.size(), 0);
EXPECT_EQ(tx2.vjoinsplit.size(), 0);
@ -1833,11 +1815,9 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
// 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 maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
auto tx = builder.Build().GetTxOrThrow();
// Wallet contains fvk1 but not fvk2
CWalletTx wtx {&wallet, tx};
@ -1985,9 +1965,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk.ovk, pk, 40000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx1 = maybe_tx.get();
auto tx1 = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx1.vin.size(), 1);
EXPECT_EQ(tx1.vout.size(), 0);
@ -2040,11 +2018,9 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// 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 maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
auto tx2 = builder2.Build().GetTxOrThrow();
EXPECT_EQ(tx2.vin.size(), 0);
EXPECT_EQ(tx2.vout.size(), 0);