diff --git a/README.md b/README.md index b3c1eb995..30d55fb62 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BitcoinZ 2.0.7-10 +# BitcoinZ 2.0.8 **Keep running wallet to strengthen the BitcoinZ network. Backup your wallet in many locations & keep your coins wallet offline.** ### Ports: diff --git a/configure.ac b/configure.ac index 100475a3d..7f0df9106 100644 --- a/configure.ac +++ b/configure.ac @@ -2,8 +2,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 2) define(_CLIENT_VERSION_MINOR, 0) -define(_CLIENT_VERSION_REVISION, 7) -define(_CLIENT_VERSION_BUILD, 60) +define(_CLIENT_VERSION_REVISION, 8) +define(_CLIENT_VERSION_BUILD, 50) define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50))) define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1))) define(_CLIENT_VERSION_IS_RELEASE, true) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index fc7eb8c58..3547e241c 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,3 +1,9 @@ +bitcoinz (2.0.8) stable; urgency=low + + * 2.0.8 release. + + -- The BitcoinZ Community Sep 2022 + bitcoinz (2.0.7-10) stable; urgency=medium * 2.0.7-10 release. diff --git a/contrib/debian/copyright b/contrib/debian/copyright index a1fdbe1f7..c918e8889 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -61,6 +61,10 @@ Files: depends/sources/qpid-proton-*.tar.gz Copyright: 2012-2017 The Apache Software Foundation License: Apache-Qpid-Proton-with-BSD-Subcomponents +Files: depends/sources/utfcpp-*.tar.gz +Copyright: 2006 Nemanja Trifunovic +License: Boost-Software-License-1.0 + Files: src/secp256k1/build-aux/m4/ax_jni_include_dir.m4 Copyright: 2008 Don Anderson License: GNU-All-permissive-License @@ -1095,7 +1099,7 @@ License: LGPL-with-ZeroMQ-exception the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you must extend this exception to your version of the library. - + Note: this exception relieves you of any obligations under sections 4 and 5 of this license, and section 6 of the GNU General Public License. Comment: @@ -1323,4 +1327,3 @@ License: GNU-All-permissive-License permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. - diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index 49a4c8d41..7df13dec6 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -1,5 +1,5 @@ --- -name: "bitcoinz-2.0.7-10" +name: "bitcoinz-2.0.8" enable_cache: true distro: "debian" suites: diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index e88bbd441..91f7b9934 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -34,7 +34,7 @@ rust_crates := \ crate_winapi_x86_64_pc_windows_gnu rust_packages := rust $(rust_crates) librustzcash proton_packages := proton -zcash_packages := libgmp libsodium +zcash_packages := libgmp libsodium utfcpp packages := boost openssl libevent zeromq $(zcash_packages) googletest native_packages := native_ccache diff --git a/depends/packages/utfcpp.mk b/depends/packages/utfcpp.mk new file mode 100644 index 000000000..40e6026f7 --- /dev/null +++ b/depends/packages/utfcpp.mk @@ -0,0 +1,10 @@ +package=utfcpp +$(package)_version=3.1 +$(package)_download_path=https://github.com/nemtrif/$(package)/archive/ +$(package)_file_name=$(package)-$($(package)_version).tar.gz +$(package)_download_file=v$($(package)_version).tar.gz +$(package)_sha256_hash=ab531c3fd5d275150430bfaca01d7d15e017a188183be932322f2f651506b096 + +define $(package)_stage_cmds + cp -a ./source $($(package)_staging_dir)$(host_prefix)/include +endef diff --git a/doc/man/bitcoinz-cli.1 b/doc/man/bitcoinz-cli.1 index 28516c5e4..34c461886 100644 --- a/doc/man/bitcoinz-cli.1 +++ b/doc/man/bitcoinz-cli.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOINZ-CLI "1" "May 2022" "bitcoinz-cli v2.0.7-10" "User Commands" +.TH BITCOINZ-CLI "1" "Sep 2022" "bitcoinz-cli v2.0.8" "User Commands" .SH NAME -bitcoinz-cli \- manual page for bitcoinz-cli v2.0.7-10 +bitcoinz-cli \- manual page for bitcoinz-cli v2.0.8 .SH DESCRIPTION -BitcoinZ RPC client version v2.0.7-10 +BitcoinZ RPC client version v2.0.8 .PP In order to ensure you are adequately protecting your privacy when using BitcoinZ, please see . diff --git a/doc/man/bitcoinz-tx.1 b/doc/man/bitcoinz-tx.1 index 1fc0b4931..9eb45c1c9 100644 --- a/doc/man/bitcoinz-tx.1 +++ b/doc/man/bitcoinz-tx.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOINZ-TX "1" "May 2022" "bitcoinz-tx v2.0.7-10" "User Commands" +.TH BITCOINZ-TX "1" "Sep 2022" "bitcoinz-tx v2.0.8" "User Commands" .SH NAME -bitcoinz-tx \- manual page for bitcoinz-tx v2.0.7-10 +bitcoinz-tx \- manual page for bitcoinz-tx v2.0.8 .SH DESCRIPTION -BitcoinZ bitcoinz\-tx utility version v2.0.7-10 +BitcoinZ bitcoinz\-tx utility version v2.0.8 .SS "Usage:" .TP bitcoinz\-tx [options] [commands] diff --git a/doc/man/bitcoinzd.1 b/doc/man/bitcoinzd.1 index 31b8dfc89..ee85749ca 100644 --- a/doc/man/bitcoinzd.1 +++ b/doc/man/bitcoinzd.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOINZD "1" "May 2022" "bitcoinzd v2.0.7-10" "User Commands" +.TH BITCOINZD "1" "Sep 2022" "bitcoinzd v2.0.8" "User Commands" .SH NAME -bitcoinzd \- manual page for bitcoinzd v2.0.7-10 +bitcoinzd \- manual page for bitcoinzd v2.0.8 .SH DESCRIPTION -BitcoinZ Daemon version v2.0.7-10 +BitcoinZ Daemon version v2.0.8 .PP In order to ensure you are adequately protecting your privacy when using BitcoinZ, please see . diff --git a/doc/release-notes/release-notes-2.0.8-RC1.md b/doc/release-notes/release-notes-2.0.8-RC1.md new file mode 100644 index 000000000..4c7be84fe --- /dev/null +++ b/doc/release-notes/release-notes-2.0.8-RC1.md @@ -0,0 +1,2 @@ +Notable changes +=============== diff --git a/qa/rpc-tests/finalsaplingroot.py b/qa/rpc-tests/finalsaplingroot.py index 6d66cd83a..3d37edd4e 100755 --- a/qa/rpc-tests/finalsaplingroot.py +++ b/qa/rpc-tests/finalsaplingroot.py @@ -17,6 +17,7 @@ from decimal import Decimal +SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7" SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb" NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000" @@ -50,12 +51,42 @@ def run_test(self): blk = self.nodes[0].getblock("0") assert_equal(blk["finalsaplingroot"], NULL_FIELD) + + + treestate = self.nodes[0].z_gettreestate("0") + assert_equal(treestate["height"], 0) + assert_equal(treestate["hash"], self.nodes[0].getblockhash(0)) + + assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT) + assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000") + assert("skipHash" not in treestate["sprout"]) + + assert_equal(treestate["sapling"]["commitments"]["finalRoot"], NULL_FIELD) + # There is no sapling state tree yet, and trying to find it in an earlier + # block won't succeed (we're at genesis block), so skipHash is absent. + assert("finalState" not in treestate["sapling"]) + assert("skipHash" not in treestate["sapling"]) + + + # Verify all generated blocks contain the empty root of the Sapling tree. blockcount = self.nodes[0].getblockcount() for height in xrange(1, blockcount + 1): blk = self.nodes[0].getblock(str(height)) assert_equal(blk["finalsaplingroot"], SAPLING_TREE_EMPTY_ROOT) + treestate = self.nodes[0].z_gettreestate(str(height)) + assert_equal(treestate["height"], height) + assert_equal(treestate["hash"], self.nodes[0].getblockhash(height)) + + assert("skipHash" not in treestate["sprout"]) + assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT) + assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000") + + assert("skipHash" not in treestate["sapling"]) + assert_equal(treestate["sapling"]["commitments"]["finalRoot"], SAPLING_TREE_EMPTY_ROOT) + assert_equal(treestate["sapling"]["commitments"]["finalState"], "000000") + # Node 0 shields some funds taddr0 = get_coinbase_address(self.nodes[0]) saplingAddr0 = self.nodes[0].z_getnewaddress('sapling') @@ -71,13 +102,30 @@ def run_test(self): # Verify the final Sapling root has changed blk = self.nodes[0].getblock("201") root = blk["finalsaplingroot"] - assert(root is not SAPLING_TREE_EMPTY_ROOT) - assert(root is not NULL_FIELD) + assert(root is not SAPLING_TREE_EMPTY_ROOT) + assert(root is not NULL_FIELD) # Verify there is a Sapling output description (its commitment was added to tree) result = self.nodes[0].getrawtransaction(mytxid, 1) assert_equal(len(result["vShieldedOutput"]), 1) + + + + # Since there is a now sapling shielded input in the blockchain, + # the sapling values should have changed + new_treestate = self.nodes[0].z_gettreestate(str(-1)) + assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root) + assert_equal(new_treestate["sprout"], treestate["sprout"]) + assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]) + assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]) + assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64) + assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 70) + treestate = new_treestate + + + + # Mine an empty block and verify the final Sapling root does not change self.sync_all() self.nodes[0].generate(1) @@ -111,6 +159,22 @@ def run_test(self): assert_equal(self.nodes[1].z_getbalance(zaddr1), Decimal("10")) assert_equal(root, self.nodes[0].getblock("204")["finalsaplingroot"]) + + + + new_treestate = self.nodes[0].z_gettreestate(str(-1)) + assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root) + assert_equal(new_treestate["sapling"], treestate["sapling"]) + assert(new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"]) + assert(new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"]) + assert_equal(len(new_treestate["sprout"]["commitments"]["finalRoot"]), 64) + assert_equal(len(new_treestate["sprout"]["commitments"]["finalState"]), 134) + treestate = new_treestate + + + + + # Mine a block with a Sapling shielded recipient and verify the final Sapling root changes saplingAddr1 = self.nodes[1].z_getnewaddress("sapling") recipients = [] @@ -130,6 +194,19 @@ def run_test(self): result = self.nodes[0].getrawtransaction(mytxid, 1) assert_equal(len(result["vShieldedOutput"]), 2) # there is Sapling shielded change + + + new_treestate = self.nodes[0].z_gettreestate(str(-1)) + assert_equal(new_treestate["sprout"], treestate["sprout"]) + assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]) + assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]) + assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64) + assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 136) + treestate = new_treestate + + + + # Mine a block with a Sapling shielded sender and transparent recipient and verify the final Sapling root doesn't change taddr2 = self.nodes[0].getnewaddress() recipients = [] @@ -149,5 +226,12 @@ def run_test(self): assert_equal(root, self.nodes[0].getblock("205")["finalsaplingroot"]) + + new_treestate = self.nodes[0].z_gettreestate(str(-1)) + assert_equal(new_treestate["sprout"], treestate["sprout"]) + assert_equal(new_treestate["sapling"], treestate["sapling"]) + + + if __name__ == '__main__': FinalSaplingRootTest().main() diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index 17d2bc5cf..311ef743a 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -18,15 +18,16 @@ class RawTransactionsTest(BitcoinTestFramework): def setup_chain(self): print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) + initialize_chain_clean(self.options.tmpdir, 4) def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir, + self.nodes = start_nodes(4, self.options.tmpdir, extra_args=[['-experimentalfeatures', '-developerencryptwallet']] * 4) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes,0,3) self.is_network_split=False self.sync_all() @@ -37,11 +38,20 @@ def run_test(self): self.nodes[2].generate(1) self.sync_all() - self.nodes[0].generate(101) + self.nodes[0].generate(201) self.sync_all() + + watchonly_address = self.nodes[0].getnewaddress() + watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"] + watchonly_amount = 200 + self.nodes[3].importpubkey(watchonly_pubkey, "", True) + watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount) + self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10); + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5); self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0); self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0); + self.sync_all() self.nodes[0].generate(1) self.sync_all() @@ -434,11 +444,12 @@ def run_test(self): stop_nodes(self.nodes) wait_bitcoinds() - self.nodes = start_nodes(3, self.options.tmpdir) + self.nodes = start_nodes(4, self.options.tmpdir) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes,0,3) self.is_network_split=False self.sync_all() @@ -547,5 +558,46 @@ def run_test(self): assert_equal(len(dec_tx['vout']), 2) # one change output added + ################################################## + # test a fundrawtransaction using only watchonly # + ################################################## + + inputs = [] + outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2} + rawtx = self.nodes[3].createrawtransaction(inputs, outputs) + + result = self.nodes[3].fundrawtransaction(rawtx, True) + res_dec = self.nodes[0].decoderawtransaction(result["hex"]) + assert_equal(len(res_dec["vin"]), 1) + assert_equal(res_dec["vin"][0]["txid"], watchonly_txid) + + assert_equal("fee" in result.keys(), True) + assert_greater_than(result["changepos"], -1) + + ############################################################### + # test fundrawtransaction using the entirety of watched funds # + ############################################################### + + inputs = [] + outputs = {self.nodes[2].getnewaddress() : watchonly_amount} + rawtx = self.nodes[3].createrawtransaction(inputs, outputs) + + result = self.nodes[3].fundrawtransaction(rawtx, True) + res_dec = self.nodes[0].decoderawtransaction(result["hex"]) + assert_equal(len(res_dec["vin"]), 2) + assert(res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid) + + assert_greater_than(result["fee"], 0) + assert_greater_than(result["changepos"], -1) + assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10) + + signedtx = self.nodes[3].signrawtransaction(result["hex"]) + assert(not signedtx["complete"]) + signedtx = self.nodes[0].signrawtransaction(signedtx["hex"]) + assert(signedtx["complete"]) + self.nodes[0].sendrawtransaction(signedtx["hex"]) + + + if __name__ == '__main__': RawTransactionsTest().main() diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py index 28764cd28..473c88306 100755 --- a/qa/rpc-tests/listtransactions.py +++ b/qa/rpc-tests/listtransactions.py @@ -95,6 +95,15 @@ def run_test(self): {"category":"receive","amount":Decimal("0.44")}, {"txid":txid, "account" : ""} ) + multisig = self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()]) + self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True) + txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) + self.nodes[1].generate(1) + self.sync_all() + assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0) + check_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True), + {"category":"receive","amount":Decimal("0.1")}, + {"txid":txid, "account" : "watchonly"} ) + if __name__ == '__main__': ListTransactionsTest().main() - diff --git a/qa/rpc-tests/mempool_resurrect_test.py b/qa/rpc-tests/mempool_resurrect_test.py index b72bc19c5..dac7d199f 100755 --- a/qa/rpc-tests/mempool_resurrect_test.py +++ b/qa/rpc-tests/mempool_resurrect_test.py @@ -56,6 +56,7 @@ def run_test(self): spends2_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw ] blocks.extend(self.nodes[0].generate(1)) + self.sync_all() # mempool should be empty, all txns confirmed assert_equal(set(self.nodes[0].getrawmempool()), set()) @@ -76,6 +77,8 @@ def run_test(self): # Generate another block, they should all get mined self.nodes[0].generate(1) + self.sync_all() + # mempool should be empty, all txns confirmed assert_equal(set(self.nodes[0].getrawmempool()), set()) for txid in spends1_id+spends2_id: diff --git a/qa/rpc-tests/mergetoaddress_helper.py b/qa/rpc-tests/mergetoaddress_helper.py index 973800104..346b3c7fd 100755 --- a/qa/rpc-tests/mergetoaddress_helper.py +++ b/qa/rpc-tests/mergetoaddress_helper.py @@ -63,6 +63,7 @@ def run_test(self, test): do_not_shield_taddr = test.nodes[0].getnewaddress() test.nodes[0].generate(4) + test.sync_all() walletinfo = test.nodes[0].getwalletinfo() assert_equal(walletinfo['immature_balance'], 50) assert_equal(walletinfo['balance'], 0) diff --git a/qa/rpc-tests/paymentdisclosure.py b/qa/rpc-tests/paymentdisclosure.py index a2e37e733..4fe106c6e 100755 --- a/qa/rpc-tests/paymentdisclosure.py +++ b/qa/rpc-tests/paymentdisclosure.py @@ -37,6 +37,7 @@ def run_test (self): print "Mining blocks..." self.nodes[0].generate(4) + self.sync_all() walletinfo = self.nodes[0].getwalletinfo() assert_equal(walletinfo['immature_balance'], 50000) assert_equal(walletinfo['balance'], 0) diff --git a/qa/rpc-tests/shorter_block_times.py b/qa/rpc-tests/shorter_block_times.py index 4c059aed6..7bbf3c618 100755 --- a/qa/rpc-tests/shorter_block_times.py +++ b/qa/rpc-tests/shorter_block_times.py @@ -20,8 +20,8 @@ class ShorterBlockTimes(BitcoinTestFramework): def setup_nodes(self): return start_nodes(4, self.options.tmpdir, [[ - '-nuparams=5ba81b19:0', # Overwinter - '-nuparams=76b809bb:0', # Sapling + #'-nuparams=5ba81b19:0', # Overwinter + #'-nuparams=76b809bb:0', # Sapling '-nuparams=2bb40e60:106', # Blossom ]] * 4) diff --git a/qa/rpc-tests/signrawtransactions.py b/qa/rpc-tests/signrawtransactions.py index e6eff05ed..acb1cbfd8 100755 --- a/qa/rpc-tests/signrawtransactions.py +++ b/qa/rpc-tests/signrawtransactions.py @@ -38,7 +38,9 @@ def successful_signing_test(self): outputs = {'tmJXomn8fhYy3AFqDEteifjHRMUdKtBuTGM': 0.1} - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + # rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + # Also test setting an expiry height of 0. + rawTx = self.nodes[0].createrawtransaction(inputs, outputs, 0, 0) rawTxSigned = self.nodes[0].signrawtransaction(rawTx, inputs, privKeys) # 1) The transaction has a complete set of signatures diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index eaabd31e1..2ac601fe8 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -46,7 +46,8 @@ def str_to_b64str(string): def sync_blocks(rpc_connections, wait=1): """ - Wait until everybody has the same block count + Wait until everybody has the same block count, and has notified + all internal listeners of them """ while True: counts = [ x.getblockcount() for x in rpc_connections ] @@ -54,6 +55,14 @@ def sync_blocks(rpc_connections, wait=1): break time.sleep(wait) + # Now that the block counts are in sync, wait for the internal + # notifications to finish + while True: + notified = [ x.getblockchaininfo()['fullyNotified'] for x in rpc_connections ] + if notified == [ True ] * len(notified): + break + time.sleep(wait) + def sync_mempools(rpc_connections, wait=1): """ Wait until everybody has the same transactions in their memory diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py index aafc67aed..fcf8ee131 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -32,7 +32,8 @@ def run_test (self): print "Mining blocks..." self.nodes[0].generate(4) - + self.sync_all() + walletinfo = self.nodes[0].getwalletinfo() assert_equal(walletinfo['immature_balance'], 50000) assert_equal(walletinfo['balance'], 0) @@ -393,7 +394,7 @@ def run_test (self): recipients = [] recipients.append({"address":self.nodes[0].getnewaddress(), "amount":1}) recipients.append({"address":self.nodes[2].getnewaddress(), "amount":1.0}) - + wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients)) self.sync_all() diff --git a/qa/rpc-tests/wallet_1941.py b/qa/rpc-tests/wallet_1941.py index 4df88b050..aabc40e50 100755 --- a/qa/rpc-tests/wallet_1941.py +++ b/qa/rpc-tests/wallet_1941.py @@ -48,7 +48,8 @@ def run_test (self): self.nodes[0].setmocktime(starttime) self.nodes[0].generate(101) - + self.sync_all() + mytaddr = get_coinbase_address(self.nodes[0]) myzaddr = self.nodes[0].z_getnewaddress('sprout') diff --git a/qa/rpc-tests/wallet_anchorfork.py b/qa/rpc-tests/wallet_anchorfork.py index 177f8e490..81c254713 100755 --- a/qa/rpc-tests/wallet_anchorfork.py +++ b/qa/rpc-tests/wallet_anchorfork.py @@ -29,7 +29,8 @@ def setup_network(self, split=False): def run_test (self): print "Mining blocks..." self.nodes[0].generate(4) - + self.sync_all() + walletinfo = self.nodes[0].getwalletinfo() assert_equal(walletinfo['immature_balance'], 40) assert_equal(walletinfo['balance'], 0) @@ -101,10 +102,10 @@ def run_test (self): # Mine a new block and let it propagate self.nodes[1].generate(1) - + # Due to a bug in v1.0.0-1.0.3, node 0 will die with a tree root assertion, so sync_all() will throw an exception. self.sync_all() - + # v1.0.4 will reach here safely assert_equal( self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash()) assert_equal( self.nodes[1].getbestblockhash(), self.nodes[2].getbestblockhash()) diff --git a/qa/rpc-tests/wallet_listreceived.py b/qa/rpc-tests/wallet_listreceived.py index 16b384ab8..0ab104c64 100755 --- a/qa/rpc-tests/wallet_listreceived.py +++ b/qa/rpc-tests/wallet_listreceived.py @@ -10,7 +10,8 @@ from test_framework.util import start_nodes, wait_and_assert_operationid_status from decimal import Decimal -my_memo = 'c0ffee' # stay awake +my_memo_str = 'c0ffee' # stay awake +my_memo = '633066666565' my_memo = my_memo + '0'*(1024-len(my_memo)) no_memo = 'f6' + ('0'*1022) # see section 5.5 of the protocol spec @@ -36,15 +37,73 @@ def run_test_release(self, release, height): self.generate_and_sync(height+1) taddr = self.nodes[1].getnewaddress() zaddr1 = self.nodes[1].z_getnewaddress(release) + zaddrExt = self.nodes[3].z_getnewaddress(release) - self.nodes[0].sendtoaddress(taddr, 2.0) + self.nodes[0].sendtoaddress(taddr, 4.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}]) + opid = self.nodes[1].z_sendmany(taddr, [ + {'address': zaddr1, 'amount': 1, 'memo': my_memo}, + {'address': zaddrExt, 'amount': 2}, + ]) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() + + # Decrypted transaction details should be correct + pt = self.nodes[1].z_viewtransaction(txid) + assert_equal(pt['txid'], txid) + assert_equal(len(pt['spends']), 0) + assert_equal(len(pt['outputs']), 1 if release == 'sprout' else 2) + + # Output orders can be randomized, so we check the output + # positions and contents separately + outputs = [] + + assert_equal(pt['outputs'][0]['type'], release) + if release == 'sprout': + assert_equal(pt['outputs'][0]['js'], 0) + jsOutputPrev = pt['outputs'][0]['jsOutput'] + elif pt['outputs'][0]['address'] == zaddr1: + assert_equal(pt['outputs'][0]['outgoing'], False) + assert_equal(pt['outputs'][0]['memoStr'], my_memo_str) + else: + assert_equal(pt['outputs'][0]['outgoing'], True) + outputs.append({ + 'address': pt['outputs'][0]['address'], + 'value': pt['outputs'][0]['value'], + 'valueZat': pt['outputs'][0]['valueZat'], + 'memo': pt['outputs'][0]['memo'], + }) + + if release != 'sprout': + assert_equal(pt['outputs'][1]['type'], release) + if pt['outputs'][1]['address'] == zaddr1: + assert_equal(pt['outputs'][1]['outgoing'], False) + assert_equal(pt['outputs'][1]['memoStr'], my_memo_str) + else: + assert_equal(pt['outputs'][1]['outgoing'], True) + outputs.append({ + 'address': pt['outputs'][1]['address'], + 'value': pt['outputs'][1]['value'], + 'valueZat': pt['outputs'][1]['valueZat'], + 'memo': pt['outputs'][1]['memo'], + }) + + assert({ + 'address': zaddr1, + 'value': Decimal('1'), + 'valueZat': 100000000, + 'memo': my_memo, + } in outputs) + if release != 'sprout': + assert({ + 'address': zaddrExt, + 'value': Decimal('2'), + 'valueZat': 200000000, + 'memo': no_memo, + } in outputs) + r = self.nodes[1].z_listreceivedbyaddress(zaddr1) assert_equal(0, len(r), "Should have received no confirmed note") @@ -63,6 +122,7 @@ def run_test_release(self, release, height): assert_equal(r, self.nodes[1].z_listreceivedbyaddress(zaddr1)) # Generate some change by sending part of zaddr1 to zaddr2 + txidPrev = txid zaddr2 = self.nodes[1].z_getnewaddress(release) opid = self.nodes[1].z_sendmany(zaddr1, [{'address': zaddr2, 'amount': 0.6}]) @@ -70,6 +130,65 @@ def run_test_release(self, release, height): self.sync_all() self.generate_and_sync(height+4) + # Decrypted transaction details should be correct + pt = self.nodes[1].z_viewtransaction(txid) + assert_equal(pt['txid'], txid) + assert_equal(len(pt['spends']), 1) + assert_equal(len(pt['outputs']), 2) + + assert_equal(pt['spends'][0]['type'], release) + assert_equal(pt['spends'][0]['txidPrev'], txidPrev) + if release == 'sprout': + assert_equal(pt['spends'][0]['js'], 0) + # jsSpend is randomised during transaction creation + assert_equal(pt['spends'][0]['jsPrev'], 0) + assert_equal(pt['spends'][0]['jsOutputPrev'], jsOutputPrev) + else: + assert_equal(pt['spends'][0]['spend'], 0) + assert_equal(pt['spends'][0]['outputPrev'], 0) + assert_equal(pt['spends'][0]['address'], zaddr1) + assert_equal(pt['spends'][0]['value'], Decimal('1.0')) + assert_equal(pt['spends'][0]['valueZat'], 100000000) + + # Output orders can be randomized, so we check the output + # positions and contents separately + outputs = [] + + assert_equal(pt['outputs'][0]['type'], release) + if release == 'sapling': + assert_equal(pt['outputs'][0]['output'], 0) + assert_equal(pt['outputs'][0]['outgoing'], False) + outputs.append({ + 'address': pt['outputs'][0]['address'], + 'value': pt['outputs'][0]['value'], + 'valueZat': pt['outputs'][0]['valueZat'], + 'memo': pt['outputs'][0]['memo'], + }) + + assert_equal(pt['outputs'][1]['type'], release) + if release == 'sapling': + assert_equal(pt['outputs'][1]['output'], 1) + assert_equal(pt['outputs'][1]['outgoing'], False) + outputs.append({ + 'address': pt['outputs'][1]['address'], + 'value': pt['outputs'][1]['value'], + 'valueZat': pt['outputs'][1]['valueZat'], + 'memo': pt['outputs'][1]['memo'], + }) + + assert({ + 'address': zaddr2, + 'value': Decimal('0.6'), + 'valueZat': 60000000, + 'memo': no_memo, + } in outputs) + assert({ + 'address': zaddr1, + 'value': Decimal('0.3999'), + 'valueZat': 39990000, + 'memo': no_memo, + } in outputs) + # zaddr1 should have a note with change r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0) r = sorted(r, key = lambda received: received['amount']) diff --git a/qa/rpc-tests/wallet_persistence.py b/qa/rpc-tests/wallet_persistence.py index ed34f416b..2d6bc5b46 100755 --- a/qa/rpc-tests/wallet_persistence.py +++ b/qa/rpc-tests/wallet_persistence.py @@ -19,10 +19,10 @@ class WalletPersistenceTest (BitcoinTestFramework): def setup_chain(self): print("Initializing test directory " + self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) + initialize_chain_clean(self.options.tmpdir, 4) def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir, + self.nodes = start_nodes(4, self.options.tmpdir, extra_args=[[ '-nuparams=5ba81b19:100', # Overwinter '-nuparams=76b809bb:201', # Sapling @@ -116,6 +116,13 @@ def run_test(self): self.nodes[2].z_importkey(sk0, "yes") assert_equal(self.nodes[2].z_getbalance(sapling_addr), Decimal('5')) + # Verify importing a viewing key will update and persist the nullifiers and witnesses correctly + extfvk0 = self.nodes[0].z_exportviewingkey(sapling_addr) + self.nodes[3].z_importviewingkey(extfvk0, "yes") + assert_equal(self.nodes[3].z_getbalance(sapling_addr), Decimal('5')) + assert_equal(self.nodes[3].z_gettotalbalance()['private'], '0.00') + assert_equal(self.nodes[3].z_gettotalbalance(1, True)['private'], '5.00') + # Restart the nodes stop_nodes(self.nodes) wait_bitcoinds() @@ -125,6 +132,9 @@ def run_test(self): # Prior to PR #3590, there will be an error as spent notes are considered unspent: # Assertion failed: expected: <25.00000000> but was: <5> assert_equal(self.nodes[2].z_getbalance(sapling_addr), Decimal('5')) + assert_equal(self.nodes[3].z_getbalance(sapling_addr), Decimal('5')) + assert_equal(self.nodes[3].z_gettotalbalance()['private'], '0.00') + assert_equal(self.nodes[3].z_gettotalbalance(1, True)['private'], '5.00') # Verity witnesses persisted correctly by sending shielded funds recipients = [] @@ -141,4 +151,5 @@ def run_test(self): assert_equal(self.nodes[1].z_getbalance(dest_addr), Decimal('16')) if __name__ == '__main__': - WalletPersistenceTest().main() \ No newline at end of file + + WalletPersistenceTest().main() diff --git a/qa/rpc-tests/wallet_sapling.py b/qa/rpc-tests/wallet_sapling.py index fed4fe256..3ac4d02d8 100755 --- a/qa/rpc-tests/wallet_sapling.py +++ b/qa/rpc-tests/wallet_sapling.py @@ -186,11 +186,25 @@ def run_test(self): # Verify importing a spending key will update the nullifiers and witnesses correctly sk0 = self.nodes[0].z_exportkey(saplingAddr0) - self.nodes[2].z_importkey(sk0, "yes") - assert_equal(self.nodes[2].z_getbalance(saplingAddr0), Decimal('10')) + saplingAddrInfo0 = self.nodes[2].z_importkey(sk0, "yes") + assert_equal(saplingAddrInfo0["type"], "sapling") + assert_equal(self.nodes[2].z_getbalance(saplingAddrInfo0["address"]), Decimal('10')) sk1 = self.nodes[1].z_exportkey(saplingAddr1) - self.nodes[2].z_importkey(sk1, "yes") - assert_equal(self.nodes[2].z_getbalance(saplingAddr1), Decimal('5')) + saplingAddrInfo1 = self.nodes[2].z_importkey(sk1, "yes") + assert_equal(saplingAddrInfo1["type"], "sapling") + assert_equal(self.nodes[2].z_getbalance(saplingAddrInfo1["address"]), Decimal('5')) + + # Verify importing a viewing key will update the nullifiers and witnesses correctly + extfvk0 = self.nodes[0].z_exportviewingkey(saplingAddr0) + self.nodes[3].z_importviewingkey(extfvk0, "yes") + assert_equal(self.nodes[3].z_getbalance(saplingAddr0), Decimal('10')) + extfvk1 = self.nodes[1].z_exportviewingkey(saplingAddr1) + self.nodes[3].z_importviewingkey(extfvk1, "yes") + assert_equal(self.nodes[3].z_getbalance(saplingAddr1), Decimal('5')) + + # Verify that z_gettotalbalance only includes watch-only addresses when requested + assert_equal(self.nodes[3].z_gettotalbalance()['private'], '0.00') + assert_equal(self.nodes[3].z_gettotalbalance(1, True)['private'], '15.00') # Make sure we get a useful error when trying to send to both sprout and sapling node4_sproutaddr = self.nodes[3].z_getnewaddress('sprout') diff --git a/qa/rpc-tests/wallet_shieldcoinbase.py b/qa/rpc-tests/wallet_shieldcoinbase.py index 5e906adf5..69e02f8a2 100755 --- a/qa/rpc-tests/wallet_shieldcoinbase.py +++ b/qa/rpc-tests/wallet_shieldcoinbase.py @@ -47,6 +47,7 @@ def run_test (self): self.nodes[0].generate(1) self.nodes[0].generate(4) + self.sync_all() walletinfo = self.nodes[0].getwalletinfo() assert_equal(walletinfo['immature_balance'], 62500) assert_equal(walletinfo['balance'], 0) diff --git a/qa/rpc-tests/walletbackup.py b/qa/rpc-tests/walletbackup.py index ee8ce6f6d..305845841 100755 --- a/qa/rpc-tests/walletbackup.py +++ b/qa/rpc-tests/walletbackup.py @@ -93,6 +93,7 @@ def do_one_round(self): # Must sync mempools before mining. sync_mempools(self.nodes) self.nodes[3].generate(1) + self.sync_all() # As above, this mirrors the original bash test. def start_three(self): diff --git a/qa/rpc-tests/zcjoinsplit.py b/qa/rpc-tests/zcjoinsplit.py index 710a93be3..ae42d9d1a 100755 --- a/qa/rpc-tests/zcjoinsplit.py +++ b/qa/rpc-tests/zcjoinsplit.py @@ -32,6 +32,7 @@ def run_test(self): protect_tx = self.nodes[0].signrawtransaction(joinsplit_result["rawtxn"]) self.nodes[0].sendrawtransaction(protect_tx["hex"]) self.nodes[0].generate(1) + self.sync_all() receive_result = self.nodes[0].zcrawreceive(zcsecretkey, joinsplit_result["encryptednote1"]) assert_equal(receive_result["exists"], True) @@ -41,6 +42,7 @@ def run_test(self): addrtest = self.nodes[0].getnewaddress() for xx in range(0,10): self.nodes[0].generate(1) + self.sync_all() for x in range(0,50): self.nodes[0].sendtoaddress(addrtest, 0.01); @@ -49,7 +51,8 @@ def run_test(self): self.nodes[0].sendrawtransaction(joinsplit_result["rawtxn"]) self.nodes[0].generate(1) - + self.sync_all() + print "Done!" receive_result = self.nodes[0].zcrawreceive(zcsecretkey, joinsplit_result["encryptednote1"]) assert_equal(receive_result["exists"], True) diff --git a/qa/rpc-tests/zkey_import_export.py b/qa/rpc-tests/zkey_import_export.py index b0311e16b..7d8646be2 100755 --- a/qa/rpc-tests/zkey_import_export.py +++ b/qa/rpc-tests/zkey_import_export.py @@ -74,11 +74,6 @@ def get_private_balance(node): balance = node.z_gettotalbalance() return balance['private'] - def find_imported_key(node, import_zaddr): - zaddrs = node.z_listaddresses() - assert(import_zaddr in zaddrs) - return import_zaddr - # Seed Alice with some funds alice.generate(10) self.sync_all() @@ -122,27 +117,28 @@ def find_imported_key(node, import_zaddr): logging.info("Importing bob_privkey into charlie...") # z_importkey rescan defaults to "whenkeyisnew", so should rescan here - charlie.z_importkey(bob_privkey) - ipk_zaddr = find_imported_key(charlie, bob_zaddr) + ipk_zaddr = charlie.z_importkey(bob_privkey) # z_importkey should have rescanned for new key, so this should pass: - verify_utxos(charlie, amounts[:4], ipk_zaddr) + verify_utxos(charlie, amounts[:4], ipk_zaddr["address"]) + + # address is sprout + assert_equal(ipk_zaddr["type"], "sprout") # Verify idempotent behavior: - charlie.z_importkey(bob_privkey) - ipk_zaddr2 = find_imported_key(charlie, bob_zaddr) - assert_equal(ipk_zaddr, ipk_zaddr2) + ipk_zaddr2 = charlie.z_importkey(bob_privkey) + assert_equal(ipk_zaddr["address"], ipk_zaddr2["address"]) # amounts should be unchanged - verify_utxos(charlie, amounts[:4], ipk_zaddr2) + verify_utxos(charlie, amounts[:4], ipk_zaddr2["address"]) logging.info("Sending post-import txns...") for amount in amounts[4:]: z_send(alice, alice_zaddr, bob_zaddr, amount) verify_utxos(bob, amounts, bob_zaddr) - verify_utxos(charlie, amounts, ipk_zaddr) - verify_utxos(charlie, amounts, ipk_zaddr2) + verify_utxos(charlie, amounts, ipk_zaddr["address"]) + verify_utxos(charlie, amounts, ipk_zaddr2["address"]) # keep track of the fees incurred by bob (his sends) bob_fee = Decimal(0) @@ -158,12 +154,11 @@ def find_imported_key(node, import_zaddr): assert_equal(bob.z_getbalance(bob_zaddr), bob_balance) # z_import onto new node "david" (blockchain rescan, default or True?) - david.z_importkey(bob_privkey) - d_ipk_zaddr = find_imported_key(david, bob_zaddr) + d_ipk_zaddr = david.z_importkey(bob_privkey) # Check if amt bob spent is deducted for charlie and david - assert_equal(charlie.z_getbalance(ipk_zaddr), bob_balance) - assert_equal(david.z_getbalance(d_ipk_zaddr), bob_balance) + assert_equal(charlie.z_getbalance(ipk_zaddr["address"]), bob_balance) + assert_equal(david.z_getbalance(d_ipk_zaddr["address"]), bob_balance) if __name__ == '__main__': ZkeyImportExportTest().main() diff --git a/qa/rpc-tests/zmq_test.py b/qa/rpc-tests/zmq_test.py index 69b754240..b4a98fc61 100755 --- a/qa/rpc-tests/zmq_test.py +++ b/qa/rpc-tests/zmq_test.py @@ -41,21 +41,21 @@ def run_test(self): print "listen..." msg = self.zmqSubSocket.recv_multipart() topic = msg[0] - assert_equal(topic, b"hashtx") body = msg[1] - nseq = msg[2] - [nseq] # hush pyflakes msgSequence = struct.unpack(' #include +/** Minimum alert priority for enabling safe mode. */ +static const int ALERT_PRIORITY_SAFE_MODE = 4000; + class CAlert; class CNode; class uint256; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 80407d505..6dfd29373 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -176,6 +176,7 @@ class CMainParams : public CChainParams { bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviews"; bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivks"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-main"; + bech32HRPs[SAPLING_EXTENDED_FVK] = "zxviews"; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -427,6 +428,7 @@ class CTestNetParams : public CChainParams { bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewtestsapling"; bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivktestsapling"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-test"; + bech32HRPs[SAPLING_EXTENDED_FVK] = "zxviewtestsapling"; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); @@ -671,6 +673,7 @@ class CRegTestParams : public CChainParams { bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewregtestsapling"; bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivkregtestsapling"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-regtest"; + bech32HRPs[SAPLING_EXTENDED_FVK] = "zxviewregtestsapling"; // Founders reward script expects a vector of 2-of-3 multisig addresses vCommunityFeeAddress = { diff --git a/src/chainparams.h b/src/chainparams.h index 3ab2fde8a..3e81298d0 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -79,6 +79,7 @@ class CChainParams SAPLING_FULL_VIEWING_KEY, SAPLING_INCOMING_VIEWING_KEY, SAPLING_EXTENDED_SPEND_KEY, + SAPLING_EXTENDED_FVK, MAX_BECH32_TYPES }; diff --git a/src/clientversion.cpp b/src/clientversion.cpp index f71685979..2a300ce81 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -24,7 +24,7 @@ const std::string CLIENT_NAME("YODA"); /** * Client version number */ -#define CLIENT_VERSION_SUFFIX "" +#define CLIENT_VERSION_SUFFIX "1" /** diff --git a/src/clientversion.h b/src/clientversion.h index 864bf1b53..be5b3698b 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -18,8 +18,8 @@ //! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 2 #define CLIENT_VERSION_MINOR 0 -#define CLIENT_VERSION_REVISION 7 -#define CLIENT_VERSION_BUILD 60 +#define CLIENT_VERSION_REVISION 8 +#define CLIENT_VERSION_BUILD 50 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true diff --git a/src/coincontrol.h b/src/coincontrol.h index dc0e8e51a..1adc84757 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -14,6 +14,8 @@ class CCoinControl CTxDestination destChange; //! If false, allows unselected inputs, but requires all selected inputs be used bool fAllowOtherInputs; + //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria + bool fAllowWatchOnly; CCoinControl() { @@ -24,6 +26,7 @@ class CCoinControl { destChange = CNoDestination(); fAllowOtherInputs = false; + fAllowWatchOnly = false; setSelected.clear(); } diff --git a/src/deprecation.h b/src/deprecation.h index a28830561..b19f16fad 100644 --- a/src/deprecation.h +++ b/src/deprecation.h @@ -9,7 +9,7 @@ // Deprecation policy: // * Shut down 16 weeks' worth of blocks after the estimated release block height. // * A warning is shown during the 2 weeks' worth of blocks prior to shut down. -static const int APPROX_RELEASE_HEIGHT = 1028680; // +6 month +static const int APPROX_RELEASE_HEIGHT = 1208650; // Aug 2023 static const int WEEKS_UNTIL_DEPRECATION = 16; static const int DEPRECATION_HEIGHT = APPROX_RELEASE_HEIGHT + (WEEKS_UNTIL_DEPRECATION * 7 * 24 * 24); diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index fba275f59..a74ef066d 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include "utiltest.h" @@ -28,6 +27,20 @@ TEST(Keys, EncodeAndDecodeSapling) auto sk2 = boost::get(spendingkey2); EXPECT_EQ(sk, sk2); } + { + auto extfvk = sk.ToXFVK(); + std::string vk_string = EncodeViewingKey(extfvk); + EXPECT_EQ( + vk_string.substr(0, 7), + Params().Bech32HRP(CChainParams::SAPLING_EXTENDED_FVK)); + + auto viewingkey2 = DecodeViewingKey(vk_string); + EXPECT_TRUE(IsValidViewingKey(viewingkey2)); + + ASSERT_TRUE(boost::get(&viewingkey2) != nullptr); + auto extfvk2 = boost::get(viewingkey2); + EXPECT_EQ(extfvk, extfvk2); + } { auto addr = sk.DefaultAddress(); diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 7dee41f98..fd1e6c303 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -9,7 +9,6 @@ #endif #include "utiltest.h" #include "zcash/Address.hpp" -#include "zcash/zip32.h" #include "json_test_vectors.h" @@ -45,7 +44,7 @@ TEST(keystore_tests, StoreAndRetrieveHDSeed) { TEST(keystore_tests, sapling_keys) { // ["sk, ask, nsk, ovk, ak, nk, ivk, default_d, default_pk_d, note_v, note_r, note_cm, note_pos, note_nf"], UniValue sapling_keys = read_json(MAKE_STRING(json_tests::sapling_key_components)); - + // Skipping over comments in sapling_key_components.json file for (size_t i = 2; i < 12; i++) { uint256 skSeed, ask, nsk, ovk, ak, nk, ivk; @@ -56,40 +55,40 @@ TEST(keystore_tests, sapling_keys) { ak.SetHex(sapling_keys[i][4].getValStr()); nk.SetHex(sapling_keys[i][5].getValStr()); ivk.SetHex(sapling_keys[i][6].getValStr()); - + libzcash::diversifier_t default_d; std::copy_n(ParseHex(sapling_keys[i][7].getValStr()).begin(), 11, default_d.begin()); - + uint256 default_pk_d; default_pk_d.SetHex(sapling_keys[i][8].getValStr()); - + auto sk = libzcash::SaplingSpendingKey(skSeed); - + // Check that expanded spending key from primitives and from sk are the same auto exp_sk_2 = libzcash::SaplingExpandedSpendingKey(ask, nsk, ovk); auto exp_sk = sk.expanded_spending_key(); EXPECT_EQ(exp_sk, exp_sk_2); - + // Check that full viewing key derived from sk and expanded sk are the same auto full_viewing_key = sk.full_viewing_key(); EXPECT_EQ(full_viewing_key, exp_sk.full_viewing_key()); - + // Check that full viewing key from primitives and from sk are the same auto full_viewing_key_2 = libzcash::SaplingFullViewingKey(ak, nk, ovk); EXPECT_EQ(full_viewing_key, full_viewing_key_2); - + // Check that incoming viewing key from primitives and from sk are the same auto in_viewing_key = full_viewing_key.in_viewing_key(); auto in_viewing_key_2 = libzcash::SaplingIncomingViewingKey(ivk); EXPECT_EQ(in_viewing_key, in_viewing_key_2); - + // Check that the default address from primitives and from sk method are the same auto default_addr = sk.default_address(); auto addrOpt2 = in_viewing_key.address(default_d); EXPECT_TRUE(addrOpt2); auto default_addr_2 = addrOpt2.value(); EXPECT_EQ(default_addr, default_addr_2); - + auto default_addr_3 = libzcash::SaplingPaymentAddress(default_d, default_pk_d); EXPECT_EQ(default_addr_2, default_addr_3); EXPECT_EQ(default_addr, default_addr_3); @@ -196,35 +195,83 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) { TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) { CBasicKeyStore keyStore; libzcash::SaplingExtendedSpendingKey skOut; - libzcash::SaplingFullViewingKey fvkOut; + libzcash::SaplingExtendedFullViewingKey extfvkOut; libzcash::SaplingIncomingViewingKey ivkOut; auto sk = GetTestMasterSaplingSpendingKey(); - auto fvk = sk.expsk.full_viewing_key(); - auto ivk = fvk.in_viewing_key(); + auto extfvk = sk.ToXFVK(); + auto ivk = extfvk.fvk.in_viewing_key(); auto addr = sk.DefaultAddress(); // Sanity-check: we can't get a key we haven't added - EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(fvk)); - EXPECT_FALSE(keyStore.GetSaplingSpendingKey(fvk, skOut)); + EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk)); + EXPECT_FALSE(keyStore.GetSaplingSpendingKey(extfvk, skOut)); // Sanity-check: we can't get a full viewing key we haven't added EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk)); - EXPECT_FALSE(keyStore.GetSaplingFullViewingKey(ivk, fvkOut)); + EXPECT_FALSE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut)); // Sanity-check: we can't get an incoming viewing key we haven't added EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr)); EXPECT_FALSE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut)); // When we specify the default address, we get the full mapping - keyStore.AddSaplingSpendingKey(sk, addr); - EXPECT_TRUE(keyStore.HaveSaplingSpendingKey(fvk)); - EXPECT_TRUE(keyStore.GetSaplingSpendingKey(fvk, skOut)); + keyStore.AddSaplingSpendingKey(sk); + EXPECT_TRUE(keyStore.HaveSaplingSpendingKey(extfvk)); + EXPECT_TRUE(keyStore.GetSaplingSpendingKey(extfvk, skOut)); EXPECT_TRUE(keyStore.HaveSaplingFullViewingKey(ivk)); - EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, fvkOut)); + EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut)); EXPECT_TRUE(keyStore.HaveSaplingIncomingViewingKey(addr)); EXPECT_TRUE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut)); EXPECT_EQ(sk, skOut); - EXPECT_EQ(fvk, fvkOut); + EXPECT_EQ(extfvk, extfvkOut); + EXPECT_EQ(ivk, ivkOut); +} + + +TEST(KeystoreTests, StoreAndRetrieveSaplingFullViewingKey) { + CBasicKeyStore keyStore; + libzcash::SaplingExtendedSpendingKey skOut; + libzcash::SaplingExtendedFullViewingKey extfvkOut; + libzcash::SaplingIncomingViewingKey ivkOut; + + auto sk = GetTestMasterSaplingSpendingKey(); + auto extfvk = sk.ToXFVK(); + auto ivk = extfvk.fvk.in_viewing_key(); + auto addr = sk.DefaultAddress(); + + // Sanity-check: we can't get a full viewing key we haven't added + EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk)); + EXPECT_FALSE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut)); + + // and we shouldn't have a spending key or incoming viewing key either + EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk)); + EXPECT_FALSE(keyStore.GetSaplingSpendingKey(extfvk, skOut)); + EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr)); + EXPECT_FALSE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut)); + + // and we can't find the default address in our list of addresses + std::set addresses; + keyStore.GetSaplingPaymentAddresses(addresses); + EXPECT_FALSE(addresses.count(addr)); + + // When we add the full viewing key, we should have it + keyStore.AddSaplingFullViewingKey(extfvk); + EXPECT_TRUE(keyStore.HaveSaplingFullViewingKey(ivk)); + EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut)); + EXPECT_EQ(extfvk, extfvkOut); + + // We should still not have the spending key... + EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk)); + EXPECT_FALSE(keyStore.GetSaplingSpendingKey(extfvk, skOut)); + + // ... but we should have an incoming viewing key + EXPECT_TRUE(keyStore.HaveSaplingIncomingViewingKey(addr)); + EXPECT_TRUE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut)); EXPECT_EQ(ivk, ivkOut); + + // ... and we should find the default address in our list of addresses + addresses.clear(); + keyStore.GetSaplingPaymentAddresses(addresses); + EXPECT_TRUE(addresses.count(addr)); } #ifdef ENABLE_WALLET diff --git a/src/gtest/test_rpc.cpp b/src/gtest/test_rpc.cpp index 3733379a8..a7dc398cb 100644 --- a/src/gtest/test_rpc.cpp +++ b/src/gtest/test_rpc.cpp @@ -26,3 +26,84 @@ TEST(rpc, check_blockToJSON_returns_minified_solution) { UniValue obj = blockToJSON(block, &index); EXPECT_EQ("009f44ff7505d789b964d6817734b8ce1377d456255994370d06e59ac99bd5791b6ad174a66fd71c70e60cfc7fd88243ffe06f80b1ad181625f210779c745524629448e25348a5fce4f346a1735e60fdf53e144c0157dbc47c700a21a236f1efb7ee75f65b8d9d9e29026cfd09048233175202b211b9a49de4ab46f1cac71b6ea57a686377bd612378746e70c61a659c9cd683269e9c2a5cbc1d19f1149345302bbd0a1e62bf4bab01e9caeea789a1519441a61b146de35a4cc75dbdf01029127e311ad5073e7e96397f47226a7df9df66b2086b70756db013bbaeb068260157014b2602fc7dc71336e1439c887d2742d9730b4e79b08ec7839c3e2a037ae1565d04e05e351bb3531e5ef42cf7b71ca1482a9205245dd41f4db0f71644f8bdb88e845558537c03834c06ac83f336651e54e2edfc12e15ea9b7ea2c074e6155654d44c4d3bd90d9511050e9ad87d170db01448e5be6f45419cd86008978db5e3ceab79890234f992648d69bf1053855387db646ccdee5575c65f81dd0f670b016d9f9a84707d91f77b862f697b8bb08365ba71fbe6bfa47af39155a75ebdcb1e5d69f59c40c9e3a64988c1ec26f7f5159eef5c244d504a9e46125948ecc389c2ec3028ac4ff39ffd66e7743970819272b21e0c2df75b308bc62896873952147e57ed79446db4cdb5a563e76ec4c25899d41128afb9a5f8fc8063621efb7a58b9dd666d30c73e318cdcf3393bfec200e160f500e645f7baac263db99fa4a7c1cb4fea219fc512193102034d379f244c21a81821301b8d47c90247713a3e902c762d7bafa6cdb744eeb6d3b50dd175599d02b6e9f5bbda59366e04862aa765135968426e7ac0116de7351940dc57c0ae451d63f667e39891bc81e09e6c76f6f8a7582f7447c6f5945f717b0e52a7e3dd0c6db4061362123cc53fd8ede4abed4865201dc4d8eb4e5d48baa565183b69a5304a44c0600bb24dcaeee9d95ceebd27c1b0a33e0b46f23797d7d7907300b2bb7d62ef2fc5aa139250c73930c621bb5f41fc235534ee8014dfaddd5245aeb01198420ba7b5c076545329c94d54fa725a8e807579f5f0cc9d98170598023268f5930893620190275e6b3c6f5181e36310a9a475208316911d78f917d724c5946c553b7ec042c563c540114b6b78bd4c6e808ee391a4a9d93e127032983c5b3708037b14aa604cfb034e7c8b0ffdd6936446fe80216178506a87402653a373926eeff66e704daf992a0a9a5c3ad80566c0339be9e5b8e35b3b3226b2f7767e20d992ea6c3d6e322eca37b0c7f7e60060802f5abcc1975841365cadbdc3867063addfc803766ae525375ecddee61f9df9ffcd20343c83ab82b0e91de039c59cb435c8d3159cc338b4901f40c9b5c27043bcf2bd5fa9b685b65c9ba5a1e11a51dd3f773051560341f9ec81d05bf259e2d4b7161f896fbb6812cfc924a32120b7367d5e40439e267adda6a1315bb0d6200ce6a503174c8d2a638ea6fd6b1f486d68db11bdca63c4f4a725d1ab6231ea875484e70b27d293c05803386924f283d4c12bb953474d92b7dd43d2d97193bd96281ebb63fa075d2f9ecd310c70ee1d97b5330bd8fb5791c5943ecf084e5f2c83915acac57519c46b166136068d6f9ec0dd598616e32c591128ce13705a283ca39d5b211409600e07b3713113374d9700207a45394eac5b3b7afc9b1b2bad7d89fd3f35f6b2413ce615ee7869b3569009403b96fdacdb32ef0a7e5229e2b666d51e95bdfb009b892e88bde70621a9b6509f068781392df4bdbc5723bb15071993f0d9a11575af5ff6ef85eaea39bc86805b35d8beee91b779354147f2d85304b8b49d053e7444fdd3deb9d16de331f2552af5b3be7766bb8f3f6a78c62148efb231f2268", find_value(obj, "solution").get_str()); } + + + + + +// +// TEST(rpc, CheckExperimentalDisabledHelpMsg) { +// EXPECT_EQ(experimentalDisabledHelpMsg("somerpc", {"somevalue"}), +// "\nWARNING: somerpc is disabled.\n" +// "To enable it, restart zcashd with the following command line options:\n" +// "-experimentalfeatures and -somevalue\n\n" +// "Alternatively add these two lines to the zcash.conf file:\n\n" +// "experimentalfeatures=1\n" +// "somevalue=1\n"); +// EXPECT_EQ(experimentalDisabledHelpMsg("somerpc", {"somevalue", "someothervalue"}), +// "\nWARNING: somerpc is disabled.\n" +// "To enable it, restart zcashd with the following command line options:\n" +// "-experimentalfeatures and -somevalue" +// " or:\n-experimentalfeatures and -someothervalue" +// "\n\n" +// "Alternatively add these two lines to the zcash.conf file:\n\n" +// "experimentalfeatures=1\n" +// "somevalue=1\n" +// "\nor:\n\n" +// "experimentalfeatures=1\n" +// "someothervalue=1\n"); +// EXPECT_EQ(experimentalDisabledHelpMsg("somerpc", {"somevalue", "someothervalue", "athirdvalue"}), +// "\nWARNING: somerpc is disabled.\n" +// "To enable it, restart zcashd with the following command line options:\n" +// "-experimentalfeatures and -somevalue" +// " or:\n-experimentalfeatures and -someothervalue" +// " or:\n-experimentalfeatures and -athirdvalue" +// "\n\n" +// "Alternatively add these two lines to the zcash.conf file:\n\n" +// "experimentalfeatures=1\n" +// "somevalue=1\n" +// "\nor:\n\n" +// "experimentalfeatures=1\n" +// "someothervalue=1\n" +// "\nor:\n\n" +// "experimentalfeatures=1\n" +// "athirdvalue=1\n"); +// } + +TEST(rpc, ParseHeightArg) { + EXPECT_EQ(parseHeightArg("15", 21), 15); + EXPECT_EQ(parseHeightArg("21", 21), 21); + ASSERT_THROW(parseHeightArg("22", 21), UniValue); + EXPECT_EQ(parseHeightArg("0", 21), 0); + EXPECT_EQ(parseHeightArg("011", 21), 11); // allowed and parsed as decimal, not octal + + // negative values count back from current height + EXPECT_EQ(parseHeightArg("-1", 21), 21); + EXPECT_EQ(parseHeightArg("-2", 21), 20); + EXPECT_EQ(parseHeightArg("-22", 21), 0); + ASSERT_THROW(parseHeightArg("-23", 21), UniValue); + ASSERT_THROW(parseHeightArg("-0", 21), UniValue); + + // currentHeight zero + EXPECT_EQ(parseHeightArg("0", 0), 0); + EXPECT_EQ(parseHeightArg("-1", 0), 0); + + // maximum possible height, just beyond, far beyond + EXPECT_EQ(parseHeightArg("2147483647", 2147483647), 2147483647); + ASSERT_THROW(parseHeightArg("2147483648", 2147483647), UniValue); + ASSERT_THROW(parseHeightArg("999999999999999999999999999999999999999", 21), UniValue); + + // disallowed characters and formats + ASSERT_THROW(parseHeightArg("5.21", 21), UniValue); + ASSERT_THROW(parseHeightArg("5.0", 21), UniValue); + ASSERT_THROW(parseHeightArg("a21", 21), UniValue); + ASSERT_THROW(parseHeightArg(" 21", 21), UniValue); + ASSERT_THROW(parseHeightArg("21 ", 21), UniValue); + ASSERT_THROW(parseHeightArg("21x", 21), UniValue); + ASSERT_THROW(parseHeightArg("+21", 21), UniValue); + ASSERT_THROW(parseHeightArg("0x15", 21), UniValue); + ASSERT_THROW(parseHeightArg("-0", 21), UniValue); + ASSERT_THROW(parseHeightArg("-01", 21), UniValue); + ASSERT_THROW(parseHeightArg("-0x15", 21), UniValue); + ASSERT_THROW(parseHeightArg("", 21), UniValue); +} diff --git a/src/gtest/test_zip32.cpp b/src/gtest/test_zip32.cpp index d93ff2492..e7715fd03 100644 --- a/src/gtest/test_zip32.cpp +++ b/src/gtest/test_zip32.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py // Sapling consistently uses little-endian encoding, but uint256S takes its input in diff --git a/src/init.cpp b/src/init.cpp index ad7fbb7ae..70c159a75 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -41,6 +41,7 @@ #include "wallet/wallet.h" #include "wallet/walletdb.h" #endif +#include "warnings.h" #include #include @@ -663,20 +664,22 @@ void ThreadImport(std::vector vImportFiles) } } -void ThreadNotifyRecentlyAdded() +bool InitExperimentalMode() { - while (true) { - // Run the notifier on an integer second in the steady clock. - auto now = std::chrono::steady_clock::now().time_since_epoch(); - auto nextFire = std::chrono::duration_cast( - now + std::chrono::seconds(1)); - std::this_thread::sleep_until( - std::chrono::time_point(nextFire)); - - boost::this_thread::interruption_point(); - - mempool.NotifyRecentlyAdded(); + fExperimentalMode = GetBoolArg("-experimentalfeatures", false); + // Fail if user has set experimental options without the global flag + if (!fExperimentalMode) { + if (mapArgs.count("-developerencryptwallet")) { + return InitError(_("Wallet encryption requires -experimentalfeatures.")); + } else if (mapArgs.count("-developersetpoolsizezero")) { + return InitError(_("Setting the size of shielded pools to zero requires -experimentalfeatures.")); + } else if (mapArgs.count("-paymentdisclosure")) { + return InitError(_("Payment disclosure requires -experimentalfeatures.")); + } else if (mapArgs.count("-insightexplorer")) { + return InitError(_("Insight explorer requires -experimentalfeatures.")); + } } + return true; } /** Sanity checks @@ -940,16 +943,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) nMaxConnections = nFD - MIN_CORE_FILEDESCRIPTORS; // if using block pruning, then disable txindex - // also disable the wallet (for now, until SPV support is implemented in wallet) if (GetArg("-prune", 0)) { if (GetBoolArg("-txindex", false)) return InitError(_("Prune mode is incompatible with -txindex.")); #ifdef ENABLE_WALLET - if (!GetBoolArg("-disablewallet", false)) { - if (SoftSetBoolArg("-disablewallet", true)) - LogPrintf("%s : parameter interaction: -prune -> setting -disablewallet=1\n", __func__); - else - return InitError(_("Can't run with a wallet in prune mode.")); + if (GetBoolArg("-rescan", false)) { + return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again.")); } #endif } @@ -1740,6 +1739,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { + + // We can't rescan beyond non-pruned blocks, so stop and throw an error. + // This might happen if a user uses a old wallet within a pruned node, + // or if they ran -disablewallet for a longer time, then decided to re-enable. + if (fPruneMode) + { + CBlockIndex *block = chainActive.Tip(); + while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block) + block = block->pprev; + + if (pindexRescan != block) + return InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); + } + uiInterface.InitMessage(_("Rescanning...")); LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); @@ -1877,7 +1890,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Start the thread that notifies listeners of transactions that have been // recently added to the mempool. - threadGroup.create_thread(boost::bind(&TraceThread, "txnotify", &ThreadNotifyRecentlyAdded)); + // recently added to the mempool, or have been added to or removed from the + // chain. + threadGroup.create_thread(boost::bind(&TraceThread, "txnotify", &ThreadNotifyWallets)); if (GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) StartTorControl(threadGroup, scheduler); diff --git a/src/key_io.cpp b/src/key_io.cpp index f118e3625..4f41c16b8 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -120,6 +120,22 @@ class ViewingKeyEncoder : public boost::static_visitor return ret; } + std::string operator()(const libzcash::SaplingExtendedFullViewingKey& extfvk) const + { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << extfvk; + // ConvertBits requires unsigned char, but CDataStream uses char + std::vector serkey(ss.begin(), ss.end()); + std::vector data; + // See calculation comment below + data.reserve((serkey.size() * 8 + 4) / 5); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end()); + std::string ret = bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_EXTENDED_FVK), data); + memory_cleanse(serkey.data(), serkey.size()); + memory_cleanse(data.data(), data.size()); + return ret; + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; @@ -161,11 +177,12 @@ class SpendingKeyEncoder : public boost::static_visitor std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; -// Sizes of SaplingPaymentAddress and SaplingSpendingKey after -// ConvertBits<8, 5, true>(). The calculations below take the -// regular serialized size in bytes, convert to bits, and then +// Sizes of SaplingPaymentAddress, SaplingExtendedFullViewingKey, and +// SaplingExtendedSpendingKey after ConvertBits<8, 5, true>(). The calculations +// below take the regular serialized size in bytes, convert to bits, and then // perform ceiling division to get the number of 5-bit clusters. const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5; +const size_t ConvertedSaplingExtendedFullViewingKeySize = (ZIP32_XFVK_SIZE * 8 + 4) / 5; const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5; } // namespace @@ -270,36 +287,57 @@ std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) return boost::apply_visitor(PaymentAddressEncoder(Params()), zaddr); } -libzcash::PaymentAddress DecodePaymentAddress(const std::string& str) +template +T1 DecodeAny( + const std::string& str, + std::pair sprout, + std::pair sapling) { std::vector data; if (DecodeBase58Check(str, data)) { - const std::vector& zaddr_prefix = Params().Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS); - if ((data.size() == libzcash::SerializedSproutPaymentAddressSize + zaddr_prefix.size()) && - std::equal(zaddr_prefix.begin(), zaddr_prefix.end(), data.begin())) { - CSerializeData serialized(data.begin() + zaddr_prefix.size(), data.end()); + const std::vector& prefix = Params().Base58Prefix(sprout.first); + if ((data.size() == sprout.second + prefix.size()) && + std::equal(prefix.begin(), prefix.end(), data.begin())) { + CSerializeData serialized(data.begin() + prefix.size(), data.end()); CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); - libzcash::SproutPaymentAddress ret; + T2 ret; ss >> ret; + memory_cleanse(serialized.data(), serialized.size()); + memory_cleanse(data.data(), data.size()); return ret; } } + data.clear(); auto bech = bech32::Decode(str); - if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS) && - bech.second.size() == ConvertedSaplingPaymentAddressSize) { + if (bech.first == Params().Bech32HRP(sapling.first) && + bech.second.size() == sapling.second) { // Bech32 decoding data.reserve((bech.second.size() * 5) / 8); if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) { CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); - libzcash::SaplingPaymentAddress ret; + T3 ret; ss >> ret; + memory_cleanse(data.data(), data.size()); return ret; } } + + memory_cleanse(data.data(), data.size()); return libzcash::InvalidEncoding(); } +libzcash::PaymentAddress DecodePaymentAddress(const std::string& str) +{ + return DecodeAny( + str, + std::make_pair(CChainParams::ZCPAYMENT_ADDRRESS, libzcash::SerializedSproutPaymentAddressSize), + std::make_pair(CChainParams::SAPLING_PAYMENT_ADDRESS, ConvertedSaplingPaymentAddressSize) + ); +} + bool IsValidPaymentAddressString(const std::string& str) { return IsValidPaymentAddress(DecodePaymentAddress(str)); } @@ -311,22 +349,13 @@ std::string EncodeViewingKey(const libzcash::ViewingKey& vk) libzcash::ViewingKey DecodeViewingKey(const std::string& str) { - std::vector data; - if (DecodeBase58Check(str, data)) { - const std::vector& vk_prefix = Params().Base58Prefix(CChainParams::ZCVIEWING_KEY); - if ((data.size() == libzcash::SerializedSproutViewingKeySize + vk_prefix.size()) && - std::equal(vk_prefix.begin(), vk_prefix.end(), data.begin())) { - CSerializeData serialized(data.begin() + vk_prefix.size(), data.end()); - CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); - libzcash::SproutViewingKey ret; - ss >> ret; - memory_cleanse(serialized.data(), serialized.size()); - memory_cleanse(data.data(), data.size()); - return ret; - } - } - memory_cleanse(data.data(), data.size()); - return libzcash::InvalidEncoding(); + return DecodeAny( + str, + std::make_pair(CChainParams::ZCVIEWING_KEY, libzcash::SerializedSproutViewingKeySize), + std::make_pair(CChainParams::SAPLING_EXTENDED_FVK, ConvertedSaplingExtendedFullViewingKeySize) + ); } std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey) diff --git a/src/key_io.h b/src/key_io.h index 57ec67107..8a4af64d4 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -13,7 +13,6 @@ #include #include