// Copyright (c) 2017 The Zcash developers // Copyright (c) 2016-2020 The Hush developers // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html . /****************************************************************************** * 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_mergetoaddress.h" #include "amount.h" #include "asyncrpcqueue.h" #include "core_io.h" #include "init.h" #include "key_io.h" #include "main.h" #include "miner.h" #include "net.h" #include "netbase.h" #include "rpc/protocol.h" #include "rpc/server.h" #include "script/interpreter.h" #include "sodium.h" #include "timedata.h" #include "util.h" #include "utilmoneystr.h" #include "utiltime.h" #include "wallet.h" #include "walletdb.h" #include "zcash/IncrementalMerkleTree.hpp" #include #include #include #include int32_t komodo_blockheight(uint256 hash); using namespace libzcash; extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); int mta_find_output(UniValue obj, int n) { UniValue outputMapValue = find_value(obj, "outputmap"); if (!outputMapValue.isArray()) { throw JSONRPCError(RPC_WALLET_ERROR, "Missing outputmap for JoinSplit operation"); } UniValue outputMap = outputMapValue.get_array(); assert(outputMap.size() == HUSH_NUM_JS_OUTPUTS); for (size_t i = 0; i < outputMap.size(); i++) { if (outputMap[i].get_int() == n) { return i; } } throw std::logic_error("n is not present in outputmap"); } AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress( boost::optional builder, CMutableTransaction contextualTx, std::vector utxoInputs, std::vector saplingNoteInputs, MergeToAddressRecipient recipient, CAmount fee, UniValue contextInfo) : tx_(contextualTx), utxoInputs_(utxoInputs), saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo) { if (fee < 0 || fee > MAX_MONEY) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range"); } if (utxoInputs.empty() && saplingNoteInputs.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs"); } if (std::get<0>(recipient).size() == 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing"); } isUsingBuilder_ = false; if (builder) { isUsingBuilder_ = true; builder_ = builder.get(); } toTaddr_ = DecodeDestination(std::get<0>(recipient)); isToTaddr_ = IsValidDestination(toTaddr_); isToZaddr_ = false; if (!isToTaddr_) { auto address = DecodePaymentAddress(std::get<0>(recipient)); if (IsValidPaymentAddress(address)) { isToZaddr_ = true; toPaymentAddress_ = address; } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid recipient address"); } } // Log the context info i.e. the call parameters to z_mergetoaddress if (LogAcceptCategory("zrpcunsafe")) { LogPrint("zrpcunsafe", "%s: z_mergetoaddress initialized (params=%s)\n", getId(), contextInfo.write()); } else { LogPrint("zrpc", "%s: z_mergetoaddress initialized\n", getId()); } // Lock UTXOs lock_utxos(); lock_notes(); } AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress() { } void AsyncRPCOperation_mergetoaddress::main() { if (isCancelled()) { unlock_utxos(); // clean up unlock_notes(); 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_mergetoaddress 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); unlock_utxos(); // clean up unlock_notes(); // clean up } // Notes: // 1. #1359 Currently there is no limit set on the number of inputs+outputs, so size of tx could be invalid. // 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them. bool AsyncRPCOperation_mergetoaddress::main_impl() { assert(isToTaddr_ != isToZaddr_); bool isPureTaddrOnlyTx = (saplingNoteInputs_.empty() && isToTaddr_); CAmount minersFee = fee_; size_t numInputs = utxoInputs_.size(); // 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 && numInputs > limit) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Number of transparent inputs %d is greater than mempooltxinputlimit of %d", numInputs, limit)); } CAmount t_inputs_total = 0; for (MergeToAddressInputUTXO& t : utxoInputs_) { t_inputs_total += std::get<1>(t); } CAmount z_inputs_total = 0; for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) { z_inputs_total += std::get<2>(t); } CAmount targetAmount = z_inputs_total + t_inputs_total; if (targetAmount <= minersFee) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Insufficient funds, have %s and miners fee is %s", FormatMoney(targetAmount), FormatMoney(minersFee))); } CAmount sendAmount = targetAmount - minersFee; // update the transaction with the UTXO inputs and output (if any) if (!isUsingBuilder_) { CMutableTransaction rawTx(tx_); for (const MergeToAddressInputUTXO& t : utxoInputs_) { CTxIn in(std::get<0>(t)); rawTx.vin.push_back(in); } if (isToTaddr_) { CScript scriptPubKey = GetScriptForDestination(toTaddr_); CTxOut out(sendAmount, scriptPubKey); rawTx.vout.push_back(out); } tx_ = CTransaction(rawTx); } LogPrint(isPureTaddrOnlyTx ? "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\n", getId(), FormatMoney(t_inputs_total)); LogPrint("zrpcunsafe", "%s: private input: %s\n", getId(), FormatMoney(z_inputs_total)); if (isToTaddr_) { LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(sendAmount)); } else { LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(sendAmount)); } LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee)); // Grab the current consensus branch ID { LOCK(cs_main); consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); } /** * SCENARIO #0 * * Only sapling involved, so we just use the TransactionBuilder and we're done. * * This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored. */ if (isUsingBuilder_) { builder_.SetFee(minersFee); for (const MergeToAddressInputUTXO& t : utxoInputs_) { COutPoint outPoint = std::get<0>(t); CAmount amount = std::get<1>(t); CScript scriptPubKey = std::get<2>(t); builder_.AddTransparentInput(outPoint, scriptPubKey, amount); } boost::optional ovk; // Select Sapling notes std::vector saplingOPs; std::vector saplingNotes; std::vector expsks; for (const MergeToAddressInputSaplingNote& saplingNoteInput: saplingNoteInputs_) { saplingOPs.push_back(std::get<0>(saplingNoteInput)); saplingNotes.push_back(std::get<1>(saplingNoteInput)); auto expsk = std::get<3>(saplingNoteInput); expsks.push_back(expsk); if (!ovk) { ovk = expsk.full_viewing_key().ovk; } } // Fetch Sapling anchor and witnesses uint256 anchor; std::vector> witnesses; { LOCK2(cs_main, pwalletMain->cs_wallet); pwalletMain->GetSaplingNoteWitnesses(saplingOPs, witnesses, anchor); } // Add Sapling spends for (size_t i = 0; i < saplingNotes.size(); i++) { if (!witnesses[i]) { throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note"); } assert(builder_.AddSaplingSpend(expsks[i], saplingNotes[i], anchor, witnesses[i].get())); } if (isToTaddr_) { if (!builder_.AddTransparentOutput(toTaddr_, sendAmount)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr."); } } else { std::string zaddr = std::get<0>(recipient_); std::string memo = std::get<1>(recipient_); std::array hexMemo = get_memo_from_hex_string(memo); auto saplingPaymentAddress = boost::get(&toPaymentAddress_); if (saplingPaymentAddress == nullptr) { // This should never happen as we have already determined that the payment is to sapling throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not get Sapling payment address."); } if (saplingNoteInputs_.size() == 0 && utxoInputs_.size() > 0) { // Sending from t-addresses, which we don't have ovks 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: HD seed not found"); } ovk = ovkForShieldingFromTaddr(seed); } if (!ovk) { throw JSONRPCError(RPC_WALLET_ERROR, "Sending to a Sapling address requires an ovk."); } builder_.AddSaplingOutput(ovk.get(), *saplingPaymentAddress, sendAmount, hexMemo); } // Build the transaction auto maybe_tx = builder_.Build(); if (!maybe_tx) { throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction."); } tx_ = maybe_tx.get(); // Send the transaction // TODO: Use CWallet::CommitTransaction instead of sendrawtransaction auto signedtxn = EncodeHexTx(tx_); if (!testmode) { UniValue params = UniValue(UniValue::VARR); params.push_back(signedtxn); UniValue sendResultValue = sendrawtransaction(params, false, CPubKey()); if (sendResultValue.isNull()) { throw JSONRPCError(RPC_WALLET_ERROR, "sendrawtransaction did not return an error or a txid."); } auto 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. 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); } return true; } /** * END SCENARIO #0 */ /** * SCENARIO #1 * * taddrs -> taddr * * There are no zaddrs or joinsplits involved. */ if (isPureTaddrOnlyTx) { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("rawtxn", EncodeHexTx(tx_))); sign_send_raw_transaction(obj); return true; } /** * END SCENARIO #1 */ return false; } extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); /** * Sign and send a raw transaction. * Raw transaction as hex string should be in object field "rawtxn" */ void AsyncRPCOperation_mergetoaddress::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; } std::array AsyncRPCOperation_mergetoaddress::get_memo_from_hex_string(std::string s) { std::array memo = {{0x00}}; 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() > HUSH_MEMO_SIZE) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Memo size of %d is too big, maximum allowed is %d", rawMemo.size(), HUSH_MEMO_SIZE)); } // copy vector into boost array int lenMemo = rawMemo.size(); for (int i = 0; i < HUSH_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_mergetoaddress::getStatus() const { UniValue v = AsyncRPCOperation::getStatus(); if (contextinfo_.isNull()) { return v; } UniValue obj = v.get_obj(); obj.push_back(Pair("method", "z_mergetoaddress")); obj.push_back(Pair("params", contextinfo_)); return obj; } /** * Lock input utxos */ void AsyncRPCOperation_mergetoaddress::lock_utxos() { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto utxo : utxoInputs_) { pwalletMain->LockCoin(std::get<0>(utxo)); } } /** * Unlock input utxos */ void AsyncRPCOperation_mergetoaddress::unlock_utxos() { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto utxo : utxoInputs_) { pwalletMain->UnlockCoin(std::get<0>(utxo)); } } /** * Lock input notes */ void AsyncRPCOperation_mergetoaddress::lock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto note : saplingNoteInputs_) { pwalletMain->LockNote(std::get<0>(note)); } } /** * Unlock input notes */ void AsyncRPCOperation_mergetoaddress::unlock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto note : saplingNoteInputs_) { pwalletMain->UnlockNote(std::get<0>(note)); } }