diff --git a/depends/packages/libcurl.mk b/depends/packages/libcurl.mk index 642fc066d..159817139 100644 --- a/depends/packages/libcurl.mk +++ b/depends/packages/libcurl.mk @@ -1,9 +1,10 @@ package=libcurl -$(package)_version=7.54.0 +$(package)_version=7.64.1 +$(package)_dependencies=openssl $(package)_download_path=https://curl.haxx.se/download $(package)_file_name=curl-$($(package)_version).tar.gz -$(package)_sha256_hash=a84b635941c74e26cce69dd817489bec687eb1f230e7d1897fc5b5f108b59adf -$(package)_config_opts_linux=--disable-shared --enable-static --prefix=$(host_prefix) +$(package)_sha256_hash=432d3f466644b9416bc5b649d344116a753aeaa520c8beaf024a90cba9d3d35d +$(package)_config_opts_linux=--disable-shared --enable-static --prefix=$(host_prefix) --host=x86_64-unknown-linux-gnu $(package)_config_opts_mingw32=--enable-mingw --disable-shared --enable-static --prefix=$(host_prefix) --host=x86_64-w64-mingw32 $(package)_config_opts_darwin=--disable-shared --enable-static --prefix=$(host_prefix) $(package)_cflags_darwin=-mmacosx-version-min=10.9 @@ -15,14 +16,23 @@ define $(package)_set_vars endef endif -define $(package)_config_cmds - $($(package)_conf_tool) $($(package)_config_opts) +ifeq ($(build_os),linux) +define $(package)_set_vars + $(package)_config_env=LD_LIBRARY_PATH="$(host_prefix)/lib" PKG_CONFIG_LIBDIR="$(host_prefix)/lib/pkgconfig" CPPFLAGS="-I$(host_prefix)/include" LDFLAGS="-L$(host_prefix)/lib" endef +endif +define $(package)_config_cmds + echo '=== config for $(package):' && \ + echo '$($(package)_config_env) $($(package)_conf_tool) $($(package)_config_opts)' && \ + echo '=== ' && \ + $($(package)_config_env) $($(package)_conf_tool) $($(package)_config_opts) +endef + ifeq ($(build_os),darwin) define $(package)_build_cmds - $(MAKE) CPPFLAGS='-fPIC' CFLAGS='-mmacosx-version-min=10.9' + $(MAKE) CPPFLAGS="-I$(host_prefix)/include -fPIC" CFLAGS='-mmacosx-version-min=10.9' endef else define $(package)_build_cmds @@ -31,5 +41,6 @@ endef endif define $(package)_stage_cmds + echo 'Staging dir: $($(package)_staging_dir)$(host_prefix)/' && \ $(MAKE) DESTDIR=$($(package)_staging_dir) install endef diff --git a/depends/packages/openssl.mk b/depends/packages/openssl.mk index e378088e6..c5ac5bb32 100644 --- a/depends/packages/openssl.mk +++ b/depends/packages/openssl.mk @@ -3,6 +3,7 @@ $(package)_version=1.1.1a $(package)_download_path=https://www.openssl.org/source $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=fc20130f8b7cbd2fb918b2f14e2f429e109c31ddd0fb38fc5d71d9ffed3f9f41 +$(package)_patches=ssl_fix.patch define $(package)_set_vars $(package)_config_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" @@ -22,7 +23,6 @@ $(package)_config_opts+=no-comp $(package)_config_opts+=no-crypto-mdebug $(package)_config_opts+=no-crypto-mdebug-backtrace $(package)_config_opts+=no-ct -$(package)_config_opts+=no-des $(package)_config_opts+=no-dgram $(package)_config_opts+=no-dsa $(package)_config_opts+=no-dso @@ -58,15 +58,12 @@ $(package)_config_opts+=no-scrypt $(package)_config_opts+=no-sctp $(package)_config_opts+=no-seed $(package)_config_opts+=no-shared -$(package)_config_opts+=no-sock $(package)_config_opts+=no-srp $(package)_config_opts+=no-srtp -$(package)_config_opts+=no-ssl $(package)_config_opts+=no-ssl3 $(package)_config_opts+=no-ssl3-method $(package)_config_opts+=no-ssl-trace $(package)_config_opts+=no-stdio -$(package)_config_opts+=no-tls $(package)_config_opts+=no-tls1 $(package)_config_opts+=no-tls1-method $(package)_config_opts+=no-ts @@ -93,7 +90,8 @@ endef define $(package)_preprocess_cmds sed -i.old 's/built on: $date/built on: not available/' util/mkbuildinf.pl && \ - sed -i.old "s|\"engines\", \"apps\", \"test\"|\"engines\"|" Configure + sed -i.old "s|\"engines\", \"apps\", \"test\"|\"engines\"|" Configure && \ + patch -p1 < $($(package)_patch_dir)/ssl_fix.patch endef define $(package)_config_cmds diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 5bc8fcda6..e29c62580 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -47,7 +47,7 @@ native_packages := native_ccache wallet_packages=bdb ifeq ($(host_os),linux) - packages := boost openssl libevent zeromq $(zcash_packages) googletest #googlemock + packages := boost openssl libevent zeromq $(zcash_packages) googletest libcurl #googlemock else packages := boost openssl libevent zeromq $(zcash_packages) libcurl googletest #googlemock endif diff --git a/depends/patches/openssl/ssl_fix.patch b/depends/patches/openssl/ssl_fix.patch new file mode 100644 index 000000000..d7f79fed5 --- /dev/null +++ b/depends/patches/openssl/ssl_fix.patch @@ -0,0 +1,273 @@ +From f725fe5b4b6504df08e30f5194d321c3025e2336 Mon Sep 17 00:00:00 2001 +From: Matt Caswell +Date: Tue, 20 Nov 2018 15:32:55 +0000 +Subject: [PATCH] Fix a RUN_ONCE bug + +We have a number of instances where there are multiple "init" functions for +a single CRYPTO_ONCE variable, e.g. to load config automatically or to not +load config automatically. Unfortunately the RUN_ONCE mechanism was not +correctly giving the right return value where an alternative init function +was being used. + +Reviewed-by: Tim Hudson +(Merged from https://github.com/openssl/openssl/pull/7983) +--- + crypto/init.c | 38 +++++++++----- + include/internal/thread_once.h | 92 ++++++++++++++++++++++++++++++++++ + ssl/ssl_init.c | 6 ++- + 3 files changed, 121 insertions(+), 15 deletions(-) + +diff --git a/crypto/init.c b/crypto/init.c +index 209d1a483da..f20a12f069a 100644 +--- a/crypto/init.c ++++ b/crypto/init.c +@@ -177,12 +177,6 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_nodelete) + + static CRYPTO_ONCE load_crypto_strings = CRYPTO_ONCE_STATIC_INIT; + static int load_crypto_strings_inited = 0; +-DEFINE_RUN_ONCE_STATIC(ossl_init_no_load_crypto_strings) +-{ +- /* Do nothing in this case */ +- return 1; +-} +- + DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_strings) + { + int ret = 1; +@@ -201,6 +195,13 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_strings) + return ret; + } + ++DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_load_crypto_strings, ++ ossl_init_load_crypto_strings) ++{ ++ /* Do nothing in this case */ ++ return 1; ++} ++ + static CRYPTO_ONCE add_all_ciphers = CRYPTO_ONCE_STATIC_INIT; + DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_ciphers) + { +@@ -218,6 +219,13 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_ciphers) + return 1; + } + ++DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_add_all_ciphers, ++ ossl_init_add_all_ciphers) ++{ ++ /* Do nothing */ ++ return 1; ++} ++ + static CRYPTO_ONCE add_all_digests = CRYPTO_ONCE_STATIC_INIT; + DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_digests) + { +@@ -235,7 +243,8 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_digests) + return 1; + } + +-DEFINE_RUN_ONCE_STATIC(ossl_init_no_add_algs) ++DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_add_all_digests, ++ ossl_init_add_all_digests) + { + /* Do nothing */ + return 1; +@@ -255,7 +264,7 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_config) + config_inited = 1; + return 1; + } +-DEFINE_RUN_ONCE_STATIC(ossl_init_no_config) ++DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_config, ossl_init_config) + { + #ifdef OPENSSL_INIT_DEBUG + fprintf(stderr, +@@ -595,8 +604,9 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) + return 0; + + if ((opts & OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS) +- && !RUN_ONCE(&load_crypto_strings, +- ossl_init_no_load_crypto_strings)) ++ && !RUN_ONCE_ALT(&load_crypto_strings, ++ ossl_init_no_load_crypto_strings, ++ ossl_init_load_crypto_strings)) + return 0; + + if ((opts & OPENSSL_INIT_LOAD_CRYPTO_STRINGS) +@@ -604,7 +614,8 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) + return 0; + + if ((opts & OPENSSL_INIT_NO_ADD_ALL_CIPHERS) +- && !RUN_ONCE(&add_all_ciphers, ossl_init_no_add_algs)) ++ && !RUN_ONCE_ALT(&add_all_ciphers, ossl_init_no_add_all_ciphers, ++ ossl_init_add_all_ciphers)) + return 0; + + if ((opts & OPENSSL_INIT_ADD_ALL_CIPHERS) +@@ -612,7 +623,8 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) + return 0; + + if ((opts & OPENSSL_INIT_NO_ADD_ALL_DIGESTS) +- && !RUN_ONCE(&add_all_digests, ossl_init_no_add_algs)) ++ && !RUN_ONCE_ALT(&add_all_digests, ossl_init_no_add_all_digests, ++ ossl_init_add_all_digests)) + return 0; + + if ((opts & OPENSSL_INIT_ADD_ALL_DIGESTS) +@@ -624,7 +636,7 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) + return 0; + + if ((opts & OPENSSL_INIT_NO_LOAD_CONFIG) +- && !RUN_ONCE(&config, ossl_init_no_config)) ++ && !RUN_ONCE_ALT(&config, ossl_init_no_config, ossl_init_config)) + return 0; + + if (opts & OPENSSL_INIT_LOAD_CONFIG) { +diff --git a/include/internal/thread_once.h b/include/internal/thread_once.h +index 224244353ab..e268a959ef3 100644 +--- a/include/internal/thread_once.h ++++ b/include/internal/thread_once.h +@@ -9,6 +9,20 @@ + + #include + ++/* ++ * DEFINE_RUN_ONCE: Define an initialiser function that should be run exactly ++ * once. It takes no arguments and returns and int result (1 for success or ++ * 0 for failure). Typical usage might be: ++ * ++ * DEFINE_RUN_ONCE(myinitfunc) ++ * { ++ * do_some_initialisation(); ++ * if (init_is_successful()) ++ * return 1; ++ * ++ * return 0; ++ * } ++ */ + #define DEFINE_RUN_ONCE(init) \ + static int init(void); \ + int init##_ossl_ret_ = 0; \ +@@ -17,10 +31,30 @@ + init##_ossl_ret_ = init(); \ + } \ + static int init(void) ++ ++/* ++ * DECLARE_RUN_ONCE: Declare an initialiser function that should be run exactly ++ * once that has been defined in another file via DEFINE_RUN_ONCE(). ++ */ + #define DECLARE_RUN_ONCE(init) \ + extern int init##_ossl_ret_; \ + void init##_ossl_(void); + ++/* ++ * DEFINE_RUN_ONCE_STATIC: Define an initialiser function that should be run ++ * exactly once. This function will be declared as static within the file. It ++ * takes no arguments and returns and int result (1 for success or 0 for ++ * failure). Typical usage might be: ++ * ++ * DEFINE_RUN_ONCE_STATIC(myinitfunc) ++ * { ++ * do_some_initialisation(); ++ * if (init_is_successful()) ++ * return 1; ++ * ++ * return 0; ++ * } ++ */ + #define DEFINE_RUN_ONCE_STATIC(init) \ + static int init(void); \ + static int init##_ossl_ret_ = 0; \ +@@ -30,6 +64,46 @@ + } \ + static int init(void) + ++/* ++ * DEFINE_RUN_ONCE_STATIC_ALT: Define an alternative initialiser function. This ++ * function will be declared as static within the file. It takes no arguments ++ * and returns an int result (1 for success or 0 for failure). An alternative ++ * initialiser function is expected to be associated with a primary initialiser ++ * function defined via DEFINE_ONCE_STATIC where both functions use the same ++ * CRYPTO_ONCE object to synchronise. Where an alternative initialiser function ++ * is used only one of the primary or the alternative initialiser function will ++ * ever be called - and that function will be called exactly once. Definitition ++ * of an alternative initialiser function MUST occur AFTER the definition of the ++ * primary initialiser function. ++ * ++ * Typical usage might be: ++ * ++ * DEFINE_RUN_ONCE_STATIC(myinitfunc) ++ * { ++ * do_some_initialisation(); ++ * if (init_is_successful()) ++ * return 1; ++ * ++ * return 0; ++ * } ++ * ++ * DEFINE_RUN_ONCE_STATIC_ALT(myaltinitfunc, myinitfunc) ++ * { ++ * do_some_alternative_initialisation(); ++ * if (init_is_successful()) ++ * return 1; ++ * ++ * return 0; ++ * } ++ */ ++#define DEFINE_RUN_ONCE_STATIC_ALT(initalt, init) \ ++ static int initalt(void); \ ++ static void initalt##_ossl_(void) \ ++ { \ ++ init##_ossl_ret_ = initalt(); \ ++ } \ ++ static int initalt(void) ++ + /* + * RUN_ONCE - use CRYPTO_THREAD_run_once, and check if the init succeeded + * @once: pointer to static object of type CRYPTO_ONCE +@@ -43,3 +117,21 @@ + */ + #define RUN_ONCE(once, init) \ + (CRYPTO_THREAD_run_once(once, init##_ossl_) ? init##_ossl_ret_ : 0) ++ ++/* ++ * RUN_ONCE_ALT - use CRYPTO_THREAD_run_once, to run an alternative initialiser ++ * function and check if that initialisation succeeded ++ * @once: pointer to static object of type CRYPTO_ONCE ++ * @initalt: alternative initialiser function name that was previously given to ++ * DEFINE_RUN_ONCE_STATIC_ALT. This function must return 1 for ++ * success or 0 for failure. ++ * @init: primary initialiser function name that was previously given to ++ * DEFINE_RUN_ONCE_STATIC. This function must return 1 for success or ++ * 0 for failure. ++ * ++ * The return value is 1 on success (*) or 0 in case of error. ++ * ++ * (*) by convention, since the init function must return 1 on success. ++ */ ++#define RUN_ONCE_ALT(once, initalt, init) \ ++ (CRYPTO_THREAD_run_once(once, initalt##_ossl_) ? init##_ossl_ret_ : 0) +diff --git a/ssl/ssl_init.c b/ssl/ssl_init.c +index c0ccb9304a6..96526472c57 100644 +--- a/ssl/ssl_init.c ++++ b/ssl/ssl_init.c +@@ -134,7 +134,8 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_ssl_strings) + return 1; + } + +-DEFINE_RUN_ONCE_STATIC(ossl_init_no_load_ssl_strings) ++DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_load_ssl_strings, ++ ossl_init_load_ssl_strings) + { + /* Do nothing in this case */ + return 1; +@@ -207,7 +208,8 @@ int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS * settings) + return 0; + + if ((opts & OPENSSL_INIT_NO_LOAD_SSL_STRINGS) +- && !RUN_ONCE(&ssl_strings, ossl_init_no_load_ssl_strings)) ++ && !RUN_ONCE_ALT(&ssl_strings, ossl_init_no_load_ssl_strings, ++ ossl_init_load_ssl_strings)) + return 0; + + if ((opts & OPENSSL_INIT_LOAD_SSL_STRINGS) diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index c4775fa85..24e8566d5 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -65,6 +65,7 @@ testScripts=( 'disablewallet.py' 'zcjoinsplit.py' 'zcjoinsplitdoublespend.py' + 'ivk_import_export.py' 'zkey_import_export.py' 'reorg_limit.py' 'getblocktemplate.py' diff --git a/qa/rpc-tests/ivk_import_export.py b/qa/rpc-tests/ivk_import_export.py new file mode 100755 index 000000000..0546b0a44 --- /dev/null +++ b/qa/rpc-tests/ivk_import_export.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python2 +# Copyright (c) 2019 Bartlomiej Lisiecki +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from decimal import Decimal +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_greater_than, start_nodes,\ + initialize_chain_clean, connect_nodes_bi, wait_and_assert_operationid_status + +import logging + +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + +fee = Decimal('0.0001') # constant (but can be changed within reason) + +class IVKImportExportTest (BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self, split=False): + self.nodes = start_nodes(4, self.options.tmpdir, [[ + '-nuparams=5ba81b19:101', # Overwinter + '-nuparams=76b809bb:102', # Sapling + ]] * 4) + + connect_nodes_bi(self.nodes,0,1) + connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes,0,3) + self.is_network_split=False + self.sync_all() + + def run_test(self): + [alice, bob, charlie, miner] = self.nodes + + # the sender loses 'amount' plus fee; to_addr receives exactly 'amount' + def z_send(from_node, from_addr, to_addr, amount): + global fee + opid = from_node.z_sendmany(from_addr, + [{"address": to_addr, "amount": Decimal(amount)}], 1, fee) + wait_and_assert_operationid_status(from_node, opid) + self.sync_all() + miner.generate(1) + self.sync_all() + + def verify_utxos(node, amts, zaddr): + amts.sort(reverse=True) + txs = node.z_listreceivedbyaddress(zaddr) + + def cmp_confirmations_high_to_low(a, b): + return cmp(b["amount"], a["amount"]) + + txs.sort(cmp_confirmations_high_to_low) + print("Sorted txs", txs) + print("amts", amts) + + try: + assert_equal(amts, [tx["amount"] for tx in txs]) + except AssertionError: + logging.error( + 'Expected amounts: %r; txs: %r', + amts, txs) + raise + + def get_private_balance(node): + balance = node.z_gettotalbalance() + return balance['private'] + + def find_imported_zaddr(node, import_zaddr): + zaddrs = node.z_listaddresses() + assert(import_zaddr in zaddrs) + return import_zaddr + + # activate sapling + alice.generate(102) + self.sync_all() + + # sanity-check the test harness + assert_equal(self.nodes[0].getblockcount(), 102) + + # shield alice's coinbase funds to her zaddr + alice_zaddr = alice.z_getnewaddress('sapling') + res = alice.z_shieldcoinbase("*", alice_zaddr) + wait_and_assert_operationid_status(alice, res['opid']) + self.sync_all() + miner.generate(1) + self.sync_all() + + # the amounts of each txn embodied which generates a single utxo: + amounts = map(Decimal, ['2.3', '3.7', '0.1', '0.5', '1.0', '0.19']) + + # internal test consistency assertion: + assert_greater_than( + get_private_balance(alice), + reduce(Decimal.__add__, amounts)) + + + # now get a pristine z-address for receiving transfers: + bob_zaddr = bob.z_getnewaddress('sapling') + verify_utxos(bob, [], bob_zaddr) + + logging.info("sending pre-export txns...") + for amount in amounts[0:2]: + z_send(alice, alice_zaddr, bob_zaddr, amount) + + logging.info("exporting ivk from bob...") + bob_ivk = bob.z_exportviewingkey(bob_zaddr) + + logging.info("sending post-export txns...") + for amount in amounts[2:4]: + z_send(alice, alice_zaddr, bob_zaddr, amount) + + verify_utxos(bob, amounts[:4], bob_zaddr) + + logging.info("importing bob_ivk into charlie...") + # we need to pass bob_zaddr since it's a sapling address + charlie.z_importviewingkey(bob_ivk, 'yes', 0, bob_zaddr) + + # z_importkey should have rescanned for new key, so this should pass: + verify_utxos(charlie, amounts[:4], bob_zaddr) + + # verify idempotent behavior: + charlie.z_importviewingkey(bob_ivk, 'yes', 0, bob_zaddr) + verify_utxos(charlie, amounts[:4], bob_zaddr) + + + 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, bob_zaddr) + +if __name__ == '__main__': + IVKImportExportTest().main() diff --git a/src/cc/CCinclude.h b/src/cc/CCinclude.h index 5a515634d..db1ee7475 100644 --- a/src/cc/CCinclude.h +++ b/src/cc/CCinclude.h @@ -226,8 +226,11 @@ bool priv2addr(char *coinaddr,uint8_t buf33[33],uint8_t priv32[32]); CPubKey buf2pk(uint8_t *buf33); void endiancpy(uint8_t *dest,uint8_t *src,int32_t len); uint256 DiceHashEntropy(uint256 &entropy,uint256 _txidpriv,int32_t entropyvout,int32_t usevout); -CTxOut MakeCC1vout(uint8_t evalcode,CAmount nValue,CPubKey pk, const std::vector>* vData = NULL); -CTxOut MakeCC1of2vout(uint8_t evalcode,CAmount nValue,CPubKey pk,CPubKey pk2, const std::vector>* vData = NULL); +CTxOut MakeCC1vout(uint8_t evalcode,CAmount nValue,CPubKey pk, std::vector>* vData = NULL); +CTxOut MakeCC1of2vout(uint8_t evalcode,CAmount nValue,CPubKey pk,CPubKey pk2, std::vector>* vData = NULL); +int32_t has_opret(const CTransaction &tx, uint8_t evalcode); +CScript getCCopret(const CScript &scriptPubKey); +bool makeCCopret(CScript &opret, std::vector> &vData); CC *MakeCCcond1(uint8_t evalcode,CPubKey pk); CC *MakeCCcond1of2(uint8_t evalcode,CPubKey pk1,CPubKey pk2); CC* GetCryptoCondition(CScript const& scriptSig); diff --git a/src/cc/CCutils.cpp b/src/cc/CCutils.cpp index e9acfbe20..f47bc4212 100644 --- a/src/cc/CCutils.cpp +++ b/src/cc/CCutils.cpp @@ -58,35 +58,70 @@ CC *MakeCCcond1(uint8_t evalcode,CPubKey pk) return CCNewThreshold(2, {condCC, Sig}); } -CTxOut MakeCC1vout(uint8_t evalcode,CAmount nValue, CPubKey pk, const std::vector>* vData) +int32_t has_opret(const CTransaction &tx, uint8_t evalcode) +{ + int i = 0; + for ( auto vout : tx.vout ) + { + if ( vout.scriptPubKey[0] == OP_RETURN && vout.scriptPubKey[1] == evalcode ) + return i; + i++; + } + return 0; +} + +CScript getCCopret(const CScript &scriptPubKey) +{ + std::vector> vParams = std::vector>(); + CScript dummy; CScript opret; + if ( scriptPubKey.IsPayToCryptoCondition(&dummy, vParams) ) + { + //opret << E_MARSHAL(ss << vParams[0]); + opret = CScript(vParams[0].begin()+6, vParams[0].end()); + } + //fprintf(stderr, "params_size.%li parmas_hexstr.%s\n", vParams.size(), HexStr(vParams[0].begin(),vParams[0].end()).c_str()); + //opret = CScript(vParams[0].begin(), vParams[0].end()); + return opret; +} + +bool makeCCopret(CScript &opret, std::vector> &vData) +{ + if ( opret.empty() ) + return false; + vData.push_back(std::vector(opret.begin(), opret.end())); + return true; +} + +CTxOut MakeCC1vout(uint8_t evalcode,CAmount nValue, CPubKey pk, std::vector>* vData) { CTxOut vout; CC *payoutCond = MakeCCcond1(evalcode,pk); vout = CTxOut(nValue,CCPubKey(payoutCond)); if ( vData ) { - std::vector> vtmpData = std::vector>(vData->begin(), vData->end()); + //std::vector> vtmpData = std::vector>(vData->begin(), vData->end()); std::vector vPubKeys = std::vector(); - vPubKeys.push_back(pk); - COptCCParams ccp = COptCCParams(COptCCParams::VERSION, evalcode, 1, 1, vPubKeys, vtmpData); + //vPubKeys.push_back(pk); + COptCCParams ccp = COptCCParams(COptCCParams::VERSION, evalcode, 1, 1, vPubKeys, ( * vData)); vout.scriptPubKey << ccp.AsVector() << OP_DROP; } cc_free(payoutCond); return(vout); } -CTxOut MakeCC1of2vout(uint8_t evalcode,CAmount nValue,CPubKey pk1,CPubKey pk2, const std::vector>* vData) +CTxOut MakeCC1of2vout(uint8_t evalcode,CAmount nValue,CPubKey pk1,CPubKey pk2, std::vector>* vData) { CTxOut vout; CC *payoutCond = MakeCCcond1of2(evalcode,pk1,pk2); vout = CTxOut(nValue,CCPubKey(payoutCond)); if ( vData ) { - std::vector> vtmpData = std::vector>(vData->begin(), vData->end()); + //std::vector> vtmpData = std::vector>(vData->begin(), vData->end()); std::vector vPubKeys = std::vector(); - vPubKeys.push_back(pk1); - vPubKeys.push_back(pk2); - COptCCParams ccp = COptCCParams(COptCCParams::VERSION, evalcode, 1, 2, vPubKeys, vtmpData); + // skip pubkeys. These need to maybe be optional and we need some way to get them out that is easy! + //vPubKeys.push_back(pk1); + //vPubKeys.push_back(pk2); + COptCCParams ccp = COptCCParams(COptCCParams::VERSION, evalcode, 1, 2, vPubKeys, ( * vData)); vout.scriptPubKey << ccp.AsVector() << OP_DROP; } cc_free(payoutCond); @@ -114,8 +149,12 @@ bool IsCCInput(CScript const& scriptSig) bool CheckTxFee(const CTransaction &tx, uint64_t txfee, uint32_t height, uint64_t blocktime) { + LOCK(mempool.cs); + CCoinsView dummy; + CCoinsViewCache view(&dummy); int64_t interest; uint64_t valuein; - CCoinsViewCache &view = *pcoinsTip; + CCoinsViewMemPool viewMemPool(pcoinsTip, mempool); + view.SetBackend(viewMemPool); valuein = view.GetValueIn(height,&interest,tx,blocktime); if ( valuein-tx.GetValueOut() > txfee ) { @@ -584,7 +623,7 @@ bool komodo_txnotarizedconfirmed(uint256 txid) fprintf(stderr,"komodo_txnotarizedconfirmed no hashBlock for txid %s\n",txid.ToString().c_str()); return(0); } - else if ( (pindex= mapBlockIndex[hashBlock]) == 0 || (txheight= pindex->GetHeight()) <= 0 ) + else if ( (pindex= komodo_blockindex(hashBlock)) == 0 || (txheight= pindex->GetHeight()) <= 0 ) { fprintf(stderr,"komodo_txnotarizedconfirmed no txheight.%d %p for txid %s\n",txheight,pindex,txid.ToString().c_str()); return(0); diff --git a/src/cc/channels.cpp b/src/cc/channels.cpp index 254f7e3a5..64bb95572 100644 --- a/src/cc/channels.cpp +++ b/src/cc/channels.cpp @@ -250,10 +250,8 @@ bool ChannelsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & return eval->Invalid("channelopen is not yet confirmed(notarised)!"); else if ( IsCCInput(tx.vin[0].scriptSig) != 0 ) return eval->Invalid("vin.0 is normal for channelpayment!"); - else if ( IsCCInput(tx.vin[1].scriptSig) == 0 ) - return eval->Invalid("vin.1 is CC for channelpayment!"); - else if ( IsCCInput(tx.vin[2].scriptSig) == 0 ) - return eval->Invalid("vin.2 is CC for channelpayment!"); + else if ( IsCCInput(tx.vin[tx.vin.size()-2].scriptSig) == 0 ) + return eval->Invalid("vin."+std::to_string(tx.vin.size()-2)+" is CC for channelpayment!"); else if ( ConstrainVout(tx.vout[1],1,srcmarker,CC_MARKER_VALUE)==0 ) return eval->Invalid("vout.1 is CC marker to srcpub or invalid amount for channelpayment!"); else if ( ConstrainVout(tx.vout[2],1,destmarker,CC_MARKER_VALUE)==0 ) @@ -283,8 +281,8 @@ bool ChannelsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & return eval->Invalid("invalid previous tx OP_RETURN data!"); else if ( ConstrainVout(tx.vout[0],1,channeladdress,(p1-param2)*payment)==0 ) return eval->Invalid("vout.0 is CC or invalid CC change amount for channelpayment!"); - else if ((*cp->ismyvin)(tx.vin[2].scriptSig) == 0 || prevTx.vout[tx.vin[2].prevout.n].nValue!=CC_MARKER_VALUE) - return eval->Invalid("vin.2 is CC marker or invalid marker amount for channelpayment!"); + else if ((*cp->ismyvin)(tx.vin[tx.vin.size()-1].scriptSig) == 0 || prevTx.vout[tx.vin[tx.vin.size()-1].prevout.n].nValue!=CC_MARKER_VALUE) + return eval->Invalid("vin."+std::to_string(tx.vin.size()-1)+" is CC marker or invalid marker amount for channelpayment!"); else if (param1+param2!=p1) return eval->Invalid("invalid payment depth!"); else if (tx.vout[3].nValue > prevTx.vout[0].nValue) @@ -313,10 +311,8 @@ bool ChannelsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & return eval->Invalid("channelopen is not yet confirmed(notarised)!"); else if ( IsCCInput(tx.vin[0].scriptSig) != 0 ) return eval->Invalid("vin.0 is normal for channelclose!"); - else if ( IsCCInput(tx.vin[1].scriptSig) == 0 ) - return eval->Invalid("vin.1 is CC for channelclose!"); - else if ( IsCCInput(tx.vin[2].scriptSig) == 0 ) - return eval->Invalid("vin.2 is CC for channelclose!"); + else if ( IsCCInput(tx.vin[tx.vin.size()-2].scriptSig) == 0 ) + return eval->Invalid("vin."+std::to_string(tx.vin.size()-2)+" is CC for channelclose!"); else if ( ConstrainVout(tx.vout[0],1,channeladdress,0)==0 ) return eval->Invalid("vout.0 is CC for channelclose!"); else if ( ConstrainVout(tx.vout[1],1,srcmarker,CC_MARKER_VALUE)==0 ) @@ -329,8 +325,8 @@ bool ChannelsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & { if ((numvouts=prevTx.vout.size()) > 0 && DecodeChannelsOpRet(prevTx.vout[numvouts-1].scriptPubKey, tokenid, tmp_txid, srcpub, destpub, p1, p2, p3) == 0) return eval->Invalid("invalid previous tx OP_RETURN data!"); - else if ((*cp->ismyvin)(tx.vin[2].scriptSig) == 0 || prevTx.vout[tx.vin[2].prevout.n].nValue!=CC_MARKER_VALUE) - return eval->Invalid("vin.2 is CC marker or invalid marker amount for channelclose!"); + else if ((*cp->ismyvin)(tx.vin[tx.vin.size()-1].scriptSig) == 0 || prevTx.vout[tx.vin[tx.vin.size()-1].prevout.n].nValue!=CC_MARKER_VALUE) + return eval->Invalid("vin."+std::to_string(tx.vin.size()-1)+" is CC marker or invalid marker amount for channelclose!"); else if (tx.vout[0].nValue != prevTx.vout[0].nValue) return eval->Invalid("invalid CC amount, amount must match funds in channel"); } @@ -358,10 +354,8 @@ bool ChannelsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & return eval->Invalid("channelClose is not yet confirmed(notarised)!"); else if ( IsCCInput(tx.vin[0].scriptSig) != 0 ) return eval->Invalid("vin.0 is normal for channelrefund!"); - else if ( IsCCInput(tx.vin[1].scriptSig) == 0 ) - return eval->Invalid("vin.1 is CC for channelrefund!"); - else if ( IsCCInput(tx.vin[2].scriptSig) == 0 ) - return eval->Invalid("vin.2 is CC for channelrefund!"); + else if ( IsCCInput(tx.vin[tx.vin.size()-2].scriptSig) == 0 ) + return eval->Invalid("vin."+std::to_string(tx.vin.size()-2)+" CC for channelrefund!"); else if ( ConstrainVout(tx.vout[0],1,srcmarker,CC_MARKER_VALUE)==0 ) return eval->Invalid("vout.0 is CC marker to srcpub or invalid amount for channelrefund!"); else if ( ConstrainVout(tx.vout[1],1,destmarker,CC_MARKER_VALUE)==0 ) @@ -376,8 +370,8 @@ bool ChannelsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & { if ((numvouts=prevTx.vout.size()) > 0 && DecodeChannelsOpRet(prevTx.vout[numvouts-1].scriptPubKey, tokenid, tmp_txid, srcpub, destpub, p1, p2, p3) == 0) return eval->Invalid("invalid previous tx OP_RETURN data!"); - else if ((*cp->ismyvin)(tx.vin[2].scriptSig) == 0 || prevTx.vout[tx.vin[2].prevout.n].nValue!=CC_MARKER_VALUE) - return eval->Invalid("vin.2 is CC marker or invalid marker amount for channelrefund!"); + else if ((*cp->ismyvin)(tx.vin[tx.vin.size()-1].scriptSig) == 0 || prevTx.vout[tx.vin[tx.vin.size()-1].prevout.n].nValue!=CC_MARKER_VALUE) + return eval->Invalid("vin."+std::to_string(tx.vin.size()-1)+" is CC marker or invalid marker amount for channelrefund!"); else if (tx.vout[2].nValue != prevTx.vout[0].nValue) return eval->Invalid("invalid amount, refund amount and funds in channel must match!"); } diff --git a/src/cc/customcc.cpp b/src/cc/customcc.cpp index 2e174f510..a8b0bf871 100644 --- a/src/cc/customcc.cpp +++ b/src/cc/customcc.cpp @@ -67,38 +67,26 @@ UniValue custom_func1(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) // make op_return payload as normal. CScript opret = custom_opret('1',mypk); std::vector> vData = std::vector>(); - vData.push_back(std::vector(opret.begin(), opret.end())); - // make vout0 with op_return included as payload. - mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount,mypk,&vData)); - fprintf(stderr, "vout size2.%li\n", mtx.vout.size()); - rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,CScript()); - return(custom_rawtxresult(result,rawtx,broadcastflag)); + if ( makeCCopret(opret, vData) ) + { + // make vout0 with op_return included as payload. + mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount,mypk,&vData)); + fprintf(stderr, "vout size2.%li\n", mtx.vout.size()); + rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,CScript()); + return(custom_rawtxresult(result,rawtx,broadcastflag)); + } } return(result); } -bool has_opret(const CTransaction &tx, uint8_t evalcode) -{ - for ( auto vout : tx.vout ) - { - if ( vout.scriptPubKey[0] == OP_RETURN && vout.scriptPubKey[1] == evalcode ) - return true; - } - return false; -} - bool custom_validate(struct CCcontract_info *cp,int32_t height,Eval *eval,const CTransaction tx) { char expectedaddress[64]; CPubKey pk; CScript opret; int32_t numvout; - if ( !has_opret(tx, EVAL_CUSTOM) ) + if ( has_opret(tx, EVAL_CUSTOM) == 0 ) { std::vector> vParams = std::vector>(); - CScript dummy; - if ( tx.vout[0].scriptPubKey.IsPayToCryptoCondition(&dummy, vParams) && vParams.size() == 1 ) - { - opret << E_MARSHAL(ss << vParams[0]); - } + opret = getCCopret(tx.vout[0].scriptPubKey); numvout = 1; } else diff --git a/src/cc/dapps/oraclefeed.c b/src/cc/dapps/oraclefeed.c index 6ac014ad1..4687837ac 100644 --- a/src/cc/dapps/oraclefeed.c +++ b/src/cc/dapps/oraclefeed.c @@ -592,7 +592,7 @@ void addmultisigaddress(char *refcoin,char *acname,int32_t M, char *pubkeys) cJSON *getinputarray(int64_t *totalp,cJSON *unspents,int64_t required) { - cJSON *vin,*item,*vins = cJSON_CreateArray(); int32_t i,n,v; int64_t satoshis; bits256 txid; + cJSON *vin,*item,*vins = cJSON_CreateArray(); int32_t i,j=0,n,v; int64_t satoshis; bits256 txid; *totalp = 0; if ( (n= cJSON_GetArraySize(unspents)) > 0 ) { @@ -610,7 +610,10 @@ cJSON *getinputarray(int64_t *totalp,cJSON *unspents,int64_t required) jaddi(vins,vin); *totalp += satoshis; if ( (*totalp) >= required ) - break; + { + if (j<3) j++; + else break; + } } } } @@ -910,10 +913,11 @@ void update_gatewayspending(int8_t type,char *refcoin,char *acname,char *bindtxi processed++; } free(rawtx); - } else fprintf(stderr,"couldnt create rawtx\n"); + } else fprintf(stderr,"couldnt create rawtx\n"); } else { + rawtx=0; lasttxid = jbits256(item,"last_txid"); if ( lasttxid.txid==withdrawtxid.txid) { @@ -921,24 +925,27 @@ void update_gatewayspending(int8_t type,char *refcoin,char *acname,char *bindtxi } else rawtx=jstr(item,"hex"); K=jint(item,"number_of_signs"); - if ( rawtx!=0 && (clijson=addsignature(refcoin,"",rawtx,M)) != 0 ) + if (rawtx!=0) { - if ( is_cJSON_True(jobj(clijson,"complete")) != 0 ) - { - txid=gatewayscompletesigning(type,refcoin,acname,lasttxid,jstr(clijson,"hex")); - if (txid.txid!=zeroid.txid) fprintf(stderr,"### SIGNING withdraw %s %dof%d\n",bits256_str(str,withdrawtxid),K+1,N); - else fprintf(stderr,"### SIGNING error broadcasting tx on %s\n",acname); - } - else if ( jint(clijson,"partialtx") != 0 ) + if ((clijson=addsignature(refcoin,"",rawtx,M)) != 0 ) { - txid=gatewayspartialsign(type,refcoin,acname,lasttxid,jstr(clijson,"hex")); - if (txid.txid!=zeroid.txid) fprintf(stderr,"### SIGNING withdraw %s %d/%dof%d\n",bits256_str(str,withdrawtxid),K+1,M,N); - else fprintf(stderr,"### SIGNING error broadcasting tx on %s\n",acname); - } - free_json(clijson); - processed++; - if ( lasttxid.txid==withdrawtxid.txid) free(rawtx); - } + if ( is_cJSON_True(jobj(clijson,"complete")) != 0 ) + { + txid=gatewayscompletesigning(type,refcoin,acname,lasttxid,jstr(clijson,"hex")); + if (txid.txid!=zeroid.txid) fprintf(stderr,"### SIGNING withdraw %s %dof%d\n",bits256_str(str,withdrawtxid),K+1,N); + else fprintf(stderr,"### SIGNING error broadcasting tx on %s\n",acname); + } + else if ( jint(clijson,"partialtx") != 0 ) + { + txid=gatewayspartialsign(type,refcoin,acname,lasttxid,jstr(clijson,"hex")); + if (txid.txid!=zeroid.txid) fprintf(stderr,"### SIGNING withdraw %s %d/%dof%d\n",bits256_str(str,withdrawtxid),K+1,M,N); + else fprintf(stderr,"### SIGNING error broadcasting tx on %s\n",acname); + } + free_json(clijson); + processed++; + if ( lasttxid.txid==withdrawtxid.txid) free(rawtx); + } + } else fprintf(stderr,"couldnt create rawtx or find previous partial signed tx\n"); } } } diff --git a/src/cc/gateways.cpp b/src/cc/gateways.cpp index d88b3d25f..00c80082b 100644 --- a/src/cc/gateways.cpp +++ b/src/cc/gateways.cpp @@ -628,20 +628,25 @@ bool GatewaysValidate(struct CCcontract_info *cp,Eval *eval,const CTransaction & return eval->Invalid("deposit amount greater then bind total supply"); else if (komodo_txnotarizedconfirmed(deposittxid) == false) return eval->Invalid("gatewaysdeposit tx is not yet confirmed(notarised)!"); - else if (myGetTransaction(tx.vin[2].prevout.hash,tmptx,hashblock) == 0) - return eval->Invalid("invalid gatewaysdeposittxid!"); else if (IsCCInput(tx.vin[0].scriptSig) != 0) return eval->Invalid("vin.0 is normal for gatewaysclaim!"); - else if ((*cp->ismyvin)(tx.vin[tx.vin.size()-1].scriptSig) == 0 || myGetTransaction(tx.vin[tx.vin.size()-1].prevout.hash,tmptx,hashblock)==0 || tmptx.vout[tx.vin[tx.vin.size()-1].prevout.n].nValue!=CC_MARKER_VALUE) + else if (tx.vin.size()>2) + { + i=1; + while (i<=tx.vin.size()-2) + { + if (IsCCInput(tx.vin[i].scriptSig)==0) return eval->Invalid("vin."+std::to_string(i)+" is CC for gatewaysclaim!"); + i++; + } + } + else if ((*cp->ismyvin)(tx.vin[tx.vin.size()-1].scriptSig) == 0 || tmptx.vout[tx.vin[tx.vin.size()-1].prevout.n].nValue!=CC_MARKER_VALUE) return eval->Invalid("vin."+std::to_string(tx.vin.size()-1)+" is CC marker for gatewaysclaim or invalid marker amount!"); else if (_GetCCaddress(destaddr,EVAL_TOKENS,pubkey)==0 || ConstrainVout(tx.vout[0],1,destaddr,amount)==0) return eval->Invalid("invalid vout tokens to destpub for gatewaysclaim!"); - else if (numvouts>2 && (myGetTransaction(tx.vin[1].prevout.hash,tmptx,hashblock)==0 || ConstrainVout(tx.vout[1],1,gatewaystokensaddr,tmptx.vout[tx.vin[1].prevout.n].nValue-amount)==0)) + else if (numvouts>2 && tx.vout[1].scriptPubKey.IsPayToCryptoCondition() && (myGetTransaction(tx.vin[1].prevout.hash,tmptx,hashblock)==0 || ConstrainVout(tx.vout[1],1,gatewaystokensaddr,tmptx.vout[tx.vin[1].prevout.n].nValue-amount)==0)) return eval->Invalid("invalid CC change vout for gatewaysclaim!"); else if (amount!=tmpamount) - return eval->Invalid("claimed amount different then deposit amount"); - else if (tx.vout[0].nValue!=amount) - return eval->Invalid("claim amount not matching amount in opret"); + return eval->Invalid("claimed amount different then deposit amount"); else if (pubkey!=tmppubkey) return eval->Invalid("claim destination pubkey different than in deposit tx"); else @@ -697,8 +702,6 @@ bool GatewaysValidate(struct CCcontract_info *cp,Eval *eval,const CTransaction & return eval->Invalid("invalid marker vout for gatewaysWithdraw!"); else if ( ConstrainVout(tmptx.vout[1],1,gatewaystokensaddr,amount)==0) return eval->Invalid("invalid tokens to gateways vout for gatewaysWithdraw!"); - else if (tmptx.vout[1].nValue!=amount) - return eval->Invalid("amount in opret not matching tx tokens amount!"); else if (komodo_txnotarizedconfirmed(withdrawtxid) == false) return eval->Invalid("gatewayswithdraw tx is not yet confirmed(notarised)!"); else if (myGetTransaction(bindtxid,tmptx,hashblock) == 0) @@ -953,7 +956,7 @@ std::string GatewaysBind(uint64_t txfee,std::string coin,uint256 tokenid,int64_t LOGSTREAM("gatewayscc",CCLOG_INFO, stream << CCerror << std::endl); return(""); } - if ( AddNormalinputs(mtx,mypk,txfee+CC_MARKER_VALUE,3) > 0 ) + if ( AddNormalinputs(mtx,mypk,txfee+CC_MARKER_VALUE,2) > 0 ) { if (AddTokenCCInputs(cpTokens, mtx, mypk, tokenid, totalsupply, 64)>0) { @@ -1032,7 +1035,7 @@ std::string GatewaysDeposit(uint64_t txfee,uint256 bindtxid,int32_t height,std:: LOGSTREAM("gatewayscc",CCLOG_INFO, stream << CCerror << std::endl); return(""); } - if ( AddNormalinputs(mtx,mypk,txfee+2*CC_MARKER_VALUE,4) > 0 ) + if ( AddNormalinputs(mtx,mypk,txfee+2*CC_MARKER_VALUE,3) > 0 ) { mtx.vout.push_back(MakeCC1vout(cp->evalcode,CC_MARKER_VALUE,destpub)); mtx.vout.push_back(CTxOut(CC_MARKER_VALUE,CScript() << ParseHex(HexStr(CCtxidaddr(txidaddr,cointxid))) << OP_CHECKSIG)); @@ -1104,7 +1107,7 @@ std::string GatewaysClaim(uint64_t txfee,uint256 bindtxid,std::string refcoin,ui LOGSTREAM("gatewayscc",CCLOG_INFO, stream << CCerror << std::endl); return(""); } - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + if ( AddNormalinputs(mtx,mypk,txfee,1) > 0 ) { if ((inputs=AddGatewaysInputs(cp, mtx, gatewayspk, bindtxid, amount, 60)) > 0) { @@ -1182,7 +1185,7 @@ std::string GatewaysWithdraw(uint64_t txfee,uint256 bindtxid,std::string refcoin } } } - if( AddNormalinputs(mtx, mypk, txfee+CC_MARKER_VALUE, 4) > 0 ) + if( AddNormalinputs(mtx, mypk, txfee+CC_MARKER_VALUE, 2) > 0 ) { if ((inputs = AddTokenCCInputs(cpTokens, mtx, mypk, tokenid, amount, 60)) > 0) { @@ -1294,7 +1297,7 @@ std::string GatewaysPartialSign(uint64_t txfee,uint256 lasttxid,std::string refc return(""); } } - if (AddNormalinputs(mtx,mypk,txfee,3)!=0) + if (AddNormalinputs(mtx,mypk,txfee,1)!=0) { mtx.vin.push_back(CTxIn(tx.GetHash(),0,CScript())); mtx.vout.push_back(MakeCC1vout(EVAL_GATEWAYS,CC_MARKER_VALUE,gatewayspk)); @@ -1393,7 +1396,7 @@ std::string GatewaysCompleteSigning(uint64_t txfee,uint256 lasttxid,std::string return(""); } } - if (AddNormalinputs(mtx,mypk,txfee,3)!=0) + if (AddNormalinputs(mtx,mypk,txfee,1)!=0) { mtx.vin.push_back(CTxIn(lasttxid,0,CScript())); mtx.vout.push_back(MakeCC1vout(EVAL_GATEWAYS,CC_MARKER_VALUE,gatewayspk)); @@ -1458,7 +1461,7 @@ std::string GatewaysMarkDone(uint64_t txfee,uint256 completetxid,std::string ref LOGSTREAM("gatewayscc",CCLOG_INFO, stream << CCerror << std::endl); return(""); } - if (AddNormalinputs(mtx,mypk,txfee,3)!=0) + if (AddNormalinputs(mtx,mypk,txfee,1)!=0) { mtx.vin.push_back(CTxIn(completetxid,0,CScript())); mtx.vout.push_back(CTxOut(CC_MARKER_VALUE,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); diff --git a/src/cc/payments.cpp b/src/cc/payments.cpp index d83cd0bec..8bd41b333 100644 --- a/src/cc/payments.cpp +++ b/src/cc/payments.cpp @@ -16,6 +16,12 @@ #include "CCPayments.h" /* + +-earlytxid is not an -ac_param, so it doesnt affect the chain magics +extra data after the normal CCvout is whatever data we want and can represent whatever we want +so -ac_script= +in the validation if you see the useearlytxid in the opreturn data or extra data, you use the earlytxid as the txid that specifies the payment + 0) txidopret <- allocation, scriptPubKey, opret 1) create <- locked_blocks, minrelease, list of txidopret @@ -193,11 +199,10 @@ bool PaymentsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & { if ( lockedblocks < 0 || minrelease < 0 || totalallocations <= 0 || txidoprets.size() < 2 ) return(eval->Invalid("negative values")); - if ( !CheckTxFee(tx, PAYMENTS_TXFEE, chainActive.LastTip()->GetHeight(), chainActive.LastTip()->nTime) ) - return eval->Invalid("txfee is too high"); Paymentspk = GetUnspendable(cp,0); //fprintf(stderr, "lockedblocks.%i minrelease.%i totalallocations.%i txidopret1.%s txidopret2.%s\n",lockedblocks, minrelease, totalallocations, txidoprets[0].ToString().c_str(), txidoprets[1].ToString().c_str() ); - + if ( !CheckTxFee(tx, PAYMENTS_TXFEE+1, chainActive.LastTip()->GetHeight(), chainActive.LastTip()->nTime) ) + return eval->Invalid("txfee is too high"); // Get all the script pubkeys and allocations std::vector allocations; std::vector scriptPubKeys; @@ -212,6 +217,20 @@ bool PaymentsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & allocations.push_back(allocation); //fprintf(stderr, "i.%i scriptpubkey.%s allocation.%li\n",i,scriptPubKeys[i].ToString().c_str(),allocation); checkallocations += allocation; + // if we have an op_return to pay to need to check it exists and is paying the correct opret. + if ( !opret.empty() ) + { + if ( !fHasOpret ) + { + fprintf(stderr, "missing opret.%s in payments release.\n",HexStr(opret.begin(), opret.end()).c_str()); + return(eval->Invalid("missing opret in payments release")); + } + else if ( CScript(opret.begin(),opret.end()) != tx.vout[tx.vout.size()-1].scriptPubKey ) + { + fprintf(stderr, "opret.%s vs opret.%s\n",HexStr(opret.begin(), opret.end()).c_str(), HexStr(tx.vout[tx.vout.size()-1].scriptPubKey.begin(), tx.vout[tx.vout.size()-1].scriptPubKey.end()).c_str()); + return(eval->Invalid("pays incorrect opret")); + } + } } i++; } @@ -247,7 +266,7 @@ bool PaymentsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & int64_t test = allocations[n]; test *= checkamount; test /= totalallocations; - if ( test != tx.vout[i].nValue ) + if ( test != tx.vout[i].nValue && test != tx.vout[i].nValue-1 ) { fprintf(stderr, "vout.%i test.%li vs nVlaue.%li\n",i, test, tx.vout[i].nValue); return(eval->Invalid("amounts do not match")); @@ -277,18 +296,28 @@ bool PaymentsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction & Getscriptaddress(destaddr,txin.vout[vin.prevout.n].scriptPubKey); if ( strcmp(destaddr,coinaddr) != 0 ) { - std::vector scriptPubKey,opret; uint256 checktxid; - if ( txin.vout.size() < 2 || DecodePaymentsFundOpRet(txin.vout[txin.vout.size()-1].scriptPubKey,checktxid) != 'F' || checktxid != createtxid ) + CScript opret; uint256 checktxid; int32_t opret_ind; + if ( (opret_ind= has_opret(txin, EVAL_PAYMENTS)) == 0 ) + { + // get op_return from CCvout + opret = getCCopret(txin.vout[0].scriptPubKey); + } + else + { + // get op_return from the op_return + opret = txin.vout[opret_ind].scriptPubKey; + } // else return(eval->Invalid("vin has wrong amount of vouts")); // dont think this is needed? + if ( DecodePaymentsFundOpRet(opret,checktxid) != 'F' || checktxid != createtxid ) { fprintf(stderr, "vin.%i is not a payments CC vout: txid.%s\n", i, txin.GetHash().ToString().c_str()); return(eval->Invalid("vin is not paymentsCC type")); - } //else fprintf(stderr, "vin.%i opret type txid.%s\n", i, txin.GetHash().ToString().c_str()); + } } - // check the chain depth vs locked blcoks requirement. - CBlockIndex* pblockindex = mapBlockIndex[blockhash]; - if ( pblockindex->GetHeight() > ht-lockedblocks ) + // check the chain depth vs locked blocks requirement. + CBlockIndex* pblockindex = komodo_blockindex(blockhash); + if ( pblockindex == 0 || pblockindex->GetHeight() > ht-lockedblocks ) { - fprintf(stderr, "vin.%i is not elegible to be spent yet height.%i vs elegible_ht.%i\n", i, pblockindex->GetHeight(), ht-lockedblocks); + fprintf(stderr, "vin.%i is not elegible to be spent yet height.%i vs elegible_ht.%i\n", i, pblockindex!=0?pblockindex->GetHeight():0, ht-lockedblocks); return(eval->Invalid("vin not elegible")); } } else return(eval->Invalid("cant get vin transaction")); @@ -340,8 +369,18 @@ int64_t AddPaymentsInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CP } if ( iter == 0 ) { - std::vector scriptPubKey,opret; - if ( myGetTransaction(txid,tx,hashBlock) == 0 || tx.vout.size() < 2 || DecodePaymentsFundOpRet(tx.vout[tx.vout.size()-1].scriptPubKey,checktxid) != 'F' || checktxid != createtxid ) + CScript opret; uint256 checktxid; int32_t opret_ind; + if ( (opret_ind= has_opret(vintx, EVAL_PAYMENTS)) == 0 ) + { + // get op_return from CCvout + opret = getCCopret(vintx.vout[0].scriptPubKey); + } + else + { + // get op_return from the op_return + opret = vintx.vout[opret_ind].scriptPubKey; + } + if ( myGetTransaction(txid,tx,hashBlock) == 0 || DecodePaymentsFundOpRet(opret,checktxid) != 'F' || checktxid != createtxid ) { fprintf(stderr,"bad opret %s vs %s\n",checktxid.GetHex().c_str(),createtxid.GetHex().c_str()); continue; @@ -524,7 +563,7 @@ UniValue PaymentsRelease(struct CCcontract_info *cp,char *jsonstr) newamount += (PAYMENTS_TXFEE - mtx.vout[i+1].nValue); mtx.vout[i+1].nValue = PAYMENTS_TXFEE; } - } + } if ( (inputsum= AddPaymentsInputs(cp,mtx,txidpk,newamount+2*PAYMENTS_TXFEE,CC_MAXVINS/2,createtxid,latestheight)) >= newamount+2*PAYMENTS_TXFEE ) { std::string rawtx; @@ -603,10 +642,16 @@ UniValue PaymentsFund(struct CCcontract_info *cp,char *jsonstr) } else { - mtx.vout.push_back(MakeCC1vout(EVAL_PAYMENTS,amount,Paymentspk)); opret = EncodePaymentsFundOpRet(txid); + fprintf(stderr, "opret.%s\n", HexStr(opret.begin(), opret.end()).c_str()); + std::vector> vData = std::vector>(); + if ( makeCCopret(opret, vData) ) + { + mtx.vout.push_back(MakeCC1vout(EVAL_PAYMENTS,amount,Paymentspk,&vData)); + fprintf(stderr, "params_size.%li parmas_hexstr.%s\n", vData.size(), HexStr(vData[0].begin(),vData[0].end()).c_str()); + } } - rawtx = FinalizeCCTx(0,cp,mtx,mypk,PAYMENTS_TXFEE,opret); + rawtx = FinalizeCCTx(0,cp,mtx,mypk,PAYMENTS_TXFEE,CScript()); if ( params != 0 ) free_json(params); return(payments_rawtxresult(result,rawtx,1)); @@ -630,24 +675,34 @@ UniValue PaymentsFund(struct CCcontract_info *cp,char *jsonstr) UniValue PaymentsTxidopret(struct CCcontract_info *cp,char *jsonstr) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); UniValue result(UniValue::VOBJ); CPubKey mypk; std::string rawtx; - std::vector scriptPubKey,opret; int32_t allocation,n,retval0,retval1=0; + std::vector scriptPubKey,opret; int32_t n,retval0,retval1=0; int64_t allocation; cJSON *params = payments_reparse(&n,jsonstr); mypk = pubkey2pk(Mypubkey()); if ( params != 0 && n > 1 && n <= 3 ) { - allocation = juint(jitem(params,0),0); + allocation = (int64_t)jint(jitem(params,0),0); retval0 = payments_parsehexdata(scriptPubKey,jitem(params,1),0); - if ( n == 3 ) - retval1 = payments_parsehexdata(opret,jitem(params,2),0); - if ( allocation > 0 && retval0 == 0 && retval1 == 0 && AddNormalinputs(mtx,mypk,PAYMENTS_TXFEE,10) > 0 ) + CScript test = CScript(scriptPubKey.begin(),scriptPubKey.end()); + txnouttype whichType; + if (!::IsStandard(test, whichType)) { - rawtx = FinalizeCCTx(0,cp,mtx,mypk,PAYMENTS_TXFEE,EncodePaymentsTxidOpRet(allocation,scriptPubKey,opret)); - if ( params != 0 ) - free_json(params); - return(payments_rawtxresult(result,rawtx,1)); + result.push_back(Pair("result","error")); + result.push_back(Pair("error","scriptPubkey is not valid payment.")); + } + else + { + if ( n == 3 ) + retval1 = payments_parsehexdata(opret,jitem(params,2),0); + if ( allocation > 0 && retval0 == 0 && retval1 == 0 && AddNormalinputs(mtx,mypk,PAYMENTS_TXFEE*2,10) > 0 ) + { + rawtx = FinalizeCCTx(0,cp,mtx,mypk,PAYMENTS_TXFEE,EncodePaymentsTxidOpRet(allocation,scriptPubKey,opret)); + if ( params != 0 ) + free_json(params); + return(payments_rawtxresult(result,rawtx,1)); + } + result.push_back(Pair("result","error")); + result.push_back(Pair("error","invalid params or cant find txfee")); } - result.push_back(Pair("result","error")); - result.push_back(Pair("error","invalid params or cant find txfee")); } else { @@ -867,7 +922,7 @@ UniValue PaymentsInfo(struct CCcontract_info *cp,char *jsonstr) funds = CCaddress_balance(fundsaddr,1); result.push_back(Pair(fundsaddr,ValueFromAmount(funds))); GetCCaddress(cp,fundsopretaddr,Paymentspk); - fundsopret = CCaddress_balance(fundsopretaddr,1); + fundsopret = CCaddress_balance(fundsopretaddr,1); // Shows balance for ALL payments plans, not just the one asked for! result.push_back(Pair(fundsopretaddr,ValueFromAmount(fundsopret))); result.push_back(Pair("totalfunds",ValueFromAmount(funds+fundsopret))); result.push_back(Pair("result","success")); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b09ccf7c3..b8b2998b7 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -289,6 +289,8 @@ void *chainparams_commandline(void *ptr) mainParams.SetNValue(ASSETCHAINS_NK[0]); mainParams.SetKValue(ASSETCHAINS_NK[1]); } + if ( KOMODO_TESTNODE != 0 ) + mainParams.SetMiningRequiresPeers(false); if ( ASSETCHAINS_RPCPORT == 0 ) ASSETCHAINS_RPCPORT = ASSETCHAINS_P2PPORT + 1; mainParams.pchMessageStart[0] = ASSETCHAINS_MAGIC & 0xff; diff --git a/src/chainparams.h b/src/chainparams.h index 3b1ad1f72..daa16af8c 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -121,6 +121,7 @@ public: void SetCheckpointData(CCheckpointData checkpointData); void SetNValue(uint64_t n) { nEquihashN = n; } void SetKValue(uint64_t k) { nEquihashK = k; } + void SetMiningRequiresPeers(bool flag) { fMiningRequiresPeers = flag; } //void setnonce(uint32_t nonce) { memcpy(&genesis.nNonce,&nonce,sizeof(nonce)); } //void settimestamp(uint32_t timestamp) { genesis.nTime = timestamp; } diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index bd9599421..ae5831261 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -44,5 +44,19 @@ TEST(Keys, EncodeAndDecodeSapling) auto addr2 = boost::get(paymentaddr2); EXPECT_EQ(addr, addr2); } + { + auto ivk = sk.ToXFVK().fvk.in_viewing_key(); + std::string ivk_string = EncodeViewingKey(ivk); + EXPECT_EQ( + ivk_string.substr(0, 5), + Params().Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY)); + + auto viewing_key = DecodeViewingKey(ivk_string); + EXPECT_TRUE(IsValidViewingKey(viewing_key)); + + auto ivk2 = boost::get(&viewing_key); + ASSERT_TRUE(ivk2 != nullptr); + EXPECT_EQ(*ivk2, ivk); + } } } diff --git a/src/key_io.cpp b/src/key_io.cpp index 1953d9623..014159d65 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -128,6 +128,19 @@ public: return ret; } + std::string operator()(const libzcash::SaplingIncomingViewingKey& vk) const + { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << vk; + std::vector serkey(ss.begin(), ss.end()); + std::vector data; + 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_INCOMING_VIEWING_KEY), data); + memory_cleanse(serkey.data(), serkey.size()); + memory_cleanse(data.data(), data.size()); + return ret; + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; @@ -175,6 +188,7 @@ public: // perform ceiling division to get the number of 5-bit clusters. const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5; const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5; +const size_t ConvertedSaplingIncomingViewingKeySize = (32 * 8 + 4) / 5; } // namespace CKey DecodeSecret(const std::string& str) @@ -346,7 +360,19 @@ libzcash::ViewingKey DecodeViewingKey(const std::string& str) return ret; } } - memory_cleanse(data.data(), data.size()); + data.clear(); + auto bech = bech32::Decode(str); + if(bech.first == Params().Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY) && + bech.second.size() == ConvertedSaplingIncomingViewingKeySize) { + // 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::SaplingIncomingViewingKey ret; + ss >> ret; + return ret; + } + } return libzcash::InvalidEncoding(); } diff --git a/src/komodo_bitcoind.h b/src/komodo_bitcoind.h index bb5a093d3..80460b706 100644 --- a/src/komodo_bitcoind.h +++ b/src/komodo_bitcoind.h @@ -75,7 +75,8 @@ int tx_height( const uint256 &hash ){ nHeight = it->second->GetHeight(); //fprintf(stderr,"blockHash %s height %d\n",hashBlock.ToString().c_str(), nHeight); } else { - fprintf(stderr,"block hash %s does not exist!\n", hashBlock.ToString().c_str() ); + // Unconfirmed xtns + //fprintf(stderr,"block hash %s does not exist!\n", hashBlock.ToString().c_str() ); } return nHeight; } @@ -206,8 +207,11 @@ try_again: if ( strncmp(url,"https",5) == 0 ) { - curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYPEER,0); - curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYHOST,0); + + /* printf("[ Decker ] SSL: %s\n", curl_version()); */ + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); + //curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); // this is useful for debug, but seems crash on libcurl/7.64.1 OpenSSL/1.1.1b zlib/1.2.8 librtmp/2.3 } if ( userpass != 0 ) curl_easy_setopt(curl_handle,CURLOPT_USERPWD, userpass); @@ -1091,6 +1095,17 @@ int32_t komodo_blockheight(uint256 hash) return(0); } +uint32_t komodo_blocktime(uint256 hash) +{ + BlockMap::const_iterator it; CBlockIndex *pindex = 0; + if ( (it = mapBlockIndex.find(hash)) != mapBlockIndex.end() ) + { + if ( (pindex= it->second) != 0 ) + return(pindex->nTime); + } + return(0); +} + int32_t komodo_checkpoint(int32_t *notarized_heightp,int32_t nHeight,uint256 hash) { int32_t notarized_height,MoMdepth; uint256 MoM,notarized_hash,notarized_desttxid; CBlockIndex *notary,*pindex; diff --git a/src/komodo_defs.h b/src/komodo_defs.h index 964ec852d..ee44132bc 100644 --- a/src/komodo_defs.h +++ b/src/komodo_defs.h @@ -78,6 +78,7 @@ extern std::string DONATION_PUBKEY; extern uint8_t ASSETCHAINS_PRIVATE; extern int32_t USE_EXTERNAL_PUBKEY; extern char NOTARYADDRS[64][64]; +extern int32_t KOMODO_TESTNODE; int tx_height( const uint256 &hash ); extern std::vector vWhiteListAddress; void komodo_netevent(std::vector payload); @@ -94,6 +95,7 @@ char *bitcoin_address(char *coinaddr,uint8_t addrtype,uint8_t *pubkey_or_rmd160, int32_t komodo_minerids(uint8_t *minerids,int32_t height,int32_t width); int32_t komodo_kvsearch(uint256 *refpubkeyp,int32_t current_height,uint32_t *flagsp,int32_t *heightp,uint8_t value[IGUANA_MAXSCRIPTSIZE],uint8_t *key,int32_t keylen); +uint32_t komodo_blocktime(uint256 hash); int32_t komodo_longestchain(); int32_t komodo_dpowconfs(int32_t height,int32_t numconfs); int8_t komodo_segid(int32_t nocache,int32_t height); diff --git a/src/komodo_globals.h b/src/komodo_globals.h index 6070e9326..dc8cbda35 100644 --- a/src/komodo_globals.h +++ b/src/komodo_globals.h @@ -109,6 +109,8 @@ uint64_t PENDING_KOMODO_TX; extern int32_t KOMODO_LOADINGBLOCKS; unsigned int MAX_BLOCK_SIGOPS = 20000; +int32_t KOMODO_TESTNODE; + struct komodo_kv *KOMODO_KV; pthread_mutex_t KOMODO_KV_mutex,KOMODO_CC_mutex; diff --git a/src/komodo_utils.h b/src/komodo_utils.h index 638dde77d..3a25d1bc5 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -1710,6 +1710,7 @@ void komodo_args(char *argv0) DONATION_PUBKEY = GetArg("-donation", ""); NOTARY_PUBKEY = GetArg("-pubkey", ""); KOMODO_DEALERNODE = GetArg("-dealer",0); + KOMODO_TESTNODE = GetArg("-testnode",0); if ( strlen(NOTARY_PUBKEY.c_str()) == 66 ) { decode_hex(NOTARY_PUBKEY33,33,(char *)NOTARY_PUBKEY.c_str()); diff --git a/src/main.cpp b/src/main.cpp index 0fd9cc57e..cde12cc46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6420,8 +6420,8 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp) nLoaded++; if (state.IsError()) break; - } else if (hash != chainparams.GetConsensus().hashGenesisBlock && mapBlockIndex[hash]->GetHeight() % 1000 == 0) { - LogPrintf("Block Import: already had block %s at height %d\n", hash.ToString(), mapBlockIndex[hash]->GetHeight()); + } else if (hash != chainparams.GetConsensus().hashGenesisBlock && komodo_blockheight(hash) % 1000 == 0) { + LogPrintf("Block Import: already had block %s at height %d\n", hash.ToString(), komodo_blockheight(hash)); } // Recursively process earlier encountered successors of this block diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c5d2bea86..1c3cb96f2 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -217,7 +217,8 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& { if (rawproof.size() > 0) { - std::string sourceSymbol(rawproof.begin(), rawproof.end()); + std::string sourceSymbol; + E_UNMARSHAL(rawproof, ss >> sourceSymbol); in.push_back(Pair("address", "IMP-" + sourceSymbol + "-" + burnTx.GetHash().ToString())); } } diff --git a/src/wallet-utility.cpp b/src/wallet-utility.cpp index 655a3cab4..0b664ecb3 100644 --- a/src/wallet-utility.cpp +++ b/src/wallet-utility.cpp @@ -24,6 +24,7 @@ int32_t ASSETCHAINS_LWMAPOS = 0; int32_t VERUS_BLOCK_POSUNITS = 1000; int32_t ASSETCHAINS_OVERWINTER = 227520; int32_t ASSETCHAINS_SAPLING = 227520; +int32_t KOMODO_TESTNODE = 0; unsigned int MAX_BLOCK_SIGOPS = 20000; diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.cpp b/src/wallet/asyncrpcoperation_mergetoaddress.cpp index bba44fe50..82860d18e 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.cpp +++ b/src/wallet/asyncrpcoperation_mergetoaddress.cpp @@ -46,6 +46,7 @@ #include #include "paymentdisclosuredb.h" +int32_t komodo_blockheight(uint256 hash); using namespace libzcash; @@ -677,7 +678,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl() if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString())); } - wtxHeight = mapBlockIndex[wtx.hashBlock]->GetHeight(); + wtxHeight = komodo_blockheight(wtx.hashBlock); wtxDepth = wtx.GetDepthInMainChain(); } LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n", diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 0ea17a5f8..bc335fc4c 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -55,6 +55,7 @@ using namespace libzcash; extern char ASSETCHAINS_SYMBOL[65]; int32_t komodo_dpowconfs(int32_t height,int32_t numconfs); +int32_t komodo_blockheight(uint256 hash); int tx_height( const uint256 &hash ); extern UniValue signrawtransaction(const UniValue& params, bool fHelp); extern UniValue sendrawtransaction(const UniValue& params, bool fHelp); @@ -836,7 +837,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString())); } - wtxHeight = mapBlockIndex[wtx.hashBlock]->GetHeight(); + wtxHeight = komodo_blockheight(wtx.hashBlock); wtxDepth = wtx.GetDepthInMainChain(); } LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n", diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index 2b1a89cc7..1fe9db11a 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -526,6 +526,59 @@ TEST(WalletTests, FindMySaplingNotes) { UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } +TEST(WalletTests, FindMySaplingNotesWithIvkOnly) { + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + auto consensusParams = Params().GetConsensus(); + + TestWallet wallet; + + // Generate dummy Sapling address + std::vector> rawSeed(32); + HDSeed seed(rawSeed); + auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed); + auto addr = sk.DefaultAddress(); + auto expsk = sk.expsk; + auto fvk = expsk.full_viewing_key(); + auto pk = sk.DefaultAddress(); + auto ivk = fvk.in_viewing_key(); + + // Generate dummy Sapling note + libzcash::SaplingNote note(pk, 50000); + auto cm = note.cm().get(); + SaplingMerkleTree tree; + tree.append(cm); + auto anchor = tree.root(); + auto witness = tree.witness(); + + // Generate transaction + auto builder = TransactionBuilder(consensusParams, 1); + ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness)); + builder.AddSaplingOutput(fvk.ovk, pk, 25000, {}); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + // No Sapling notes can be found in tx which does not belong to the wallet + CWalletTx wtx {&wallet, tx}; + ASSERT_FALSE(wallet.HaveSaplingSpendingKey(fvk)); + ASSERT_FALSE(wallet.HaveSaplingIncomingViewingKey(addr)); + auto noteMap = wallet.FindMySaplingNotes(wtx).first; + EXPECT_EQ(0, noteMap.size()); + + // Add ivk to wallet, so Sapling notes can be found + ASSERT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, addr)); + ASSERT_FALSE(wallet.HaveSaplingSpendingKey(fvk)); + ASSERT_TRUE(wallet.HaveSaplingIncomingViewingKey(addr)); + noteMap = wallet.FindMySaplingNotes(wtx).first; + EXPECT_EQ(2, noteMap.size()); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} + TEST(WalletTests, FindMySproutNotes) { CWallet wallet; diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 5f022c5d5..365533b6c 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -82,6 +82,9 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) { EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress())); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa)); + // verify that resets nTimeFirstKey, since there is no birthday info for watch-only keys + EXPECT_EQ(wallet.nTimeFirstKey, 1); + // Load a third key into the wallet auto sk2 = m.Derive(1); ASSERT_TRUE(wallet.LoadSaplingZKey(sk2)); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c66ce3485..05552b50b 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -694,7 +694,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() < 1 || params.size() > 3) + if (fHelp || params.size() < 1 || params.size() > 4) throw runtime_error( "z_importviewingkey \"vkey\" ( rescan startHeight )\n" "\nAdds a viewing key (as returned by z_exportviewingkey) to your wallet.\n" @@ -702,6 +702,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) "1. \"vkey\" (string, required) The viewing key (see z_exportviewingkey)\n" "2. rescan (string, optional, default=\"whenkeyisnew\") Rescan the wallet for transactions - can be \"yes\", \"no\" or \"whenkeyisnew\"\n" "3. startHeight (numeric, optional, default=0) Block height to start rescan from\n" + "4. zaddr (string, optional, default=\"\") zaddr in case of importing viewing key for Sapling\n" "\nNote: This call can take minutes to complete if rescan is true.\n" "\nExamples:\n" "\nImport a viewing key\n" @@ -712,6 +713,8 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) + HelpExampleCli("z_importviewingkey", "\"vkey\" whenkeyisnew 30000") + "\nRe-import the viewing key with longer partial rescan\n" + HelpExampleCli("z_importviewingkey", "\"vkey\" yes 20000") + + "\nImport the viewing key for Sapling address\n" + + HelpExampleCli("z_importviewingkey", "\"vkey\" no 0 \"zaddr\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("z_importviewingkey", "\"vkey\", \"no\"") ); @@ -751,14 +754,34 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) if (!IsValidViewingKey(viewingkey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key"); } - // TODO: Add Sapling support. For now, return an error to the user. - if (boost::get(&viewingkey) == nullptr) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout viewing keys are supported"); - } - auto vkey = boost::get(viewingkey); - auto addr = vkey.address(); - { + if (boost::get(&viewingkey) == nullptr) { + if (params.size() < 4) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key."); + } + string strAddress = params[3].get_str(); + auto address = DecodePaymentAddress(strAddress); + if (!IsValidPaymentAddress(address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); + } + + auto addr = boost::get(address); + auto ivk = boost::get(viewingkey); + + if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) { + if (fIgnoreExistingKey) { + return NullUniValue; + } + } else { + pwalletMain->MarkDirty(); + + if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); + } + } + } else { + auto vkey = boost::get(viewingkey); + auto addr = vkey.address(); if (pwalletMain->HaveSproutSpendingKey(addr)) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key"); } @@ -775,13 +798,12 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); } } - - // We want to scan for transactions and notes - if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); - } } + // We want to scan for transactions and notes + if (fRescan) { + pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); + } return NullUniValue; } @@ -853,12 +875,17 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp) if (!IsValidPaymentAddress(address)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); } - // TODO: Add Sapling support. For now, return an error to the user. - if (boost::get(&address) == nullptr) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout zaddrs are supported"); - } - auto addr = boost::get(address); + if (boost::get(&address) == nullptr) { + auto addr = boost::get(address); + libzcash::SaplingIncomingViewingKey ivk; + if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr"); + } + return EncodeViewingKey(ivk); + } + + auto addr = boost::get(address); libzcash::SproutViewingKey vk; if (!pwalletMain->GetSproutViewingKey(addr, vk)) { libzcash::SproutSpendingKey k; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 847205ec1..6fe061727 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -122,10 +122,10 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) entry.push_back(Pair("generated", true)); if (confirms > 0) { - entry.push_back(Pair("confirmations", komodo_dpowconfs((int32_t)mapBlockIndex[wtx.hashBlock]->GetHeight(),confirms))); + entry.push_back(Pair("confirmations", komodo_dpowconfs((int32_t)komodo_blockheight(wtx.hashBlock),confirms))); entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); entry.push_back(Pair("blockindex", wtx.nIndex)); - entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime())); + entry.push_back(Pair("blocktime", (uint64_t)komodo_blocktime(wtx.hashBlock))); entry.push_back(Pair("expiryheight", (int64_t)wtx.nExpiryHeight)); } else entry.push_back(Pair("confirmations", confirms)); uint256 hash = wtx.GetHash(); @@ -1624,7 +1624,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) tallyitem& item = mapTally[address]; item.nAmount += txout.nValue; // komodo_interest? item.nConf = min(item.nConf, nDepth); - item.nHeight = mapBlockIndex[wtx.hashBlock]->GetHeight(); + item.nHeight = komodo_blockheight(wtx.hashBlock); item.txids.push_back(wtx.GetHash()); if (mine & ISMINE_WATCH_ONLY) item.fIsWatchonly = true; @@ -3915,7 +3915,8 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp) } // Visitor to support Sprout and Sapling addrs - if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr)) { + if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr) && + !boost::apply_visitor(IncomingViewingKeyBelongsToWallet(pwalletMain), zaddr)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found."); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9b2975db1..fa39d204a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -185,6 +185,7 @@ bool CWallet::AddSaplingZKey( return false; } + nTimeFirstKey = 1; // No birthday information for viewing keys. if (!fFileBacked) { return true; } @@ -1541,26 +1542,29 @@ void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) { } else { uint64_t position = nd.witnesses.front().position(); - SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk); - OutputDescription output = wtx.vShieldedOutput[op.n]; - auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm); - if (!optPlaintext) { - // An item in mapSaplingNoteData must have already been successfully decrypted, - // otherwise the item would not exist in the first place. - assert(false); + // Skip if we only have incoming viewing key + if (mapSaplingFullViewingKeys.count(nd.ivk) != 0) { + SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk); + OutputDescription output = wtx.vShieldedOutput[op.n]; + auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm); + if (!optPlaintext) { + // An item in mapSaplingNoteData must have already been successfully decrypted, + // otherwise the item would not exist in the first place. + assert(false); + } + auto optNote = optPlaintext.get().note(nd.ivk); + if (!optNote) { + assert(false); + } + auto optNullifier = optNote.get().nullifier(fvk, position); + if (!optNullifier) { + // This should not happen. If it does, maybe the position has been corrupted or miscalculated? + assert(false); + } + uint256 nullifier = optNullifier.get(); + mapSaplingNullifiersToNotes[nullifier] = op; + item.second.nullifier = nullifier; } - auto optNote = optPlaintext.get().note(nd.ivk); - if (!optNote) { - assert(false); - } - auto optNullifier = optNote.get().nullifier(fvk, position); - if (!optNullifier) { - // This should not happen. If it does, maybe the position has been corrupted or miscalculated? - assert(false); - } - uint256 nullifier = optNullifier.get(); - mapSaplingNullifiersToNotes[nullifier] = op; - item.second.nullifier = nullifier; } } } @@ -1991,23 +1995,40 @@ std::pair CWallet::FindMySap // Protocol Spec: 4.19 Block Chain Scanning (Sapling) for (uint32_t i = 0; i < tx.vShieldedOutput.size(); ++i) { const OutputDescription output = tx.vShieldedOutput[i]; + bool found = false; for (auto it = mapSaplingFullViewingKeys.begin(); it != mapSaplingFullViewingKeys.end(); ++it) { SaplingIncomingViewingKey ivk = it->first; auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cm); - if (!result) { - continue; + if (result) { + auto address = ivk.address(result.get().d); + if (address && mapSaplingIncomingViewingKeys.count(address.get()) == 0) { + viewingKeysToAdd[address.get()] = ivk; + } + // We don't cache the nullifier here as computing it requires knowledge of the note position + // in the commitment tree, which can only be determined when the transaction has been mined. + SaplingOutPoint op {hash, i}; + SaplingNoteData nd; + nd.ivk = ivk; + noteData.insert(std::make_pair(op, nd)); + found = true; + break; } - auto address = ivk.address(result.get().d); - if (address && mapSaplingIncomingViewingKeys.count(address.get()) == 0) { - viewingKeysToAdd[address.get()] = ivk; + } + if (!found) { + for (auto it = mapSaplingIncomingViewingKeys.begin(); it != mapSaplingIncomingViewingKeys.end(); ++it) { + SaplingIncomingViewingKey ivk = it-> second; + auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cm); + if (!result) { + continue; + } + // We don't cache the nullifier here as computing it requires knowledge of the note position + // in the commitment tree, which can only be determined when the transaction has been mined. + SaplingOutPoint op {hash, i}; + SaplingNoteData nd; + nd.ivk = ivk; + noteData.insert(std::make_pair(op, nd)); + break; } - // We don't cache the nullifier here as computing it requires knowledge of the note position - // in the commitment tree, which can only be determined when the transaction has been mined. - SaplingOutPoint op {hash, i}; - SaplingNoteData nd; - nd.ivk = ivk; - noteData.insert(std::make_pair(op, nd)); - break; } } @@ -5071,6 +5092,22 @@ void CWallet::GetFilteredNotes( // Shielded key and address generalizations // +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const +{ + return m_wallet->HaveSproutViewingKey(zaddr); +} + +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingIncomingViewingKey ivk; + return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk); +} + +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const +{ + return false; +} + bool PaymentAddressBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const { return m_wallet->HaveSproutSpendingKey(zaddr) || m_wallet->HaveSproutViewingKey(zaddr); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 10aa83ce6..b00365a77 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1392,6 +1392,19 @@ public: bool operator()(const libzcash::InvalidEncoding& no) const; }; + +class IncomingViewingKeyBelongsToWallet : public boost::static_visitor +{ +private: + CWallet *m_wallet; +public: + IncomingViewingKeyBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {} + + bool operator()(const libzcash::SproutPaymentAddress &zaddr) const; + bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const; + bool operator()(const libzcash::InvalidEncoding& no) const; +}; + class HaveSpendingKeyForPaymentAddress : public boost::static_visitor { private: diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 42f01b57b..42ee0031b 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -219,7 +219,7 @@ public: }; typedef boost::variant PaymentAddress; -typedef boost::variant ViewingKey; +typedef boost::variant ViewingKey; }