New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sapling support for z_listreceivedbyaddress #3499
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
#!/usr/bin/env python2 | ||
# Copyright (c) 2018 The Zcash developers | ||
# Distributed under the MIT software license, see the accompanying | ||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
from test_framework.test_framework import BitcoinTestFramework | ||
from test_framework.util import assert_equal, assert_true, assert_false | ||
from test_framework.util import start_nodes, wait_and_assert_operationid_status | ||
from decimal import Decimal | ||
|
||
my_memo = 'c0ffee' # stay awake | ||
my_memo = my_memo + '0'*(1024-len(my_memo)) | ||
|
||
no_memo = 'f6' + ('0'*1022) # see section 5.5 of the protocol spec | ||
# sapling generates zero_memo, but this may be fixed soon (to no_memo) | ||
# then this test can be simplified | ||
zero_memo = '0'*1024 | ||
|
||
fee = Decimal('0.0001') | ||
|
||
class ListReceivedTest (BitcoinTestFramework): | ||
|
||
def setup_nodes(self): | ||
return start_nodes(4, self.options.tmpdir, [[ | ||
"-nuparams=5ba81b19:201", # Overwinter | ||
"-nuparams=76b809bb:204", # Sapling | ||
]] * 4) | ||
|
||
def generate_and_sync(self, new_height): | ||
self.nodes[0].generate(1) | ||
self.sync_all() | ||
assert_equal(new_height, self.nodes[0].getblockcount()) | ||
|
||
def run_test_release(self, release, expected_memo, height): | ||
self.generate_and_sync(height+1) | ||
taddr = self.nodes[1].getnewaddress() | ||
zaddr1 = self.nodes[1].z_getnewaddress(release) | ||
|
||
self.nodes[0].sendtoaddress(taddr, 2.0) | ||
self.generate_and_sync(height+2) | ||
|
||
# Send 1 ZEC to zaddr1 | ||
opid = self.nodes[1].z_sendmany(taddr, | ||
[{'address': zaddr1, 'amount': 1, 'memo': my_memo}]) | ||
txid = wait_and_assert_operationid_status(self.nodes[1], opid) | ||
self.sync_all() | ||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1) | ||
assert_equal(0, len(r), "Should have received no confirmed note") | ||
|
||
# No confirmation required, one note should be present | ||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) | ||
assert_equal(1, len(r), "Should have received one (unconfirmed) note") | ||
assert_equal(txid, r[0]['txid']) | ||
assert_equal(1, r[0]['amount']) | ||
assert_false(r[0]['change'], "Note should not be change") | ||
assert_equal(my_memo, r[0]['memo']) | ||
|
||
# Confirm transaction (1 ZEC from taddr to zaddr1) | ||
self.generate_and_sync(height+3) | ||
|
||
# Require one confirmation, note should be present | ||
assert_equal(r, self.nodes[1].z_listreceivedbyaddress(zaddr1)) | ||
|
||
# Generate some change by sending part of zaddr1 to zaddr2 | ||
zaddr2 = self.nodes[1].z_getnewaddress(release) | ||
opid = self.nodes[1].z_sendmany(zaddr1, | ||
[{'address': zaddr2, 'amount': 0.6, 'memo': my_memo}]) | ||
txid = wait_and_assert_operationid_status(self.nodes[1], opid) | ||
self.sync_all() | ||
self.generate_and_sync(height+4) | ||
|
||
# zaddr1 should have a note with change | ||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) | ||
r = sorted(r, key = lambda received: received['amount']) | ||
assert_equal(2, len(r), "zaddr1 Should have received 2 notes") | ||
|
||
assert_equal(txid, r[0]['txid']) | ||
assert_equal(Decimal('0.4')-fee, r[0]['amount']) | ||
assert_true(r[0]['change'], "Note valued at (0.4-fee) should be change") | ||
assert_equal(expected_memo, r[0]['memo']) | ||
|
||
# The old note still exists (it's immutable), even though it is spent | ||
assert_equal(Decimal('1.0'), r[1]['amount']) | ||
assert_false(r[1]['change'], "Note valued at 1.0 should not be change") | ||
assert_equal(expected_memo, r[0]['memo']) | ||
|
||
# zaddr2 should not have change | ||
r = self.nodes[1].z_listreceivedbyaddress(zaddr2, 0) | ||
r = sorted(r, key = lambda received: received['amount']) | ||
assert_equal(1, len(r), "zaddr2 Should have received 1 notes") | ||
assert_equal(txid, r[0]['txid']) | ||
assert_equal(Decimal('0.6'), r[0]['amount']) | ||
assert_false(r[0]['change'], "Note valued at 0.6 should not be change") | ||
assert_equal(my_memo, r[0]['memo']) | ||
|
||
def run_test(self): | ||
self.run_test_release('sprout', no_memo, 200) | ||
self.run_test_release('sapling', zero_memo, 204) | ||
|
||
if __name__ == '__main__': | ||
ListReceivedTest().main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -512,20 +512,50 @@ void CWallet::SetBestChain(const CBlockLocator& loc) | |
SetBestChainINTERNAL(walletdb, loc); | ||
} | ||
|
||
std::set<std::pair<libzcash::PaymentAddress, uint256>> CWallet::GetNullifiersForAddresses(const std::set<libzcash::PaymentAddress> & addresses) | ||
std::set<std::pair<libzcash::PaymentAddress, uint256>> CWallet::GetNullifiersForAddresses( | ||
const std::set<libzcash::PaymentAddress> & addresses) | ||
{ | ||
std::set<std::pair<libzcash::PaymentAddress, uint256>> nullifierSet; | ||
// Sapling ivk -> list of addrs map | ||
LarryRuane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// (There may be more than one diversified address for a given ivk.) | ||
std::map<libzcash::SaplingIncomingViewingKey, std::vector<libzcash::SaplingPaymentAddress>> ivkMap; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eirik said (in a comment that github considers outdated but really isn't) that more than one payment address can be associated with a single incoming viewing key. Sean agreed, although he says this isn't possible yet (but will be in the future). So the "second" of this map is becoming a list of addresses rather than just a single address. This is not tested yet (except in the trivial case of a single address). |
||
for (const auto & addr : addresses) { | ||
auto saplingAddr = boost::get<libzcash::SaplingPaymentAddress>(&addr); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gratuitous change (calling |
||
if (saplingAddr != nullptr) { | ||
libzcash::SaplingIncomingViewingKey ivk; | ||
this->GetSaplingIncomingViewingKey(*saplingAddr, ivk); | ||
ivkMap[ivk].push_back(*saplingAddr); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pretty cool how this constructs the empty vector on the first reference to |
||
} | ||
} | ||
for (const auto & txPair : mapWallet) { | ||
// Sprout | ||
for (const auto & noteDataPair : txPair.second.mapSproutNoteData) { | ||
if (noteDataPair.second.nullifier && addresses.count(noteDataPair.second.address)) { | ||
nullifierSet.insert(std::make_pair(noteDataPair.second.address, noteDataPair.second.nullifier.get())); | ||
auto & noteData = noteDataPair.second; | ||
auto & nullifier = noteData.nullifier; | ||
auto & address = noteData.address; | ||
if (nullifier && addresses.count(address)) { | ||
nullifierSet.insert(std::make_pair(address, nullifier.get())); | ||
} | ||
} | ||
// Sapling | ||
for (const auto & noteDataPair : txPair.second.mapSaplingNoteData) { | ||
auto & noteData = noteDataPair.second; | ||
auto & nullifier = noteData.nullifier; | ||
auto & ivk = noteData.ivk; | ||
if (nullifier && ivkMap.count(ivk)) { | ||
for (const auto & addr : ivkMap[ivk]) { | ||
nullifierSet.insert(std::make_pair(addr, nullifier.get())); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is more than one (equivalent) diversified address for the ivk of this note, then this will add a pair for each of them, even though the note was sent to a specific address. This will result in more than one entry in the output of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also means that a user of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @daira So @LarryRuane and myself have gone over this. For Given the above, we don't think we need to make any changes right now for this, and we don't support diversified addresses yet. Does the above satisfy your review comment? We could open an issue about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right; for I don't understand the argument for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In any case, please add a comment to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @daira, I'm sorry, I'm not seeing what comment to make in
The third (last) loop iterates all the notes in the wallet, each of which has a nullifier and an ivk:
After this loops runs, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What needs to be documented is that this function does not consider the specific address that each note was sent to; it just finds nullifiers associated with equivalent addresses to the ones passed in. That might be correct –it depends on how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In any case, since this PR is merged, let's just track this as part of #3547. |
||
} | ||
} | ||
} | ||
return nullifierSet; | ||
} | ||
|
||
bool CWallet::IsNoteChange(const std::set<std::pair<libzcash::PaymentAddress, uint256>> & nullifierSet, const PaymentAddress & address, const JSOutPoint & jsop) | ||
bool CWallet::IsNoteSproutChange( | ||
const std::set<std::pair<libzcash::PaymentAddress, uint256>> & nullifierSet, | ||
const PaymentAddress & address, | ||
const JSOutPoint & jsop) | ||
{ | ||
// A Note is marked as "change" if the address that received it | ||
// also spent Notes in the same transaction. This will catch, | ||
|
@@ -546,6 +576,26 @@ bool CWallet::IsNoteChange(const std::set<std::pair<libzcash::PaymentAddress, ui | |
return false; | ||
} | ||
|
||
bool CWallet::IsNoteSaplingChange(const std::set<std::pair<libzcash::PaymentAddress, uint256>> & nullifierSet, | ||
const libzcash::PaymentAddress & address, | ||
const SaplingOutPoint & op) | ||
{ | ||
// A Note is marked as "change" if the address that received it | ||
// also spent Notes in the same transaction. This will catch, | ||
// for instance: | ||
// - Change created by spending fractions of Notes (because | ||
// z_sendmany sends change to the originating z-address). | ||
// - Notes created by consolidation transactions (e.g. using | ||
// z_mergetoaddress). | ||
// - Notes sent from one address to itself. | ||
for (const SpendDescription &spend : mapWallet[op.hash].vShieldedSpend) { | ||
if (nullifierSet.count(std::make_pair(address, spend.nullifier))) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit) | ||
{ | ||
LOCK(cs_wallet); // nWalletVersion | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, should the
z_listreceivedbyaddress
output have a field for each note indicating whether it is known to have been spent? Maybe file a ticket for that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done https://github.com/zcash/zcash/projects/23#card-13380398
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant a ticket; project cards don't support comments and are too easy to miss (I barely look at projects except in planning meetings).