From 9259a3cf24b83f958b0176bf0996f39a0c67fea7 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 21 Apr 2021 19:50:29 +0200 Subject: [PATCH 01/29] allow p2sh outputs and ZOMBIE test chain --- src/komodo_utils.h | 2 +- src/main.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/komodo_utils.h b/src/komodo_utils.h index 9f2ec53fce7..d01a15f47a3 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -2374,7 +2374,7 @@ fprintf(stderr,"extralen.%d before disable bits\n",extralen); } else if ( strcmp("VRSC",ASSETCHAINS_SYMBOL) == 0 ) dpowconfs = 0; - else if ( ASSETCHAINS_PRIVATE != 0 ) + else if ( ASSETCHAINS_PRIVATE != 0 && strcmp("ZOMBIE",ASSETCHAINS_SYMBOL) ) { fprintf(stderr,"-ac_private for a non-PIRATE chain is not supported. The only reason to have an -ac_private chain is for total privacy and that is best achieved with the largest anon set. PIRATE has that and it is recommended to just use PIRATE\n"); StartShutdown(); diff --git a/src/main.cpp b/src/main.cpp index 2de5a25dd7f..b07894c4a2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1434,6 +1434,7 @@ bool CheckTransaction(uint32_t tiptime,const CTransaction& tx, CValidationState } } +// ARRR notary exception int32_t komodo_isnotaryvout(char *coinaddr,uint32_t tiptime) // from ac_private chains only { int32_t season = getacseason(tiptime); @@ -1544,9 +1545,15 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio // char destaddr[65]; Getscriptaddress(destaddr,txout.scriptPubKey); + vector> vSolutions; + txnouttype whichType; + Solver(txout.scriptPubKey, whichType, vSolutions); if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; + if ( 1 && whichType == TX_SCRIPTHASH ) { // FIXME "1" represents HF timestamp + invalid_private_taddr = 0; + } //return state.DoS(100, error("CheckTransaction(): this is a private chain, no public allowed"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } } From 463b2e2a42898c58713d27f72aec194c36206b41 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 21 Apr 2021 20:42:08 +0200 Subject: [PATCH 02/29] simplify p2sh check --- src/main.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b07894c4a2c..62355479f6c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1545,13 +1545,10 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio // char destaddr[65]; Getscriptaddress(destaddr,txout.scriptPubKey); - vector> vSolutions; - txnouttype whichType; - Solver(txout.scriptPubKey, whichType, vSolutions); if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - if ( 1 && whichType == TX_SCRIPTHASH ) { // FIXME "1" represents HF timestamp + if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { // FIXME "1" represents HF timestamp invalid_private_taddr = 0; } //return state.DoS(100, error("CheckTransaction(): this is a private chain, no public allowed"),REJECT_INVALID, "bad-txns-acprivacy-chain"); @@ -1652,7 +1649,12 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio static uint32_t counter; if ( counter++ < 10 ) fprintf(stderr,"found taddr in private chain: z_z.%d z_t.%d t_z.%d vinsize.%d\n",z_z,z_t,t_z,(int32_t)tx.vin.size()); - if ( z_t == 0 || z_z != 0 || t_z != 0 || tx.vin.size() != 0 ) + if ( z_t == 0 || z_z != 0 || t_z != 0 ) // || tx.vin.size() != 0 ) // FIXME need to hack size + fprintf(stderr, "z_t == 0 %d\n", z_t == 0); + fprintf(stderr, "z_z != 0 %d\n", z_z != 0); + fprintf(stderr, "t_z != 0 %d\n", t_z != 0); + fprintf(stderr, "tx.vin.size() != 0 %d\n%s\n", tx.vin.size() != 0, EncodeHexTx(tx).data()); + return state.DoS(100, error("CheckTransaction(): this is a private chain, only sprout -> taddr allowed until deadline"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } if ( ASSETCHAINS_TXPOW != 0 ) @@ -1666,6 +1668,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio uint256 genesistxid = uint256S("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); if ( txid != genesistxid ) { + fprintf(stderr,"private chain iscoinbase.%d invalid txpow.%d txid.%s\n",iscoinbase,ASSETCHAINS_TXPOW,txid.GetHex().c_str()); return state.DoS(100, error("CheckTransaction(): this is a txpow chain, must have 0x00 ends"),REJECT_INVALID, "bad-txns-actxpow-chain"); } From 412b187d794c5757868e78e80418970daedcb7cc Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Sat, 1 May 2021 14:55:45 +0200 Subject: [PATCH 03/29] add z_sendmany_template WIP --- src/Makefile.am | 2 + src/rpc/client.cpp | 3 + src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/transaction_builder.cpp | 169 +++++++++++++++++++++ src/transaction_builder.h | 2 + src/wallet/rpcwallet.cpp | 286 ++++++++++++++++++++++++++++++++++++ 7 files changed, 464 insertions(+) diff --git a/src/Makefile.am b/src/Makefile.am index 23cff51cba9..77cb181303c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -258,6 +258,7 @@ BITCOIN_CORE_H = \ version.h \ wallet/asyncrpcoperation_mergetoaddress.h \ wallet/asyncrpcoperation_sendmany.h \ + wallet/asyncrpcoperation_sendmany_template.h \ wallet/asyncrpcoperation_shieldcoinbase.h \ wallet/crypter.h \ wallet/db.h \ @@ -384,6 +385,7 @@ libbitcoin_wallet_a_SOURCES = \ zcbenchmarks.h \ wallet/asyncrpcoperation_mergetoaddress.cpp \ wallet/asyncrpcoperation_sendmany.cpp \ + wallet/asyncrpcoperation_sendmany_template.cpp \ wallet/asyncrpcoperation_shieldcoinbase.cpp \ wallet/crypter.cpp \ wallet/db.cpp \ diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index e67153082ae..c5697b678fb 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -156,6 +156,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "z_sendmany", 1}, { "z_sendmany", 2}, { "z_sendmany", 3}, + { "z_sendmany_template", 1}, + { "z_sendmany_template", 2}, + { "z_sendmany_template", 3}, { "z_shieldcoinbase", 2}, { "z_shieldcoinbase", 3}, { "z_getoperationstatus", 0}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 1416d3bc72d..0953c49b33b 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -667,6 +667,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "z_getbalance", &z_getbalance, false }, { "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, { "wallet", "z_mergetoaddress", &z_mergetoaddress, false }, + { "wallet", "z_sendmany_template", &z_sendmany_template, false }, { "wallet", "z_sendmany", &z_sendmany, false }, { "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false }, { "wallet", "z_getoperationstatus", &z_getoperationstatus, true }, diff --git a/src/rpc/server.h b/src/rpc/server.h index 465bc832b56..3067cd9c8df 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -495,6 +495,7 @@ extern UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp, cons extern UniValue z_getbalance(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp +extern UniValue z_sendmany_template(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 07e20132611..c6dc9c3d590 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -123,6 +123,175 @@ bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr) return true; } +// Alright - using this to test initial p2sh consensus changes +// adapted z_sendmany to produce unsigned t->z transactions which can then be handled by external code to add sigs +// this could be cleaned up quite a bit if used in production +boost::optional TransactionBuilder::BuildWithoutSig() +{ + // + // Consistency checks + // + + // Valid change + CAmount change = mtx.valueBalance - fee; + for (auto tIn : tIns) { + change += tIn.value; + } + for (auto tOut : mtx.vout) { + change -= tOut.nValue; + } + if (change < 0) { + return boost::none; + } + + // + // Change output + // + + if (change > 0) { + // Send change to the specified change address. If no change address + // was set, send change to the first Sapling address given as input. + if (zChangeAddr) { + AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change); + } else if (tChangeAddr) { + // tChangeAddr has already been validated. + assert(AddTransparentOutput(tChangeAddr.value(), change)); + } else if (!spends.empty()) { + auto fvk = spends[0].expsk.full_viewing_key(); + auto note = spends[0].note; + libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d); + AddSaplingOutput(fvk.ovk, changeAddr, change); + } else { + return boost::none; + } + } + + // + // Sapling spends and outputs + // + + auto ctx = librustzcash_sapling_proving_ctx_init(); + + // Create Sapling SpendDescriptions + for (auto spend : spends) { + auto cm = spend.note.cm(); + auto nf = spend.note.nullifier( + spend.expsk.full_viewing_key(), spend.witness.position()); + if (!(cm && nf)) { + librustzcash_sapling_proving_ctx_free(ctx); + return boost::none; + } + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << spend.witness.path(); + std::vector witness(ss.begin(), ss.end()); + + SpendDescription sdesc; + if (!librustzcash_sapling_spend_proof( + ctx, + spend.expsk.full_viewing_key().ak.begin(), + spend.expsk.nsk.begin(), + spend.note.d.data(), + spend.note.r.begin(), + spend.alpha.begin(), + spend.note.value(), + spend.anchor.begin(), + witness.data(), + sdesc.cv.begin(), + sdesc.rk.begin(), + sdesc.zkproof.data())) { + librustzcash_sapling_proving_ctx_free(ctx); + return boost::none; + } + + sdesc.anchor = spend.anchor; + sdesc.nullifier = *nf; + mtx.vShieldedSpend.push_back(sdesc); + } + + // Create Sapling OutputDescriptions + for (auto output : outputs) { + auto cm = output.note.cm(); + if (!cm) { + librustzcash_sapling_proving_ctx_free(ctx); + return boost::none; + } + + libzcash::SaplingNotePlaintext notePlaintext(output.note, output.memo); + + auto res = notePlaintext.encrypt(output.note.pk_d); + if (!res) { + librustzcash_sapling_proving_ctx_free(ctx); + return boost::none; + } + auto enc = res.get(); + auto encryptor = enc.second; + + OutputDescription odesc; + if (!librustzcash_sapling_output_proof( + ctx, + encryptor.get_esk().begin(), + output.note.d.data(), + output.note.pk_d.begin(), + output.note.r.begin(), + output.note.value(), + odesc.cv.begin(), + odesc.zkproof.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return boost::none; + } + + odesc.cm = *cm; + odesc.ephemeralKey = encryptor.get_epk(); + odesc.encCiphertext = enc.first; + + libzcash::SaplingOutgoingPlaintext outPlaintext(output.note.pk_d, encryptor.get_esk()); + odesc.outCiphertext = outPlaintext.encrypt( + output.ovk, + odesc.cv, + odesc.cm, + encryptor); + mtx.vShieldedOutput.push_back(odesc); + } + + // add op_return if there is one to add + AddOpRetLast(); + + // + // Signatures + // + + auto consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams); + + // Empty output script. + uint256 dataToBeSigned; + CScript scriptCode; + try { + dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId); + } catch (std::logic_error ex) { + librustzcash_sapling_proving_ctx_free(ctx); + return boost::none; + } + + // Create Sapling spendAuth and binding signatures + for (size_t i = 0; i < spends.size(); i++) { + librustzcash_sapling_spend_sig( + spends[i].expsk.ask.begin(), + spends[i].alpha.begin(), + dataToBeSigned.begin(), + mtx.vShieldedSpend[i].spendAuthSig.data()); + } + librustzcash_sapling_binding_sig( + ctx, + mtx.valueBalance, + dataToBeSigned.begin(), + mtx.bindingSig.data()); + + librustzcash_sapling_proving_ctx_free(ctx); + + return CTransaction(mtx); +} + boost::optional TransactionBuilder::Build() { // diff --git a/src/transaction_builder.h b/src/transaction_builder.h index 49c09294d2e..bc790034d92 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -107,6 +107,8 @@ class TransactionBuilder void SetLockTime(uint32_t time) { this->mtx.nLockTime = time; } boost::optional Build(); + + boost::optional BuildWithoutSig(); }; #endif /* TRANSACTION_BUILDER_H */ diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 1231953b18f..dc59a80696c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -45,6 +45,7 @@ #include "wallet/asyncrpcoperation_mergetoaddress.h" #include "wallet/asyncrpcoperation_sendmany.h" #include "wallet/asyncrpcoperation_shieldcoinbase.h" +#include "wallet/asyncrpcoperation_sendmany_template.h" #include "consensus/upgrades.h" @@ -4473,6 +4474,291 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO #define CTXIN_SPEND_DUST_SIZE 148 #define CTXOUT_REGULAR_SIZE 34 +UniValue z_sendmany_template(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() < 2 || params.size() > 4) + throw runtime_error( + "z_sendmany_template \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( FIXME ) ( fee )\n" + "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." + "\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself." + "\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed." + + strprintf("\nBefore Sapling activates, the maximum number of zaddr outputs is %d due to transaction size limits.\n", Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING) + + HelpRequiringPassphrase() + "\n" + "\nArguments:\n" + "1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from.\n" + "2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n" + " [{\n" + " \"address\":address (string, required) The address is a taddr or zaddr\n" + " \"amount\":amount (numeric, required) The numeric amount in KMD is the value\n" + " \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n" + " }, ... ]\n" + "3. FIXME HLTC JSON, pubkey pubkey_refund locktime secret_hash input:[txid, index, amount_as_str]\n" + "4. fee (numeric, optional, default=" + + strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" + "\nResult:\n" + "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" + "\nExamples:\n" + + HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'") + + HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + //THROW_IF_SYNCING(KOMODO_INSYNC); + + // Check that the from address is valid. + auto fromaddress = params[0].get_str(); + bool fromTaddr = false; + bool fromSapling = false; + + uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); + + CTxDestination taddr = DecodeDestination(fromaddress); + fromTaddr = IsValidDestination(taddr); + if (!fromTaddr) { + auto res = DecodePaymentAddress(fromaddress); + if (!IsValidPaymentAddress(res, branchId)) { + // invalid + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); + } + + // Check that we have the spending key + if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); + } + + // Remember whether this is a Sprout or Sapling address + fromSapling = boost::get(&res) != nullptr; + } + // This logic will need to be updated if we add a new shielded pool + bool fromSprout = !(fromTaddr || fromSapling); + + UniValue outputs = params[1].get_array(); + + if (outputs.size()==0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); + + // Keep track of addresses to spot duplicates + set setAddress; + + // Track whether we see any Sprout addresses + bool noSproutAddrs = !fromSprout; + + // Recipients + std::vector taddrRecipients; + std::vector zaddrRecipients; + CAmount nTotalOut = 0; + + bool containsSproutOutput = false; + bool containsSaplingOutput = false; + + for (const UniValue& o : outputs.getValues()) { + if (!o.isObject()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); + + // sanity check, report error if unknown key-value pairs + for (const string& name_ : o.getKeys()) { + std::string s = name_; + if (s != "address" && s != "amount" && s!="memo") + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); + } + + string address = find_value(o, "address").get_str(); + bool isZaddr = false; + CTxDestination taddr = DecodeDestination(address); + if (!IsValidDestination(taddr)) { + auto res = DecodePaymentAddress(address); + if (IsValidPaymentAddress(res, branchId)) { + isZaddr = true; + + bool toSapling = boost::get(&res) != nullptr; + bool toSprout = !toSapling; + noSproutAddrs = noSproutAddrs && toSapling; + + containsSproutOutput |= toSprout; + containsSaplingOutput |= toSapling; + + // Sending to both Sprout and Sapling is currently unsupported using z_sendmany + if (containsSproutOutput && containsSaplingOutput) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "Cannot send to both Sprout and Sapling addresses using z_sendmany"); + } + if ( GetTime() > KOMODO_SAPLING_DEADLINE ) + { + if ( fromSprout || toSprout ) + throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage has expired"); + } + if ( toSapling && ASSETCHAINS_SYMBOL[0] == 0 ) + throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage will expire soon"); + + // If we are sending from a shielded address, all recipient + // shielded addresses must be of the same type. + if ((fromSprout && toSapling) || (fromSapling && toSprout)) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "Cannot send between Sprout and Sapling addresses using z_sendmany"); + } + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); + } + } + //else if ( ASSETCHAINS_PRIVATE != 0 && komodo_isnotaryvout((char *)address.c_str()) == 0 ) + // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cant use transparent addresses in private chain"); + + // Allowing duplicate receivers helps various HushList protocol operations + //if (setAddress.count(address)) + // throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+address); + setAddress.insert(address); + + UniValue memoValue = find_value(o, "memo"); + string memo; + if (!memoValue.isNull()) { + memo = memoValue.get_str(); + if (!isZaddr) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); + } else if (!IsHex(memo)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format."); + } + if (memo.length() > ZC_MEMO_SIZE*2) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE )); + } + } + + UniValue av = find_value(o, "amount"); + CAmount nAmount = AmountFromValue( av ); + if (nAmount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); + + if (isZaddr) { + zaddrRecipients.push_back( z_template::SendManyRecipient(address, nAmount, memo) ); + } else { + taddrRecipients.push_back( z_template::SendManyRecipient(address, nAmount, memo) ); + } + + nTotalOut += nAmount; + } + + int nextBlockHeight = chainActive.Height() + 1; + CMutableTransaction mtx; + mtx.fOverwintered = true; + mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; + mtx.nVersion = SAPLING_TX_VERSION; + unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; + if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { + if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { + mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; + mtx.nVersion = OVERWINTER_TX_VERSION; + } else { + mtx.fOverwintered = false; + mtx.nVersion = 2; + } + + max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; + + // Check the number of zaddr outputs does not exceed the limit. + if (zaddrRecipients.size() > Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, too many zaddr outputs"); + } + } + + // If Sapling is not active, do not allow sending from or sending to Sapling addresses. + if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { + if (fromSapling || containsSaplingOutput) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); + } + } + + // As a sanity check, estimate and verify that the size of the transaction will be valid. + // Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid. + size_t txsize = 0; + for (int i = 0; i < zaddrRecipients.size(); i++) { + auto address = std::get<0>(zaddrRecipients[i]); + auto res = DecodePaymentAddress(address); + bool toSapling = boost::get(&res) != nullptr; + if (toSapling) { + mtx.vShieldedOutput.push_back(OutputDescription()); + } else { + JSDescription jsdesc; + if (mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION)) { + jsdesc.proof = GrothProof(); + } + mtx.vjoinsplit.push_back(jsdesc); + } + } + CTransaction tx(mtx); + txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion); + if (fromTaddr) { + txsize += CTXIN_SPEND_DUST_SIZE; + txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change + } + txsize += CTXOUT_REGULAR_SIZE * taddrRecipients.size(); + if (txsize > max_tx_size) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Too many outputs, size of raw transaction would be larger than limit of %d bytes", max_tx_size )); + } + + // Minimum confirmations + int nMinDepth = 1; + + // Fee in Zatoshis, not currency format) + CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; + CAmount nDefaultFee = nFee; + + if (params.size() > 3) { + if (params[3].get_real() == 0.0) { + nFee = 0; + } else { + nFee = AmountFromValue( params[3] ); + } + + // Check that the user specified fee is not absurd. + // This allows amount=0 (and all amount < nDefaultFee) transactions to use the default network fee + // or anything less than nDefaultFee instead of being forced to use a custom fee and leak metadata + if (nTotalOut < nDefaultFee) { + if (nFee > nDefaultFee) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Small transaction amount %s has fee %s that is greater than the default fee %s", FormatMoney(nTotalOut), FormatMoney(nFee), FormatMoney(nDefaultFee))); + } + } else { + // Check that the user specified fee is not absurd. + if (nFee > nTotalOut) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s and also greater than the default fee", FormatMoney(nFee), FormatMoney(nTotalOut))); + } + } + } + + // Use input parameters as the optional context info to be returned by z_getoperationstatus and z_getoperationresult. + UniValue o(UniValue::VOBJ); + o.push_back(Pair("fromaddress", params[0])); + o.push_back(Pair("amounts", params[1])); + o.push_back(Pair("minconf", nMinDepth)); + o.push_back(Pair("fee", std::stod(FormatMoney(nFee)))); + UniValue contextInfo = o; + + // Builder (used if Sapling addresses are involved) + boost::optional builder; + if (noSproutAddrs) { + builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain); + } + + // Contextual transaction we will build on + // (used if no Sapling addresses are involved) + CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight); + bool isShielded = !fromTaddr || zaddrRecipients.size() > 0; + if (contextualTx.nVersion == 1 && isShielded) { + contextualTx.nVersion = 2; // Tx format should support vjoinsplits + } + + // Create operation and add to global queue + std::shared_ptr q = getAsyncRPCQueue(); + std::shared_ptr operation( new z_template::AsyncRPCOperation_sendmany_template(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo, params[2]) ); + q->addOperation(operation); + AsyncRPCOperationId operationId = operation->getId(); + return operationId; +} + UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) From 224307267f068d22d78bb584eb6c9fb2679430ce Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 00:25:22 +0200 Subject: [PATCH 04/29] zHLTC; ready to test --- src/main.cpp | 13 ++++++++++++- src/script/script.cpp | 39 ++++++++++++++++++++++++++++++++++----- src/script/script.h | 3 +++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 62355479f6c..c8a3251fb65 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1526,6 +1526,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio // Check for negative or overflow output values CAmount nValueOut = 0; int32_t iscoinbase = tx.IsCoinBase(); + int out_index = 0; BOOST_FOREACH(const CTxOut& txout, tx.vout) { if (txout.nValue < 0) @@ -1549,7 +1550,16 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio { invalid_private_taddr = 1; if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { // FIXME "1" represents HF timestamp - invalid_private_taddr = 0; + if (out_index == tx.vout.size()-1 ) { + // p2sh cannot be the last vout or we reach out of bounds in the next if statement + return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); + } + + if (txout.scriptPubKey.IsRedeemScriptReveal(tx.vout[out_index+1].scriptPubKey)){ + invalid_private_taddr = 0; + } else { + return state.DoS(100, error("CheckTransaction(): zHLTC missing or malformed redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-redeem-reveal-malformed"); + } } //return state.DoS(100, error("CheckTransaction(): this is a private chain, no public allowed"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } @@ -1674,6 +1684,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio } } } + out_index++; } // Ensure input values do not exceed MAX_MONEY diff --git a/src/script/script.cpp b/src/script/script.cpp index 69e340758e5..8a6391cd45b 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -24,6 +24,7 @@ #include "utilstrencodings.h" #include "script/cc.h" #include "cc/eval.h" +#include "standard.h" #include "cryptoconditions/include/cryptoconditions.h" using namespace std; @@ -231,11 +232,11 @@ bool CScript::IsPayToPublicKeyHash() const { // Extra-fast test for pay-to-pubkey-hash CScripts: return (this->size() == 25 && - (*this)[0] == OP_DUP && - (*this)[1] == OP_HASH160 && - (*this)[2] == 0x14 && - (*this)[23] == OP_EQUALVERIFY && - (*this)[24] == OP_CHECKSIG); + (*this)[0] == OP_DUP && + (*this)[1] == OP_HASH160 && + (*this)[2] == 0x14 && + (*this)[23] == OP_EQUALVERIFY && + (*this)[24] == OP_CHECKSIG); } bool CScript::IsPayToPublicKey() const @@ -255,6 +256,34 @@ bool CScript::IsPayToScriptHash() const (*this)[22] == OP_EQUAL); } +bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ + CScript check_spk, redeemScript = scriptpubkey; + if ( + // these magic numbers correspond to a typical(as of May 2021) atomicdex HLTC + redeemScript[0] == OP_RETURN && + redeemScript[3] == OP_IF && + redeemScript[9] == OP_NOP2 && + redeemScript[10] == OP_DROP && + redeemScript[45] == OP_CHECKSIG && + redeemScript[46] == OP_ELSE && + redeemScript[47] == OP_SIZE && + redeemScript[48] == 0x01 && + redeemScript[49] == 0x20 && + redeemScript[50] == OP_EQUALVERIFY && + redeemScript[51] == OP_HASH160 && + redeemScript[73] == OP_EQUALVERIFY && + redeemScript[108] == OP_CHECKSIG && + redeemScript[109] == OP_ENDIF && + redeemScript.size() == 110 + ) { + // Drop the OP_RETURN and OP_PUSHDATA1 + byte + redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); + check_spk << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + return (check_spk == (*this)); + } + return(0); +} + // this returns true if either there is nothing left and pc points at the end, or // all instructions from the pc to the end of the script are balanced pushes and pops // if there is data, it also returns all the values as byte vectors in a list of vectors diff --git a/src/script/script.h b/src/script/script.h index 1d5af105d0b..00d68e18753 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -598,6 +598,9 @@ class CScript : public CScriptBase bool IsCoinImport() const; bool MayAcceptCryptoCondition() const; + // zHLTC + bool IsRedeemScriptReveal(CScript scriptpubkey) const; + /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ bool IsPushOnly(const_iterator pc) const; bool IsPushOnly() const; From f4e2ac087b099764483b2f3fd788d21d1fa30499 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 00:28:44 +0200 Subject: [PATCH 05/29] remove debug test and prints --- src/main.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c8a3251fb65..bba41b22a05 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1659,12 +1659,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio static uint32_t counter; if ( counter++ < 10 ) fprintf(stderr,"found taddr in private chain: z_z.%d z_t.%d t_z.%d vinsize.%d\n",z_z,z_t,t_z,(int32_t)tx.vin.size()); - if ( z_t == 0 || z_z != 0 || t_z != 0 ) // || tx.vin.size() != 0 ) // FIXME need to hack size - fprintf(stderr, "z_t == 0 %d\n", z_t == 0); - fprintf(stderr, "z_z != 0 %d\n", z_z != 0); - fprintf(stderr, "t_z != 0 %d\n", t_z != 0); - fprintf(stderr, "tx.vin.size() != 0 %d\n%s\n", tx.vin.size() != 0, EncodeHexTx(tx).data()); - + if ( z_t == 0 || z_z != 0 || t_z != 0 || tx.vin.size() != 0 ) // FIXME need to hack size return state.DoS(100, error("CheckTransaction(): this is a private chain, only sprout -> taddr allowed until deadline"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } if ( ASSETCHAINS_TXPOW != 0 ) From ce4a7cdc5fe84a44b2c28e888eb9b864cd9bdac0 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 00:29:44 +0200 Subject: [PATCH 06/29] remove debug comment --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index bba41b22a05..8497d5c00bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1549,7 +1549,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { // FIXME "1" represents HF timestamp + if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { if (out_index == tx.vout.size()-1 ) { // p2sh cannot be the last vout or we reach out of bounds in the next if statement return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); From b2bd9bf18cbea3b86771f8c1eab614e617cd38b5 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 19:37:44 +0200 Subject: [PATCH 07/29] additional p2sh exception --- src/script/script.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/script/script.cpp b/src/script/script.cpp index 8a6391cd45b..b19e9fe480a 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -239,6 +239,7 @@ bool CScript::IsPayToPublicKeyHash() const (*this)[24] == OP_CHECKSIG); } + bool CScript::IsPayToPublicKey() const { // Extra-fast test for pay-to-pubkey CScripts: @@ -281,6 +282,32 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ check_spk << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; return (check_spk == (*this)); } + + if ( + // these magic numbers correspond to: + // 00000000000000000000000000000000 OP_DROP + // OP_IF e8dd8860 OP_NOP2 OP_DROP pubkey OP_CHECKSIG + // OP_ELSE pubkey OP_CHECKSIG OP_ENDIF + // as provided by artem + redeemScript[0] == OP_RETURN && + redeemScript[1] == 0x4c && // PUSH_BYTES + redeemScript[2] == 0x62 && // BYTES + redeemScript[20] == OP_DROP && + redeemScript[21] == OP_IF && + redeemScript[22] == 0x04 && // 32bit locktime + redeemScript[27] == OP_NOP2 && + redeemScript[28] == OP_DROP && + redeemScript[63] == OP_CHECKSIG && + redeemScript[64] == OP_ELSE && + redeemScript[99] == OP_CHECKSIG && + redeemScript[100] == OP_ENDIF && + redeemScript.size() == 101 + ) { + // Drop the OP_RETURN and OP_PUSHDATA1 + byte + redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); + check_spk << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + return (check_spk == (*this)); + } return(0); } From b1ca919c507b8b3bb624eb5ffd2cf2d2d410915f Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 19:50:00 +0200 Subject: [PATCH 08/29] disallow p2sh->p2sh --- src/main.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 8497d5c00bc..e7e7bcd6d23 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1549,13 +1549,15 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { + if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { // FIXME 1 represents HF timestamp ac_season check if (out_index == tx.vout.size()-1 ) { // p2sh cannot be the last vout or we reach out of bounds in the next if statement return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); } - if (txout.scriptPubKey.IsRedeemScriptReveal(tx.vout[out_index+1].scriptPubKey)){ + if (txout.scriptPubKey.IsRedeemScriptReveal(tx.vout[out_index+1].scriptPubKey)) { + if ( tx.vin.size() > 0 ) + return state.DoS(100, error("CheckTransaction(): zHLTC cannot spend t->p2sh"),REJECT_INVALID, "bad-txns-zhltc-no-t-spends"); invalid_private_taddr = 0; } else { return state.DoS(100, error("CheckTransaction(): zHLTC missing or malformed redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-redeem-reveal-malformed"); @@ -1659,7 +1661,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio static uint32_t counter; if ( counter++ < 10 ) fprintf(stderr,"found taddr in private chain: z_z.%d z_t.%d t_z.%d vinsize.%d\n",z_z,z_t,t_z,(int32_t)tx.vin.size()); - if ( z_t == 0 || z_z != 0 || t_z != 0 || tx.vin.size() != 0 ) // FIXME need to hack size + if ( z_t == 0 || z_z != 0 || t_z != 0 || tx.vin.size() != 0 ) return state.DoS(100, error("CheckTransaction(): this is a private chain, only sprout -> taddr allowed until deadline"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } if ( ASSETCHAINS_TXPOW != 0 ) @@ -1673,7 +1675,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio uint256 genesistxid = uint256S("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); if ( txid != genesistxid ) { - fprintf(stderr,"private chain iscoinbase.%d invalid txpow.%d txid.%s\n",iscoinbase,ASSETCHAINS_TXPOW,txid.GetHex().c_str()); return state.DoS(100, error("CheckTransaction(): this is a txpow chain, must have 0x00 ends"),REJECT_INVALID, "bad-txns-actxpow-chain"); } From 221cf8f605a469a7d6bccac5c5c40593372a0e1a Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 19:55:50 +0200 Subject: [PATCH 09/29] undo frivulous whitespace edit --- src/script/script.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index b19e9fe480a..f672fdb4dc9 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -232,11 +232,11 @@ bool CScript::IsPayToPublicKeyHash() const { // Extra-fast test for pay-to-pubkey-hash CScripts: return (this->size() == 25 && - (*this)[0] == OP_DUP && - (*this)[1] == OP_HASH160 && - (*this)[2] == 0x14 && - (*this)[23] == OP_EQUALVERIFY && - (*this)[24] == OP_CHECKSIG); + (*this)[0] == OP_DUP && + (*this)[1] == OP_HASH160 && + (*this)[2] == 0x14 && + (*this)[23] == OP_EQUALVERIFY && + (*this)[24] == OP_CHECKSIG); } @@ -285,8 +285,8 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ if ( // these magic numbers correspond to: - // 00000000000000000000000000000000 OP_DROP - // OP_IF e8dd8860 OP_NOP2 OP_DROP pubkey OP_CHECKSIG + // 16_arbitrary_bytes OP_DROP + // OP_IF 32bit_locktime OP_NOP2 OP_DROP pubkey OP_CHECKSIG // OP_ELSE pubkey OP_CHECKSIG OP_ENDIF // as provided by artem redeemScript[0] == OP_RETURN && From 8b15fe44a24655c169c1284c651361a6e917f452 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 4 May 2021 19:57:16 +0200 Subject: [PATCH 10/29] whitespace again --- src/script/script.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index f672fdb4dc9..a37b7710868 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -239,7 +239,6 @@ bool CScript::IsPayToPublicKeyHash() const (*this)[24] == OP_CHECKSIG); } - bool CScript::IsPayToPublicKey() const { // Extra-fast test for pay-to-pubkey CScripts: From c67c9e8ba1b1a14cfdb1cecc169cf81805320dca Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 6 May 2021 08:20:12 +0200 Subject: [PATCH 11/29] add missing z_sendmany_template files --- .../asyncrpcoperation_sendmany_template.cpp | 1472 +++++++++++++++++ .../asyncrpcoperation_sendmany_template.h | 159 ++ 2 files changed, 1631 insertions(+) create mode 100644 src/wallet/asyncrpcoperation_sendmany_template.cpp create mode 100644 src/wallet/asyncrpcoperation_sendmany_template.h diff --git a/src/wallet/asyncrpcoperation_sendmany_template.cpp b/src/wallet/asyncrpcoperation_sendmany_template.cpp new file mode 100644 index 00000000000..66f3cad8c74 --- /dev/null +++ b/src/wallet/asyncrpcoperation_sendmany_template.cpp @@ -0,0 +1,1472 @@ +// Copyright (c) 2016 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +/****************************************************************************** + * Copyright © 2014-2019 The SuperNET Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * SuperNET software, including this file may be copied, modified, propagated * + * or distributed except according to the terms contained in the LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +#include "asyncrpcoperation_sendmany.h" +#include "asyncrpcoperation_sendmany_template.h" +#include "asyncrpcqueue.h" +#include "amount.h" +#include "consensus/upgrades.h" +#include "core_io.h" +#include "init.h" +#include "key_io.h" +#include "main.h" +#include "net.h" +#include "netbase.h" +#include "rpc/protocol.h" +#include "rpc/server.h" +#include "timedata.h" +#include "util.h" +#include "utilmoneystr.h" +#include "wallet.h" +#include "walletdb.h" +#include "script/interpreter.h" +#include "utiltime.h" +#include "zcash/IncrementalMerkleTree.hpp" +#include "sodium.h" +#include "miner.h" + +#include "komodo_defs.h" + +#include + +#include +#include +#include +#include +#include + +#include "paymentdisclosuredb.h" + +using namespace z_template; +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 ); +bool komodo_hardfork_active(uint32_t time); +extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); +extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); + +extern int find_output(UniValue obj, int n); + + +AsyncRPCOperation_sendmany_template::AsyncRPCOperation_sendmany_template( + boost::optional builder, + CMutableTransaction contextualTx, + std::string fromAddress, + std::vector tOutputs, + std::vector zOutputs, + int minDepth, + CAmount fee, + UniValue contextInfo, + UniValue HltcInfo) : + tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo), hltcinfo_(HltcInfo) +{ + assert(fee_ >= 0); + + if (minDepth < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be negative"); + } + + if (fromAddress.size() == 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "From address parameter missing"); + } + + if (tOutputs.size() == 0 && zOutputs.size() == 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "No recipients"); + } + + isUsingBuilder_ = false; + if (builder) { + isUsingBuilder_ = true; + builder_ = builder.get(); + } + + fromtaddr_ = DecodeDestination(fromAddress); + isfromtaddr_ = IsValidDestination(fromtaddr_); + isfromzaddr_ = false; + + if (!isfromtaddr_) { + auto address = DecodePaymentAddress(fromAddress); + if (IsValidPaymentAddress(address)) { + // We don't need to lock on the wallet as spending key related methods are thread-safe + if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr"); + } + + isfromzaddr_ = true; + frompaymentaddress_ = address; + spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address).get(); + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address"); + } + } + + if (isfromzaddr_ && minDepth==0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from zaddr"); + } + + // Log the context info i.e. the call parameters to z_sendmany + if (LogAcceptCategory("zrpcunsafe")) { + LogPrint("zrpcunsafe", "%s: z_sendmany initialized (params=%s)\n", getId(), contextInfo.write()); + } else { + LogPrint("zrpc", "%s: z_sendmany initialized\n", getId()); + } + + + // Enable payment disclosure if requested + paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true); +} + +AsyncRPCOperation_sendmany_template::~AsyncRPCOperation_sendmany_template() { +} + +void AsyncRPCOperation_sendmany_template::main() { + if (isCancelled()) + return; + + set_state(OperationStatus::EXECUTING); + start_execution_clock(); + + bool success = false; + +#ifdef ENABLE_MINING + #ifdef ENABLE_WALLET + GenerateBitcoins(false, NULL, 0); + #else + GenerateBitcoins(false, 0); + #endif +#endif + + try { + success = main_impl(); + } catch (const UniValue& objError) { + int code = find_value(objError, "code").get_int(); + std::string message = find_value(objError, "message").get_str(); + set_error_code(code); + set_error_message(message); + } catch (const runtime_error& e) { + set_error_code(-1); + set_error_message("runtime error: " + string(e.what())); + } catch (const logic_error& e) { + set_error_code(-1); + set_error_message("logic error: " + string(e.what())); + } catch (const exception& e) { + set_error_code(-1); + set_error_message("general exception: " + string(e.what())); + } catch (...) { + set_error_code(-2); + set_error_message("unknown error"); + } + +#ifdef ENABLE_MINING + #ifdef ENABLE_WALLET + GenerateBitcoins(GetBoolArg("-gen",false), pwalletMain, GetArg("-genproclimit", 1)); + #else + GenerateBitcoins(GetBoolArg("-gen",false), GetArg("-genproclimit", 1)); + #endif +#endif + + stop_execution_clock(); + + if (success) { + set_state(OperationStatus::SUCCESS); + } else { + set_state(OperationStatus::FAILED); + } + + std::string s = strprintf("%s: z_sendmany finished (status=%s", getId(), getStateAsString()); + if (success) { + s += strprintf(", txid=%s)\n", tx_.GetHash().ToString()); + } else { + s += strprintf(", error=%s)\n", getErrorMessage()); + } + LogPrintf("%s",s); + + // !!! Payment disclosure START + if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) { + uint256 txidhash = tx_.GetHash(); + std::shared_ptr db = PaymentDisclosureDB::sharedInstance(); + for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) { + p.first.hash = txidhash; + if (!db->Put(p.first, p.second)) { + LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString()); + } else { + LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString()); + } + } + } + // !!! Payment disclosure END +} + +// Alright - MVP adaption of z_sendmany to produce unsigned t->z transactions to be signed elsewhere +// special importance should be given to the "sapling binding sig" as this requires a siganature from a temporary public key +// the preimage for the singature requires the inputs already to be in place +bool AsyncRPCOperation_sendmany_template::main_impl() { + + assert(isfromtaddr_ != isfromzaddr_); + + bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1); + bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1); + bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0); + CAmount minersFee = fee_; + + // When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere + // and if there are multiple zaddrs, we don't know where to send it. + if (0){//isfromtaddr_) { + if (isSingleZaddrOutput) { + bool b = find_utxos(true); + if (!b) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no UTXOs found for taddr from address."); + } + } else { + bool b = find_utxos(false); + if (!b) { + if (isMultipleZaddrOutput) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend. Coinbase UTXOs can only be sent to a single zaddr recipient."); + } else { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend."); + } + } + } + } + + + string pubkey_str, refund_pubkey_str, secret_hash; + uint256 input_txid; + uint64_t input_index, input_amount; + uint32_t locktime; + pubkey_str = find_value(hltcinfo_, "pubkey").get_str(); + refund_pubkey_str = find_value(hltcinfo_, "refund_pubkey").get_str(); + secret_hash = find_value(hltcinfo_, "secret_hash").get_str(); + locktime = find_value(hltcinfo_, "locktime").get_int(); + + input_txid = Parseuint256((char *)find_value(hltcinfo_, "input_txid").get_str().c_str()); + input_index = find_value(hltcinfo_, "input_index").get_int(); + input_amount = atoll(find_value(hltcinfo_, "input_amount").get_str().c_str()); + + CTxDestination dest; + CScript scriptPubKey, redeemScript; + + redeemScript << OP_IF << locktime << OP_NOP2 << OP_DROP << ParseHex(refund_pubkey_str.c_str()) << + OP_CHECKSIG << OP_ELSE << OP_SIZE << ParseHex("20") << OP_EQUALVERIFY << OP_HASH160 << + ParseHex(secret_hash.c_str()) << OP_EQUALVERIFY << + ParseHex(pubkey_str.c_str()) << OP_CHECKSIG << OP_ENDIF; + scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + + //fprintf(stderr, "FULL HLTC: %s\n", redeemScript.ToString().c_str()); + //fprintf(stderr, "REDEEM HLTC: %s\n", scriptPubKey.ToString().c_str()); + + + if (!ExtractDestination(scriptPubKey, dest)) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "FIXME Bad prev scriptpubkey"); + } + + SendManyInputUTXO utxo(input_txid, input_index, input_amount, false, dest); + t_inputs_.push_back(utxo); + + + if (isfromzaddr_ && !find_unspent_notes()) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); + } + + // At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design + assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty()); + + CAmount t_inputs_total = 0; + for (SendManyInputUTXO & t : t_inputs_) { + t_inputs_total += std::get<2>(t); + } + + CAmount z_inputs_total = 0; + for (SendManyInputJSOP & t : z_sprout_inputs_) { + z_inputs_total += std::get<2>(t); + } + for (auto t : z_sapling_inputs_) { + z_inputs_total += t.note.value(); + } + + CAmount t_outputs_total = 0; + for (SendManyRecipient & t : t_outputs_) { + t_outputs_total += std::get<1>(t); + } + + CAmount z_outputs_total = 0; + for (SendManyRecipient & t : z_outputs_) { + z_outputs_total += std::get<1>(t); + } + + CAmount sendAmount = z_outputs_total + t_outputs_total; + CAmount targetAmount = sendAmount + minersFee; + + assert(!isfromtaddr_ || z_inputs_total == 0); + assert(!isfromzaddr_ || t_inputs_total == 0); + + if (isfromtaddr_ && (t_inputs_total < targetAmount)) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, + strprintf("Insufficient transparent funds, have %s, need %s", + FormatMoney(t_inputs_total), FormatMoney(targetAmount))); + } + + if (isfromzaddr_ && (z_inputs_total < targetAmount)) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, + strprintf("Insufficient shielded funds, have %s, need %s", + FormatMoney(z_inputs_total), FormatMoney(targetAmount))); + } + + // If from address is a taddr, select UTXOs to spend + CAmount selectedUTXOAmount = 0; + bool selectedUTXOCoinbase = false; + if (isfromtaddr_) { + // Get dust threshold + CKey secret; + secret.MakeNewKey(true); + CScript scriptPubKey = GetScriptForDestination(secret.GetPubKey().GetID()); + CTxOut out(CAmount(1), scriptPubKey); + CAmount dustThreshold = out.GetDustThreshold(minRelayTxFee); + CAmount dustChange = -1; + + std::vector selectedTInputs; + for (SendManyInputUTXO & t : t_inputs_) { + bool b = std::get<3>(t); + if (b) { + selectedUTXOCoinbase = true; + } + selectedUTXOAmount += std::get<2>(t); + selectedTInputs.push_back(t); + if (selectedUTXOAmount >= targetAmount) { + // Select another utxo if there is change less than the dust threshold. + dustChange = selectedUTXOAmount - targetAmount; + if (dustChange == 0 || dustChange >= dustThreshold) { + break; + } + } + } + + // If there is transparent change, is it valid or is it dust? + if (dustChange < dustThreshold && dustChange != 0) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, + strprintf("Insufficient transparent funds, have %s, need %s more to avoid creating invalid change output %s (dust threshold is %s)", + FormatMoney(t_inputs_total), FormatMoney(dustThreshold - dustChange), FormatMoney(dustChange), FormatMoney(dustThreshold))); + } + + t_inputs_ = selectedTInputs; + t_inputs_total = selectedUTXOAmount; + + // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects + size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); + { + LOCK(cs_main); + if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { + limit = 0; + } + } + if (limit > 0) { + size_t n = t_inputs_.size(); + if (n > limit) { + throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Too many transparent inputs %zu > limit %zu", n, limit)); + } + } + + // update the transaction with these inputs + if (isUsingBuilder_) { + CScript scriptPubKey; + for (auto t : t_inputs_) { + scriptPubKey = GetScriptForDestination(std::get<4>(t)); + //printf("Checking new script: %s\n", scriptPubKey.ToString().c_str()); + uint256 txid = std::get<0>(t); + int vout = std::get<1>(t); + CAmount amount = std::get<2>(t); + builder_.AddTransparentInput(COutPoint(txid, vout), scriptPubKey, amount, 4294967294); + } + // for Komodo, set lock time to accure interest, for other chains, set + // locktime to spend time locked coinbases + // FIXME Alright + //if (ASSETCHAINS_SYMBOL[0] == 0) + //{ + //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) + //if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) + fprintf(stderr, "SET A REASONABLE LOCKTIME \n"); + builder_.SetLockTime((uint32_t)time(NULL) - 60); // set lock time for Komodo interest + //else + // builder_.SetLockTime((uint32_t)chainActive.Tip()->GetMedianTimePast()); + //} + } else { + CMutableTransaction rawTx(tx_); + for (SendManyInputUTXO & t : t_inputs_) { + uint256 txid = std::get<0>(t); + int vout = std::get<1>(t); + CAmount amount = std::get<2>(t); + CTxIn in(COutPoint(txid, vout)); + rawTx.vin.push_back(in); + } + if (ASSETCHAINS_SYMBOL[0] == 0) + { + //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) + if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) + rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 + else + rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); + } + tx_ = CTransaction(rawTx); + } + } + + LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n", + getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); + LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total)); + LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total)); + LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(t_outputs_total)); + LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total)); + LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee)); + + + /** + * SCENARIO #0 + * + * Sprout not involved, so we just use the TransactionBuilder and we're done. + * We added the transparent inputs to the builder earlier. + */ + if (isUsingBuilder_) { + builder_.SetFee(minersFee); + + // Get various necessary keys + SaplingExpandedSpendingKey expsk; + uint256 ovk; + if (isfromzaddr_) { + auto sk = boost::get(spendingkey_); + expsk = sk.expsk; + ovk = expsk.full_viewing_key().ovk; + } else { + // Sending from a t-address, which we don't have an ovk for. Instead, + // generate a common one from the HD seed. This ensures the data is + // recoverable, while keeping it logically separate from the ZIP 32 + // Sapling key hierarchy, which the user might not be using. + HDSeed seed; + if (!pwalletMain->GetHDSeed(seed)) { + throw JSONRPCError( + RPC_WALLET_ERROR, + "AsyncRPCOperation_sendmany_template::main_impl(): HD seed not found"); + } + ovk = ovkForShieldingFromTaddr(seed); + } + + // Set change address if we are using transparent funds + // TODO: Should we just use fromtaddr_ as the change address? + if (isfromtaddr_) { + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + CReserveKey keyChange(pwalletMain); + CPubKey vchPubKey; + bool ret = keyChange.GetReservedKey(vchPubKey); + if (!ret) { + // should never fail, as we just unlocked + throw JSONRPCError( + RPC_WALLET_KEYPOOL_RAN_OUT, + "Could not generate a taddr to use as a change address"); + } + + CTxDestination changeAddr = vchPubKey.GetID(); + assert(builder_.SendChangeTo(changeAddr)); + } + + // Select Sapling notes + std::vector ops; + std::vector notes; + CAmount sum = 0; + for (auto t : z_sapling_inputs_) { + ops.push_back(t.op); + notes.push_back(t.note); + sum += t.note.value(); + if (sum >= targetAmount) { + break; + } + } + + // Fetch Sapling anchor and witnesses + uint256 anchor; + std::vector> witnesses; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->GetSaplingNoteWitnesses(ops, witnesses, anchor); + } + + // Add Sapling spends + for (size_t i = 0; i < notes.size(); i++) { + if (!witnesses[i]) { + throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note"); + } + assert(builder_.AddSaplingSpend(expsk, notes[i], anchor, witnesses[i].get())); + } + + // Add Sapling outputs + for (auto r : z_outputs_) { + auto address = std::get<0>(r); + auto value = std::get<1>(r); + auto hexMemo = std::get<2>(r); + + auto addr = DecodePaymentAddress(address); + assert(boost::get(&addr) != nullptr); + auto to = boost::get(addr); + + auto memo = get_memo_from_hex_string(hexMemo); + + builder_.AddSaplingOutput(ovk, to, value, memo); + } + + + + // Add transparent outputs + for (auto r : t_outputs_) { + auto outputAddress = std::get<0>(r); + auto amount = std::get<1>(r); + + auto address = DecodeDestination(outputAddress); + if (!builder_.AddTransparentOutput(address, amount)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr."); + } + } + + // Build the transaction + auto maybe_tx = builder_.BuildWithoutSig(); + if (!maybe_tx) { + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction."); + } + tx_ = maybe_tx.get(); + + UniValue ret(UniValue::VOBJ); + string signedtxn = EncodeHexTx(tx_); + //fprintf(stderr, "HED TX: %s\n", signedtxn.c_str()); + ret.push_back(Pair("hex", signedtxn)); + set_result(ret); + return true; + + + } + /** + * END SCENARIO #0 + */ + + + // Grab the current consensus branch ID + { + LOCK(cs_main); + consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); + } + + /** + * SCENARIO #1 + * + * taddr -> taddrs + * + * There are no zaddrs or joinsplits involved. + */ + if (isPureTaddrOnlyTx) { + add_taddr_outputs_to_tx(); + + CAmount funds = selectedUTXOAmount; + CAmount fundsSpent = t_outputs_total + minersFee; + CAmount change = funds - fundsSpent; + + if (change > 0) { + add_taddr_change_output_to_tx(0,change); + + LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", + getId(), + FormatMoney(change) + ); + } + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("rawtxn", EncodeHexTx(tx_))); + sign_send_raw_transaction(obj); + return true; + } + /** + * END SCENARIO #1 + */ + + + // Prepare raw transaction to handle JoinSplits + CMutableTransaction mtx(tx_); + crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_); + mtx.joinSplitPubKey = joinSplitPubKey_; + //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) + if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) + mtx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 + else + mtx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); + + tx_ = CTransaction(mtx); + + // Copy zinputs and zoutputs to more flexible containers + std::deque zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount + CAmount tmp = 0; + for (auto o : z_sprout_inputs_) { + zInputsDeque.push_back(o); + tmp += std::get<2>(o); + if (tmp >= targetAmount) { + break; + } + } + std::deque zOutputsDeque; + for (auto o : z_outputs_) { + zOutputsDeque.push_back(o); + } + + // When spending notes, take a snapshot of note witnesses and anchors as the treestate will + // change upon arrival of new blocks which contain joinsplit transactions. This is likely + // to happen as creating a chained joinsplit transaction can take longer than the block interval. + if (z_sprout_inputs_.size() > 0) { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto t : z_sprout_inputs_) { + JSOutPoint jso = std::get<0>(t); + std::vector vOutPoints = { jso }; + uint256 inputAnchor; + std::vector> vInputWitnesses; + pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor); + jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor }; + } + } + + + /** + * SCENARIO #2 + * + * taddr -> taddrs + * -> zaddrs + * + * Note: Consensus rule states that coinbase utxos can only be sent to a zaddr. + * Local wallet rule does not allow any change when sending coinbase utxos + * since there is currently no way to specify a change address and we don't + * want users accidentally sending excess funds to a recipient. + */ + if (isfromtaddr_) { + add_taddr_outputs_to_tx(); + + CAmount funds = selectedUTXOAmount; + CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total; + CAmount change = funds - fundsSpent; + + if (change > 0) { + if (selectedUTXOCoinbase) { + assert(isSingleZaddrOutput); + throw JSONRPCError(RPC_WALLET_ERROR, strprintf( + "Change %s not allowed. When shielding coinbase funds, the wallet does not " + "allow any change as there is currently no way to specify a change address " + "in z_sendmany.", FormatMoney(change))); + } else { + CBitcoinAddress ba = CBitcoinAddress(fromtaddr_); + add_taddr_change_output_to_tx(&ba,change); + LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", + getId(), + FormatMoney(change) + ); + } + } + + // Create joinsplits, where each output represents a zaddr recipient. + UniValue obj(UniValue::VOBJ); + while (zOutputsDeque.size() > 0) { + AsyncJoinSplitInfo info; + info.vpub_old = 0; + info.vpub_new = 0; + int n = 0; + while (n++ 0) { + SendManyRecipient smr = zOutputsDeque.front(); + std::string address = std::get<0>(smr); + CAmount value = std::get<1>(smr); + std::string hexMemo = std::get<2>(smr); + zOutputsDeque.pop_front(); + + PaymentAddress pa = DecodePaymentAddress(address); + JSOutput jso = JSOutput(boost::get(pa), value); + if (hexMemo.size() > 0) { + jso.memo = get_memo_from_hex_string(hexMemo); + } + info.vjsout.push_back(jso); + + // Funds are removed from the value pool and enter the private pool + info.vpub_old += value; + } + obj = perform_joinsplit(info); + } + sign_send_raw_transaction(obj); + return true; + } + /** + * END SCENARIO #2 + */ + + + + /** + * SCENARIO #3 + * + * zaddr -> taddrs + * -> zaddrs + * + * Send to zaddrs by chaining JoinSplits together and immediately consuming any change + * Send to taddrs by creating dummy z outputs and accumulating value in a change note + * which is used to set vpub_new in the last chained joinsplit. + */ + UniValue obj(UniValue::VOBJ); + CAmount jsChange = 0; // this is updated after each joinsplit + int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0 + bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit + CAmount vpubNewTarget = minersFee; + if (t_outputs_total > 0) { + add_taddr_outputs_to_tx(); + vpubNewTarget += t_outputs_total; + } + + // Keep track of treestate within this transaction + boost::unordered_map intermediates; + std::vector previousCommitments; + + while (!vpubNewProcessed) { + AsyncJoinSplitInfo info; + info.vpub_old = 0; + info.vpub_new = 0; + + CAmount jsInputValue = 0; + uint256 jsAnchor; + std::vector> witnesses; + + JSDescription prevJoinSplit; + + // Keep track of previous JoinSplit and its commitments + if (tx_.vjoinsplit.size() > 0) { + prevJoinSplit = tx_.vjoinsplit.back(); + } + + // If there is no change, the chain has terminated so we can reset the tracked treestate. + if (jsChange==0 && tx_.vjoinsplit.size() > 0) { + intermediates.clear(); + previousCommitments.clear(); + } + + // + // Consume change as the first input of the JoinSplit. + // + if (jsChange > 0) { + LOCK2(cs_main, pwalletMain->cs_wallet); + + // Update tree state with previous joinsplit + SproutMerkleTree tree; + auto it = intermediates.find(prevJoinSplit.anchor); + if (it != intermediates.end()) { + tree = it->second; + } else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor"); + } + + assert(changeOutputIndex != -1); + boost::optional changeWitness; + int n = 0; + for (const uint256& commitment : prevJoinSplit.commitments) { + tree.append(commitment); + previousCommitments.push_back(commitment); + if (!changeWitness && changeOutputIndex == n++) { + changeWitness = tree.witness(); + } else if (changeWitness) { + changeWitness.get().append(commitment); + } + } + if (changeWitness) { + witnesses.push_back(changeWitness); + } + jsAnchor = tree.root(); + intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries) + + // Decrypt the change note's ciphertext to retrieve some data we need + ZCNoteDecryption decryptor(boost::get(spendingkey_).receiving_key()); + auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey); + try { + SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt( + decryptor, + prevJoinSplit.ciphertexts[changeOutputIndex], + prevJoinSplit.ephemeralKey, + hSig, + (unsigned char) changeOutputIndex); + + SproutNote note = plaintext.note(boost::get(frompaymentaddress_)); + info.notes.push_back(note); + + jsInputValue += plaintext.value(); + + LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n", + getId(), + FormatMoney(plaintext.value()) + ); + + } catch (const std::exception& e) { + throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what())); + } + } + + + // + // Consume spendable non-change notes + // + std::vector vInputNotes; + std::vector vOutPoints; + std::vector> vInputWitnesses; + uint256 inputAnchor; + int numInputsNeeded = (jsChange>0) ? 1 : 0; + while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) { + SendManyInputJSOP t = zInputsDeque.front(); + JSOutPoint jso = std::get<0>(t); + SproutNote note = std::get<1>(t); + CAmount noteFunds = std::get<2>(t); + zInputsDeque.pop_front(); + + WitnessAnchorData wad = jsopWitnessAnchorMap[ jso.ToString() ]; + vInputWitnesses.push_back(wad.witness); + if (inputAnchor.IsNull()) { + inputAnchor = wad.anchor; + } else if (inputAnchor != wad.anchor) { + throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor"); + } + + vOutPoints.push_back(jso); + vInputNotes.push_back(note); + + jsInputValue += noteFunds; + + int wtxHeight = -1; + int wtxDepth = -1; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash]; + // Zero-confirmation notes belong to transactions which have not yet been mined + if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) { + throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString())); + } + 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", + getId(), + jso.hash.ToString().substr(0, 10), + jso.js, + int(jso.n), // uint8_t + FormatMoney(noteFunds), + wtxHeight, + wtxDepth + ); + } + + // Add history of previous commitments to witness + if (vInputNotes.size() > 0) { + + if (vInputWitnesses.size()==0) { + throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment"); + } + + for (auto & optionalWitness : vInputWitnesses) { + if (!optionalWitness) { + throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null"); + } + SproutWitness w = *optionalWitness; // could use .get(); + if (jsChange > 0) { + for (const uint256& commitment : previousCommitments) { + w.append(commitment); + } + if (jsAnchor != w.root()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input"); + } + } + witnesses.push_back(w); + } + + // The jsAnchor is null if this JoinSplit is at the start of a new chain + if (jsAnchor.IsNull()) { + jsAnchor = inputAnchor; + } + + // Add spendable notes as inputs + std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes)); + } + + // Find recipient to transfer funds to + std::string address, hexMemo; + CAmount value = 0; + if (zOutputsDeque.size() > 0) { + SendManyRecipient smr = zOutputsDeque.front(); + address = std::get<0>(smr); + value = std::get<1>(smr); + hexMemo = std::get<2>(smr); + zOutputsDeque.pop_front(); + } + + // Reset change + jsChange = 0; + CAmount outAmount = value; + + // Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy) + if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) { + assert(!vpubNewProcessed); + if (jsInputValue < vpubNewTarget) { + throw JSONRPCError(RPC_WALLET_ERROR, + strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr outputs %s)", + FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_outputs_total))); + } + outAmount += vpubNewTarget; + info.vpub_new += vpubNewTarget; // funds flowing back to public pool + vpubNewProcessed = true; + jsChange = jsInputValue - outAmount; + assert(jsChange >= 0); + } + else { + // This is not the last joinsplit, so compute change and any amount still due to the recipient + if (jsInputValue > outAmount) { + jsChange = jsInputValue - outAmount; + } else if (outAmount > jsInputValue) { + // Any amount due is owed to the recipient. Let the miners fee get paid first. + CAmount due = outAmount - jsInputValue; + SendManyRecipient r = SendManyRecipient(address, due, hexMemo); + zOutputsDeque.push_front(r); + + // reduce the amount being sent right now to the value of all inputs + value = jsInputValue; + } + } + + // create output for recipient + if (address.empty()) { + assert(value==0); + info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new + } else { + PaymentAddress pa = DecodePaymentAddress(address); + // If we are here, we know we have no Sapling outputs. + JSOutput jso = JSOutput(boost::get(pa), value); + if (hexMemo.size() > 0) { + jso.memo = get_memo_from_hex_string(hexMemo); + } + info.vjsout.push_back(jso); + } + + // create output for any change + if (jsChange>0) { + info.vjsout.push_back(JSOutput(boost::get(frompaymentaddress_), jsChange)); + + LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n", + getId(), + FormatMoney(jsChange) + ); + } + + obj = perform_joinsplit(info, witnesses, jsAnchor); + + if (jsChange > 0) { + changeOutputIndex = find_output(obj, 1); + } + } + + // Sanity check in case changes to code block above exits loop by invoking 'break' + assert(zInputsDeque.size() == 0); + assert(zOutputsDeque.size() == 0); + assert(vpubNewProcessed); + + sign_send_raw_transaction(obj); + return true; +} + + +/** + * Sign and send a raw transaction. + * Raw transaction as hex string should be in object field "rawtxn" + */ +void AsyncRPCOperation_sendmany_template::sign_send_raw_transaction(UniValue obj) +{ + // Sign the raw transaction + UniValue rawtxnValue = find_value(obj, "rawtxn"); + if (rawtxnValue.isNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for raw transaction"); + } + std::string rawtxn = rawtxnValue.get_str(); + + UniValue params = UniValue(UniValue::VARR); + params.push_back(rawtxn); + UniValue signResultValue = signrawtransaction(params, false, CPubKey()); + UniValue signResultObject = signResultValue.get_obj(); + UniValue completeValue = find_value(signResultObject, "complete"); + bool complete = completeValue.get_bool(); + if (!complete) { + // TODO: #1366 Maybe get "errors" and print array vErrors into a string + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to sign transaction"); + } + + UniValue hexValue = find_value(signResultObject, "hex"); + if (hexValue.isNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for signed transaction"); + } + std::string signedtxn = hexValue.get_str(); + + // Send the signed transaction + if (!testmode) { + params.clear(); + params.setArray(); + params.push_back(signedtxn); + UniValue sendResultValue = sendrawtransaction(params, false, CPubKey()); + if (sendResultValue.isNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Send raw transaction did not return an error or a txid."); + } + + std::string txid = sendResultValue.get_str(); + + UniValue o(UniValue::VOBJ); + o.push_back(Pair("txid", txid)); + set_result(o); + } else { + // Test mode does not send the transaction to the network. + + CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + + UniValue o(UniValue::VOBJ); + o.push_back(Pair("test", 1)); + o.push_back(Pair("txid", tx.GetHash().ToString())); + o.push_back(Pair("hex", signedtxn)); + set_result(o); + } + + // Keep the signed transaction so we can hash to the same txid + CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + tx_ = tx; +} + +bool AsyncRPCOperation_sendmany_template::find_utxos(bool fAcceptCoinbase=false) { + std::set destinations; + destinations.insert(fromtaddr_); + + //printf("Looking for %s\n", boost::apply_visitor(AddressVisitorString(), fromtaddr_).c_str()); + + vector vecOutputs; + + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->AvailableCoins(vecOutputs, false, NULL, true, fAcceptCoinbase); + + BOOST_FOREACH(const COutput& out, vecOutputs) { + CTxDestination dest; + + fprintf(stderr, "HASH CHECK %s\n", out.tx->GetHash().ToString().c_str()); + + + if (!out.fSpendable) { + continue; + } + + if( mindepth_ > 1 ) { + int nHeight = tx_height(out.tx->GetHash()); + int dpowconfs = komodo_dpowconfs(nHeight, out.nDepth); + if (dpowconfs < mindepth_) { + continue; + } + } else { + if (out.nDepth < mindepth_) { + continue; + } + } + + const CScript &scriptPubKey = out.tx->vout[out.i].scriptPubKey; + + if (destinations.size()) { + if (!ExtractDestination(scriptPubKey, dest)) { + continue; + } + + //printf("%s\n", boost::apply_visitor(AddressVisitorString(), dest).c_str()); + if (!destinations.count(dest)) { + continue; + } + } + + // By default we ignore coinbase outputs + bool isCoinbase = out.tx->IsCoinBase(); + if (isCoinbase && fAcceptCoinbase==false) { + continue; + } + + if (!ExtractDestination(scriptPubKey, dest, true)) + continue; + + CAmount nValue = out.tx->vout[out.i].nValue; + + + // Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) + //typedef std::tuple SendManyInputUTXO; + SendManyInputUTXO utxo(out.tx->GetHash(), out.i, nValue, isCoinbase, dest); + t_inputs_.push_back(utxo); + } + + // sort in ascending order, so smaller utxos appear first + std::sort(t_inputs_.begin(), t_inputs_.end(), [](SendManyInputUTXO i, SendManyInputUTXO j) -> bool { + return ( std::get<2>(i) < std::get<2>(j)); + }); + + return t_inputs_.size() > 0; +} + + +bool AsyncRPCOperation_sendmany_template::find_unspent_notes() { + std::vector sproutEntries; + std::vector saplingEntries; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_); + } + + // If using the TransactionBuilder, we only want Sapling notes. + // If not using it, we only want Sprout notes. + // TODO: Refactor `GetFilteredNotes()` so we only fetch what we need. + if (isUsingBuilder_) { + sproutEntries.clear(); + } else { + saplingEntries.clear(); + } + + for (CSproutNotePlaintextEntry & entry : sproutEntries) { + z_sprout_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get(frompaymentaddress_)), CAmount(entry.plaintext.value()))); + std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end()); + LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n", + getId(), + entry.jsop.hash.ToString().substr(0, 10), + entry.jsop.js, + int(entry.jsop.n), // uint8_t + FormatMoney(entry.plaintext.value()), + HexStr(data).substr(0, 10) + ); + } + + for (auto entry : saplingEntries) { + z_sapling_inputs_.push_back(entry); + std::string data(entry.memo.begin(), entry.memo.end()); + LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", + getId(), + entry.op.hash.ToString().substr(0, 10), + entry.op.n, + FormatMoney(entry.note.value()), + HexStr(data).substr(0, 10)); + } + + if (z_sprout_inputs_.empty() && z_sapling_inputs_.empty()) { + return false; + } + + // sort in descending order, so big notes appear first + std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(), + [](SendManyInputJSOP i, SendManyInputJSOP j) -> bool { + return std::get<2>(i) > std::get<2>(j); + }); + std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(), + [](SaplingNoteEntry i, SaplingNoteEntry j) -> bool { + return i.note.value() > j.note.value(); + }); + + return true; +} + +UniValue AsyncRPCOperation_sendmany_template::perform_joinsplit(AsyncJoinSplitInfo & info) { + std::vector> witnesses; + uint256 anchor; + { + LOCK(cs_main); + anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor + } + return perform_joinsplit(info, witnesses, anchor); +} + + +UniValue AsyncRPCOperation_sendmany_template::perform_joinsplit(AsyncJoinSplitInfo & info, std::vector & outPoints) { + std::vector> witnesses; + uint256 anchor; + { + LOCK(cs_main); + pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor); + } + return perform_joinsplit(info, witnesses, anchor); +} + +UniValue AsyncRPCOperation_sendmany_template::perform_joinsplit( + AsyncJoinSplitInfo & info, + std::vector> witnesses, + uint256 anchor) +{ + if (anchor.IsNull()) { + throw std::runtime_error("anchor is null"); + } + + if (!(witnesses.size() == info.notes.size())) { + throw runtime_error("number of notes and witnesses do not match"); + } + + for (size_t i = 0; i < witnesses.size(); i++) { + if (!witnesses[i]) { + throw runtime_error("joinsplit input could not be found in tree"); + } + info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], boost::get(spendingkey_))); + } + + // Make sure there are two inputs and two outputs + while (info.vjsin.size() < ZC_NUM_JS_INPUTS) { + info.vjsin.push_back(JSInput()); + } + + while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) { + info.vjsout.push_back(JSOutput()); + } + + if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) { + throw runtime_error("unsupported joinsplit input/output counts"); + } + + CMutableTransaction mtx(tx_); + + LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n", + getId(), + tx_.vjoinsplit.size(), + FormatMoney(info.vpub_old), FormatMoney(info.vpub_new), + FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()), + FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value) + ); + + // Generate the proof, this can take over a minute. + std::array inputs + {info.vjsin[0], info.vjsin[1]}; + std::array outputs + {info.vjsout[0], info.vjsout[1]}; + std::array inputMap; + std::array outputMap; + uint256 esk; // payment disclosure - secret + + JSDescription jsdesc = JSDescription::Randomized( + mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION), + *pzcashParams, + joinSplitPubKey_, + anchor, + inputs, + outputs, + inputMap, + outputMap, + info.vpub_old, + info.vpub_new, + !this->testmode, + &esk); // parameter expects pointer to esk, so pass in address + { + auto verifier = libzcash::ProofVerifier::Strict(); + if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) { + throw std::runtime_error("error verifying joinsplit"); + } + } + + mtx.vjoinsplit.push_back(jsdesc); + + // Empty output script. + CScript scriptCode; + CTransaction signTx(mtx); + uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_); + + // Add the signature + if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, + dataToBeSigned.begin(), 32, + joinSplitPrivKey_ + ) == 0)) + { + throw std::runtime_error("crypto_sign_detached failed"); + } + + // Sanity check + if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0], + dataToBeSigned.begin(), 32, + mtx.joinSplitPubKey.begin() + ) == 0)) + { + throw std::runtime_error("crypto_sign_verify_detached failed"); + } + + CTransaction rawTx(mtx); + tx_ = rawTx; + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << rawTx; + + std::string encryptedNote1; + std::string encryptedNote2; + { + CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); + ss2 << ((unsigned char) 0x00); + ss2 << jsdesc.ephemeralKey; + ss2 << jsdesc.ciphertexts[0]; + ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_); + + encryptedNote1 = HexStr(ss2.begin(), ss2.end()); + } + { + CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); + ss2 << ((unsigned char) 0x01); + ss2 << jsdesc.ephemeralKey; + ss2 << jsdesc.ciphertexts[1]; + ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_); + + encryptedNote2 = HexStr(ss2.begin(), ss2.end()); + } + + UniValue arrInputMap(UniValue::VARR); + UniValue arrOutputMap(UniValue::VARR); + for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) { + arrInputMap.push_back(static_cast(inputMap[i])); + } + for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { + arrOutputMap.push_back(static_cast(outputMap[i])); + } + + + // !!! Payment disclosure START + unsigned char buffer[32] = {0}; + memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer + std::vector vch(&buffer[0], &buffer[0] + 32); + uint256 joinSplitPrivKey = uint256(vch); + size_t js_index = tx_.vjoinsplit.size() - 1; + uint256 placeholder; + for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { + uint8_t mapped_index = outputMap[i]; + // placeholder for txid will be filled in later when tx has been finalized and signed. + PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index}; + JSOutput output = outputs[mapped_index]; + libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output + PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr}; + paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo)); + + LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr)); + } + // !!! Payment disclosure END + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("encryptednote1", encryptedNote1)); + obj.push_back(Pair("encryptednote2", encryptedNote2)); + obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end()))); + obj.push_back(Pair("inputmap", arrInputMap)); + obj.push_back(Pair("outputmap", arrOutputMap)); + return obj; +} + +void AsyncRPCOperation_sendmany_template::add_taddr_outputs_to_tx() { + + CMutableTransaction rawTx(tx_); + + for (SendManyRecipient & r : t_outputs_) { + std::string outputAddress = std::get<0>(r); + CAmount nAmount = std::get<1>(r); + + CTxDestination address = DecodeDestination(outputAddress); + if (!IsValidDestination(address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr."); + } + + CScript scriptPubKey = GetScriptForDestination(address); + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } + //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) + if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) + rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 + else + rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); + + tx_ = CTransaction(rawTx); +} + +void AsyncRPCOperation_sendmany_template::add_taddr_change_output_to_tx(CBitcoinAddress *fromaddress,CAmount amount) { + + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + CScript scriptPubKey; + CReserveKey keyChange(pwalletMain); + CPubKey vchPubKey; + if ( fromaddress != 0 ) + scriptPubKey = GetScriptForDestination(fromaddress->Get()); + else + { + bool ret = keyChange.GetReservedKey(vchPubKey); + if (!ret) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Could not generate a taddr to use as a change address"); // should never fail, as we just unlocked + } + scriptPubKey = GetScriptForDestination(vchPubKey.GetID()); + } + CTxOut out(amount, scriptPubKey); + + CMutableTransaction rawTx(tx_); + rawTx.vout.push_back(out); + //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) + if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) + rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 + else + rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); + tx_ = CTransaction(rawTx); +} + +std::array AsyncRPCOperation_sendmany_template::get_memo_from_hex_string(std::string s) { + // initialize to default memo (no_memo), see section 5.5 of the protocol spec + std::array memo = {{0xF6}}; + + std::vector rawMemo = ParseHex(s.c_str()); + + // If ParseHex comes across a non-hex char, it will stop but still return results so far. + size_t slen = s.length(); + if (slen % 2 !=0 || (slen>0 && rawMemo.size()!=slen/2)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo must be in hexadecimal format"); + } + + if (rawMemo.size() > ZC_MEMO_SIZE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Memo size of %d is too big, maximum allowed is %d", rawMemo.size(), ZC_MEMO_SIZE)); + } + + // copy vector into boost array + int lenMemo = rawMemo.size(); + for (int i = 0; i < ZC_MEMO_SIZE && i < lenMemo; i++) { + memo[i] = rawMemo[i]; + } + return memo; +} + +/** + * Override getStatus() to append the operation's input parameters to the default status object. + */ +UniValue AsyncRPCOperation_sendmany_template::getStatus() const { + UniValue v = AsyncRPCOperation::getStatus(); + if (contextinfo_.isNull()) { + return v; + } + + UniValue obj = v.get_obj(); + obj.push_back(Pair("method", "z_sendmany")); + obj.push_back(Pair("params", contextinfo_ )); + return obj; +} + diff --git a/src/wallet/asyncrpcoperation_sendmany_template.h b/src/wallet/asyncrpcoperation_sendmany_template.h new file mode 100644 index 00000000000..27d89dede77 --- /dev/null +++ b/src/wallet/asyncrpcoperation_sendmany_template.h @@ -0,0 +1,159 @@ +// Copyright (c) 2016 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +/****************************************************************************** + * Copyright © 2014-2019 The SuperNET Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * SuperNET software, including this file may be copied, modified, propagated * + * or distributed except according to the terms contained in the LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +#ifndef ASYNCRPCOPERATION_SENDMANY_TEMPLATE_H +#define ASYNCRPCOPERATION_SENDMANY_TEMPLATE_H + +#include "asyncrpcoperation.h" +#include "amount.h" +#include "primitives/transaction.h" +#include "transaction_builder.h" +#include "zcash/JoinSplit.hpp" +#include "zcash/Address.hpp" +#include "wallet.h" +#include "paymentdisclosure.h" + +#include +#include +#include + +#include + +namespace z_template { + +// Default transaction fee if caller does not specify one. +#define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000 + +using namespace libzcash; + +// A recipient is a tuple of address, amount, memo (optional if zaddr) +typedef std::tuple SendManyRecipient; + +// Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) +typedef std::tuple SendManyInputUTXO; + +// Input JSOP is a tuple of JSOutpoint, note and amount +typedef std::tuple SendManyInputJSOP; + +// Package of info which is passed to perform_joinsplit methods. +struct AsyncJoinSplitInfo +{ + std::vector vjsin; + std::vector vjsout; + std::vector notes; + CAmount vpub_old = 0; + CAmount vpub_new = 0; +}; + +// A struct to help us track the witness and anchor for a given JSOutPoint +struct WitnessAnchorData { + boost::optional witness; + uint256 anchor; +}; + +class AsyncRPCOperation_sendmany_template : public AsyncRPCOperation { +public: + AsyncRPCOperation_sendmany_template( + boost::optional builder, + CMutableTransaction contextualTx, + std::string fromAddress, + std::vector tOutputs, + std::vector zOutputs, + int minDepth, + CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE, + UniValue contextInfo = NullUniValue, + UniValue HltcInfo = NullUniValue); + virtual ~AsyncRPCOperation_sendmany_template(); + + // We don't want to be copied or moved around + AsyncRPCOperation_sendmany_template(AsyncRPCOperation_sendmany_template const&) = delete; // Copy construct + AsyncRPCOperation_sendmany_template(AsyncRPCOperation_sendmany_template&&) = delete; // Move construct + AsyncRPCOperation_sendmany_template& operator=(AsyncRPCOperation_sendmany_template const&) = delete; // Copy assign + AsyncRPCOperation_sendmany_template& operator=(AsyncRPCOperation_sendmany_template &&) = delete; // Move assign + + virtual void main(); + + virtual UniValue getStatus() const; + + bool testmode = false; // Set to true to disable sending txs and generating proofs + + bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database. + +private: + + UniValue contextinfo_; // optional data to include in return value from getStatus() + UniValue hltcinfo_; + + bool isUsingBuilder_; // Indicates that no Sprout addresses are involved + uint32_t consensusBranchId_; + CAmount fee_; + int mindepth_; + std::string fromaddress_; + bool isfromtaddr_; + bool isfromzaddr_; + CTxDestination fromtaddr_; + PaymentAddress frompaymentaddress_; + SpendingKey spendingkey_; + + uint256 joinSplitPubKey_; + unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; + + // The key is the result string from calling JSOutPoint::ToString() + std::unordered_map jsopWitnessAnchorMap; + + std::vector t_outputs_; + std::vector z_outputs_; + std::vector t_inputs_; + std::vector z_sprout_inputs_; + std::vector z_sapling_inputs_; + + TransactionBuilder builder_; + CTransaction tx_; + + void add_taddr_change_output_to_tx(CBitcoinAddress *fromaddress,CAmount amount); + void add_taddr_outputs_to_tx(); + bool find_unspent_notes(); + bool find_utxos(bool fAcceptCoinbase); + std::array get_memo_from_hex_string(std::string s); + bool main_impl(); + + // JoinSplit without any input notes to spend + UniValue perform_joinsplit(AsyncJoinSplitInfo &); + + // JoinSplit with input notes to spend (JSOutPoints)) + UniValue perform_joinsplit(AsyncJoinSplitInfo &, std::vector & ); + + // JoinSplit where you have the witnesses and anchor + UniValue perform_joinsplit( + AsyncJoinSplitInfo & info, + std::vector> witnesses, + uint256 anchor); + + void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error + + // payment disclosure! + std::vector paymentDisclosureData_; +}; + +} // z_template + + +#endif /* ASYNCRPCOPERATION_SENDMANY_TEMPLATE_H */ + + From 29d9f55596d88ee7faf372acc93392f5af626ebf Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 6 May 2021 08:29:51 +0200 Subject: [PATCH 12/29] remove debug code --- src/Makefile.am | 2 - src/komodo_utils.h | 2 +- src/rpc/client.cpp | 3 - src/rpc/server.cpp | 1 - src/rpc/server.h | 1 - src/transaction_builder.cpp | 169 --------------------- src/transaction_builder.h | 2 - src/wallet/rpcwallet.cpp | 286 ------------------------------------ 8 files changed, 1 insertion(+), 465 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 77cb181303c..23cff51cba9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -258,7 +258,6 @@ BITCOIN_CORE_H = \ version.h \ wallet/asyncrpcoperation_mergetoaddress.h \ wallet/asyncrpcoperation_sendmany.h \ - wallet/asyncrpcoperation_sendmany_template.h \ wallet/asyncrpcoperation_shieldcoinbase.h \ wallet/crypter.h \ wallet/db.h \ @@ -385,7 +384,6 @@ libbitcoin_wallet_a_SOURCES = \ zcbenchmarks.h \ wallet/asyncrpcoperation_mergetoaddress.cpp \ wallet/asyncrpcoperation_sendmany.cpp \ - wallet/asyncrpcoperation_sendmany_template.cpp \ wallet/asyncrpcoperation_shieldcoinbase.cpp \ wallet/crypter.cpp \ wallet/db.cpp \ diff --git a/src/komodo_utils.h b/src/komodo_utils.h index d01a15f47a3..9f2ec53fce7 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -2374,7 +2374,7 @@ fprintf(stderr,"extralen.%d before disable bits\n",extralen); } else if ( strcmp("VRSC",ASSETCHAINS_SYMBOL) == 0 ) dpowconfs = 0; - else if ( ASSETCHAINS_PRIVATE != 0 && strcmp("ZOMBIE",ASSETCHAINS_SYMBOL) ) + else if ( ASSETCHAINS_PRIVATE != 0 ) { fprintf(stderr,"-ac_private for a non-PIRATE chain is not supported. The only reason to have an -ac_private chain is for total privacy and that is best achieved with the largest anon set. PIRATE has that and it is recommended to just use PIRATE\n"); StartShutdown(); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index c5697b678fb..e67153082ae 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -156,9 +156,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "z_sendmany", 1}, { "z_sendmany", 2}, { "z_sendmany", 3}, - { "z_sendmany_template", 1}, - { "z_sendmany_template", 2}, - { "z_sendmany_template", 3}, { "z_shieldcoinbase", 2}, { "z_shieldcoinbase", 3}, { "z_getoperationstatus", 0}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 0953c49b33b..1416d3bc72d 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -667,7 +667,6 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "z_getbalance", &z_getbalance, false }, { "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, { "wallet", "z_mergetoaddress", &z_mergetoaddress, false }, - { "wallet", "z_sendmany_template", &z_sendmany_template, false }, { "wallet", "z_sendmany", &z_sendmany, false }, { "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false }, { "wallet", "z_getoperationstatus", &z_getoperationstatus, true }, diff --git a/src/rpc/server.h b/src/rpc/server.h index 3067cd9c8df..465bc832b56 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -495,7 +495,6 @@ extern UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp, cons extern UniValue z_getbalance(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp -extern UniValue z_sendmany_template(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index c6dc9c3d590..07e20132611 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -123,175 +123,6 @@ bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr) return true; } -// Alright - using this to test initial p2sh consensus changes -// adapted z_sendmany to produce unsigned t->z transactions which can then be handled by external code to add sigs -// this could be cleaned up quite a bit if used in production -boost::optional TransactionBuilder::BuildWithoutSig() -{ - // - // Consistency checks - // - - // Valid change - CAmount change = mtx.valueBalance - fee; - for (auto tIn : tIns) { - change += tIn.value; - } - for (auto tOut : mtx.vout) { - change -= tOut.nValue; - } - if (change < 0) { - return boost::none; - } - - // - // Change output - // - - if (change > 0) { - // Send change to the specified change address. If no change address - // was set, send change to the first Sapling address given as input. - if (zChangeAddr) { - AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change); - } else if (tChangeAddr) { - // tChangeAddr has already been validated. - assert(AddTransparentOutput(tChangeAddr.value(), change)); - } else if (!spends.empty()) { - auto fvk = spends[0].expsk.full_viewing_key(); - auto note = spends[0].note; - libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d); - AddSaplingOutput(fvk.ovk, changeAddr, change); - } else { - return boost::none; - } - } - - // - // Sapling spends and outputs - // - - auto ctx = librustzcash_sapling_proving_ctx_init(); - - // Create Sapling SpendDescriptions - for (auto spend : spends) { - auto cm = spend.note.cm(); - auto nf = spend.note.nullifier( - spend.expsk.full_viewing_key(), spend.witness.position()); - if (!(cm && nf)) { - librustzcash_sapling_proving_ctx_free(ctx); - return boost::none; - } - - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << spend.witness.path(); - std::vector witness(ss.begin(), ss.end()); - - SpendDescription sdesc; - if (!librustzcash_sapling_spend_proof( - ctx, - spend.expsk.full_viewing_key().ak.begin(), - spend.expsk.nsk.begin(), - spend.note.d.data(), - spend.note.r.begin(), - spend.alpha.begin(), - spend.note.value(), - spend.anchor.begin(), - witness.data(), - sdesc.cv.begin(), - sdesc.rk.begin(), - sdesc.zkproof.data())) { - librustzcash_sapling_proving_ctx_free(ctx); - return boost::none; - } - - sdesc.anchor = spend.anchor; - sdesc.nullifier = *nf; - mtx.vShieldedSpend.push_back(sdesc); - } - - // Create Sapling OutputDescriptions - for (auto output : outputs) { - auto cm = output.note.cm(); - if (!cm) { - librustzcash_sapling_proving_ctx_free(ctx); - return boost::none; - } - - libzcash::SaplingNotePlaintext notePlaintext(output.note, output.memo); - - auto res = notePlaintext.encrypt(output.note.pk_d); - if (!res) { - librustzcash_sapling_proving_ctx_free(ctx); - return boost::none; - } - auto enc = res.get(); - auto encryptor = enc.second; - - OutputDescription odesc; - if (!librustzcash_sapling_output_proof( - ctx, - encryptor.get_esk().begin(), - output.note.d.data(), - output.note.pk_d.begin(), - output.note.r.begin(), - output.note.value(), - odesc.cv.begin(), - odesc.zkproof.begin())) { - librustzcash_sapling_proving_ctx_free(ctx); - return boost::none; - } - - odesc.cm = *cm; - odesc.ephemeralKey = encryptor.get_epk(); - odesc.encCiphertext = enc.first; - - libzcash::SaplingOutgoingPlaintext outPlaintext(output.note.pk_d, encryptor.get_esk()); - odesc.outCiphertext = outPlaintext.encrypt( - output.ovk, - odesc.cv, - odesc.cm, - encryptor); - mtx.vShieldedOutput.push_back(odesc); - } - - // add op_return if there is one to add - AddOpRetLast(); - - // - // Signatures - // - - auto consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams); - - // Empty output script. - uint256 dataToBeSigned; - CScript scriptCode; - try { - dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId); - } catch (std::logic_error ex) { - librustzcash_sapling_proving_ctx_free(ctx); - return boost::none; - } - - // Create Sapling spendAuth and binding signatures - for (size_t i = 0; i < spends.size(); i++) { - librustzcash_sapling_spend_sig( - spends[i].expsk.ask.begin(), - spends[i].alpha.begin(), - dataToBeSigned.begin(), - mtx.vShieldedSpend[i].spendAuthSig.data()); - } - librustzcash_sapling_binding_sig( - ctx, - mtx.valueBalance, - dataToBeSigned.begin(), - mtx.bindingSig.data()); - - librustzcash_sapling_proving_ctx_free(ctx); - - return CTransaction(mtx); -} - boost::optional TransactionBuilder::Build() { // diff --git a/src/transaction_builder.h b/src/transaction_builder.h index bc790034d92..49c09294d2e 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -107,8 +107,6 @@ class TransactionBuilder void SetLockTime(uint32_t time) { this->mtx.nLockTime = time; } boost::optional Build(); - - boost::optional BuildWithoutSig(); }; #endif /* TRANSACTION_BUILDER_H */ diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index dc59a80696c..1231953b18f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -45,7 +45,6 @@ #include "wallet/asyncrpcoperation_mergetoaddress.h" #include "wallet/asyncrpcoperation_sendmany.h" #include "wallet/asyncrpcoperation_shieldcoinbase.h" -#include "wallet/asyncrpcoperation_sendmany_template.h" #include "consensus/upgrades.h" @@ -4474,291 +4473,6 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO #define CTXIN_SPEND_DUST_SIZE 148 #define CTXOUT_REGULAR_SIZE 34 -UniValue z_sendmany_template(const UniValue& params, bool fHelp, const CPubKey& mypk) -{ - if (!EnsureWalletIsAvailable(fHelp)) - return NullUniValue; - - if (fHelp || params.size() < 2 || params.size() > 4) - throw runtime_error( - "z_sendmany_template \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( FIXME ) ( fee )\n" - "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." - "\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself." - "\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed." - + strprintf("\nBefore Sapling activates, the maximum number of zaddr outputs is %d due to transaction size limits.\n", Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING) - + HelpRequiringPassphrase() + "\n" - "\nArguments:\n" - "1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from.\n" - "2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n" - " [{\n" - " \"address\":address (string, required) The address is a taddr or zaddr\n" - " \"amount\":amount (numeric, required) The numeric amount in KMD is the value\n" - " \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n" - " }, ... ]\n" - "3. FIXME HLTC JSON, pubkey pubkey_refund locktime secret_hash input:[txid, index, amount_as_str]\n" - "4. fee (numeric, optional, default=" - + strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" - "\nResult:\n" - "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" - "\nExamples:\n" - + HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'") - + HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]") - ); - - LOCK2(cs_main, pwalletMain->cs_wallet); - - //THROW_IF_SYNCING(KOMODO_INSYNC); - - // Check that the from address is valid. - auto fromaddress = params[0].get_str(); - bool fromTaddr = false; - bool fromSapling = false; - - uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); - - CTxDestination taddr = DecodeDestination(fromaddress); - fromTaddr = IsValidDestination(taddr); - if (!fromTaddr) { - auto res = DecodePaymentAddress(fromaddress); - if (!IsValidPaymentAddress(res, branchId)) { - // invalid - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); - } - - // Check that we have the spending key - if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); - } - - // Remember whether this is a Sprout or Sapling address - fromSapling = boost::get(&res) != nullptr; - } - // This logic will need to be updated if we add a new shielded pool - bool fromSprout = !(fromTaddr || fromSapling); - - UniValue outputs = params[1].get_array(); - - if (outputs.size()==0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); - - // Keep track of addresses to spot duplicates - set setAddress; - - // Track whether we see any Sprout addresses - bool noSproutAddrs = !fromSprout; - - // Recipients - std::vector taddrRecipients; - std::vector zaddrRecipients; - CAmount nTotalOut = 0; - - bool containsSproutOutput = false; - bool containsSaplingOutput = false; - - for (const UniValue& o : outputs.getValues()) { - if (!o.isObject()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); - - // sanity check, report error if unknown key-value pairs - for (const string& name_ : o.getKeys()) { - std::string s = name_; - if (s != "address" && s != "amount" && s!="memo") - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); - } - - string address = find_value(o, "address").get_str(); - bool isZaddr = false; - CTxDestination taddr = DecodeDestination(address); - if (!IsValidDestination(taddr)) { - auto res = DecodePaymentAddress(address); - if (IsValidPaymentAddress(res, branchId)) { - isZaddr = true; - - bool toSapling = boost::get(&res) != nullptr; - bool toSprout = !toSapling; - noSproutAddrs = noSproutAddrs && toSapling; - - containsSproutOutput |= toSprout; - containsSaplingOutput |= toSapling; - - // Sending to both Sprout and Sapling is currently unsupported using z_sendmany - if (containsSproutOutput && containsSaplingOutput) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - "Cannot send to both Sprout and Sapling addresses using z_sendmany"); - } - if ( GetTime() > KOMODO_SAPLING_DEADLINE ) - { - if ( fromSprout || toSprout ) - throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage has expired"); - } - if ( toSapling && ASSETCHAINS_SYMBOL[0] == 0 ) - throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage will expire soon"); - - // If we are sending from a shielded address, all recipient - // shielded addresses must be of the same type. - if ((fromSprout && toSapling) || (fromSapling && toSprout)) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - "Cannot send between Sprout and Sapling addresses using z_sendmany"); - } - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); - } - } - //else if ( ASSETCHAINS_PRIVATE != 0 && komodo_isnotaryvout((char *)address.c_str()) == 0 ) - // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cant use transparent addresses in private chain"); - - // Allowing duplicate receivers helps various HushList protocol operations - //if (setAddress.count(address)) - // throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+address); - setAddress.insert(address); - - UniValue memoValue = find_value(o, "memo"); - string memo; - if (!memoValue.isNull()) { - memo = memoValue.get_str(); - if (!isZaddr) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); - } else if (!IsHex(memo)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format."); - } - if (memo.length() > ZC_MEMO_SIZE*2) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE )); - } - } - - UniValue av = find_value(o, "amount"); - CAmount nAmount = AmountFromValue( av ); - if (nAmount < 0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); - - if (isZaddr) { - zaddrRecipients.push_back( z_template::SendManyRecipient(address, nAmount, memo) ); - } else { - taddrRecipients.push_back( z_template::SendManyRecipient(address, nAmount, memo) ); - } - - nTotalOut += nAmount; - } - - int nextBlockHeight = chainActive.Height() + 1; - CMutableTransaction mtx; - mtx.fOverwintered = true; - mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; - mtx.nVersion = SAPLING_TX_VERSION; - unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; - if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { - if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { - mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; - mtx.nVersion = OVERWINTER_TX_VERSION; - } else { - mtx.fOverwintered = false; - mtx.nVersion = 2; - } - - max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; - - // Check the number of zaddr outputs does not exceed the limit. - if (zaddrRecipients.size() > Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, too many zaddr outputs"); - } - } - - // If Sapling is not active, do not allow sending from or sending to Sapling addresses. - if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { - if (fromSapling || containsSaplingOutput) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); - } - } - - // As a sanity check, estimate and verify that the size of the transaction will be valid. - // Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid. - size_t txsize = 0; - for (int i = 0; i < zaddrRecipients.size(); i++) { - auto address = std::get<0>(zaddrRecipients[i]); - auto res = DecodePaymentAddress(address); - bool toSapling = boost::get(&res) != nullptr; - if (toSapling) { - mtx.vShieldedOutput.push_back(OutputDescription()); - } else { - JSDescription jsdesc; - if (mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION)) { - jsdesc.proof = GrothProof(); - } - mtx.vjoinsplit.push_back(jsdesc); - } - } - CTransaction tx(mtx); - txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion); - if (fromTaddr) { - txsize += CTXIN_SPEND_DUST_SIZE; - txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change - } - txsize += CTXOUT_REGULAR_SIZE * taddrRecipients.size(); - if (txsize > max_tx_size) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Too many outputs, size of raw transaction would be larger than limit of %d bytes", max_tx_size )); - } - - // Minimum confirmations - int nMinDepth = 1; - - // Fee in Zatoshis, not currency format) - CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; - CAmount nDefaultFee = nFee; - - if (params.size() > 3) { - if (params[3].get_real() == 0.0) { - nFee = 0; - } else { - nFee = AmountFromValue( params[3] ); - } - - // Check that the user specified fee is not absurd. - // This allows amount=0 (and all amount < nDefaultFee) transactions to use the default network fee - // or anything less than nDefaultFee instead of being forced to use a custom fee and leak metadata - if (nTotalOut < nDefaultFee) { - if (nFee > nDefaultFee) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Small transaction amount %s has fee %s that is greater than the default fee %s", FormatMoney(nTotalOut), FormatMoney(nFee), FormatMoney(nDefaultFee))); - } - } else { - // Check that the user specified fee is not absurd. - if (nFee > nTotalOut) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s and also greater than the default fee", FormatMoney(nFee), FormatMoney(nTotalOut))); - } - } - } - - // Use input parameters as the optional context info to be returned by z_getoperationstatus and z_getoperationresult. - UniValue o(UniValue::VOBJ); - o.push_back(Pair("fromaddress", params[0])); - o.push_back(Pair("amounts", params[1])); - o.push_back(Pair("minconf", nMinDepth)); - o.push_back(Pair("fee", std::stod(FormatMoney(nFee)))); - UniValue contextInfo = o; - - // Builder (used if Sapling addresses are involved) - boost::optional builder; - if (noSproutAddrs) { - builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain); - } - - // Contextual transaction we will build on - // (used if no Sapling addresses are involved) - CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight); - bool isShielded = !fromTaddr || zaddrRecipients.size() > 0; - if (contextualTx.nVersion == 1 && isShielded) { - contextualTx.nVersion = 2; // Tx format should support vjoinsplits - } - - // Create operation and add to global queue - std::shared_ptr q = getAsyncRPCQueue(); - std::shared_ptr operation( new z_template::AsyncRPCOperation_sendmany_template(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo, params[2]) ); - q->addOperation(operation); - AsyncRPCOperationId operationId = operation->getId(); - return operationId; -} - UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) From db5dcdbc9b5c5667127ec368c23fb452f1a0dd2c Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 6 May 2021 09:04:05 +0200 Subject: [PATCH 13/29] season activation --- src/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index e7e7bcd6d23..a1b333b9b0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1549,7 +1549,8 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { // FIXME 1 represents HF timestamp ac_season check + int32_t current_season = getacseason(tiptime); + if ( current_season > 5 && txout.scriptPubKey.IsPayToScriptHash() ) { if (out_index == tx.vout.size()-1 ) { // p2sh cannot be the last vout or we reach out of bounds in the next if statement return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); From 46d06bea5135dedaa6604858ba9aa6fb7de8459d Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 6 May 2021 09:25:57 +0200 Subject: [PATCH 14/29] simulate season hf --- src/komodo_defs.h | 73 +++++++++++++++++++++++++++++++++++++++++++++-- src/main.cpp | 3 +- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/komodo_defs.h b/src/komodo_defs.h index 747559dd0e7..9c02b0acd99 100644 --- a/src/komodo_defs.h +++ b/src/komodo_defs.h @@ -46,7 +46,7 @@ // 7113400 = 5x current KMD blockheight. // to add 4th season, change NUM_KMD_SEASONS to 4, and add timestamp and height of activation to these arrays. -#define NUM_KMD_SEASONS 5 +#define NUM_KMD_SEASONS 6 #define NUM_KMD_NOTARIES 64 extern const uint32_t nStakedDecemberHardforkTimestamp; //December 2019 hardfork @@ -55,7 +55,9 @@ extern const int32_t nDecemberHardforkHeight; //December 2019 hardfork extern const uint32_t nS4Timestamp; //dPoW Season 4 2020 hardfork extern const int32_t nS4HardforkHeight; //dPoW Season 4 2020 hardfork -static const uint32_t KMD_SEASON_TIMESTAMPS[NUM_KMD_SEASONS] = {1525132800, 1563148800, nStakedDecemberHardforkTimestamp, nS4Timestamp, 1751328000}; +#define MOCK_S5_TIMESTAMP 1620292792 + +static const uint32_t KMD_SEASON_TIMESTAMPS[NUM_KMD_SEASONS] = {1525132800, 1563148800, nStakedDecemberHardforkTimestamp, nS4Timestamp, MOCK_S5_TIMESTAMP, 1751328000}; static const int32_t KMD_SEASON_HEIGHTS[NUM_KMD_SEASONS] = {814000, 1444000, nDecemberHardforkHeight, nS4HardforkHeight, 7113400}; // Era array of pubkeys. Add extra seasons to bottom as requried, after adding appropriate info above. @@ -392,6 +394,73 @@ static const char *notaries_elected[NUM_KMD_SEASONS][NUM_KMD_NOTARIES][2] = { "artemii235_DEV", "03bb616b12430bdd0483653de18733597a4fd416623c7065c0e21fe9d96460add1" }, { "tonyl_DEV", "02d5f7fd6e25d34ab2f3318d60cdb89ff3a812ec5d0212c4c113bb12d12616cfdc" }, { "decker_DEV", "028eea44a09674dda00d88ffd199a09c9b75ba9782382cc8f1e97c0fd565fe5707" } + }, + { + // FIXME MOCK SEASON 5 - **SHOULD NOT REACH PRODUCTION** + { "alien_AR", "03911a60395801082194b6834244fa78a3c30ff3e888667498e157b4aa80b0a65f" }, + { "alien_EU", "03bb749e337b9074465fa28e757b5aa92cb1f0fea1a39589bca91a602834d443cd" }, + { "strob_NA", "02a1c0bd40b294f06d3e44a52d1b2746c260c475c725e9351f1312e49e01c9a405" }, + { "titomane_SH", "020014ad4eedf6b1aeb0ad3b101a58d0a2fc570719e46530fd98d4e585f63eb4ae" }, + { "fullmoon_AR", "03b251095e747f759505ec745a4bbff9a768b8dce1f65137300b7c21efec01a07a" }, + { "phba2061_EU", "03a9492d2a1601d0d98cfe94d8adf9689d1bb0e600088127a4f6ca937761fb1c66" }, + { "fullmoon_NA", "03931c1d654a99658998ce0ddae108d825943a821d1cddd85e948ac1d483f68fb6" }, + { "fullmoon_SH", "03c2a1ed9ddb7bb8344328946017b9d8d1357b898957dd6aaa8c190ae26740b9ff" }, + { "madmax_AR", "022be5a2829fa0291f9a51ff7aeceef702eef581f2611887c195e29da49092e6de" }, + { "titomane_EU", "0285cf1fdba761daf6f1f611c32d319cd58214972ef822793008b69dde239443dd" }, + { "cipi_NA", "022c6825a24792cc3b010b1531521eba9b5e2662d640ed700fd96167df37e75239" }, + { "indenodes_SH", "0334e6e1ec8285c4b85bd6dae67e17d67d1f20e7328efad17ce6fd24ae97cdd65e" }, + { "decker_AR", "03ffdf1a116300a78729608d9930742cd349f11a9d64fcc336b8f18592dd9c91bc" }, + { "indenodes_EU", "0221387ff95c44cb52b86552e3ec118a3c311ca65b75bf807c6c07eaeb1be8303c" }, + { "madmax_NA", "02997b7ab21b86bbea558ae79acc35d62c9cedf441578f78112f986d72e8eece08" }, + { "chainzilla_SH", "02288ba6dc57936b59d60345e397d62f5d7e7d975f34ed5c2f2e23288325661563" }, + { "peer2cloud_AR", "0250e7e43a3535731b051d1bcc7dc88fbb5163c3fe41c5dee72bd973bcc4dca9f2" }, + { "pirate_EU", "0231c0f50a06655c3d2edf8d7e722d290195d49c78d50de7786b9d196e8820c848" }, + { "webworker01_NA", "02dfd5f3cef1142879a7250752feb91ddd722c497fb98c7377c0fcc5ccc201bd55" }, + { "zatjum_SH", "036066fd638b10e555597623e97e032b28b4d1fa5a13c2b0c80c420dbddad236c2" }, + { "titomane_AR", "0268203a4c80047edcd66385c22e764ea5fb8bc42edae389a438156e7dca9a8251" }, + { "chmex_EU", "025b7209ba37df8d9695a23ea706ea2594863ab09055ca6bf485855937f3321d1d" }, + { "indenodes_NA", "02698c6f1c9e43b66e82dbb163e8df0e5a2f62f3a7a882ca387d82f86e0b3fa988" }, + { "patchkez_SH", "02cabd6c5fc0b5476c7a01e9d7b907e9f0a051d7f4f731959955d3f6b18ee9a242" }, + { "metaphilibert_AR", "02adad675fae12b25fdd0f57250b0caf7f795c43f346153a31fe3e72e7db1d6ac6" }, + { "etszombi_EU", "0341adbf238f33a33cc895633db996c3ad01275313ac6641e046a3db0b27f1c880" }, + { "pirate_NA", "02207f27a13625a0b8caef6a7bb9de613ff16e4a5f232da8d7c235c7c5bad72ffe" }, + { "metaphilibert_SH", "0284af1a5ef01503e6316a2ca4abf8423a794e9fc17ac6846f042b6f4adedc3309" }, + { "indenodes_AR", "02ec0fa5a40f47fd4a38ea5c89e375ad0b6ddf4807c99733c9c3dc15fb978ee147" }, + { "chainmakers_NA", "029415a1609c33dfe4a1016877ba35f9265d25d737649f307048efe96e76512877" }, + { "mihailo_EU", "037f9563f30c609b19fd435a19b8bde7d6db703012ba1aba72e9f42a87366d1941" }, + { "tonyl_AR", "0299684d7291abf90975fa493bf53212cf1456c374aa36f83cc94daece89350ae9" }, + { "alien_NA", "03bea1ac333b95c8669ec091907ea8713cae26f74b9e886e13593400e21c4d30a8" }, + { "pungocloud_SH", "025b97d8c23effaca6fa7efacce20bf54df73081b63004a0fe22f3f98fece5669f" }, + { "node9_EU", "029ffa793b5c3248f8ea3da47fa3cf1810dada5af032ecd0e37bab5b92dd63b34e" }, + { "smdmitry_AR", "022a2a45979a6631a25e4c96469423de720a2f4c849548957c35a35c91041ee7ac" }, + { "nodeone_NA", "03f9dd0484e81174fd50775cb9099691c7d140ff00c0f088847e38dc87da67eb9b" }, + { "gcharang_SH", "02ec4172eab854a0d8cd32bc691c83e93975a3df5a4a453a866736c56e025dc359" }, + { "cipi_EU", "02f2b6defff1c544202f66e47cfd6909c54d67c7c39b9c2a99f137dbaf6d0bd8fa" }, + { "etszombi_AR", "0329944b0ac65b6760787ede042a2fde0be9fca1d80dd756bc0ee0b98d389b7682" }, + { "pbca26_NA", "0387e0fb6f2ca951154c87e16c6cbf93a69862bb165c1a96bcd8722b3af24fe533" }, + { "mylo_SH", "03b58f57822e90fe105e6efb63fd8666033ea503d6cc165b1e479bbd8c2ba033e8" }, + { "swisscertifiers_EU", "03ebcc71b42d88994b8b2134bcde6cb269bd7e71a9dd7616371d9294ec1c1902c5" }, + { "marmarachain_AR", "035bbd81a098172592fe97f50a0ce13cbbf80e55cc7862eccdbd7310fab8a90c4c" }, + { "karasugoi_NA", "0262cf2559703464151153c12e00c4b67a969e39b330301fdcaa6667d7eb02c57d" }, + { "phm87_SH", "021773a38db1bc3ede7f28142f901a161c7b7737875edbb40082a201c55dcf0add" }, + { "oszy_EU", "03d1ffd680491b98a3ec5541715681d1a45293c8efb1722c32392a1d792622596a" }, + { "chmex_AR", "036c856ea778ea105b93c0be187004d4e51161eda32888aa307b8f72d490884005" }, + { "dragonhound_NA", "0227e5cad3731e381df157de189527aac8eb50d82a13ce2bd81153984ebc749515" }, + { "strob_SH", "025ceac4256cef83ca4b110f837a71d70a5a977ecfdf807335e00bc78b560d451a" }, + { "madmax_EU", "02ea0cf4d6d151d0528b07efa79cc7403d77cb9195e2e6c8374f5074b9a787e287" }, + { "dudezmobi_AR", "027ecd974ff2a27a37ee69956cd2e6bb31a608116206f3e31ef186823420182450" }, + { "daemonfox_NA", "022d6f4885f53cbd668ad7d03d4f8e830c233f74e3a918da1ed247edfc71820b3d" }, + { "nutellalicka_SH", "02f4b1e71bc865a79c05fe333952b97cb040d8925d13e83925e170188b3011269b" }, + { "starfleet_EU", "025c7275bd750936862b47793f1f0bb3cbed60fb75a48e7da016e557925fe375eb" }, + { "mrlynch_AR", "031987dc82b087cd53e23df5480e265a5928e9243e0e11849fa12359739d8b18a4" }, + { "greer_NA", "03e0995615d7d3cf1107effa6bdb1133e0876cf1768e923aa533a4e2ee675ec383" }, + { "mcrypt_SH", "025faab3cc2e83bf7dad6a9463cbff86c08800e937942126f258cf219bc2320043" }, + { "decker_EU", "03777777caebce56e17ca3aae4e16374335b156f1dd62ee3c7f8799c6b885f5560" }, + { "dappvader_SH", "02962e2e5af746632016bc7b24d444f7c90141a5f42ce54e361b302cf455d90e6a" }, + { "alright_DEV", "02b73a589d61691efa2ada15c006d27bc18493fea867ce6c14db3d3d28751f8ce3" }, + { "artemii235_DEV", "03bb616b12430bdd0483653de18733597a4fd416623c7065c0e21fe9d96460add1" }, + { "tonyl_DEV", "02d5f7fd6e25d34ab2f3318d60cdb89ff3a812ec5d0212c4c113bb12d12616cfdc" }, + { "decker_DEV", "028eea44a09674dda00d88ffd199a09c9b75ba9782382cc8f1e97c0fd565fe5707" } } }; diff --git a/src/main.cpp b/src/main.cpp index e7e7bcd6d23..a1b333b9b0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1549,7 +1549,8 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - if ( 1 && txout.scriptPubKey.IsPayToScriptHash() ) { // FIXME 1 represents HF timestamp ac_season check + int32_t current_season = getacseason(tiptime); + if ( current_season > 5 && txout.scriptPubKey.IsPayToScriptHash() ) { if (out_index == tx.vout.size()-1 ) { // p2sh cannot be the last vout or we reach out of bounds in the next if statement return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); From f067b8f7d514fdff940c71b947769806128ff706 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 6 May 2021 10:24:07 +0200 Subject: [PATCH 15/29] z_sendmany opreturn field --- src/wallet/asyncrpcoperation_sendmany.cpp | 11 +++++++++-- src/wallet/asyncrpcoperation_sendmany.h | 5 ++++- src/wallet/rpcwallet.cpp | 11 +++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index dca93461be0..ea1cf84904b 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -86,8 +86,9 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( std::vector zOutputs, int minDepth, CAmount fee, - UniValue contextInfo) : - tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo) + UniValue contextInfo, + CScript opret) : + tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo), opret_(opret) { assert(fee_ >= 0); @@ -517,6 +518,12 @@ bool AsyncRPCOperation_sendmany::main_impl() { } } + // Add opret if available + if ( opret_ != CScript() ) { + builder_.AddOpRet(opret_); + } + + // Build the transaction auto maybe_tx = builder_.Build(); if (!maybe_tx) { diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 8e39f341afa..2a35c874164 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -75,7 +75,8 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation { std::vector zOutputs, int minDepth, CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE, - UniValue contextInfo = NullUniValue); + UniValue contextInfo = NullUniValue, + CScript opret = CScript()); virtual ~AsyncRPCOperation_sendmany(); // We don't want to be copied or moved around @@ -97,6 +98,8 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation { UniValue contextinfo_; // optional data to include in return value from getStatus() + CScript opret_; + bool isUsingBuilder_; // Indicates that no Sprout addresses are involved uint32_t consensusBranchId_; CAmount fee_; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index dc59a80696c..ff9884f1f38 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4839,6 +4839,8 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) bool containsSproutOutput = false; bool containsSaplingOutput = false; + + CScript opret; for (const UniValue& o : outputs.getValues()) { if (!o.isObject()) @@ -4847,7 +4849,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // sanity check, report error if unknown key-value pairs for (const string& name_ : o.getKeys()) { std::string s = name_; - if (s != "address" && s != "amount" && s!="memo") + if (s != "address" && s != "amount" && s!="memo" && s!="opreturn") throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); } @@ -4899,6 +4901,11 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+address); setAddress.insert(address); + UniValue opretValue = find_value(o, "opreturn"); + if (!opretValue.isNull()) { + opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); + } + UniValue memoValue = find_value(o, "memo"); string memo; if (!memoValue.isNull()) { @@ -5044,7 +5051,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // Create operation and add to global queue std::shared_ptr q = getAsyncRPCQueue(); - std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo) ); + std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo, opret) ); q->addOperation(operation); AsyncRPCOperationId operationId = operation->getId(); return operationId; From 0a70c6fea46344c9261dd73f28be9e0c8abd6fcf Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 6 May 2021 11:35:31 +0200 Subject: [PATCH 16/29] miner failsafe --- src/miner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/miner.cpp b/src/miner.cpp index 259df6ebbee..ee83c2aa662 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1805,6 +1805,7 @@ void static BitcoinMiner() static uint32_t counter; if ( counter++ < 10 && ASSETCHAINS_STAKED == 0 ) fprintf(stderr,"created illegal blockB, retry\n"); + mempool.clear(); sleep(1); continue; } From fb6d4ef37a606996f1f9943b09c3e8cfc5f49b12 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Tue, 11 May 2021 17:29:27 +0200 Subject: [PATCH 17/29] undo miner fix --- src/miner.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/miner.cpp b/src/miner.cpp index ee83c2aa662..259df6ebbee 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1805,7 +1805,6 @@ void static BitcoinMiner() static uint32_t counter; if ( counter++ < 10 && ASSETCHAINS_STAKED == 0 ) fprintf(stderr,"created illegal blockB, retry\n"); - mempool.clear(); sleep(1); continue; } From b9eca03a8df670de17141ad12d0ab1defe5c0f99 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 12:24:57 +0200 Subject: [PATCH 18/29] fix iter position --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index a1b333b9b0a..38850232a3e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1574,6 +1574,7 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if (!MoneyRange(nValueOut)) return state.DoS(100, error("CheckTransaction(): txout total out of range"), REJECT_INVALID, "bad-txns-txouttotal-toolarge"); + out_index++; } // Check for non-zero valueBalance when there are no Sapling inputs or outputs @@ -1681,7 +1682,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio } } } - out_index++; } // Ensure input values do not exceed MAX_MONEY From f0ecfc28f50bafd5319986dd0ff1c6e182f568e3 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:10:44 +0200 Subject: [PATCH 19/29] reorder to check script size first --- src/script/script.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index a37b7710868..7b50befc66f 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -260,6 +260,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ CScript check_spk, redeemScript = scriptpubkey; if ( // these magic numbers correspond to a typical(as of May 2021) atomicdex HLTC + redeemScript.size() == 110 && redeemScript[0] == OP_RETURN && redeemScript[3] == OP_IF && redeemScript[9] == OP_NOP2 && @@ -273,8 +274,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ redeemScript[51] == OP_HASH160 && redeemScript[73] == OP_EQUALVERIFY && redeemScript[108] == OP_CHECKSIG && - redeemScript[109] == OP_ENDIF && - redeemScript.size() == 110 + redeemScript[109] == OP_ENDIF ) { // Drop the OP_RETURN and OP_PUSHDATA1 + byte redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); @@ -288,6 +288,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ // OP_IF 32bit_locktime OP_NOP2 OP_DROP pubkey OP_CHECKSIG // OP_ELSE pubkey OP_CHECKSIG OP_ENDIF // as provided by artem + redeemScript.size() == 101 && redeemScript[0] == OP_RETURN && redeemScript[1] == 0x4c && // PUSH_BYTES redeemScript[2] == 0x62 && // BYTES @@ -299,8 +300,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ redeemScript[63] == OP_CHECKSIG && redeemScript[64] == OP_ELSE && redeemScript[99] == OP_CHECKSIG && - redeemScript[100] == OP_ENDIF && - redeemScript.size() == 101 + redeemScript[100] == OP_ENDIF ) { // Drop the OP_RETURN and OP_PUSHDATA1 + byte redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); From 1ac6d358eb2d66b75282977d72929e057618c62e Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:13:15 +0200 Subject: [PATCH 20/29] refactor zhltc based on Decker feedback --- src/main.cpp | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a1b333b9b0a..077c0cc5189 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1461,7 +1461,7 @@ int32_t komodo_acpublic(uint32_t tiptime); bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransaction& tx, CValidationState &state) { // Basic checks that don't depend on any context - int32_t invalid_private_taddr=0,z_z=0,z_t=0,t_z=0,acpublic = komodo_acpublic(tiptime); + int32_t invalid_private_taddr=0,z_z=0,z_t=0,t_z=0,acpublic = komodo_acpublic(tiptime), current_season = getacseason(tiptime); /** * Previously: * 1. The consensus rule below was: @@ -1549,21 +1549,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - int32_t current_season = getacseason(tiptime); - if ( current_season > 5 && txout.scriptPubKey.IsPayToScriptHash() ) { - if (out_index == tx.vout.size()-1 ) { - // p2sh cannot be the last vout or we reach out of bounds in the next if statement - return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); - } - - if (txout.scriptPubKey.IsRedeemScriptReveal(tx.vout[out_index+1].scriptPubKey)) { - if ( tx.vin.size() > 0 ) - return state.DoS(100, error("CheckTransaction(): zHLTC cannot spend t->p2sh"),REJECT_INVALID, "bad-txns-zhltc-no-t-spends"); - invalid_private_taddr = 0; - } else { - return state.DoS(100, error("CheckTransaction(): zHLTC missing or malformed redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-redeem-reveal-malformed"); - } - } //return state.DoS(100, error("CheckTransaction(): this is a private chain, no public allowed"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } } @@ -1588,9 +1573,18 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio } if ( ASSETCHAINS_PRIVATE != 0 && invalid_private_taddr != 0 && tx.vShieldedSpend.empty() == 0 ) { - return state.DoS(100, error("CheckTransaction(): this is a private chain, no sapling -> taddr"), - REJECT_INVALID, "bad-txns-acprivate-chain"); + if ( !( current_season > 5 && + tx.vin.size() == 0 && + tx.vout.size() == 2 && + tx.vout[0].scriptPubKey.IsPayToScriptHash() && + tx.vout[0].scriptPubKey.IsRedeemScriptReveal(tx.vout[1].scriptPubKey) )) { + return state.DoS(100, error("CheckTransaction(): this is a private chain, no sapling -> taddr"), + REJECT_INVALID, "bad-txns-acprivate-chain"); + } else { + invalid_private_taddr = false; + } } + // Check for overflow valueBalance if (tx.valueBalance > MAX_MONEY || tx.valueBalance < -MAX_MONEY) { return state.DoS(100, error("CheckTransaction(): abs(tx.valueBalance) too large"), @@ -1681,7 +1675,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio } } } - out_index++; } // Ensure input values do not exceed MAX_MONEY From 6dbc119ebab83a9658fda7900321b4b84c6cd062 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:13:15 +0200 Subject: [PATCH 21/29] refactor zhltc based on Decker feedback --- src/main.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 38850232a3e..b65e34a5617 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1461,7 +1461,7 @@ int32_t komodo_acpublic(uint32_t tiptime); bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransaction& tx, CValidationState &state) { // Basic checks that don't depend on any context - int32_t invalid_private_taddr=0,z_z=0,z_t=0,t_z=0,acpublic = komodo_acpublic(tiptime); + int32_t invalid_private_taddr=0,z_z=0,z_t=0,t_z=0,acpublic = komodo_acpublic(tiptime), current_season = getacseason(tiptime); /** * Previously: * 1. The consensus rule below was: @@ -1549,21 +1549,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if ( komodo_isnotaryvout(destaddr,tiptime) == 0 ) { invalid_private_taddr = 1; - int32_t current_season = getacseason(tiptime); - if ( current_season > 5 && txout.scriptPubKey.IsPayToScriptHash() ) { - if (out_index == tx.vout.size()-1 ) { - // p2sh cannot be the last vout or we reach out of bounds in the next if statement - return state.DoS(100, error("CheckTransaction(): zHLTC no redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-no-redeem-reveal"); - } - - if (txout.scriptPubKey.IsRedeemScriptReveal(tx.vout[out_index+1].scriptPubKey)) { - if ( tx.vin.size() > 0 ) - return state.DoS(100, error("CheckTransaction(): zHLTC cannot spend t->p2sh"),REJECT_INVALID, "bad-txns-zhltc-no-t-spends"); - invalid_private_taddr = 0; - } else { - return state.DoS(100, error("CheckTransaction(): zHLTC missing or malformed redeemscript reveal"),REJECT_INVALID, "bad-txns-zhltc-redeem-reveal-malformed"); - } - } //return state.DoS(100, error("CheckTransaction(): this is a private chain, no public allowed"),REJECT_INVALID, "bad-txns-acprivacy-chain"); } } @@ -1589,9 +1574,18 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio } if ( ASSETCHAINS_PRIVATE != 0 && invalid_private_taddr != 0 && tx.vShieldedSpend.empty() == 0 ) { - return state.DoS(100, error("CheckTransaction(): this is a private chain, no sapling -> taddr"), - REJECT_INVALID, "bad-txns-acprivate-chain"); + if ( !( current_season > 5 && + tx.vin.size() == 0 && + tx.vout.size() == 2 && + tx.vout[0].scriptPubKey.IsPayToScriptHash() && + tx.vout[0].scriptPubKey.IsRedeemScriptReveal(tx.vout[1].scriptPubKey) )) { + return state.DoS(100, error("CheckTransaction(): this is a private chain, no sapling -> taddr"), + REJECT_INVALID, "bad-txns-acprivate-chain"); + } else { + invalid_private_taddr = false; + } } + // Check for overflow valueBalance if (tx.valueBalance > MAX_MONEY || tx.valueBalance < -MAX_MONEY) { return state.DoS(100, error("CheckTransaction(): abs(tx.valueBalance) too large"), From 211dde83d9c7392ef5f8bf9e75b77f85c67fdb71 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:20:11 +0200 Subject: [PATCH 22/29] remove useless iter --- src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b65e34a5617..c8a63ffb18a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1526,7 +1526,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio // Check for negative or overflow output values CAmount nValueOut = 0; int32_t iscoinbase = tx.IsCoinBase(); - int out_index = 0; BOOST_FOREACH(const CTxOut& txout, tx.vout) { if (txout.nValue < 0) @@ -1559,7 +1558,6 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio if (!MoneyRange(nValueOut)) return state.DoS(100, error("CheckTransaction(): txout total out of range"), REJECT_INVALID, "bad-txns-txouttotal-toolarge"); - out_index++; } // Check for non-zero valueBalance when there are no Sapling inputs or outputs From fe32ac74b565ba36d85dc434a12473cd4c9a32b6 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:28:26 +0200 Subject: [PATCH 23/29] check size before direct indexing --- src/script/script.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index a37b7710868..6022eb9efee 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -260,6 +260,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ CScript check_spk, redeemScript = scriptpubkey; if ( // these magic numbers correspond to a typical(as of May 2021) atomicdex HLTC + redeemScript.size() == 110 && redeemScript[0] == OP_RETURN && redeemScript[3] == OP_IF && redeemScript[9] == OP_NOP2 && @@ -273,8 +274,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ redeemScript[51] == OP_HASH160 && redeemScript[73] == OP_EQUALVERIFY && redeemScript[108] == OP_CHECKSIG && - redeemScript[109] == OP_ENDIF && - redeemScript.size() == 110 + redeemScript[109] == OP_ENDIF ) { // Drop the OP_RETURN and OP_PUSHDATA1 + byte redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); @@ -288,6 +288,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ // OP_IF 32bit_locktime OP_NOP2 OP_DROP pubkey OP_CHECKSIG // OP_ELSE pubkey OP_CHECKSIG OP_ENDIF // as provided by artem + redeemScript.size() == 101 redeemScript[0] == OP_RETURN && redeemScript[1] == 0x4c && // PUSH_BYTES redeemScript[2] == 0x62 && // BYTES @@ -299,15 +300,14 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ redeemScript[63] == OP_CHECKSIG && redeemScript[64] == OP_ELSE && redeemScript[99] == OP_CHECKSIG && - redeemScript[100] == OP_ENDIF && - redeemScript.size() == 101 + redeemScript[100] == OP_ENDIF ) { // Drop the OP_RETURN and OP_PUSHDATA1 + byte redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); check_spk << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; return (check_spk == (*this)); } - return(0); + return(false); } // this returns true if either there is nothing left and pc points at the end, or From f27a6044b50c04d7e851953cf01f4a2dd470b744 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:38:03 +0200 Subject: [PATCH 24/29] syntax --- src/script/script.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index 6022eb9efee..d1fbe24a1cc 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -288,7 +288,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ // OP_IF 32bit_locktime OP_NOP2 OP_DROP pubkey OP_CHECKSIG // OP_ELSE pubkey OP_CHECKSIG OP_ENDIF // as provided by artem - redeemScript.size() == 101 + redeemScript.size() == 101 && redeemScript[0] == OP_RETURN && redeemScript[1] == 0x4c && // PUSH_BYTES redeemScript[2] == 0x62 && // BYTES From 3df6e1b8fa0157bbf2f9eefc274bf19dc0b7c39b Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 15:54:50 +0200 Subject: [PATCH 25/29] additional script exception, OP_SHA256 HLTC --- src/script/script.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/script/script.cpp b/src/script/script.cpp index d1fbe24a1cc..74d483c2a5b 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -282,6 +282,39 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ return (check_spk == (*this)); } + if ( + /* + these magic numbers correspond to: + + OP_IF 32_bitlocktime OP_CHECKLOCKTIMEVERIFY OP_DROP pubkey OP_CHECKSIG + OP_ELSE OP_SIZE 0x20 OP_EQUALVERIFY + OP_SHA256 0000000000000000000000000000000000000000000000000000000000000000 OP_EQUALVERIFY pubkey OP_CHECKSIG OP_ENDIF + + This is neccesary for coins that do not support ripemd160. + It is a typical HLTC with OP_SHA256 instead of OP_HASH160 + */ + redeemScript.size() == 122 && + redeemScript[0] == OP_RETURN && + redeemScript[3] == OP_IF && + redeemScript[9] == OP_NOP2 && + redeemScript[10] == OP_DROP && + redeemScript[45] == OP_CHECKSIG && + redeemScript[46] == OP_ELSE && + redeemScript[47] == OP_SIZE && + redeemScript[48] == 0x01 && + redeemScript[49] == 0x20 && + redeemScript[50] == OP_EQUALVERIFY && + redeemScript[51] == OP_SHA256 && + redeemScript[88] == OP_EQUALVERIFY && + redeemScript[120] == OP_CHECKSIG && + redeemScript[121] == OP_ENDIF + ) { + // Drop the OP_RETURN and OP_PUSHDATA1 + byte + redeemScript.erase(redeemScript.begin(),redeemScript.begin()+3 ); + check_spk << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + return (check_spk == (*this)); + } + if ( // these magic numbers correspond to: // 16_arbitrary_bytes OP_DROP From 1e6c909dfd1d40155f0a2d03f1657b4ab104bcd4 Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 16:49:24 +0200 Subject: [PATCH 26/29] Revert "Merge branch 'ARRR-HLTC-debug' into ARRR-HLTC" This reverts commit a92eb61d481b9c247fc141ea9abd8733a2154fe8, reversing changes made to 3df6e1b8fa0157bbf2f9eefc274bf19dc0b7c39b. --- src/komodo_defs.h | 73 +- src/wallet/asyncrpcoperation_sendmany.cpp | 11 +- src/wallet/asyncrpcoperation_sendmany.h | 5 +- .../asyncrpcoperation_sendmany_template.cpp | 1472 ----------------- .../asyncrpcoperation_sendmany_template.h | 159 -- src/wallet/rpcwallet.cpp | 11 +- 6 files changed, 7 insertions(+), 1724 deletions(-) delete mode 100644 src/wallet/asyncrpcoperation_sendmany_template.cpp delete mode 100644 src/wallet/asyncrpcoperation_sendmany_template.h diff --git a/src/komodo_defs.h b/src/komodo_defs.h index 9c02b0acd99..747559dd0e7 100644 --- a/src/komodo_defs.h +++ b/src/komodo_defs.h @@ -46,7 +46,7 @@ // 7113400 = 5x current KMD blockheight. // to add 4th season, change NUM_KMD_SEASONS to 4, and add timestamp and height of activation to these arrays. -#define NUM_KMD_SEASONS 6 +#define NUM_KMD_SEASONS 5 #define NUM_KMD_NOTARIES 64 extern const uint32_t nStakedDecemberHardforkTimestamp; //December 2019 hardfork @@ -55,9 +55,7 @@ extern const int32_t nDecemberHardforkHeight; //December 2019 hardfork extern const uint32_t nS4Timestamp; //dPoW Season 4 2020 hardfork extern const int32_t nS4HardforkHeight; //dPoW Season 4 2020 hardfork -#define MOCK_S5_TIMESTAMP 1620292792 - -static const uint32_t KMD_SEASON_TIMESTAMPS[NUM_KMD_SEASONS] = {1525132800, 1563148800, nStakedDecemberHardforkTimestamp, nS4Timestamp, MOCK_S5_TIMESTAMP, 1751328000}; +static const uint32_t KMD_SEASON_TIMESTAMPS[NUM_KMD_SEASONS] = {1525132800, 1563148800, nStakedDecemberHardforkTimestamp, nS4Timestamp, 1751328000}; static const int32_t KMD_SEASON_HEIGHTS[NUM_KMD_SEASONS] = {814000, 1444000, nDecemberHardforkHeight, nS4HardforkHeight, 7113400}; // Era array of pubkeys. Add extra seasons to bottom as requried, after adding appropriate info above. @@ -394,73 +392,6 @@ static const char *notaries_elected[NUM_KMD_SEASONS][NUM_KMD_NOTARIES][2] = { "artemii235_DEV", "03bb616b12430bdd0483653de18733597a4fd416623c7065c0e21fe9d96460add1" }, { "tonyl_DEV", "02d5f7fd6e25d34ab2f3318d60cdb89ff3a812ec5d0212c4c113bb12d12616cfdc" }, { "decker_DEV", "028eea44a09674dda00d88ffd199a09c9b75ba9782382cc8f1e97c0fd565fe5707" } - }, - { - // FIXME MOCK SEASON 5 - **SHOULD NOT REACH PRODUCTION** - { "alien_AR", "03911a60395801082194b6834244fa78a3c30ff3e888667498e157b4aa80b0a65f" }, - { "alien_EU", "03bb749e337b9074465fa28e757b5aa92cb1f0fea1a39589bca91a602834d443cd" }, - { "strob_NA", "02a1c0bd40b294f06d3e44a52d1b2746c260c475c725e9351f1312e49e01c9a405" }, - { "titomane_SH", "020014ad4eedf6b1aeb0ad3b101a58d0a2fc570719e46530fd98d4e585f63eb4ae" }, - { "fullmoon_AR", "03b251095e747f759505ec745a4bbff9a768b8dce1f65137300b7c21efec01a07a" }, - { "phba2061_EU", "03a9492d2a1601d0d98cfe94d8adf9689d1bb0e600088127a4f6ca937761fb1c66" }, - { "fullmoon_NA", "03931c1d654a99658998ce0ddae108d825943a821d1cddd85e948ac1d483f68fb6" }, - { "fullmoon_SH", "03c2a1ed9ddb7bb8344328946017b9d8d1357b898957dd6aaa8c190ae26740b9ff" }, - { "madmax_AR", "022be5a2829fa0291f9a51ff7aeceef702eef581f2611887c195e29da49092e6de" }, - { "titomane_EU", "0285cf1fdba761daf6f1f611c32d319cd58214972ef822793008b69dde239443dd" }, - { "cipi_NA", "022c6825a24792cc3b010b1531521eba9b5e2662d640ed700fd96167df37e75239" }, - { "indenodes_SH", "0334e6e1ec8285c4b85bd6dae67e17d67d1f20e7328efad17ce6fd24ae97cdd65e" }, - { "decker_AR", "03ffdf1a116300a78729608d9930742cd349f11a9d64fcc336b8f18592dd9c91bc" }, - { "indenodes_EU", "0221387ff95c44cb52b86552e3ec118a3c311ca65b75bf807c6c07eaeb1be8303c" }, - { "madmax_NA", "02997b7ab21b86bbea558ae79acc35d62c9cedf441578f78112f986d72e8eece08" }, - { "chainzilla_SH", "02288ba6dc57936b59d60345e397d62f5d7e7d975f34ed5c2f2e23288325661563" }, - { "peer2cloud_AR", "0250e7e43a3535731b051d1bcc7dc88fbb5163c3fe41c5dee72bd973bcc4dca9f2" }, - { "pirate_EU", "0231c0f50a06655c3d2edf8d7e722d290195d49c78d50de7786b9d196e8820c848" }, - { "webworker01_NA", "02dfd5f3cef1142879a7250752feb91ddd722c497fb98c7377c0fcc5ccc201bd55" }, - { "zatjum_SH", "036066fd638b10e555597623e97e032b28b4d1fa5a13c2b0c80c420dbddad236c2" }, - { "titomane_AR", "0268203a4c80047edcd66385c22e764ea5fb8bc42edae389a438156e7dca9a8251" }, - { "chmex_EU", "025b7209ba37df8d9695a23ea706ea2594863ab09055ca6bf485855937f3321d1d" }, - { "indenodes_NA", "02698c6f1c9e43b66e82dbb163e8df0e5a2f62f3a7a882ca387d82f86e0b3fa988" }, - { "patchkez_SH", "02cabd6c5fc0b5476c7a01e9d7b907e9f0a051d7f4f731959955d3f6b18ee9a242" }, - { "metaphilibert_AR", "02adad675fae12b25fdd0f57250b0caf7f795c43f346153a31fe3e72e7db1d6ac6" }, - { "etszombi_EU", "0341adbf238f33a33cc895633db996c3ad01275313ac6641e046a3db0b27f1c880" }, - { "pirate_NA", "02207f27a13625a0b8caef6a7bb9de613ff16e4a5f232da8d7c235c7c5bad72ffe" }, - { "metaphilibert_SH", "0284af1a5ef01503e6316a2ca4abf8423a794e9fc17ac6846f042b6f4adedc3309" }, - { "indenodes_AR", "02ec0fa5a40f47fd4a38ea5c89e375ad0b6ddf4807c99733c9c3dc15fb978ee147" }, - { "chainmakers_NA", "029415a1609c33dfe4a1016877ba35f9265d25d737649f307048efe96e76512877" }, - { "mihailo_EU", "037f9563f30c609b19fd435a19b8bde7d6db703012ba1aba72e9f42a87366d1941" }, - { "tonyl_AR", "0299684d7291abf90975fa493bf53212cf1456c374aa36f83cc94daece89350ae9" }, - { "alien_NA", "03bea1ac333b95c8669ec091907ea8713cae26f74b9e886e13593400e21c4d30a8" }, - { "pungocloud_SH", "025b97d8c23effaca6fa7efacce20bf54df73081b63004a0fe22f3f98fece5669f" }, - { "node9_EU", "029ffa793b5c3248f8ea3da47fa3cf1810dada5af032ecd0e37bab5b92dd63b34e" }, - { "smdmitry_AR", "022a2a45979a6631a25e4c96469423de720a2f4c849548957c35a35c91041ee7ac" }, - { "nodeone_NA", "03f9dd0484e81174fd50775cb9099691c7d140ff00c0f088847e38dc87da67eb9b" }, - { "gcharang_SH", "02ec4172eab854a0d8cd32bc691c83e93975a3df5a4a453a866736c56e025dc359" }, - { "cipi_EU", "02f2b6defff1c544202f66e47cfd6909c54d67c7c39b9c2a99f137dbaf6d0bd8fa" }, - { "etszombi_AR", "0329944b0ac65b6760787ede042a2fde0be9fca1d80dd756bc0ee0b98d389b7682" }, - { "pbca26_NA", "0387e0fb6f2ca951154c87e16c6cbf93a69862bb165c1a96bcd8722b3af24fe533" }, - { "mylo_SH", "03b58f57822e90fe105e6efb63fd8666033ea503d6cc165b1e479bbd8c2ba033e8" }, - { "swisscertifiers_EU", "03ebcc71b42d88994b8b2134bcde6cb269bd7e71a9dd7616371d9294ec1c1902c5" }, - { "marmarachain_AR", "035bbd81a098172592fe97f50a0ce13cbbf80e55cc7862eccdbd7310fab8a90c4c" }, - { "karasugoi_NA", "0262cf2559703464151153c12e00c4b67a969e39b330301fdcaa6667d7eb02c57d" }, - { "phm87_SH", "021773a38db1bc3ede7f28142f901a161c7b7737875edbb40082a201c55dcf0add" }, - { "oszy_EU", "03d1ffd680491b98a3ec5541715681d1a45293c8efb1722c32392a1d792622596a" }, - { "chmex_AR", "036c856ea778ea105b93c0be187004d4e51161eda32888aa307b8f72d490884005" }, - { "dragonhound_NA", "0227e5cad3731e381df157de189527aac8eb50d82a13ce2bd81153984ebc749515" }, - { "strob_SH", "025ceac4256cef83ca4b110f837a71d70a5a977ecfdf807335e00bc78b560d451a" }, - { "madmax_EU", "02ea0cf4d6d151d0528b07efa79cc7403d77cb9195e2e6c8374f5074b9a787e287" }, - { "dudezmobi_AR", "027ecd974ff2a27a37ee69956cd2e6bb31a608116206f3e31ef186823420182450" }, - { "daemonfox_NA", "022d6f4885f53cbd668ad7d03d4f8e830c233f74e3a918da1ed247edfc71820b3d" }, - { "nutellalicka_SH", "02f4b1e71bc865a79c05fe333952b97cb040d8925d13e83925e170188b3011269b" }, - { "starfleet_EU", "025c7275bd750936862b47793f1f0bb3cbed60fb75a48e7da016e557925fe375eb" }, - { "mrlynch_AR", "031987dc82b087cd53e23df5480e265a5928e9243e0e11849fa12359739d8b18a4" }, - { "greer_NA", "03e0995615d7d3cf1107effa6bdb1133e0876cf1768e923aa533a4e2ee675ec383" }, - { "mcrypt_SH", "025faab3cc2e83bf7dad6a9463cbff86c08800e937942126f258cf219bc2320043" }, - { "decker_EU", "03777777caebce56e17ca3aae4e16374335b156f1dd62ee3c7f8799c6b885f5560" }, - { "dappvader_SH", "02962e2e5af746632016bc7b24d444f7c90141a5f42ce54e361b302cf455d90e6a" }, - { "alright_DEV", "02b73a589d61691efa2ada15c006d27bc18493fea867ce6c14db3d3d28751f8ce3" }, - { "artemii235_DEV", "03bb616b12430bdd0483653de18733597a4fd416623c7065c0e21fe9d96460add1" }, - { "tonyl_DEV", "02d5f7fd6e25d34ab2f3318d60cdb89ff3a812ec5d0212c4c113bb12d12616cfdc" }, - { "decker_DEV", "028eea44a09674dda00d88ffd199a09c9b75ba9782382cc8f1e97c0fd565fe5707" } } }; diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index ea1cf84904b..dca93461be0 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -86,9 +86,8 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( std::vector zOutputs, int minDepth, CAmount fee, - UniValue contextInfo, - CScript opret) : - tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo), opret_(opret) + UniValue contextInfo) : + tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo) { assert(fee_ >= 0); @@ -518,12 +517,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { } } - // Add opret if available - if ( opret_ != CScript() ) { - builder_.AddOpRet(opret_); - } - - // Build the transaction auto maybe_tx = builder_.Build(); if (!maybe_tx) { diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 2a35c874164..8e39f341afa 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -75,8 +75,7 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation { std::vector zOutputs, int minDepth, CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE, - UniValue contextInfo = NullUniValue, - CScript opret = CScript()); + UniValue contextInfo = NullUniValue); virtual ~AsyncRPCOperation_sendmany(); // We don't want to be copied or moved around @@ -98,8 +97,6 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation { UniValue contextinfo_; // optional data to include in return value from getStatus() - CScript opret_; - bool isUsingBuilder_; // Indicates that no Sprout addresses are involved uint32_t consensusBranchId_; CAmount fee_; diff --git a/src/wallet/asyncrpcoperation_sendmany_template.cpp b/src/wallet/asyncrpcoperation_sendmany_template.cpp deleted file mode 100644 index 66f3cad8c74..00000000000 --- a/src/wallet/asyncrpcoperation_sendmany_template.cpp +++ /dev/null @@ -1,1472 +0,0 @@ -// Copyright (c) 2016 The Zcash developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -/****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ - -#include "asyncrpcoperation_sendmany.h" -#include "asyncrpcoperation_sendmany_template.h" -#include "asyncrpcqueue.h" -#include "amount.h" -#include "consensus/upgrades.h" -#include "core_io.h" -#include "init.h" -#include "key_io.h" -#include "main.h" -#include "net.h" -#include "netbase.h" -#include "rpc/protocol.h" -#include "rpc/server.h" -#include "timedata.h" -#include "util.h" -#include "utilmoneystr.h" -#include "wallet.h" -#include "walletdb.h" -#include "script/interpreter.h" -#include "utiltime.h" -#include "zcash/IncrementalMerkleTree.hpp" -#include "sodium.h" -#include "miner.h" - -#include "komodo_defs.h" - -#include - -#include -#include -#include -#include -#include - -#include "paymentdisclosuredb.h" - -using namespace z_template; -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 ); -bool komodo_hardfork_active(uint32_t time); -extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); -extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); - -extern int find_output(UniValue obj, int n); - - -AsyncRPCOperation_sendmany_template::AsyncRPCOperation_sendmany_template( - boost::optional builder, - CMutableTransaction contextualTx, - std::string fromAddress, - std::vector tOutputs, - std::vector zOutputs, - int minDepth, - CAmount fee, - UniValue contextInfo, - UniValue HltcInfo) : - tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo), hltcinfo_(HltcInfo) -{ - assert(fee_ >= 0); - - if (minDepth < 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be negative"); - } - - if (fromAddress.size() == 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "From address parameter missing"); - } - - if (tOutputs.size() == 0 && zOutputs.size() == 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "No recipients"); - } - - isUsingBuilder_ = false; - if (builder) { - isUsingBuilder_ = true; - builder_ = builder.get(); - } - - fromtaddr_ = DecodeDestination(fromAddress); - isfromtaddr_ = IsValidDestination(fromtaddr_); - isfromzaddr_ = false; - - if (!isfromtaddr_) { - auto address = DecodePaymentAddress(fromAddress); - if (IsValidPaymentAddress(address)) { - // We don't need to lock on the wallet as spending key related methods are thread-safe - if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), address)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr"); - } - - isfromzaddr_ = true; - frompaymentaddress_ = address; - spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address).get(); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address"); - } - } - - if (isfromzaddr_ && minDepth==0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from zaddr"); - } - - // Log the context info i.e. the call parameters to z_sendmany - if (LogAcceptCategory("zrpcunsafe")) { - LogPrint("zrpcunsafe", "%s: z_sendmany initialized (params=%s)\n", getId(), contextInfo.write()); - } else { - LogPrint("zrpc", "%s: z_sendmany initialized\n", getId()); - } - - - // Enable payment disclosure if requested - paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true); -} - -AsyncRPCOperation_sendmany_template::~AsyncRPCOperation_sendmany_template() { -} - -void AsyncRPCOperation_sendmany_template::main() { - if (isCancelled()) - return; - - set_state(OperationStatus::EXECUTING); - start_execution_clock(); - - bool success = false; - -#ifdef ENABLE_MINING - #ifdef ENABLE_WALLET - GenerateBitcoins(false, NULL, 0); - #else - GenerateBitcoins(false, 0); - #endif -#endif - - try { - success = main_impl(); - } catch (const UniValue& objError) { - int code = find_value(objError, "code").get_int(); - std::string message = find_value(objError, "message").get_str(); - set_error_code(code); - set_error_message(message); - } catch (const runtime_error& e) { - set_error_code(-1); - set_error_message("runtime error: " + string(e.what())); - } catch (const logic_error& e) { - set_error_code(-1); - set_error_message("logic error: " + string(e.what())); - } catch (const exception& e) { - set_error_code(-1); - set_error_message("general exception: " + string(e.what())); - } catch (...) { - set_error_code(-2); - set_error_message("unknown error"); - } - -#ifdef ENABLE_MINING - #ifdef ENABLE_WALLET - GenerateBitcoins(GetBoolArg("-gen",false), pwalletMain, GetArg("-genproclimit", 1)); - #else - GenerateBitcoins(GetBoolArg("-gen",false), GetArg("-genproclimit", 1)); - #endif -#endif - - stop_execution_clock(); - - if (success) { - set_state(OperationStatus::SUCCESS); - } else { - set_state(OperationStatus::FAILED); - } - - std::string s = strprintf("%s: z_sendmany finished (status=%s", getId(), getStateAsString()); - if (success) { - s += strprintf(", txid=%s)\n", tx_.GetHash().ToString()); - } else { - s += strprintf(", error=%s)\n", getErrorMessage()); - } - LogPrintf("%s",s); - - // !!! Payment disclosure START - if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) { - uint256 txidhash = tx_.GetHash(); - std::shared_ptr db = PaymentDisclosureDB::sharedInstance(); - for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) { - p.first.hash = txidhash; - if (!db->Put(p.first, p.second)) { - LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString()); - } else { - LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString()); - } - } - } - // !!! Payment disclosure END -} - -// Alright - MVP adaption of z_sendmany to produce unsigned t->z transactions to be signed elsewhere -// special importance should be given to the "sapling binding sig" as this requires a siganature from a temporary public key -// the preimage for the singature requires the inputs already to be in place -bool AsyncRPCOperation_sendmany_template::main_impl() { - - assert(isfromtaddr_ != isfromzaddr_); - - bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1); - bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1); - bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0); - CAmount minersFee = fee_; - - // When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere - // and if there are multiple zaddrs, we don't know where to send it. - if (0){//isfromtaddr_) { - if (isSingleZaddrOutput) { - bool b = find_utxos(true); - if (!b) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no UTXOs found for taddr from address."); - } - } else { - bool b = find_utxos(false); - if (!b) { - if (isMultipleZaddrOutput) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend. Coinbase UTXOs can only be sent to a single zaddr recipient."); - } else { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any non-coinbase UTXOs to spend."); - } - } - } - } - - - string pubkey_str, refund_pubkey_str, secret_hash; - uint256 input_txid; - uint64_t input_index, input_amount; - uint32_t locktime; - pubkey_str = find_value(hltcinfo_, "pubkey").get_str(); - refund_pubkey_str = find_value(hltcinfo_, "refund_pubkey").get_str(); - secret_hash = find_value(hltcinfo_, "secret_hash").get_str(); - locktime = find_value(hltcinfo_, "locktime").get_int(); - - input_txid = Parseuint256((char *)find_value(hltcinfo_, "input_txid").get_str().c_str()); - input_index = find_value(hltcinfo_, "input_index").get_int(); - input_amount = atoll(find_value(hltcinfo_, "input_amount").get_str().c_str()); - - CTxDestination dest; - CScript scriptPubKey, redeemScript; - - redeemScript << OP_IF << locktime << OP_NOP2 << OP_DROP << ParseHex(refund_pubkey_str.c_str()) << - OP_CHECKSIG << OP_ELSE << OP_SIZE << ParseHex("20") << OP_EQUALVERIFY << OP_HASH160 << - ParseHex(secret_hash.c_str()) << OP_EQUALVERIFY << - ParseHex(pubkey_str.c_str()) << OP_CHECKSIG << OP_ENDIF; - scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; - - //fprintf(stderr, "FULL HLTC: %s\n", redeemScript.ToString().c_str()); - //fprintf(stderr, "REDEEM HLTC: %s\n", scriptPubKey.ToString().c_str()); - - - if (!ExtractDestination(scriptPubKey, dest)) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "FIXME Bad prev scriptpubkey"); - } - - SendManyInputUTXO utxo(input_txid, input_index, input_amount, false, dest); - t_inputs_.push_back(utxo); - - - if (isfromzaddr_ && !find_unspent_notes()) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); - } - - // At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design - assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty()); - - CAmount t_inputs_total = 0; - for (SendManyInputUTXO & t : t_inputs_) { - t_inputs_total += std::get<2>(t); - } - - CAmount z_inputs_total = 0; - for (SendManyInputJSOP & t : z_sprout_inputs_) { - z_inputs_total += std::get<2>(t); - } - for (auto t : z_sapling_inputs_) { - z_inputs_total += t.note.value(); - } - - CAmount t_outputs_total = 0; - for (SendManyRecipient & t : t_outputs_) { - t_outputs_total += std::get<1>(t); - } - - CAmount z_outputs_total = 0; - for (SendManyRecipient & t : z_outputs_) { - z_outputs_total += std::get<1>(t); - } - - CAmount sendAmount = z_outputs_total + t_outputs_total; - CAmount targetAmount = sendAmount + minersFee; - - assert(!isfromtaddr_ || z_inputs_total == 0); - assert(!isfromzaddr_ || t_inputs_total == 0); - - if (isfromtaddr_ && (t_inputs_total < targetAmount)) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, - strprintf("Insufficient transparent funds, have %s, need %s", - FormatMoney(t_inputs_total), FormatMoney(targetAmount))); - } - - if (isfromzaddr_ && (z_inputs_total < targetAmount)) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, - strprintf("Insufficient shielded funds, have %s, need %s", - FormatMoney(z_inputs_total), FormatMoney(targetAmount))); - } - - // If from address is a taddr, select UTXOs to spend - CAmount selectedUTXOAmount = 0; - bool selectedUTXOCoinbase = false; - if (isfromtaddr_) { - // Get dust threshold - CKey secret; - secret.MakeNewKey(true); - CScript scriptPubKey = GetScriptForDestination(secret.GetPubKey().GetID()); - CTxOut out(CAmount(1), scriptPubKey); - CAmount dustThreshold = out.GetDustThreshold(minRelayTxFee); - CAmount dustChange = -1; - - std::vector selectedTInputs; - for (SendManyInputUTXO & t : t_inputs_) { - bool b = std::get<3>(t); - if (b) { - selectedUTXOCoinbase = true; - } - selectedUTXOAmount += std::get<2>(t); - selectedTInputs.push_back(t); - if (selectedUTXOAmount >= targetAmount) { - // Select another utxo if there is change less than the dust threshold. - dustChange = selectedUTXOAmount - targetAmount; - if (dustChange == 0 || dustChange >= dustThreshold) { - break; - } - } - } - - // If there is transparent change, is it valid or is it dust? - if (dustChange < dustThreshold && dustChange != 0) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, - strprintf("Insufficient transparent funds, have %s, need %s more to avoid creating invalid change output %s (dust threshold is %s)", - FormatMoney(t_inputs_total), FormatMoney(dustThreshold - dustChange), FormatMoney(dustChange), FormatMoney(dustThreshold))); - } - - t_inputs_ = selectedTInputs; - t_inputs_total = selectedUTXOAmount; - - // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects - size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); - { - LOCK(cs_main); - if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { - limit = 0; - } - } - if (limit > 0) { - size_t n = t_inputs_.size(); - if (n > limit) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Too many transparent inputs %zu > limit %zu", n, limit)); - } - } - - // update the transaction with these inputs - if (isUsingBuilder_) { - CScript scriptPubKey; - for (auto t : t_inputs_) { - scriptPubKey = GetScriptForDestination(std::get<4>(t)); - //printf("Checking new script: %s\n", scriptPubKey.ToString().c_str()); - uint256 txid = std::get<0>(t); - int vout = std::get<1>(t); - CAmount amount = std::get<2>(t); - builder_.AddTransparentInput(COutPoint(txid, vout), scriptPubKey, amount, 4294967294); - } - // for Komodo, set lock time to accure interest, for other chains, set - // locktime to spend time locked coinbases - // FIXME Alright - //if (ASSETCHAINS_SYMBOL[0] == 0) - //{ - //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) - //if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - fprintf(stderr, "SET A REASONABLE LOCKTIME \n"); - builder_.SetLockTime((uint32_t)time(NULL) - 60); // set lock time for Komodo interest - //else - // builder_.SetLockTime((uint32_t)chainActive.Tip()->GetMedianTimePast()); - //} - } else { - CMutableTransaction rawTx(tx_); - for (SendManyInputUTXO & t : t_inputs_) { - uint256 txid = std::get<0>(t); - int vout = std::get<1>(t); - CAmount amount = std::get<2>(t); - CTxIn in(COutPoint(txid, vout)); - rawTx.vin.push_back(in); - } - if (ASSETCHAINS_SYMBOL[0] == 0) - { - //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) - if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 - else - rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); - } - tx_ = CTransaction(rawTx); - } - } - - LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n", - getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); - LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total)); - LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total)); - LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(t_outputs_total)); - LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total)); - LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee)); - - - /** - * SCENARIO #0 - * - * Sprout not involved, so we just use the TransactionBuilder and we're done. - * We added the transparent inputs to the builder earlier. - */ - if (isUsingBuilder_) { - builder_.SetFee(minersFee); - - // Get various necessary keys - SaplingExpandedSpendingKey expsk; - uint256 ovk; - if (isfromzaddr_) { - auto sk = boost::get(spendingkey_); - expsk = sk.expsk; - ovk = expsk.full_viewing_key().ovk; - } else { - // Sending from a t-address, which we don't have an ovk for. Instead, - // generate a common one from the HD seed. This ensures the data is - // recoverable, while keeping it logically separate from the ZIP 32 - // Sapling key hierarchy, which the user might not be using. - HDSeed seed; - if (!pwalletMain->GetHDSeed(seed)) { - throw JSONRPCError( - RPC_WALLET_ERROR, - "AsyncRPCOperation_sendmany_template::main_impl(): HD seed not found"); - } - ovk = ovkForShieldingFromTaddr(seed); - } - - // Set change address if we are using transparent funds - // TODO: Should we just use fromtaddr_ as the change address? - if (isfromtaddr_) { - LOCK2(cs_main, pwalletMain->cs_wallet); - - EnsureWalletIsUnlocked(); - CReserveKey keyChange(pwalletMain); - CPubKey vchPubKey; - bool ret = keyChange.GetReservedKey(vchPubKey); - if (!ret) { - // should never fail, as we just unlocked - throw JSONRPCError( - RPC_WALLET_KEYPOOL_RAN_OUT, - "Could not generate a taddr to use as a change address"); - } - - CTxDestination changeAddr = vchPubKey.GetID(); - assert(builder_.SendChangeTo(changeAddr)); - } - - // Select Sapling notes - std::vector ops; - std::vector notes; - CAmount sum = 0; - for (auto t : z_sapling_inputs_) { - ops.push_back(t.op); - notes.push_back(t.note); - sum += t.note.value(); - if (sum >= targetAmount) { - break; - } - } - - // Fetch Sapling anchor and witnesses - uint256 anchor; - std::vector> witnesses; - { - LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->GetSaplingNoteWitnesses(ops, witnesses, anchor); - } - - // Add Sapling spends - for (size_t i = 0; i < notes.size(); i++) { - if (!witnesses[i]) { - throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note"); - } - assert(builder_.AddSaplingSpend(expsk, notes[i], anchor, witnesses[i].get())); - } - - // Add Sapling outputs - for (auto r : z_outputs_) { - auto address = std::get<0>(r); - auto value = std::get<1>(r); - auto hexMemo = std::get<2>(r); - - auto addr = DecodePaymentAddress(address); - assert(boost::get(&addr) != nullptr); - auto to = boost::get(addr); - - auto memo = get_memo_from_hex_string(hexMemo); - - builder_.AddSaplingOutput(ovk, to, value, memo); - } - - - - // Add transparent outputs - for (auto r : t_outputs_) { - auto outputAddress = std::get<0>(r); - auto amount = std::get<1>(r); - - auto address = DecodeDestination(outputAddress); - if (!builder_.AddTransparentOutput(address, amount)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr."); - } - } - - // Build the transaction - auto maybe_tx = builder_.BuildWithoutSig(); - if (!maybe_tx) { - throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction."); - } - tx_ = maybe_tx.get(); - - UniValue ret(UniValue::VOBJ); - string signedtxn = EncodeHexTx(tx_); - //fprintf(stderr, "HED TX: %s\n", signedtxn.c_str()); - ret.push_back(Pair("hex", signedtxn)); - set_result(ret); - return true; - - - } - /** - * END SCENARIO #0 - */ - - - // Grab the current consensus branch ID - { - LOCK(cs_main); - consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); - } - - /** - * SCENARIO #1 - * - * taddr -> taddrs - * - * There are no zaddrs or joinsplits involved. - */ - if (isPureTaddrOnlyTx) { - add_taddr_outputs_to_tx(); - - CAmount funds = selectedUTXOAmount; - CAmount fundsSpent = t_outputs_total + minersFee; - CAmount change = funds - fundsSpent; - - if (change > 0) { - add_taddr_change_output_to_tx(0,change); - - LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", - getId(), - FormatMoney(change) - ); - } - - UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("rawtxn", EncodeHexTx(tx_))); - sign_send_raw_transaction(obj); - return true; - } - /** - * END SCENARIO #1 - */ - - - // Prepare raw transaction to handle JoinSplits - CMutableTransaction mtx(tx_); - crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_); - mtx.joinSplitPubKey = joinSplitPubKey_; - //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) - if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - mtx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 - else - mtx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); - - tx_ = CTransaction(mtx); - - // Copy zinputs and zoutputs to more flexible containers - std::deque zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount - CAmount tmp = 0; - for (auto o : z_sprout_inputs_) { - zInputsDeque.push_back(o); - tmp += std::get<2>(o); - if (tmp >= targetAmount) { - break; - } - } - std::deque zOutputsDeque; - for (auto o : z_outputs_) { - zOutputsDeque.push_back(o); - } - - // When spending notes, take a snapshot of note witnesses and anchors as the treestate will - // change upon arrival of new blocks which contain joinsplit transactions. This is likely - // to happen as creating a chained joinsplit transaction can take longer than the block interval. - if (z_sprout_inputs_.size() > 0) { - LOCK2(cs_main, pwalletMain->cs_wallet); - for (auto t : z_sprout_inputs_) { - JSOutPoint jso = std::get<0>(t); - std::vector vOutPoints = { jso }; - uint256 inputAnchor; - std::vector> vInputWitnesses; - pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor); - jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor }; - } - } - - - /** - * SCENARIO #2 - * - * taddr -> taddrs - * -> zaddrs - * - * Note: Consensus rule states that coinbase utxos can only be sent to a zaddr. - * Local wallet rule does not allow any change when sending coinbase utxos - * since there is currently no way to specify a change address and we don't - * want users accidentally sending excess funds to a recipient. - */ - if (isfromtaddr_) { - add_taddr_outputs_to_tx(); - - CAmount funds = selectedUTXOAmount; - CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total; - CAmount change = funds - fundsSpent; - - if (change > 0) { - if (selectedUTXOCoinbase) { - assert(isSingleZaddrOutput); - throw JSONRPCError(RPC_WALLET_ERROR, strprintf( - "Change %s not allowed. When shielding coinbase funds, the wallet does not " - "allow any change as there is currently no way to specify a change address " - "in z_sendmany.", FormatMoney(change))); - } else { - CBitcoinAddress ba = CBitcoinAddress(fromtaddr_); - add_taddr_change_output_to_tx(&ba,change); - LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", - getId(), - FormatMoney(change) - ); - } - } - - // Create joinsplits, where each output represents a zaddr recipient. - UniValue obj(UniValue::VOBJ); - while (zOutputsDeque.size() > 0) { - AsyncJoinSplitInfo info; - info.vpub_old = 0; - info.vpub_new = 0; - int n = 0; - while (n++ 0) { - SendManyRecipient smr = zOutputsDeque.front(); - std::string address = std::get<0>(smr); - CAmount value = std::get<1>(smr); - std::string hexMemo = std::get<2>(smr); - zOutputsDeque.pop_front(); - - PaymentAddress pa = DecodePaymentAddress(address); - JSOutput jso = JSOutput(boost::get(pa), value); - if (hexMemo.size() > 0) { - jso.memo = get_memo_from_hex_string(hexMemo); - } - info.vjsout.push_back(jso); - - // Funds are removed from the value pool and enter the private pool - info.vpub_old += value; - } - obj = perform_joinsplit(info); - } - sign_send_raw_transaction(obj); - return true; - } - /** - * END SCENARIO #2 - */ - - - - /** - * SCENARIO #3 - * - * zaddr -> taddrs - * -> zaddrs - * - * Send to zaddrs by chaining JoinSplits together and immediately consuming any change - * Send to taddrs by creating dummy z outputs and accumulating value in a change note - * which is used to set vpub_new in the last chained joinsplit. - */ - UniValue obj(UniValue::VOBJ); - CAmount jsChange = 0; // this is updated after each joinsplit - int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0 - bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit - CAmount vpubNewTarget = minersFee; - if (t_outputs_total > 0) { - add_taddr_outputs_to_tx(); - vpubNewTarget += t_outputs_total; - } - - // Keep track of treestate within this transaction - boost::unordered_map intermediates; - std::vector previousCommitments; - - while (!vpubNewProcessed) { - AsyncJoinSplitInfo info; - info.vpub_old = 0; - info.vpub_new = 0; - - CAmount jsInputValue = 0; - uint256 jsAnchor; - std::vector> witnesses; - - JSDescription prevJoinSplit; - - // Keep track of previous JoinSplit and its commitments - if (tx_.vjoinsplit.size() > 0) { - prevJoinSplit = tx_.vjoinsplit.back(); - } - - // If there is no change, the chain has terminated so we can reset the tracked treestate. - if (jsChange==0 && tx_.vjoinsplit.size() > 0) { - intermediates.clear(); - previousCommitments.clear(); - } - - // - // Consume change as the first input of the JoinSplit. - // - if (jsChange > 0) { - LOCK2(cs_main, pwalletMain->cs_wallet); - - // Update tree state with previous joinsplit - SproutMerkleTree tree; - auto it = intermediates.find(prevJoinSplit.anchor); - if (it != intermediates.end()) { - tree = it->second; - } else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor"); - } - - assert(changeOutputIndex != -1); - boost::optional changeWitness; - int n = 0; - for (const uint256& commitment : prevJoinSplit.commitments) { - tree.append(commitment); - previousCommitments.push_back(commitment); - if (!changeWitness && changeOutputIndex == n++) { - changeWitness = tree.witness(); - } else if (changeWitness) { - changeWitness.get().append(commitment); - } - } - if (changeWitness) { - witnesses.push_back(changeWitness); - } - jsAnchor = tree.root(); - intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries) - - // Decrypt the change note's ciphertext to retrieve some data we need - ZCNoteDecryption decryptor(boost::get(spendingkey_).receiving_key()); - auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey); - try { - SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt( - decryptor, - prevJoinSplit.ciphertexts[changeOutputIndex], - prevJoinSplit.ephemeralKey, - hSig, - (unsigned char) changeOutputIndex); - - SproutNote note = plaintext.note(boost::get(frompaymentaddress_)); - info.notes.push_back(note); - - jsInputValue += plaintext.value(); - - LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n", - getId(), - FormatMoney(plaintext.value()) - ); - - } catch (const std::exception& e) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what())); - } - } - - - // - // Consume spendable non-change notes - // - std::vector vInputNotes; - std::vector vOutPoints; - std::vector> vInputWitnesses; - uint256 inputAnchor; - int numInputsNeeded = (jsChange>0) ? 1 : 0; - while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) { - SendManyInputJSOP t = zInputsDeque.front(); - JSOutPoint jso = std::get<0>(t); - SproutNote note = std::get<1>(t); - CAmount noteFunds = std::get<2>(t); - zInputsDeque.pop_front(); - - WitnessAnchorData wad = jsopWitnessAnchorMap[ jso.ToString() ]; - vInputWitnesses.push_back(wad.witness); - if (inputAnchor.IsNull()) { - inputAnchor = wad.anchor; - } else if (inputAnchor != wad.anchor) { - throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor"); - } - - vOutPoints.push_back(jso); - vInputNotes.push_back(note); - - jsInputValue += noteFunds; - - int wtxHeight = -1; - int wtxDepth = -1; - { - LOCK2(cs_main, pwalletMain->cs_wallet); - const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash]; - // Zero-confirmation notes belong to transactions which have not yet been mined - if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString())); - } - 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", - getId(), - jso.hash.ToString().substr(0, 10), - jso.js, - int(jso.n), // uint8_t - FormatMoney(noteFunds), - wtxHeight, - wtxDepth - ); - } - - // Add history of previous commitments to witness - if (vInputNotes.size() > 0) { - - if (vInputWitnesses.size()==0) { - throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment"); - } - - for (auto & optionalWitness : vInputWitnesses) { - if (!optionalWitness) { - throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null"); - } - SproutWitness w = *optionalWitness; // could use .get(); - if (jsChange > 0) { - for (const uint256& commitment : previousCommitments) { - w.append(commitment); - } - if (jsAnchor != w.root()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input"); - } - } - witnesses.push_back(w); - } - - // The jsAnchor is null if this JoinSplit is at the start of a new chain - if (jsAnchor.IsNull()) { - jsAnchor = inputAnchor; - } - - // Add spendable notes as inputs - std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes)); - } - - // Find recipient to transfer funds to - std::string address, hexMemo; - CAmount value = 0; - if (zOutputsDeque.size() > 0) { - SendManyRecipient smr = zOutputsDeque.front(); - address = std::get<0>(smr); - value = std::get<1>(smr); - hexMemo = std::get<2>(smr); - zOutputsDeque.pop_front(); - } - - // Reset change - jsChange = 0; - CAmount outAmount = value; - - // Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy) - if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) { - assert(!vpubNewProcessed); - if (jsInputValue < vpubNewTarget) { - throw JSONRPCError(RPC_WALLET_ERROR, - strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr outputs %s)", - FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_outputs_total))); - } - outAmount += vpubNewTarget; - info.vpub_new += vpubNewTarget; // funds flowing back to public pool - vpubNewProcessed = true; - jsChange = jsInputValue - outAmount; - assert(jsChange >= 0); - } - else { - // This is not the last joinsplit, so compute change and any amount still due to the recipient - if (jsInputValue > outAmount) { - jsChange = jsInputValue - outAmount; - } else if (outAmount > jsInputValue) { - // Any amount due is owed to the recipient. Let the miners fee get paid first. - CAmount due = outAmount - jsInputValue; - SendManyRecipient r = SendManyRecipient(address, due, hexMemo); - zOutputsDeque.push_front(r); - - // reduce the amount being sent right now to the value of all inputs - value = jsInputValue; - } - } - - // create output for recipient - if (address.empty()) { - assert(value==0); - info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new - } else { - PaymentAddress pa = DecodePaymentAddress(address); - // If we are here, we know we have no Sapling outputs. - JSOutput jso = JSOutput(boost::get(pa), value); - if (hexMemo.size() > 0) { - jso.memo = get_memo_from_hex_string(hexMemo); - } - info.vjsout.push_back(jso); - } - - // create output for any change - if (jsChange>0) { - info.vjsout.push_back(JSOutput(boost::get(frompaymentaddress_), jsChange)); - - LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n", - getId(), - FormatMoney(jsChange) - ); - } - - obj = perform_joinsplit(info, witnesses, jsAnchor); - - if (jsChange > 0) { - changeOutputIndex = find_output(obj, 1); - } - } - - // Sanity check in case changes to code block above exits loop by invoking 'break' - assert(zInputsDeque.size() == 0); - assert(zOutputsDeque.size() == 0); - assert(vpubNewProcessed); - - sign_send_raw_transaction(obj); - return true; -} - - -/** - * Sign and send a raw transaction. - * Raw transaction as hex string should be in object field "rawtxn" - */ -void AsyncRPCOperation_sendmany_template::sign_send_raw_transaction(UniValue obj) -{ - // Sign the raw transaction - UniValue rawtxnValue = find_value(obj, "rawtxn"); - if (rawtxnValue.isNull()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for raw transaction"); - } - std::string rawtxn = rawtxnValue.get_str(); - - UniValue params = UniValue(UniValue::VARR); - params.push_back(rawtxn); - UniValue signResultValue = signrawtransaction(params, false, CPubKey()); - UniValue signResultObject = signResultValue.get_obj(); - UniValue completeValue = find_value(signResultObject, "complete"); - bool complete = completeValue.get_bool(); - if (!complete) { - // TODO: #1366 Maybe get "errors" and print array vErrors into a string - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to sign transaction"); - } - - UniValue hexValue = find_value(signResultObject, "hex"); - if (hexValue.isNull()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for signed transaction"); - } - std::string signedtxn = hexValue.get_str(); - - // Send the signed transaction - if (!testmode) { - params.clear(); - params.setArray(); - params.push_back(signedtxn); - UniValue sendResultValue = sendrawtransaction(params, false, CPubKey()); - if (sendResultValue.isNull()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Send raw transaction did not return an error or a txid."); - } - - std::string txid = sendResultValue.get_str(); - - UniValue o(UniValue::VOBJ); - o.push_back(Pair("txid", txid)); - set_result(o); - } else { - // Test mode does not send the transaction to the network. - - CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); - CTransaction tx; - stream >> tx; - - UniValue o(UniValue::VOBJ); - o.push_back(Pair("test", 1)); - o.push_back(Pair("txid", tx.GetHash().ToString())); - o.push_back(Pair("hex", signedtxn)); - set_result(o); - } - - // Keep the signed transaction so we can hash to the same txid - CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); - CTransaction tx; - stream >> tx; - tx_ = tx; -} - -bool AsyncRPCOperation_sendmany_template::find_utxos(bool fAcceptCoinbase=false) { - std::set destinations; - destinations.insert(fromtaddr_); - - //printf("Looking for %s\n", boost::apply_visitor(AddressVisitorString(), fromtaddr_).c_str()); - - vector vecOutputs; - - LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->AvailableCoins(vecOutputs, false, NULL, true, fAcceptCoinbase); - - BOOST_FOREACH(const COutput& out, vecOutputs) { - CTxDestination dest; - - fprintf(stderr, "HASH CHECK %s\n", out.tx->GetHash().ToString().c_str()); - - - if (!out.fSpendable) { - continue; - } - - if( mindepth_ > 1 ) { - int nHeight = tx_height(out.tx->GetHash()); - int dpowconfs = komodo_dpowconfs(nHeight, out.nDepth); - if (dpowconfs < mindepth_) { - continue; - } - } else { - if (out.nDepth < mindepth_) { - continue; - } - } - - const CScript &scriptPubKey = out.tx->vout[out.i].scriptPubKey; - - if (destinations.size()) { - if (!ExtractDestination(scriptPubKey, dest)) { - continue; - } - - //printf("%s\n", boost::apply_visitor(AddressVisitorString(), dest).c_str()); - if (!destinations.count(dest)) { - continue; - } - } - - // By default we ignore coinbase outputs - bool isCoinbase = out.tx->IsCoinBase(); - if (isCoinbase && fAcceptCoinbase==false) { - continue; - } - - if (!ExtractDestination(scriptPubKey, dest, true)) - continue; - - CAmount nValue = out.tx->vout[out.i].nValue; - - - // Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) - //typedef std::tuple SendManyInputUTXO; - SendManyInputUTXO utxo(out.tx->GetHash(), out.i, nValue, isCoinbase, dest); - t_inputs_.push_back(utxo); - } - - // sort in ascending order, so smaller utxos appear first - std::sort(t_inputs_.begin(), t_inputs_.end(), [](SendManyInputUTXO i, SendManyInputUTXO j) -> bool { - return ( std::get<2>(i) < std::get<2>(j)); - }); - - return t_inputs_.size() > 0; -} - - -bool AsyncRPCOperation_sendmany_template::find_unspent_notes() { - std::vector sproutEntries; - std::vector saplingEntries; - { - LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_); - } - - // If using the TransactionBuilder, we only want Sapling notes. - // If not using it, we only want Sprout notes. - // TODO: Refactor `GetFilteredNotes()` so we only fetch what we need. - if (isUsingBuilder_) { - sproutEntries.clear(); - } else { - saplingEntries.clear(); - } - - for (CSproutNotePlaintextEntry & entry : sproutEntries) { - z_sprout_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get(frompaymentaddress_)), CAmount(entry.plaintext.value()))); - std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end()); - LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n", - getId(), - entry.jsop.hash.ToString().substr(0, 10), - entry.jsop.js, - int(entry.jsop.n), // uint8_t - FormatMoney(entry.plaintext.value()), - HexStr(data).substr(0, 10) - ); - } - - for (auto entry : saplingEntries) { - z_sapling_inputs_.push_back(entry); - std::string data(entry.memo.begin(), entry.memo.end()); - LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", - getId(), - entry.op.hash.ToString().substr(0, 10), - entry.op.n, - FormatMoney(entry.note.value()), - HexStr(data).substr(0, 10)); - } - - if (z_sprout_inputs_.empty() && z_sapling_inputs_.empty()) { - return false; - } - - // sort in descending order, so big notes appear first - std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(), - [](SendManyInputJSOP i, SendManyInputJSOP j) -> bool { - return std::get<2>(i) > std::get<2>(j); - }); - std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(), - [](SaplingNoteEntry i, SaplingNoteEntry j) -> bool { - return i.note.value() > j.note.value(); - }); - - return true; -} - -UniValue AsyncRPCOperation_sendmany_template::perform_joinsplit(AsyncJoinSplitInfo & info) { - std::vector> witnesses; - uint256 anchor; - { - LOCK(cs_main); - anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor - } - return perform_joinsplit(info, witnesses, anchor); -} - - -UniValue AsyncRPCOperation_sendmany_template::perform_joinsplit(AsyncJoinSplitInfo & info, std::vector & outPoints) { - std::vector> witnesses; - uint256 anchor; - { - LOCK(cs_main); - pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor); - } - return perform_joinsplit(info, witnesses, anchor); -} - -UniValue AsyncRPCOperation_sendmany_template::perform_joinsplit( - AsyncJoinSplitInfo & info, - std::vector> witnesses, - uint256 anchor) -{ - if (anchor.IsNull()) { - throw std::runtime_error("anchor is null"); - } - - if (!(witnesses.size() == info.notes.size())) { - throw runtime_error("number of notes and witnesses do not match"); - } - - for (size_t i = 0; i < witnesses.size(); i++) { - if (!witnesses[i]) { - throw runtime_error("joinsplit input could not be found in tree"); - } - info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], boost::get(spendingkey_))); - } - - // Make sure there are two inputs and two outputs - while (info.vjsin.size() < ZC_NUM_JS_INPUTS) { - info.vjsin.push_back(JSInput()); - } - - while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) { - info.vjsout.push_back(JSOutput()); - } - - if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) { - throw runtime_error("unsupported joinsplit input/output counts"); - } - - CMutableTransaction mtx(tx_); - - LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n", - getId(), - tx_.vjoinsplit.size(), - FormatMoney(info.vpub_old), FormatMoney(info.vpub_new), - FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()), - FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value) - ); - - // Generate the proof, this can take over a minute. - std::array inputs - {info.vjsin[0], info.vjsin[1]}; - std::array outputs - {info.vjsout[0], info.vjsout[1]}; - std::array inputMap; - std::array outputMap; - uint256 esk; // payment disclosure - secret - - JSDescription jsdesc = JSDescription::Randomized( - mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION), - *pzcashParams, - joinSplitPubKey_, - anchor, - inputs, - outputs, - inputMap, - outputMap, - info.vpub_old, - info.vpub_new, - !this->testmode, - &esk); // parameter expects pointer to esk, so pass in address - { - auto verifier = libzcash::ProofVerifier::Strict(); - if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) { - throw std::runtime_error("error verifying joinsplit"); - } - } - - mtx.vjoinsplit.push_back(jsdesc); - - // Empty output script. - CScript scriptCode; - CTransaction signTx(mtx); - uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_); - - // Add the signature - if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, - dataToBeSigned.begin(), 32, - joinSplitPrivKey_ - ) == 0)) - { - throw std::runtime_error("crypto_sign_detached failed"); - } - - // Sanity check - if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0], - dataToBeSigned.begin(), 32, - mtx.joinSplitPubKey.begin() - ) == 0)) - { - throw std::runtime_error("crypto_sign_verify_detached failed"); - } - - CTransaction rawTx(mtx); - tx_ = rawTx; - - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << rawTx; - - std::string encryptedNote1; - std::string encryptedNote2; - { - CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); - ss2 << ((unsigned char) 0x00); - ss2 << jsdesc.ephemeralKey; - ss2 << jsdesc.ciphertexts[0]; - ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_); - - encryptedNote1 = HexStr(ss2.begin(), ss2.end()); - } - { - CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); - ss2 << ((unsigned char) 0x01); - ss2 << jsdesc.ephemeralKey; - ss2 << jsdesc.ciphertexts[1]; - ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_); - - encryptedNote2 = HexStr(ss2.begin(), ss2.end()); - } - - UniValue arrInputMap(UniValue::VARR); - UniValue arrOutputMap(UniValue::VARR); - for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) { - arrInputMap.push_back(static_cast(inputMap[i])); - } - for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { - arrOutputMap.push_back(static_cast(outputMap[i])); - } - - - // !!! Payment disclosure START - unsigned char buffer[32] = {0}; - memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer - std::vector vch(&buffer[0], &buffer[0] + 32); - uint256 joinSplitPrivKey = uint256(vch); - size_t js_index = tx_.vjoinsplit.size() - 1; - uint256 placeholder; - for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { - uint8_t mapped_index = outputMap[i]; - // placeholder for txid will be filled in later when tx has been finalized and signed. - PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index}; - JSOutput output = outputs[mapped_index]; - libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output - PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr}; - paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo)); - - LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr)); - } - // !!! Payment disclosure END - - UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("encryptednote1", encryptedNote1)); - obj.push_back(Pair("encryptednote2", encryptedNote2)); - obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end()))); - obj.push_back(Pair("inputmap", arrInputMap)); - obj.push_back(Pair("outputmap", arrOutputMap)); - return obj; -} - -void AsyncRPCOperation_sendmany_template::add_taddr_outputs_to_tx() { - - CMutableTransaction rawTx(tx_); - - for (SendManyRecipient & r : t_outputs_) { - std::string outputAddress = std::get<0>(r); - CAmount nAmount = std::get<1>(r); - - CTxDestination address = DecodeDestination(outputAddress); - if (!IsValidDestination(address)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr."); - } - - CScript scriptPubKey = GetScriptForDestination(address); - - CTxOut out(nAmount, scriptPubKey); - rawTx.vout.push_back(out); - } - //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) - if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 - else - rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); - - tx_ = CTransaction(rawTx); -} - -void AsyncRPCOperation_sendmany_template::add_taddr_change_output_to_tx(CBitcoinAddress *fromaddress,CAmount amount) { - - LOCK2(cs_main, pwalletMain->cs_wallet); - - EnsureWalletIsUnlocked(); - CScript scriptPubKey; - CReserveKey keyChange(pwalletMain); - CPubKey vchPubKey; - if ( fromaddress != 0 ) - scriptPubKey = GetScriptForDestination(fromaddress->Get()); - else - { - bool ret = keyChange.GetReservedKey(vchPubKey); - if (!ret) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Could not generate a taddr to use as a change address"); // should never fail, as we just unlocked - } - scriptPubKey = GetScriptForDestination(vchPubKey.GetID()); - } - CTxOut out(amount, scriptPubKey); - - CMutableTransaction rawTx(tx_); - rawTx.vout.push_back(out); - //if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP) - if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 - else - rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); - tx_ = CTransaction(rawTx); -} - -std::array AsyncRPCOperation_sendmany_template::get_memo_from_hex_string(std::string s) { - // initialize to default memo (no_memo), see section 5.5 of the protocol spec - std::array memo = {{0xF6}}; - - std::vector rawMemo = ParseHex(s.c_str()); - - // If ParseHex comes across a non-hex char, it will stop but still return results so far. - size_t slen = s.length(); - if (slen % 2 !=0 || (slen>0 && rawMemo.size()!=slen/2)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo must be in hexadecimal format"); - } - - if (rawMemo.size() > ZC_MEMO_SIZE) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Memo size of %d is too big, maximum allowed is %d", rawMemo.size(), ZC_MEMO_SIZE)); - } - - // copy vector into boost array - int lenMemo = rawMemo.size(); - for (int i = 0; i < ZC_MEMO_SIZE && i < lenMemo; i++) { - memo[i] = rawMemo[i]; - } - return memo; -} - -/** - * Override getStatus() to append the operation's input parameters to the default status object. - */ -UniValue AsyncRPCOperation_sendmany_template::getStatus() const { - UniValue v = AsyncRPCOperation::getStatus(); - if (contextinfo_.isNull()) { - return v; - } - - UniValue obj = v.get_obj(); - obj.push_back(Pair("method", "z_sendmany")); - obj.push_back(Pair("params", contextinfo_ )); - return obj; -} - diff --git a/src/wallet/asyncrpcoperation_sendmany_template.h b/src/wallet/asyncrpcoperation_sendmany_template.h deleted file mode 100644 index 27d89dede77..00000000000 --- a/src/wallet/asyncrpcoperation_sendmany_template.h +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2016 The Zcash developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -/****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ - -#ifndef ASYNCRPCOPERATION_SENDMANY_TEMPLATE_H -#define ASYNCRPCOPERATION_SENDMANY_TEMPLATE_H - -#include "asyncrpcoperation.h" -#include "amount.h" -#include "primitives/transaction.h" -#include "transaction_builder.h" -#include "zcash/JoinSplit.hpp" -#include "zcash/Address.hpp" -#include "wallet.h" -#include "paymentdisclosure.h" - -#include -#include -#include - -#include - -namespace z_template { - -// Default transaction fee if caller does not specify one. -#define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000 - -using namespace libzcash; - -// A recipient is a tuple of address, amount, memo (optional if zaddr) -typedef std::tuple SendManyRecipient; - -// Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) -typedef std::tuple SendManyInputUTXO; - -// Input JSOP is a tuple of JSOutpoint, note and amount -typedef std::tuple SendManyInputJSOP; - -// Package of info which is passed to perform_joinsplit methods. -struct AsyncJoinSplitInfo -{ - std::vector vjsin; - std::vector vjsout; - std::vector notes; - CAmount vpub_old = 0; - CAmount vpub_new = 0; -}; - -// A struct to help us track the witness and anchor for a given JSOutPoint -struct WitnessAnchorData { - boost::optional witness; - uint256 anchor; -}; - -class AsyncRPCOperation_sendmany_template : public AsyncRPCOperation { -public: - AsyncRPCOperation_sendmany_template( - boost::optional builder, - CMutableTransaction contextualTx, - std::string fromAddress, - std::vector tOutputs, - std::vector zOutputs, - int minDepth, - CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE, - UniValue contextInfo = NullUniValue, - UniValue HltcInfo = NullUniValue); - virtual ~AsyncRPCOperation_sendmany_template(); - - // We don't want to be copied or moved around - AsyncRPCOperation_sendmany_template(AsyncRPCOperation_sendmany_template const&) = delete; // Copy construct - AsyncRPCOperation_sendmany_template(AsyncRPCOperation_sendmany_template&&) = delete; // Move construct - AsyncRPCOperation_sendmany_template& operator=(AsyncRPCOperation_sendmany_template const&) = delete; // Copy assign - AsyncRPCOperation_sendmany_template& operator=(AsyncRPCOperation_sendmany_template &&) = delete; // Move assign - - virtual void main(); - - virtual UniValue getStatus() const; - - bool testmode = false; // Set to true to disable sending txs and generating proofs - - bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database. - -private: - - UniValue contextinfo_; // optional data to include in return value from getStatus() - UniValue hltcinfo_; - - bool isUsingBuilder_; // Indicates that no Sprout addresses are involved - uint32_t consensusBranchId_; - CAmount fee_; - int mindepth_; - std::string fromaddress_; - bool isfromtaddr_; - bool isfromzaddr_; - CTxDestination fromtaddr_; - PaymentAddress frompaymentaddress_; - SpendingKey spendingkey_; - - uint256 joinSplitPubKey_; - unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; - - // The key is the result string from calling JSOutPoint::ToString() - std::unordered_map jsopWitnessAnchorMap; - - std::vector t_outputs_; - std::vector z_outputs_; - std::vector t_inputs_; - std::vector z_sprout_inputs_; - std::vector z_sapling_inputs_; - - TransactionBuilder builder_; - CTransaction tx_; - - void add_taddr_change_output_to_tx(CBitcoinAddress *fromaddress,CAmount amount); - void add_taddr_outputs_to_tx(); - bool find_unspent_notes(); - bool find_utxos(bool fAcceptCoinbase); - std::array get_memo_from_hex_string(std::string s); - bool main_impl(); - - // JoinSplit without any input notes to spend - UniValue perform_joinsplit(AsyncJoinSplitInfo &); - - // JoinSplit with input notes to spend (JSOutPoints)) - UniValue perform_joinsplit(AsyncJoinSplitInfo &, std::vector & ); - - // JoinSplit where you have the witnesses and anchor - UniValue perform_joinsplit( - AsyncJoinSplitInfo & info, - std::vector> witnesses, - uint256 anchor); - - void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error - - // payment disclosure! - std::vector paymentDisclosureData_; -}; - -} // z_template - - -#endif /* ASYNCRPCOPERATION_SENDMANY_TEMPLATE_H */ - - diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7d20967fe41..1231953b18f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4553,8 +4553,6 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) bool containsSproutOutput = false; bool containsSaplingOutput = false; - - CScript opret; for (const UniValue& o : outputs.getValues()) { if (!o.isObject()) @@ -4563,7 +4561,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // sanity check, report error if unknown key-value pairs for (const string& name_ : o.getKeys()) { std::string s = name_; - if (s != "address" && s != "amount" && s!="memo" && s!="opreturn") + if (s != "address" && s != "amount" && s!="memo") throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); } @@ -4615,11 +4613,6 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+address); setAddress.insert(address); - UniValue opretValue = find_value(o, "opreturn"); - if (!opretValue.isNull()) { - opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); - } - UniValue memoValue = find_value(o, "memo"); string memo; if (!memoValue.isNull()) { @@ -4765,7 +4758,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // Create operation and add to global queue std::shared_ptr q = getAsyncRPCQueue(); - std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo, opret) ); + std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo) ); q->addOperation(operation); AsyncRPCOperationId operationId = operation->getId(); return operationId; From e29c60696ca0fc46df478f932a33e97d2e85718d Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Wed, 12 May 2021 17:54:27 +0200 Subject: [PATCH 27/29] notaryid fix --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index c8a63ffb18a..7d0d52424ed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5170,7 +5170,7 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C { int32_t notaryid; int32_t special = komodo_chosennotary(¬aryid,height,pubkey33,tiptime); - if (notaryid > 0) { + if (notaryid > 0 || ( notaryid == 0 && height > nS5HardforkHeight ) ) { CScript merkleroot = CScript(); CBlock blockcopy = block; // block shouldn't be changed below, so let's make it's copy CBlock *pblockcopy = (CBlock *)&blockcopy; From e08234e342bf7e8f8b46fe764dd17199adb8b3ae Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 13 May 2021 10:30:47 +0200 Subject: [PATCH 28/29] the danger of magic numebrs --- src/script/script.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index 74d483c2a5b..e447b764068 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -305,7 +305,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ redeemScript[49] == 0x20 && redeemScript[50] == OP_EQUALVERIFY && redeemScript[51] == OP_SHA256 && - redeemScript[88] == OP_EQUALVERIFY && + redeemScript[85] == OP_EQUALVERIFY && redeemScript[120] == OP_CHECKSIG && redeemScript[121] == OP_ENDIF ) { From 4e501bff6755e4535404d94c5e5dbfb3e7509f3d Mon Sep 17 00:00:00 2001 From: Alrighttt Date: Thu, 13 May 2021 11:16:58 +0200 Subject: [PATCH 29/29] final script exception --- src/script/script.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index e447b764068..0df33bf2cff 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -304,7 +304,7 @@ bool CScript::IsRedeemScriptReveal(CScript scriptpubkey) const{ redeemScript[48] == 0x01 && redeemScript[49] == 0x20 && redeemScript[50] == OP_EQUALVERIFY && - redeemScript[51] == OP_SHA256 && + ( redeemScript[51] == OP_SHA256 || redeemScript[51] == OP_HASH256 ) && redeemScript[85] == OP_EQUALVERIFY && redeemScript[120] == OP_CHECKSIG && redeemScript[121] == OP_ENDIF