From bffa3caeb57f39259696c3e48facf07b284ec992 Mon Sep 17 00:00:00 2001 From: jl777 Date: Sat, 24 Nov 2018 05:41:04 -1100 Subject: [PATCH] Remove limitation of trader for private chain --- src/wallet/rpcwallet.cpp | 379 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7e820871b..c3c290242 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4907,6 +4907,385 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) } +UniValue z_migrate(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + string enableArg = "zmigrate"; + auto fEnableMergeToAddress = fExperimentalMode && GetBoolArg("-" + enableArg, false); + std::string strDisabledMsg = ""; + + if (fHelp || params.size() < 2 || params.size() > 6) + throw runtime_error( + "z_migrate [\"fromaddress\", ... ] \"toaddress\" ( fee ) ( transparent_limit ) ( shielded_limit ) ( memo )\n" + + strDisabledMsg + + "\nMerge multiple UTXOs and notes into a single UTXO or note. Coinbase UTXOs are ignored; use `z_shieldcoinbase`" + "\nto combine those into a single note." + "\n\nThis is an asynchronous operation, and UTXOs selected for merging will be locked. If there is an error, they" + "\nare unlocked. The RPC call `listlockunspent` can be used to return a list of locked UTXOs." + "\n\nThe number of UTXOs and notes selected for merging can be limited by the caller. If the transparent limit" + "\nparameter is set to zero, and Overwinter is not yet active, the -mempooltxinputlimit option will determine the" + "\nnumber of UTXOs. Any limit is constrained by the consensus rule defining a maximum transaction size of" + + strprintf("\n%d bytes before Sapling, and %d bytes once Sapling activates.", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) + + HelpRequiringPassphrase() + "\n" + "\nArguments:\n" + "1. fromaddresses (string, required) A JSON array with addresses.\n" + " The following special strings are accepted inside the array:\n" + " - \"*\": Merge both UTXOs and notes from all addresses belonging to the wallet.\n" + " - \"ANY_TADDR\": Merge UTXOs from all t-addrs belonging to the wallet.\n" + " - \"ANY_ZADDR\": Merge notes from all z-addrs belonging to the wallet.\n" + " If a special string is given, any given addresses of that type will be ignored.\n" + " [\n" + " \"address\" (string) Can be a t-addr or a z-addr\n" + " ,...\n" + " ]\n" + "2. \"toaddress\" (string, required) The t-addr or z-addr to send the funds to.\n" + "3. fee (numeric, optional, default=" + + strprintf("%s", FormatMoney(MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" + "4. transparent_limit (numeric, optional, default=" + + strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT) + ") Limit on the maximum number of UTXOs to merge. Set to 0 to use node option -mempooltxinputlimit (before Overwinter), or as many as will fit in the transaction (after Overwinter).\n" + "4. shielded_limit (numeric, optional, default=" + + strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT) + ") Limit on the maximum number of notes to merge. Set to 0 to merge as many as will fit in the transaction.\n" + "5. \"memo\" (string, optional) Encoded as hex. When toaddress is a z-addr, this will be stored in the memo field of the new note.\n" + "\nResult:\n" + "{\n" + " \"remainingUTXOs\": xxx (numeric) Number of UTXOs still available for merging.\n" + " \"remainingTransparentValue\": xxx (numeric) Value of UTXOs still available for merging.\n" + " \"remainingNotes\": xxx (numeric) Number of notes still available for merging.\n" + " \"remainingShieldedValue\": xxx (numeric) Value of notes still available for merging.\n" + " \"mergingUTXOs\": xxx (numeric) Number of UTXOs being merged.\n" + " \"mergingTransparentValue\": xxx (numeric) Value of UTXOs being merged.\n" + " \"mergingNotes\": xxx (numeric) Number of notes being merged.\n" + " \"mergingShieldedValue\": xxx (numeric) Value of notes being merged.\n" + " \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("z_migrate", "'[\"ANY_ZADDR\"]' ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf") + ); + + if (!fEnableMergeToAddress) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: z_migrate is disabled."); + } + + LOCK2(cs_main, pwalletMain->cs_wallet); + + bool useAny = false; + bool useAnyUTXO = false; + bool useAnyNote = false; + std::set taddrs = {}; + std::set zaddrs = {}; + + uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); + + UniValue addresses = params[0].get_array(); + if (addresses.size()==0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, fromaddresses array is empty."); + + // Keep track of addresses to spot duplicates + std::set setAddress; + + // Sources + bool containsSaplingZaddrSource = false; + for (const UniValue& o : addresses.getValues()) { + if (!o.isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string"); + + std::string address = o.get_str(); + if (address == "*") { + useAny = true; + } else if (address == "ANY_TADDR") { + useAnyUTXO = true; + } else if (address == "ANY_ZADDR") { + useAnyNote = true; + } else { + CTxDestination taddr = DecodeDestination(address); + if (IsValidDestination(taddr)) { + // Ignore any listed t-addrs if we are using all of them + if (!(useAny || useAnyUTXO)) { + taddrs.insert(taddr); + } + } else { + auto zaddr = DecodePaymentAddress(address); + if (IsValidPaymentAddress(zaddr, branchId)) { + // Ignore listed z-addrs if we are using all of them + if (!(useAny || useAnyNote)) { + zaddrs.insert(zaddr); + } + // Check if z-addr is Sapling + bool isSapling = boost::get(&zaddr) != nullptr; + containsSaplingZaddrSource |= isSapling; + } else { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + string("Invalid parameter, unknown address format: ") + address); + } + } + } + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ") + address); + setAddress.insert(address); + } + if ( address != "*" ) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("z_migrate can only have * as input array ")); + + + // Validate the destination address + auto destaddress = params[1].get_str(); + bool isToZaddr = false; + bool isToSaplingZaddr = false; + CTxDestination taddr = DecodeDestination(destaddress); + if (!IsValidDestination(taddr)) { + if (IsValidPaymentAddressString(destaddress, branchId)) { + isToZaddr = true; + + // Is this a Sapling address? + auto res = DecodePaymentAddress(destaddress); + if (IsValidPaymentAddress(res)) { + isToSaplingZaddr = boost::get(&res) != nullptr; + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); + } + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); + } + } + //else if ( ASSETCHAINS_PRIVATE != 0 ) + // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cant use transparent addresses in private chain"); + + // Convert fee from currency format to zatoshis + CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE; + if (params.size() > 2) { + if (params[2].get_real() == 0.0) { + nFee = 0; + } else { + nFee = AmountFromValue( params[2] ); + } + } + + int nUTXOLimit = MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT; + if (params.size() > 3) { + nUTXOLimit = params[3].get_int(); + if (nUTXOLimit < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of UTXOs cannot be negative"); + } + } + + int nNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT; + if (params.size() > 4) { + nNoteLimit = params[4].get_int(); + if (nNoteLimit < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative"); + } + } + + std::string memo; + if (params.size() > 5) { + memo = params[5].get_str(); + if (!isToZaddr) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not 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 )); + } + } + + MergeToAddressRecipient recipient(destaddress, memo); + + int nextBlockHeight = chainActive.Height() + 1; + bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); + unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; + if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { + max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; + } + + // This RPC does not support Sapling yet. + if (isToSaplingZaddr || containsSaplingZaddrSource) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling is not supported yet by z_mergetoadress"); + } + + // If this RPC does support Sapling... + // If Sapling is not active, do not allow sending from or sending to Sapling addresses. + if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { + if (isToSaplingZaddr || containsSaplingZaddrSource) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); + } + } + + // Prepare to get UTXOs and notes + std::vector utxoInputs; + std::vector noteInputs; + CAmount mergedUTXOValue = 0; + CAmount mergedNoteValue = 0; + CAmount remainingUTXOValue = 0; + CAmount remainingNoteValue = 0; +#ifdef __LP64__ + uint64_t utxoCounter = 0; + uint64_t noteCounter = 0; +#else + size_t utxoCounter = 0; + size_t noteCounter = 0; +#endif + bool maxedOutUTXOsFlag = false; + bool maxedOutNotesFlag = false; + size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0)); + + size_t estimatedTxSize = 200; // tx overhead + wiggle room + if (isToZaddr) { + estimatedTxSize += JOINSPLIT_SIZE; + } + + if (useAny || useAnyUTXO || taddrs.size() > 0) { + // Get available utxos + vector vecOutputs; + pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false); + + // Find unspent utxos and update estimated size + for (const COutput& out : vecOutputs) { + if (!out.fSpendable) { + continue; + } + + CTxDestination address; + if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { + continue; + } + // If taddr is not wildcard "*", filter utxos + if (taddrs.size() > 0 && !taddrs.count(address)) { + continue; + } + + utxoCounter++; + CAmount nValue = out.tx->vout[out.i].nValue; + + if (!maxedOutUTXOsFlag) { + size_t increase = (boost::get(&address) != nullptr) ? CTXIN_SPEND_P2SH_SIZE : CTXIN_SPEND_DUST_SIZE; + if (estimatedTxSize + increase >= max_tx_size || + (mempoolLimit > 0 && utxoCounter > mempoolLimit)) + { + maxedOutUTXOsFlag = true; + } else { + estimatedTxSize += increase; + COutPoint utxo(out.tx->GetHash(), out.i); + utxoInputs.emplace_back(utxo, nValue); + mergedUTXOValue += nValue; + } + } + + if (maxedOutUTXOsFlag) { + remainingUTXOValue += nValue; + } + } + } + + if (useAny || useAnyNote || zaddrs.size() > 0) { + // Get available notes + std::vector sproutEntries; + std::vector saplingEntries; + pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs); + + // Find unspent notes and update estimated size + for (CSproutNotePlaintextEntry& entry : sproutEntries) { + noteCounter++; + CAmount nValue = entry.plaintext.value(); + + if (!maxedOutNotesFlag) { + // If we haven't added any notes yet and the merge is to a + // z-address, we have already accounted for the first JoinSplit. + size_t increase = (noteInputs.empty() && !isToZaddr) || (noteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0; + if (estimatedTxSize + increase >= max_tx_size || + (nNoteLimit > 0 && noteCounter > nNoteLimit)) + { + maxedOutNotesFlag = true; + } else { + estimatedTxSize += increase; + auto zaddr = entry.address; + SproutSpendingKey zkey; + pwalletMain->GetSproutSpendingKey(zaddr, zkey); + noteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey); + mergedNoteValue += nValue; + } + } + + if (maxedOutNotesFlag) { + remainingNoteValue += nValue; + } + } + // TODO: Add Sapling support + } + +#ifdef __LP64__ + uint64_t numUtxos = utxoInputs.size(); //ca333 + uint64_t numNotes = noteInputs.size(); +#else + size_t numUtxos = utxoInputs.size(); + size_t numNotes = noteInputs.size(); +#endif + + + if (numUtxos == 0 && numNotes == 0) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge."); + } + + // Sanity check: Don't do anything if: + // - We only have one from address + // - It's equal to toaddress + // - The address only contains a single UTXO or note + if (setAddress.size() == 1 && setAddress.count(destaddress) && (numUtxos + numNotes) == 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Destination address is also the only source address, and all its funds are already merged."); + } + + CAmount mergedValue = mergedUTXOValue + mergedNoteValue; + if (mergedValue < nFee) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, + strprintf("Insufficient funds, have %s, which is less than miners fee %s", + FormatMoney(mergedValue), FormatMoney(nFee))); + } + + // Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee) + CAmount netAmount = mergedValue - nFee; + if (nFee > netAmount) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the net amount to be shielded %s", FormatMoney(nFee), FormatMoney(netAmount))); + } + + // Keep record of parameters in context object + UniValue contextInfo(UniValue::VOBJ); + contextInfo.push_back(Pair("fromaddresses", params[0])); + contextInfo.push_back(Pair("toaddress", params[1])); + contextInfo.push_back(Pair("fee", ValueFromAmount(nFee))); + + // Contextual transaction we will build on + CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction( + Params().GetConsensus(), + nextBlockHeight); + bool isShielded = numNotes > 0 || isToZaddr; + if (contextualTx.nVersion == 1 && isShielded) { + contextualTx.nVersion = 2; // Tx format should support vjoinsplit + } + + // Create operation and add to global queue + std::shared_ptr q = getAsyncRPCQueue(); + std::shared_ptr operation( + new AsyncRPCOperation_mergetoaddress(contextualTx, utxoInputs, noteInputs, recipient, nFee, contextInfo) ); + q->addOperation(operation); + AsyncRPCOperationId operationId = operation->getId(); + + // Return continuation information + UniValue o(UniValue::VOBJ); + o.push_back(Pair("remainingUTXOs", static_cast(utxoCounter - numUtxos))); + o.push_back(Pair("remainingTransparentValue", ValueFromAmount(remainingUTXOValue))); + o.push_back(Pair("remainingNotes", static_cast(noteCounter - numNotes))); + o.push_back(Pair("remainingShieldedValue", ValueFromAmount(remainingNoteValue))); + o.push_back(Pair("mergingUTXOs", static_cast(numUtxos))); + o.push_back(Pair("mergingTransparentValue", ValueFromAmount(mergedUTXOValue))); + o.push_back(Pair("mergingNotes", static_cast(numNotes))); + o.push_back(Pair("mergingShieldedValue", ValueFromAmount(mergedNoteValue))); + o.push_back(Pair("opid", operationId)); + return o; +} + UniValue z_listoperationids(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp))