From 0dce846d5a7956f7d1a588c07552a462c1a6759f Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 28 Dec 2018 12:24:48 +0300 Subject: [PATCH] Add an option to use specific address as a source of funds in protx rpc commands (otherwise use payoutAddress/operatorPayoutAddress) (#2581) * Add an option to use specific address as a source of funds in protx rpc commands * fix typo "must" * Always use payoutAddress (or operatorPayoutAddress) as a default if no fundAddress was specified * make sure we actually selected some inputs * fundAddress -> feeSourceAddress * Align help text with new logic * fix protx_update_service * fix protx_revoke * Add fRequireAllInputs to CCoinControl and use it in FundSpecialTx This avoids adding all inputs even though they are not needed. * Allow to specify empty operator payout address and simplify fee code in update_service DIP3 tests were failing to call "protx update_service" as it did not accept an empty "operatorPayoutAddress". This also simplifies handling of the feeSourceAddr. * Specify source for funds and fees in DIP3 tests --- qa/rpc-tests/dip3-deterministicmns.py | 25 +++- src/rpc/rpcevo.cpp | 178 +++++++++++++++++++++----- src/wallet/coincontrol.h | 5 +- src/wallet/wallet.cpp | 5 + 4 files changed, 171 insertions(+), 42 deletions(-) diff --git a/qa/rpc-tests/dip3-deterministicmns.py b/qa/rpc-tests/dip3-deterministicmns.py index 0ee75ae2d1..09ed892075 100755 --- a/qa/rpc-tests/dip3-deterministicmns.py +++ b/qa/rpc-tests/dip3-deterministicmns.py @@ -415,6 +415,7 @@ class DIP3Test(BitcoinTestFramework): mn.p2p_port = p2p_port(mn.idx) blsKey = node.bls('generate') + mn.fundsAddr = node.getnewaddress() mn.ownerAddr = node.getnewaddress() mn.operatorAddr = blsKey['public'] mn.votingAddr = mn.ownerAddr @@ -426,9 +427,11 @@ class DIP3Test(BitcoinTestFramework): # create a protx MN and also fund it (using collateral inside ProRegTx) def create_mn_protx_fund(self, node, idx, alias, legacy_mn_key=None): mn = self.create_mn_protx_base(node, idx, alias, legacy_mn_key=legacy_mn_key) + node.sendtoaddress(mn.fundsAddr, 1000.001) + mn.collateral_address = node.getnewaddress() - mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.collateral_address) + mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.collateral_address, mn.fundsAddr) mn.collateral_txid = mn.protx_hash mn.collateral_vout = -1 @@ -444,9 +447,11 @@ class DIP3Test(BitcoinTestFramework): # create a protx MN which refers to an existing collateral def create_mn_protx(self, node, idx, alias, collateral_txid, collateral_vout, legacy_mn_key=None): mn = self.create_mn_protx_base(node, idx, alias, legacy_mn_key=legacy_mn_key) + node.sendtoaddress(mn.fundsAddr, 0.001) + mn.rewards_address = node.getnewaddress() - mn.protx_hash = node.protx('register', collateral_txid, collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.rewards_address) + mn.protx_hash = node.protx('register', collateral_txid, collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.rewards_address, mn.fundsAddr) mn.collateral_txid = collateral_txid mn.collateral_vout = collateral_vout @@ -476,14 +481,16 @@ class DIP3Test(BitcoinTestFramework): return mn def update_mn_payee(self, mn, payee): - self.nodes[0].protx('update_registrar', mn.protx_hash, '', '', payee) + self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) + self.nodes[0].protx('update_registrar', mn.protx_hash, '', '', payee, mn.fundsAddr) self.nodes[0].generate(1) self.sync_all() info = self.nodes[0].protx('info', mn.protx_hash) assert(info['state']['payoutAddress'] == payee) def test_protx_update_service(self, mn): - self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.2:%d' % mn.p2p_port, mn.blsMnkey) + self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) + self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.2:%d' % mn.p2p_port, mn.blsMnkey, "", mn.fundsAddr) self.nodes[0].generate(1) self.sync_all() for node in self.nodes: @@ -493,7 +500,7 @@ class DIP3Test(BitcoinTestFramework): assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.p2p_port) # undo - self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.1:%d' % mn.p2p_port, mn.blsMnkey) + self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.1:%d' % mn.p2p_port, mn.blsMnkey, "", mn.fundsAddr) self.nodes[0].generate(1) def force_finish_mnsync(self, node): @@ -732,16 +739,20 @@ class DIP3Test(BitcoinTestFramework): def test_fail_create_protx(self, node): # Try to create ProTx (should still fail) + fund_address = node.getnewaddress() address = node.getnewaddress() + node.sendtoaddress(fund_address, 1000.001) # +0.001 for fees key = node.getnewaddress() blsKey = node.bls('generate') - assert_raises_jsonrpc(None, "bad-tx-type", node.protx, 'register_fund', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address) + assert_raises_jsonrpc(None, "bad-tx-type", node.protx, 'register_fund', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address, fund_address) def test_success_create_protx(self, node): + fund_address = node.getnewaddress() address = node.getnewaddress() + txid = node.sendtoaddress(fund_address, 1000.001) # +0.001 for fees key = node.getnewaddress() blsKey = node.bls('generate') - txid = node.protx('register_fund', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address) + node.protx('register_fund', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address, fund_address) rawtx = node.getrawtransaction(txid, 1) self.mine_double_spend(node, rawtx['vin'], address, use_mnmerkleroot_from_tip=True) self.sync_all() diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index e7d17f22e3..754763e39c 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -12,6 +12,7 @@ #include "validation.h" #ifdef ENABLE_WALLET +#include "wallet/coincontrol.h" #include "wallet/wallet.h" #endif//ENABLE_WALLET @@ -89,32 +90,69 @@ static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::str #ifdef ENABLE_WALLET template -static void FundSpecialTx(CMutableTransaction& tx, SpecialTxPayload payload) +static void FundSpecialTx(CMutableTransaction& tx, const SpecialTxPayload& payload, const CTxDestination& fundDest) { + assert(pwalletMain != NULL); + LOCK2(cs_main, pwalletMain->cs_wallet); + + CTxDestination nodest = CNoDestination(); + if (fundDest == nodest) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "No source of funds specified"); + } + CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); ds << payload; tx.vExtraPayload.assign(ds.begin(), ds.end()); static CTxOut dummyTxOut(0, CScript() << OP_RETURN); + std::vector vecSend; bool dummyTxOutAdded = false; + if (tx.vout.empty()) { - // add dummy txout as FundTransaction requires at least one output + // add dummy txout as CreateTransaction requires at least one recipient tx.vout.emplace_back(dummyTxOut); dummyTxOutAdded = true; } + for (const auto& txOut : tx.vout) { + CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false}; + vecSend.push_back(recipient); + } + + CCoinControl coinControl; + coinControl.destChange = fundDest; + coinControl.fRequireAllInputs = false; + + std::vector vecOutputs; + pwalletMain->AvailableCoins(vecOutputs); + + for (const auto& out : vecOutputs) { + CTxDestination txDest; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == fundDest) { + coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + } + } + + if (!coinControl.HasSelected()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "No funds at specified address"); + } + + CWalletTx wtx; + CReserveKey reservekey(pwalletMain); CAmount nFee; - CFeeRate feeRate = CFeeRate(0); int nChangePos = -1; std::string strFailReason; - std::set setSubtractFeeFromOutputs; - if (!pwalletMain->FundTransaction(tx, nFee, false, feeRate, nChangePos, strFailReason, false, false, setSubtractFeeFromOutputs, true, CNoDestination())) { + + if (!pwalletMain->CreateTransaction(vecSend, wtx, reservekey, nFee, nChangePos, strFailReason, &coinControl, false, ALL_COINS, false, tx.vExtraPayload.size())) { throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); } + tx.vin = wtx.tx->vin; + tx.vout = wtx.tx->vout; + if (dummyTxOutAdded && tx.vout.size() > 1) { - // FundTransaction added a change output, so we don't need the dummy txout anymore - // Removing it results in slight overpayment of fees, but we ignore this for now (as it's a very low amount) + // CreateTransaction added a change output, so we don't need the dummy txout anymore. + // Removing it results in slight overpayment of fees, but we ignore this for now (as it's a very low amount). auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut); assert(it != tx.vout.end()); tx.vout.erase(it); @@ -186,7 +224,7 @@ static std::string SignAndSendSpecialTx(const CMutableTransaction& tx) void protx_register_fund_help() { throw std::runtime_error( - "protx register_fund \"collateralAddress\" \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" + "protx register_fund \"collateralAddress\" \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\" ( \"fundAddress\" )\n" "\nCreates, funds and sends a ProTx to the network. The resulting transaction will move 1000 Dash\n" "to the address specified by collateralAddress and will then function as the collateral of your\n" "masternode.\n" @@ -198,7 +236,7 @@ void protx_register_fund_help() "2. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n" " Must be unique on the network. Can be set to 0, which will require a ProUpServTx afterwards.\n" "3. \"ownerKeyAddr\" (string, required) The owner key used for payee updates and proposal voting.\n" - " The private key belonging to this address be known in your wallet. The address must\n" + " The private key belonging to this address must be known in your wallet. The address must\n" " be unused and must differ from the collateralAddress\n" "4. \"operatorPubKey\" (string, required) The operator BLS public key. The private key does not have to be known.\n" " It has to match the private key which is later used when operating the masternode.\n" @@ -207,7 +245,10 @@ void protx_register_fund_help() " If set to \"0\" or an empty string, ownerAddr will be used.\n" "6. \"operatorReward\" (numeric, required) The fraction in % to share with the operator. The value must be\n" " between 0.00 and 100.00.\n" - "7. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments." + "7. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments.\n" + "8. \"fundAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\n" + " If not specified, payoutAddress is the one that is going to be used.\n" + " The private key belonging to this address must be known in your wallet.\n" "\nExamples:\n" + HelpExampleCli("protx", "register_fund \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"") ); @@ -216,7 +257,7 @@ void protx_register_fund_help() void protx_register_help() { throw std::runtime_error( - "protx register \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" + "protx register \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\" ( \"feeSourceAddress\" )\n" "\nSame as \"protx register_fund\", but with an externally referenced collateral.\n" "The collateral is specified through \"collateralHash\" and \"collateralIndex\" and must be an unspent\n" "transaction output. It must also not be used by any other masternode.\n" @@ -224,6 +265,7 @@ void protx_register_help() "1. \"collateralHash\" (string, required) The collateral transaction hash.\n" "2. collateralIndex (numeric, required) The collateral transaction output index.\n" "3., 4., 5. ... See help text of \"protx register_fund\"\n" + "feeSourceAddress See help text for \"fundAddress\" of \"protx register_fund\"\n" "\nExamples:\n" + HelpExampleCli("protx", "register \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"") ); @@ -232,12 +274,13 @@ void protx_register_help() void protx_register_prepare_help() { throw std::runtime_error( - "protx register_prepare \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n" + "protx register_prepare \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\" ( \"feeSourceAddress\" )\n" "\nCreates an unsigned ProTx and returns it. The ProTx must be signed externally with the collateral\n" "key and then passed to \"protx register_submit\". The prepared transaction will also contain inputs\n" "and outputs to cover fees.\n" "\nArguments:\n" "1., 2., 3., ... See help text of \"protx register\".\n" + "feeSourceAddress See help text for \"fundAddress\" of \"protx register_fund\"\n" "\nResult:\n" "{ (json object)\n" " \"tx\" : (string) The serialized ProTx in hex format.\n" @@ -271,11 +314,11 @@ UniValue protx_register(const JSONRPCRequest& request) bool isFundRegister = request.params[0].get_str() == "register_fund"; bool isPrepareRegister = request.params[0].get_str() == "register_prepare"; - if (isFundRegister && (request.fHelp || request.params.size() != 8)) { + if (isFundRegister && (request.fHelp || (request.params.size() != 8 && request.params.size() != 9))) { protx_register_fund_help(); - } else if (isExternalRegister && (request.fHelp || request.params.size() != 9)) { + } else if (isExternalRegister && (request.fHelp || (request.params.size() != 9 && request.params.size() != 10))) { protx_register_help(); - } else if (isPrepareRegister && (request.fHelp || request.params.size() != 9)) { + } else if (isPrepareRegister && (request.fHelp || (request.params.size() != 9 && request.params.size() != 10))) { protx_register_prepare_help(); } @@ -353,7 +396,14 @@ UniValue protx_register(const JSONRPCRequest& request) ptx.vchSig.resize(65); } - FundSpecialTx(tx, ptx); + CBitcoinAddress fundAddress = payoutAddress; + if (request.params.size() > paramIdx + 6) { + fundAddress = CBitcoinAddress(request.params[paramIdx + 6].get_str()); + if (!fundAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 6].get_str()); + } + + FundSpecialTx(tx, ptx, fundAddress.Get()); UpdateSpecialTxInputsHash(tx, ptx); if (isFundRegister) { @@ -435,7 +485,7 @@ UniValue protx_register_submit(const JSONRPCRequest& request) void protx_update_service_help() { throw std::runtime_error( - "protx update_service \"proTxHash\" \"ipAndPort\" \"operatorKey\" (\"operatorPayoutAddress\")\n" + "protx update_service \"proTxHash\" \"ipAndPort\" \"operatorKey\" (\"operatorPayoutAddress\" \"feeSourceAddress\" )\n" "\nCreates and sends a ProUpServTx to the network. This will update the IP address\n" "of a masternode.\n" "If this is done for a masternode that got PoSe-banned, the ProUpServTx will also revive this masternode.\n" @@ -447,6 +497,9 @@ void protx_update_service_help() " registered operator public key.\n" "4. \"operatorPayoutAddress\" (string, optional) The address used for operator reward payments.\n" " Only allowed when the ProRegTx had a non-zero operatorReward value.\n" + "5. \"feeSourceAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\n" + " If not specified, operatorPayoutAddress is the one that is going to be used.\n" + " The private key belonging to this address must be known in your wallet.\n" "\nExamples:\n" + HelpExampleCli("protx", "update_service \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" 5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd") ); @@ -454,7 +507,7 @@ void protx_update_service_help() UniValue protx_update_service(const JSONRPCRequest& request) { - if (request.fHelp || (request.params.size() != 4 && request.params.size() != 5)) + if (request.fHelp || (request.params.size() < 4 || request.params.size() > 6)) protx_update_service_help(); CProUpServTx ptx; @@ -467,14 +520,6 @@ UniValue protx_update_service(const JSONRPCRequest& request) CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[3].get_str(), "operatorKey"); - if (request.params.size() > 4) { - CBitcoinAddress payoutAddress(request.params[4].get_str()); - if (!payoutAddress.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[4].get_str())); - } - ptx.scriptOperatorPayout = GetScriptForDestination(payoutAddress.Get()); - } - auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(ptx.proTxHash); if (!dmn) { throw std::runtime_error(strprintf("masternode with proTxHash %s not found", ptx.proTxHash.ToString())); @@ -488,7 +533,41 @@ UniValue protx_update_service(const JSONRPCRequest& request) tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE; - FundSpecialTx(tx, ptx); + // param operatorPayoutAddress + if (request.params.size() >= 5) { + if (request.params[4].get_str().empty() || request.params[4].get_str() == "0") { + ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout; + } else { + CBitcoinAddress payoutAddress(request.params[4].get_str()); + if (!payoutAddress.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[4].get_str())); + } + ptx.scriptOperatorPayout = GetScriptForDestination(payoutAddress.Get()); + } + } else { + ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout; + } + + CTxDestination feeSource; + + // param feeSourceAddress + if (request.params.size() >= 6) { + CBitcoinAddress feeSourceAddress = CBitcoinAddress(request.params[5].get_str()); + if (!feeSourceAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[5].get_str()); + feeSource = feeSourceAddress.Get(); + } else { + if (ptx.scriptOperatorPayout != CScript()) { + // use operator reward address as default source for fees + ExtractDestination(ptx.scriptOperatorPayout, feeSource); + } else { + // use payout address as default source for fees + ExtractDestination(dmn->pdmnState->scriptPayout, feeSource); + } + } + + FundSpecialTx(tx, ptx, feeSource); + SignSpecialTxPayloadByHash(tx, ptx, keyOperator); SetTxPayload(tx, ptx); @@ -498,7 +577,7 @@ UniValue protx_update_service(const JSONRPCRequest& request) void protx_update_registrar_help() { throw std::runtime_error( - "protx update_registrar \"proTxHash\" \"operatorKeyAddr\" \"votingKeyAddr\" \"payoutAddress\"\n" + "protx update_registrar \"proTxHash\" \"operatorKeyAddr\" \"votingKeyAddr\" \"payoutAddress\" ( \"feeSourceAddress\" )\n" "\nCreates and sends a ProUpRegTx to the network. This will update the operator key, voting key and payout\n" "address of the masternode specified by \"proTxHash\".\n" "The owner key of the masternode must be known to your wallet.\n" @@ -512,6 +591,9 @@ void protx_update_registrar_help() " If set to \"0\" or an empty string, the last on-chain voting key of the masternode will be used.\n" "4. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n" " If set to \"0\" or an empty string, the last on-chain payout address of the masternode will be used.\n" + "5. \"feeSourceAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\n" + " If not specified, payoutAddress is the one that is going to be used.\n" + " The private key belonging to this address must be known in your wallet.\n" "\nExamples:\n" + HelpExampleCli("protx", "update_registrar \"0123456701234567012345670123456701234567012345670123456701234567\" \"982eb34b7c7f614f29e5c665bc3605f1beeef85e3395ca12d3be49d2868ecfea5566f11cedfad30c51b2403f2ad95b67\" \"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwG\"") ); @@ -519,7 +601,7 @@ void protx_update_registrar_help() UniValue protx_update_registrar(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 5) { + if (request.fHelp || (request.params.size() != 5 && request.params.size() != 6)) { protx_update_registrar_help(); } @@ -560,7 +642,14 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) // make sure we get anough fees added ptx.vchSig.resize(65); - FundSpecialTx(tx, ptx); + CBitcoinAddress feeSourceAddress = payoutAddress; + if (request.params.size() > 5) { + feeSourceAddress = CBitcoinAddress(request.params[5].get_str()); + if (!feeSourceAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[5].get_str()); + } + + FundSpecialTx(tx, ptx, feeSourceAddress.Get()); SignSpecialTxPayloadByHash(tx, ptx, keyOwner); SetTxPayload(tx, ptx); @@ -570,7 +659,7 @@ UniValue protx_update_registrar(const JSONRPCRequest& request) void protx_revoke_help() { throw std::runtime_error( - "protx revoke \"proTxHash\" \"operatorKey\" ( reason )\n" + "protx revoke \"proTxHash\" \"operatorKey\" ( reason \"feeSourceAddress\")\n" "\nCreates and sends a ProUpRevTx to the network. This will revoke the operator key of the masternode and\n" "put it into the PoSe-banned state. It will also set the service field of the masternode\n" "to zero. Use this in case your operator key got compromised or you want to stop providing your service\n" @@ -579,7 +668,10 @@ void protx_revoke_help() "1. \"proTxHash\" (string, required) The hash of the initial ProRegTx.\n" "2. \"operatorKey\" (string, required) The operator private key belonging to the\n" " registered operator public key.\n" - "3. reason (numeric, optional) The reason for revocation.\n" + "3. reason (numeric, optional) The reason for revocation.\n" + "4. \"feeSourceAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\n" + " If not specified, operatorPayoutAddress specified earlier is the one that is going to be used.\n" + " The private key belonging to this address must be known in your wallet.\n" "\nExamples:\n" + HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"") ); @@ -587,7 +679,7 @@ void protx_revoke_help() UniValue protx_revoke(const JSONRPCRequest& request) { - if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4)) { + if (request.fHelp || (request.params.size() < 3 || request.params.size() > 5)) { protx_revoke_help(); } @@ -618,7 +710,25 @@ UniValue protx_revoke(const JSONRPCRequest& request) tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE; - FundSpecialTx(tx, ptx); + if (request.params.size() > 4) { + CBitcoinAddress feeSourceAddress = CBitcoinAddress(request.params[4].get_str()); + if (!feeSourceAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str()); + FundSpecialTx(tx, ptx, feeSourceAddress.Get()); + } else if (dmn->pdmnState->scriptOperatorPayout != CScript()) { + // Using funds from previousely specified operator payout address + CTxDestination txDest; + ExtractDestination(dmn->pdmnState->scriptOperatorPayout, txDest); + FundSpecialTx(tx, ptx, txDest); + } else if (dmn->pdmnState->scriptPayout != CScript()) { + // Using funds from previousely specified masternode payout address + CTxDestination txDest; + ExtractDestination(dmn->pdmnState->scriptPayout, txDest); + FundSpecialTx(tx, ptx, txDest); + } else { + throw JSONRPCError(RPC_INTERNAL_ERROR, "No payout or fee source addresses found, can't revoke"); + } + SignSpecialTxPayloadByHash(tx, ptx, keyOperator); SetTxPayload(tx, ptx); diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 50469dc725..2d06635f61 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -14,8 +14,10 @@ public: CTxDestination destChange; bool fUsePrivateSend; bool fUseInstantSend; - //! If false, allows unselected inputs, but requires all selected inputs be used + //! If false, allows unselected inputs, but requires all selected inputs be used if fAllowOtherInputs is true (default) bool fAllowOtherInputs; + //! If false, only include as many inputs as necessary to fulfill a coin selection request. Only usable together with fAllowOtherInputs + bool fRequireAllInputs; //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria bool fAllowWatchOnly; //! Minimum absolute fee (not per kilobyte) @@ -36,6 +38,7 @@ public: { destChange = CNoDestination(); fAllowOtherInputs = false; + fRequireAllInputs = true; fAllowWatchOnly = false; setSelected.clear(); fUseInstantSend = false; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f1bb0b4cce..553a912d7b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2889,6 +2889,11 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm } nValueRet += out.tx->tx->vout[out.i].nValue; setCoinsRet.insert(std::make_pair(out.tx, out.i)); + + if (!coinControl->fRequireAllInputs && nValueRet >= nTargetValue) { + // stop when we added at least one input and enough inputs to have at least nTargetValue funds + return true; + } } return (nValueRet >= nTargetValue);