Browse Source

Add watch-only support to Zcash RPC methods

Balance totals do not include spends linked to viewing key addresses, as
nullifiers cannot be calculated and therefore spends cannot be detected.
metaverse
Jack Grigg 7 years ago
parent
commit
44e37656bf
No known key found for this signature in database GPG Key ID: 665DBCD284F7DAFF
  1. 45
      qa/rpc-tests/wallet_nullifiers.py
  2. 13
      src/gtest/test_keystore.cpp
  3. 6
      src/keystore.h
  4. 3
      src/rpcclient.cpp
  5. 52
      src/wallet/rpcwallet.cpp

45
qa/rpc-tests/wallet_nullifiers.py

@ -170,5 +170,50 @@ class WalletNullifiersTest (BitcoinTestFramework):
assert_equal(self.nodes[1].z_getbalance(myzaddr), zaddrremaining2)
assert_equal(self.nodes[2].z_getbalance(myzaddr), zaddrremaining2)
# Test viewing keys
node3mined = Decimal('250.0')
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, {
'transparent': node3mined,
'private': zsendmany2notevalue,
'total': node3mined + zsendmany2notevalue,
})
# add node 1 address and node 2 viewing key to node 3
myzvkey = self.nodes[2].z_exportviewingkey(myzaddr)
self.nodes[3].importaddress(mytaddr1)
self.nodes[3].z_importviewingkey(myzvkey)
# Check the address has been imported
assert_equal(myzaddr in self.nodes[3].z_listaddresses(), False)
assert_equal(myzaddr in self.nodes[3].z_listaddresses(True), True)
# Node 3 should see the same received notes as node 2
assert_equal(
self.nodes[2].z_listreceivedbyaddress(myzaddr),
self.nodes[3].z_listreceivedbyaddress(myzaddr))
# Node 3's balances should be unchanged without explicitly requesting
# to include watch-only balances
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, {
'transparent': node3mined,
'private': zsendmany2notevalue,
'total': node3mined + zsendmany2notevalue,
})
# Wallet can't cache nullifiers for notes received by addresses it only has a
# viewing key for, and therefore can't detect spends. So it sees a balance
# corresponding to the sum of all notes the address received.
# TODO: Fix this during the Sapling upgrade (via #2277)
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance(1, True).items()}, {
'transparent': node3mined + Decimal('1.0'),
'private': zsendmany2notevalue + zsendmanynotevalue + zaddrremaining + zaddrremaining2,
'total': node3mined + Decimal('1.0') + zsendmany2notevalue + zsendmanynotevalue + zaddrremaining + zaddrremaining2,
})
# Check individual balances reflect the above
assert_equal(self.nodes[3].z_getbalance(mytaddr1), Decimal('1.0'))
assert_equal(self.nodes[3].z_getbalance(myzaddr), zsendmanynotevalue + zaddrremaining + zaddrremaining2)
if __name__ == '__main__':
WalletNullifiersTest().main ()

13
src/gtest/test_keystore.cpp

@ -65,6 +65,11 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) {
EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut));
EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut));
// and we can't find it in our list of addresses
std::set<libzcash::PaymentAddress> addresses;
keyStore.GetPaymentAddresses(addresses);
EXPECT_FALSE(addresses.count(addr));
keyStore.AddViewingKey(vk);
EXPECT_TRUE(keyStore.HaveViewingKey(addr));
EXPECT_TRUE(keyStore.GetViewingKey(addr, vkOut));
@ -78,11 +83,19 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) {
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
// ... and we should find it in our list of addresses
addresses.clear();
keyStore.GetPaymentAddresses(addresses);
EXPECT_TRUE(addresses.count(addr));
keyStore.RemoveViewingKey(vk);
EXPECT_FALSE(keyStore.HaveViewingKey(addr));
EXPECT_FALSE(keyStore.GetViewingKey(addr, vkOut));
EXPECT_FALSE(keyStore.HaveSpendingKey(addr));
EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut));
addresses.clear();
keyStore.GetPaymentAddresses(addresses);
EXPECT_FALSE(addresses.count(addr));
// We still have a decryptor because those are cached in memory
// (and also we only remove viewing keys when adding a spending key)

6
src/keystore.h

@ -174,6 +174,12 @@ public:
setAddress.insert((*mi).first);
mi++;
}
ViewingKeyMap::const_iterator mvi = mapViewingKeys.begin();
while (mvi != mapViewingKeys.end())
{
setAddress.insert((*mvi).first);
mvi++;
}
}
}

3
src/rpcclient.cpp

@ -103,9 +103,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "zcbenchmark", 1 },
{ "zcbenchmark", 2 },
{ "getblocksubsidy", 0},
{ "z_listaddresses", 0},
{ "z_listreceivedbyaddress", 1},
{ "z_getbalance", 1},
{ "z_gettotalbalance", 0},
{ "z_gettotalbalance", 1},
{ "z_gettotalbalance", 2},
{ "z_sendmany", 1},
{ "z_sendmany", 2},
{ "z_sendmany", 3},

52
src/wallet/rpcwallet.cpp

@ -2960,9 +2960,10 @@ UniValue z_listaddresses(const UniValue& params, bool fHelp)
if (fHelp || params.size() > 1)
throw runtime_error(
"z_listaddresses\n"
"z_listaddresses ( includeWatchonly )\n"
"\nReturns the list of zaddr belonging to the wallet.\n"
"\nArguments:\n"
"1. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n"
"\nResult:\n"
"[ (json array of string)\n"
" \"zaddr\" (string) a zaddr belonging to the wallet\n"
@ -2975,16 +2976,23 @@ UniValue z_listaddresses(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
bool fIncludeWatchonly = false;
if (params.size() > 0) {
fIncludeWatchonly = params[0].get_bool();
}
UniValue ret(UniValue::VARR);
std::set<libzcash::PaymentAddress> addresses;
pwalletMain->GetPaymentAddresses(addresses);
for (auto addr : addresses ) {
ret.push_back(CZCPaymentAddress(addr).ToString());
if (fIncludeWatchonly || pwalletMain->HaveSpendingKey(addr)) {
ret.push_back(CZCPaymentAddress(addr).ToString());
}
}
return ret;
}
CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1) {
CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ignoreUnspendable=true) {
set<CBitcoinAddress> setAddress;
vector<COutput> vecOutputs;
CAmount balance = 0;
@ -3006,6 +3014,10 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1) {
continue;
}
if (ignoreUnspendable && !out.fSpendable) {
continue;
}
if (setAddress.size()) {
CTxDestination address;
if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) {
@ -3023,11 +3035,11 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1) {
return balance;
}
CAmount getBalanceZaddr(std::string address, int minDepth = 1) {
CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) {
CAmount balance = 0;
std::vector<CNotePlaintextEntry> entries;
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(entries, address, minDepth);
pwalletMain->GetFilteredNotes(entries, address, minDepth, true, ignoreUnspendable);
for (auto & entry : entries) {
balance += CAmount(entry.plaintext.value);
}
@ -3076,14 +3088,14 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr.");
}
if (!pwalletMain->HaveSpendingKey(zaddr)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found.");
if (!(pwalletMain->HaveSpendingKey(zaddr) || pwalletMain->HaveViewingKey(zaddr))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
}
UniValue result(UniValue::VARR);
std::vector<CNotePlaintextEntry> entries;
pwalletMain->GetFilteredNotes(entries, fromaddress, nMinDepth, false);
pwalletMain->GetFilteredNotes(entries, fromaddress, nMinDepth, false, false);
for (CNotePlaintextEntry & entry : entries) {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("txid",entry.jsop.hash.ToString()));
@ -3142,16 +3154,16 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
} catch (const std::runtime_error&) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr.");
}
if (!pwalletMain->HaveSpendingKey(zaddr)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found.");
if (!(pwalletMain->HaveSpendingKey(zaddr) || pwalletMain->HaveViewingKey(zaddr))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
}
}
CAmount nBalance = 0;
if (fromTaddr) {
nBalance = getBalanceTaddr(fromaddress, nMinDepth);
nBalance = getBalanceTaddr(fromaddress, nMinDepth, false);
} else {
nBalance = getBalanceZaddr(fromaddress, nMinDepth);
nBalance = getBalanceZaddr(fromaddress, nMinDepth, false);
}
return ValueFromAmount(nBalance);
@ -3163,12 +3175,13 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
if (fHelp || params.size() > 2)
throw runtime_error(
"z_gettotalbalance ( minconf )\n"
"z_gettotalbalance ( minconf includeWatchonly )\n"
"\nReturn the total value of funds stored in the node’s wallet.\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"
@ -3187,19 +3200,24 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
int nMinDepth = 1;
if (params.size() == 1) {
if (params.size() > 0) {
nMinDepth = params[0].get_int();
}
if (nMinDepth < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
}
bool fIncludeWatchonly = false;
if (params.size() > 1) {
fIncludeWatchonly = params[1].get_bool();
}
// getbalance and "getbalance * 1 true" should return the same number
// but they don't because wtx.GetAmounts() does not handle tx where there are no outputs
// pwalletMain->GetBalance() does not accept min depth parameter
// so we use our own method to get balance of utxos.
CAmount nBalance = getBalanceTaddr("", nMinDepth);
CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth);
CAmount nBalance = getBalanceTaddr("", nMinDepth, !fIncludeWatchonly);
CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth, !fIncludeWatchonly);
CAmount nTotalBalance = nBalance + nPrivateBalance;
UniValue result(UniValue::VOBJ);
result.push_back(Pair("transparent", FormatMoney(nBalance)));

Loading…
Cancel
Save