Skip to content

Commit

Permalink
Auto merge of #3436 - str4d:3214-z_getbalance, r=str4d
Browse files Browse the repository at this point in the history
Add Sapling support to z_getbalance and z_gettotalbalance

Also includes preparatory changes for various other RPCs that depend on `GetFilteredNotes` etc.

Closes #3214.
  • Loading branch information
zkbot committed Aug 25, 2018
2 parents 6868aaf + 573de71 commit c53884d
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 84 deletions.
8 changes: 5 additions & 3 deletions src/wallet/asyncrpcoperation_sendmany.cpp
Expand Up @@ -888,13 +888,14 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {


bool AsyncRPCOperation_sendmany::find_unspent_notes() {
std::vector<CSproutNotePlaintextEntry> entries;
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(entries, fromaddress_, mindepth_);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
}

for (CSproutNotePlaintextEntry & entry : entries) {
for (CSproutNotePlaintextEntry & entry : sproutEntries) {
z_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)), CAmount(entry.plaintext.value())));
std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end());
LogPrint("zrpcunsafe", "%s: found unspent note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n",
Expand All @@ -906,6 +907,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
HexStr(data).substr(0, 10)
);
}
// TODO: Do something with Sapling notes

if (z_inputs_.size() == 0) {
return false;
Expand Down
94 changes: 54 additions & 40 deletions src/wallet/gtest/test_wallet.cpp
Expand Up @@ -186,13 +186,16 @@ TEST(WalletTests, FindUnspentSproutNotes) {
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));

// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
std::vector<CSproutNotePlaintextEntry> entries;
wallet.GetFilteredNotes(entries, "", 0);
EXPECT_EQ(0, entries.size());
entries.clear();
wallet.GetFilteredNotes(entries, "", -1);
EXPECT_EQ(1, entries.size());
entries.clear();
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", -1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();

// Fake-mine the transaction
EXPECT_EQ(-1, chainActive.Height());
Expand All @@ -212,15 +215,18 @@ TEST(WalletTests, FindUnspentSproutNotes) {


// We now have an unspent and confirmed note in the wallet (depth of 1)
wallet.GetFilteredNotes(entries, "", 0);
EXPECT_EQ(1, entries.size());
entries.clear();
wallet.GetFilteredNotes(entries, "", 1);
EXPECT_EQ(1, entries.size());
entries.clear();
wallet.GetFilteredNotes(entries, "", 2);
EXPECT_EQ(0, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();


// Let's spend the note.
Expand All @@ -247,21 +253,25 @@ TEST(WalletTests, FindUnspentSproutNotes) {
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));

// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
wallet.GetFilteredNotes(entries, "", 0);
EXPECT_EQ(0, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's include spent notes to retrieve it.
wallet.GetFilteredNotes(entries, "", 0, false);
EXPECT_EQ(1, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// The spent note has two confirmations.
wallet.GetFilteredNotes(entries, "", 2, false);
EXPECT_EQ(1, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// It does not have 3 confirmations.
wallet.GetFilteredNotes(entries, "", 3, false);
EXPECT_EQ(0, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 3, false);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();


// Let's receive a new note
Expand Down Expand Up @@ -301,21 +311,25 @@ TEST(WalletTests, FindUnspentSproutNotes) {
wallet.AddToWallet(wtx3, true, NULL);

// We now have an unspent note which has one confirmation, in addition to our spent note.
wallet.GetFilteredNotes(entries, "", 1);
EXPECT_EQ(1, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's return the spent note too.
wallet.GetFilteredNotes(entries, "", 1, false);
EXPECT_EQ(2, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1, false);
EXPECT_EQ(2, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Increasing number of confirmations will exclude our new unspent note.
wallet.GetFilteredNotes(entries, "", 2, false);
EXPECT_EQ(1, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// If we also ignore spent notes at this depth, we won't find any notes.
wallet.GetFilteredNotes(entries, "", 2, true);
EXPECT_EQ(0, entries.size());
entries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, true);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();

// Tear down
chainActive.SetTip(NULL);
Expand Down
55 changes: 31 additions & 24 deletions src/wallet/rpcwallet.cpp
Expand Up @@ -2556,10 +2556,11 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
UniValue results(UniValue::VARR);

if (zaddrs.size() > 0) {
std::vector<CUnspentSproutNotePlaintextEntry> entries;
pwalletMain->GetUnspentFilteredNotes(entries, zaddrs, nMinDepth, nMaxDepth, !fIncludeWatchonly);
std::vector<CUnspentSproutNotePlaintextEntry> sproutEntries;
std::vector<UnspentSaplingNoteEntry> saplingEntries;
pwalletMain->GetUnspentFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, !fIncludeWatchonly);
std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs);
for (CUnspentSproutNotePlaintextEntry & entry : entries) {
for (CUnspentSproutNotePlaintextEntry & entry : sproutEntries) {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("txid", entry.jsop.hash.ToString()));
obj.push_back(Pair("jsindex", (int)entry.jsop.js ));
Expand All @@ -2576,6 +2577,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
}
results.push_back(obj);
}
// TODO: Sapling
}

return results;
Expand Down Expand Up @@ -3248,12 +3250,16 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ign

CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) {
CAmount balance = 0;
std::vector<CSproutNotePlaintextEntry> entries;
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(entries, address, minDepth, true, ignoreUnspendable);
for (auto & entry : entries) {
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, address, minDepth, true, ignoreUnspendable);
for (auto & entry : sproutEntries) {
balance += CAmount(entry.plaintext.value());
}
for (auto & entry : saplingEntries) {
balance += CAmount(entry.note.value());
}
return balance;
}

Expand Down Expand Up @@ -3309,10 +3315,11 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
}

UniValue result(UniValue::VARR);
std::vector<CSproutNotePlaintextEntry> entries;
pwalletMain->GetFilteredNotes(entries, fromaddress, nMinDepth, false, false);
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress, nMinDepth, false, false);
auto nullifierSet = hasSproutSpendingKey ? pwalletMain->GetNullifiersForAddresses({zaddr}) : std::set<std::pair<PaymentAddress, uint256>>();
for (CSproutNotePlaintextEntry & entry : entries) {
for (CSproutNotePlaintextEntry & entry : sproutEntries) {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("txid", entry.jsop.hash.ToString()));
obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.plaintext.value()))));
Expand All @@ -3326,6 +3333,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
}
result.push_back(obj);
}
// TODO: Sapling
return result;
}

Expand All @@ -3338,8 +3346,8 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
throw runtime_error(
"z_getbalance \"address\" ( minconf )\n"
"\nReturns the balance of a taddr or zaddr belonging to the node’s wallet.\n"
"\nCAUTION: If address is a watch-only zaddr, the returned balance may be larger than the actual balance,"
"\nbecause spends cannot be detected with incoming viewing keys.\n"
"\nCAUTION: If the wallet has only an incoming viewing key for this address, then spends cannot be"
"\ndetected, and so the returned balance may be larger than the actual balance.\n"
"\nArguments:\n"
"1. \"address\" (string) The selected address. It may be a transparent or private address.\n"
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
Expand Down Expand Up @@ -3374,11 +3382,8 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
if (!IsValidPaymentAddress(res)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr.");
}
// TODO: Add Sapling support. For now, ensure we can freely convert.
assert(boost::get<libzcash::SproutPaymentAddress>(&res) != nullptr);
auto zaddr = boost::get<libzcash::SproutPaymentAddress>(res);
if (!(pwalletMain->HaveSproutSpendingKey(zaddr) || pwalletMain->HaveSproutViewingKey(zaddr))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), res)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, spending key or viewing key not found.");
}
}

Expand All @@ -3402,15 +3407,16 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
throw runtime_error(
"z_gettotalbalance ( minconf includeWatchonly )\n"
"\nReturn the total value of funds stored in the node’s wallet.\n"
"\nCAUTION: If the wallet contains watch-only zaddrs, the returned private balance may be larger than the actual balance,"
"\nbecause spends cannot be detected with incoming viewing keys.\n"
"\nCAUTION: If the wallet contains any addresses for which it only has incoming viewing keys,"
"\nthe returned private balance may be larger than the actual balance, because spends cannot"
"\nbe detected with incoming viewing keys.\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) Only include private and transparent transactions confirmed at least this many times.\n"
"2. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress' and 'z_importviewingkey')\n"
"\nResult:\n"
"{\n"
" \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n"
" \"private\": xxxxx, (numeric) the total balance of private funds\n"
" \"private\": xxxxx, (numeric) the total balance of private funds (in both Sprout and Sapling addresses)\n"
" \"total\": xxxxx, (numeric) the total balance of both transparent and private funds\n"
"}\n"
"\nExamples:\n"
Expand Down Expand Up @@ -4247,11 +4253,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)

if (useAny || useAnyNote || zaddrs.size() > 0) {
// Get available notes
std::vector<CSproutNotePlaintextEntry> entries;
pwalletMain->GetFilteredNotes(entries, zaddrs);
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs);

// Find unspent notes and update estimated size
for (CSproutNotePlaintextEntry& entry : entries) {
for (CSproutNotePlaintextEntry& entry : sproutEntries) {
noteCounter++;
CAmount nValue = entry.plaintext.value();

Expand All @@ -4265,8 +4272,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
maxedOutNotesFlag = true;
} else {
estimatedTxSize += increase;
// TODO: Add Sapling support
auto zaddr = boost::get<SproutPaymentAddress>(entry.address);
auto zaddr = entry.address;
SproutSpendingKey zkey;
pwalletMain->GetSproutSpendingKey(zaddr, zkey);
noteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey);
Expand All @@ -4278,6 +4284,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
remainingNoteValue += nValue;
}
}
// TODO: Add Sapling support
}

size_t numUtxos = utxoInputs.size();
Expand Down

0 comments on commit c53884d

Please sign in to comment.