Skip to content
Merged
128 changes: 103 additions & 25 deletions src/rpc/rpcevo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "validation.h"

#ifdef ENABLE_WALLET
#include "wallet/coincontrol.h"
#include "wallet/wallet.h"
#endif//ENABLE_WALLET

Expand Down Expand Up @@ -89,32 +90,64 @@ static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::str
#ifdef ENABLE_WALLET

template<typename SpecialTxPayload>
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<CRecipient> 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;

std::vector<COutput> 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));
}
}

CWalletTx wtx;
CReserveKey reservekey(pwalletMain);
CAmount nFee;
CFeeRate feeRate = CFeeRate(0);
int nChangePos = -1;
std::string strFailReason;
std::set<int> 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);
Expand Down Expand Up @@ -186,7 +219,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"
Expand All @@ -198,7 +231,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"
Expand All @@ -207,7 +240,9 @@ 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"
" 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\"")
);
Expand All @@ -216,7 +251,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\" ( \"fundAddress\" )\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"
Expand All @@ -232,7 +267,7 @@ 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\" ( \"fundAddress\" )\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"
Expand Down Expand Up @@ -271,11 +306,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();
}

Expand Down Expand Up @@ -353,7 +388,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) {
Expand Down Expand Up @@ -435,7 +477,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\" \"fundAddress\" )\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"
Expand All @@ -447,14 +489,16 @@ 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. \"fundAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\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")
);
}

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;
Expand Down Expand Up @@ -488,7 +532,14 @@ UniValue protx_update_service(const JSONRPCRequest& request)
tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;

FundSpecialTx(tx, ptx);
CBitcoinAddress fundAddress = payoutAddress;
if (request.params.size() > 5) {
fundAddress = CBitcoinAddress(request.params[5].get_str());
if (!fundAddress.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[5].get_str());
}

FundSpecialTx(tx, ptx, fundAddress.Get());
SignSpecialTxPayloadByHash(tx, ptx, keyOperator);
SetTxPayload(tx, ptx);

Expand All @@ -498,7 +549,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\" ( \"fundAddress\" )\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"
Expand All @@ -512,14 +563,16 @@ 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. \"fundAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\n"
" The private key belonging to this address must be known in your wallet.\n"
"\nExamples:\n"
+ HelpExampleCli("protx", "update_registrar \"0123456701234567012345670123456701234567012345670123456701234567\" \"982eb34b7c7f614f29e5c665bc3605f1beeef85e3395ca12d3be49d2868ecfea5566f11cedfad30c51b2403f2ad95b67\" \"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwG\"")
);
}

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();
}

Expand Down Expand Up @@ -560,7 +613,14 @@ UniValue protx_update_registrar(const JSONRPCRequest& request)
// make sure we get anough fees added
ptx.vchSig.resize(65);

FundSpecialTx(tx, ptx);
CBitcoinAddress fundAddress = payoutAddress;
if (request.params.size() > 5) {
fundAddress = CBitcoinAddress(request.params[5].get_str());
if (!fundAddress.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[5].get_str());
}

FundSpecialTx(tx, ptx, fundAddress.Get());
SignSpecialTxPayloadByHash(tx, ptx, keyOwner);
SetTxPayload(tx, ptx);

Expand All @@ -570,7 +630,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 \"fundAddress\")\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"
Expand All @@ -580,14 +640,16 @@ void protx_revoke_help()
"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"
"4. \"fundAddress\" (string, optional) If specified wallet will only use coins from this address to fund ProTx.\n"
" The private key belonging to this address must be known in your wallet.\n"
"\nExamples:\n"
+ HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"")
);
}

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();
}

Expand Down Expand Up @@ -618,7 +680,23 @@ UniValue protx_revoke(const JSONRPCRequest& request)
tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;

FundSpecialTx(tx, ptx);
CBitcoinAddress fundAddress;
if (request.params.size() > 4) {
fundAddress = CBitcoinAddress(request.params[4].get_str());
if (!fundAddress.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str());
FundSpecialTx(tx, ptx, fundAddress.Get());
} else {
// Using funds in previousely specified operator payout address
if (dmn->pdmnState->scriptOperatorPayout == CScript()) {
// No scriptOperatorPayout was specified, can't revoke
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "scriptOperatorPayout was not specified earlier, can't revoke");
}
CTxDestination txDest;
ExtractDestination(dmn->pdmnState->scriptOperatorPayout, txDest);
FundSpecialTx(tx, ptx, txDest);
}

SignSpecialTxPayloadByHash(tx, ptx, keyOperator);
SetTxPayload(tx, ptx);

Expand Down