mirror of
https://github.com/dashpay/dash.git
synced 2024-12-30 14:25:53 +01:00
e3df910822
* Pass CCoinsView reference to special TX handling methods
* Allow referencing other TX outputs for ProRegTx collateral
* Remove "collateralAmount" from "protx register"
* Rename "protx register" to "protx fund_register"
* Remove UpdateSpork15Value from CDeterministicMNManager
Was not used/implemented anymore
* Lock masternode collaterals after chain/DIP3 is fully initialized
Otherwise detection of collaterals does not work.
* Implement new "protx register" RPC which uses existing collaterals
* Remove "masternode info" RPC
It is not consistent with other "masternode" RPCs anymore as it requires
the ProRegTx hash while all other RPCs work with the collateral.
* Load sporks from disk cache before initializing the chain
Otherwise spork15 is not loaded when we check it for the first time.
* Implement "protx info" RPC
* Use "protx info" instead of "masternode info" in DIP3 tests
* Test external collaterals for ProTx
* Handle review comments
* Don't pass CCoinView reference when it's not used
* Revert "Pass CCoinsView reference to special TX handling methods"
This reverts commit 28688724e1
.
* Use GetUTXOCoin instead of now removed coinsView
Also remove collateral height check as GetUTXOCoin only returns confirmed
coins.
* Add conflict handling for external collaterals to mempool
* Handle review comments (squashed Github suggestions)
Co-Authored-By: codablock <ablock84@gmail.com>
806 lines
35 KiB
C++
806 lines
35 KiB
C++
// Copyright (c) 2018 The Dash Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "base58.h"
|
|
#include "consensus/validation.h"
|
|
#include "core_io.h"
|
|
#include "init.h"
|
|
#include "messagesigner.h"
|
|
#include "rpc/server.h"
|
|
#include "utilmoneystr.h"
|
|
#include "validation.h"
|
|
|
|
#ifdef ENABLE_WALLET
|
|
#include "wallet/wallet.h"
|
|
#endif//ENABLE_WALLET
|
|
|
|
#include "netbase.h"
|
|
|
|
#include "evo/specialtx.h"
|
|
#include "evo/providertx.h"
|
|
#include "evo/deterministicmns.h"
|
|
#include "evo/simplifiedmns.h"
|
|
|
|
#include "bls/bls.h"
|
|
|
|
#ifdef ENABLE_WALLET
|
|
extern UniValue signrawtransaction(const JSONRPCRequest& request);
|
|
extern UniValue sendrawtransaction(const JSONRPCRequest& request);
|
|
#endif//ENABLE_WALLET
|
|
|
|
// Allows to specify Dash address or priv key. In case of Dash address, the priv key is taken from the wallet
|
|
static CKey ParsePrivKey(const std::string &strKeyOrAddress, bool allowAddresses = true) {
|
|
CBitcoinAddress address;
|
|
if (allowAddresses && address.SetString(strKeyOrAddress) && address.IsValid()) {
|
|
#ifdef ENABLE_WALLET
|
|
CKeyID keyId;
|
|
CKey key;
|
|
if (!address.GetKeyID(keyId) || !pwalletMain->GetKey(keyId, key))
|
|
throw std::runtime_error(strprintf("non-wallet or invalid address %s", strKeyOrAddress));
|
|
return key;
|
|
#else//ENABLE_WALLET
|
|
throw std::runtime_error("addresses not supported in no-wallet builds");
|
|
#endif//ENABLE_WALLET
|
|
}
|
|
|
|
CBitcoinSecret secret;
|
|
if (!secret.SetString(strKeyOrAddress) || !secret.IsValid())
|
|
throw std::runtime_error(strprintf("invalid priv-key/address %s", strKeyOrAddress));
|
|
return secret.GetKey();
|
|
}
|
|
|
|
static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress, const std::string& paramName)
|
|
{
|
|
CBitcoinAddress address(strAddress);
|
|
CKeyID keyID;
|
|
if (!address.IsValid() || !address.GetKeyID(keyID))
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid P2PKH address, not %s", paramName, strAddress));
|
|
return keyID;
|
|
}
|
|
|
|
static CBLSPublicKey ParseBLSPubKey(const std::string& hexKey, const std::string& paramName)
|
|
{
|
|
auto binKey = ParseHex(hexKey);
|
|
CBLSPublicKey pubKey;
|
|
pubKey.SetBuf(binKey);
|
|
if (!pubKey.IsValid()) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid BLS address, not %s", paramName, hexKey));
|
|
}
|
|
return pubKey;
|
|
}
|
|
|
|
static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::string& paramName)
|
|
{
|
|
auto binKey = ParseHex(hexKey);
|
|
CBLSSecretKey secKey;
|
|
secKey.SetBuf(binKey);
|
|
if (!secKey.IsValid()) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid BLS secret key", paramName));
|
|
}
|
|
return secKey;
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
|
|
template<typename SpecialTxPayload>
|
|
static void FundSpecialTx(CMutableTransaction& tx, SpecialTxPayload payload)
|
|
{
|
|
CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
|
|
ds << payload;
|
|
tx.vExtraPayload.assign(ds.begin(), ds.end());
|
|
|
|
static CTxOut dummyTxOut(0, CScript() << OP_RETURN);
|
|
bool dummyTxOutAdded = false;
|
|
if (tx.vout.empty()) {
|
|
// add dummy txout as FundTransaction requires at least one output
|
|
tx.vout.emplace_back(dummyTxOut);
|
|
dummyTxOutAdded = true;
|
|
}
|
|
|
|
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()))
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
|
|
|
|
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)
|
|
auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut);
|
|
assert(it != tx.vout.end());
|
|
tx.vout.erase(it);
|
|
}
|
|
}
|
|
|
|
template<typename SpecialTxPayload>
|
|
static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload)
|
|
{
|
|
payload.inputsHash = CalcTxInputsHash(tx);
|
|
}
|
|
|
|
template<typename SpecialTxPayload>
|
|
static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key)
|
|
{
|
|
UpdateSpecialTxInputsHash(tx, payload);
|
|
payload.vchSig.clear();
|
|
|
|
uint256 hash = ::SerializeHash(payload);
|
|
if (!CHashSigner::SignHash(hash, key, payload.vchSig)) {
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx");
|
|
}
|
|
}
|
|
|
|
template<typename SpecialTxPayload>
|
|
static void SignSpecialTxPayload(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key)
|
|
{
|
|
UpdateSpecialTxInputsHash(tx, payload);
|
|
|
|
uint256 hash = ::SerializeHash(payload);
|
|
payload.sig = key.Sign(hash);
|
|
}
|
|
|
|
static std::string SignAndSendSpecialTx(const CMutableTransaction& tx)
|
|
{
|
|
LOCK(cs_main);
|
|
|
|
CValidationState state;
|
|
if (!CheckSpecialTx(tx, chainActive.Tip(), state))
|
|
throw std::runtime_error(FormatStateMessage(state));
|
|
|
|
CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
|
|
ds << tx;
|
|
|
|
JSONRPCRequest signReqeust;
|
|
signReqeust.params.setArray();
|
|
signReqeust.params.push_back(HexStr(ds.begin(), ds.end()));
|
|
UniValue signResult = signrawtransaction(signReqeust);
|
|
|
|
JSONRPCRequest sendRequest;
|
|
sendRequest.params.setArray();
|
|
sendRequest.params.push_back(signResult["hex"].get_str());
|
|
return sendrawtransaction(sendRequest).get_str();
|
|
}
|
|
|
|
void protx_fund_register_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx fund_register \"collateralAddress\" \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\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"
|
|
"A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\n"
|
|
"is fully deployed.\n"
|
|
"\nArguments:\n"
|
|
"1. \"collateralAddress\" (string, required) The dash address to send the collateral to.\n"
|
|
" Must be a P2PKH address.\n"
|
|
"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"
|
|
" be unused and must differ from the collateralAddress\n"
|
|
"4. \"operatorKeyAddr\" (string, required) The operator key address. The private key does not have to be known by your wallet.\n"
|
|
" It has to match the private key which is later used when operating the masternode.\n"
|
|
" If set to \"0\" or an empty string, ownerAddr will be used.\n"
|
|
"5. \"votingKeyAddr\" (string, required) The voting key address. The private key does not have to be known by your wallet.\n"
|
|
" It has to match the private key which is later used when voting on proposals.\n"
|
|
" 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. If non-zero,\n"
|
|
" \"ipAndPort\" must be zero as well. The value must be between 0 and 100.\n"
|
|
"7. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n"
|
|
" Must match \"collateralAddress\"."
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("protx", "fund_register \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"")
|
|
);
|
|
}
|
|
|
|
void protx_register_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx register \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n"
|
|
"\nSame as \"protx fund_register\", 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"
|
|
"\nArguments:\n"
|
|
"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 fund_register\"\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("protx", "register \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"")
|
|
);
|
|
}
|
|
|
|
// handles register and fund_register in one method
|
|
UniValue protx_register(const JSONRPCRequest& request)
|
|
{
|
|
bool isFundRegister = request.params[0].get_str() == "fund_register";
|
|
|
|
if (isFundRegister && (request.fHelp || request.params.size() != 8)) {
|
|
protx_fund_register_help();
|
|
} else if (!isFundRegister && (request.fHelp || request.params.size() != 9)) {
|
|
protx_register_help();
|
|
}
|
|
|
|
size_t paramIdx = 1;
|
|
|
|
CAmount collateralAmount = 1000 * COIN;
|
|
|
|
CMutableTransaction tx;
|
|
tx.nVersion = 3;
|
|
tx.nType = TRANSACTION_PROVIDER_REGISTER;
|
|
|
|
CProRegTx ptx;
|
|
ptx.nVersion = CProRegTx::CURRENT_VERSION;
|
|
|
|
if (isFundRegister) {
|
|
CBitcoinAddress collateralAddress(request.params[paramIdx].get_str());
|
|
if (!collateralAddress.IsValid())
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[paramIdx].get_str()));
|
|
CScript collateralScript = GetScriptForDestination(collateralAddress.Get());
|
|
|
|
CTxOut collateralTxOut(collateralAmount, collateralScript);
|
|
tx.vout.emplace_back(collateralTxOut);
|
|
|
|
paramIdx++;
|
|
} else {
|
|
uint256 collateralHash = ParseHashV(request.params[paramIdx], "collateralHash");
|
|
int32_t collateralIndex = ParseInt32V(request.params[paramIdx + 1], "collateralIndex");
|
|
if (collateralHash.IsNull() || collateralIndex < 0) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex));
|
|
}
|
|
|
|
ptx.collateralOutpoint = COutPoint(collateralHash, (uint32_t)collateralIndex);
|
|
paramIdx += 2;
|
|
|
|
// TODO unlock on failure
|
|
LOCK(pwalletMain->cs_wallet);
|
|
pwalletMain->LockCoin(ptx.collateralOutpoint);
|
|
}
|
|
|
|
if (request.params[paramIdx].get_str() != "0" && request.params[paramIdx].get_str() != "") {
|
|
if (!Lookup(request.params[paramIdx].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false))
|
|
throw std::runtime_error(strprintf("invalid network address %s", request.params[paramIdx].get_str()));
|
|
}
|
|
|
|
CKey keyOwner = ParsePrivKey(request.params[paramIdx + 1].get_str(), true);
|
|
CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address");
|
|
CKeyID keyIDVoting = keyOwner.GetPubKey().GetID();
|
|
if (request.params[paramIdx + 3].get_str() != "0" && request.params[paramIdx + 3].get_str() != "") {
|
|
keyIDVoting = ParsePubKeyIDFromAddress(request.params[paramIdx + 3].get_str(), "voting address");
|
|
}
|
|
|
|
double operatorReward = ParseDoubleV(request.params[paramIdx + 4], "operatorReward");
|
|
if (operatorReward < 0 || operatorReward > 100)
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 100");
|
|
ptx.nOperatorReward = (uint16_t)(operatorReward * 100);
|
|
|
|
CBitcoinAddress payoutAddress(request.params[paramIdx + 5].get_str());
|
|
if (!payoutAddress.IsValid())
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str()));
|
|
|
|
ptx.keyIDOwner = keyOwner.GetPubKey().GetID();
|
|
ptx.pubKeyOperator = pubKeyOperator;
|
|
ptx.keyIDVoting = keyIDVoting;
|
|
ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get());
|
|
|
|
if (!isFundRegister) {
|
|
// make sure fee calculation works
|
|
ptx.vchSig.resize(65);
|
|
}
|
|
|
|
UpdateSpecialTxInputsHash(tx, ptx);
|
|
FundSpecialTx(tx, ptx);
|
|
|
|
if (isFundRegister) {
|
|
uint32_t collateralIndex = (uint32_t) -1;
|
|
for (uint32_t i = 0; i < tx.vout.size(); i++) {
|
|
if (tx.vout[i].nValue == collateralAmount) {
|
|
collateralIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
assert(collateralIndex != (uint32_t) -1);
|
|
ptx.collateralOutpoint.n = collateralIndex;
|
|
}
|
|
|
|
if (isFundRegister) {
|
|
UpdateSpecialTxInputsHash(tx, ptx);
|
|
} else {
|
|
// referencing external collateral, so lets prove we own it
|
|
|
|
Coin coin;
|
|
if (!GetUTXOCoin(ptx.collateralOutpoint, coin)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral not found: %s", ptx.collateralOutpoint.ToStringShort()));
|
|
}
|
|
CTxDestination txDest;
|
|
CKeyID keyID;
|
|
if (!ExtractDestination(coin.out.scriptPubKey, txDest) || !CBitcoinAddress(txDest).GetKeyID(keyID)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral type not supported: %s", ptx.collateralOutpoint.ToStringShort()));
|
|
}
|
|
CKey key;
|
|
if (!pwalletMain->GetKey(keyID, key)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral key not in wallet: %s", CBitcoinAddress(keyID).ToString()));
|
|
}
|
|
SignSpecialTxPayload(tx, ptx, key);
|
|
}
|
|
SetTxPayload(tx, ptx);
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
}
|
|
|
|
void protx_update_service_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx update_service \"proTxHash\" \"ipAndPort\" \"operatorKey\" (\"operatorPayoutAddress\")\n"
|
|
"\nCreates and sends a ProUpServTx to the network. This will update the IP address\n"
|
|
"of a masternode. The operator key of the masternode must be known to your wallet.\n"
|
|
"If this is done for a masternode that got PoSe-banned, the ProUpServTx will also revive this masternode.\n"
|
|
"\nArguments:\n"
|
|
"1. \"proTxHash\" (string, required) The hash of the initial ProRegTx.\n"
|
|
"2. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n"
|
|
" Must be unique on the network.\n"
|
|
"3. \"operatorKey\" (string, required) The operator private key belonging to the\n"
|
|
" 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"
|
|
"\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() != 3 && request.params.size() != 4))
|
|
protx_update_service_help();
|
|
|
|
CProUpServTx ptx;
|
|
ptx.nVersion = CProRegTx::CURRENT_VERSION;
|
|
ptx.proTxHash = ParseHashV(request.params[1], "proTxHash");
|
|
|
|
if (!Lookup(request.params[2].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) {
|
|
throw std::runtime_error(strprintf("invalid network address %s", request.params[3].get_str()));
|
|
}
|
|
|
|
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()));
|
|
}
|
|
|
|
if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
|
|
}
|
|
|
|
CMutableTransaction tx;
|
|
tx.nVersion = 3;
|
|
tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
|
|
|
|
FundSpecialTx(tx, ptx);
|
|
SignSpecialTxPayload(tx, ptx, keyOperator);
|
|
SetTxPayload(tx, ptx);
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
}
|
|
|
|
void protx_update_registrar_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx update_registrar \"proTxHash\" \"operatorKeyAddr\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\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"
|
|
"\nArguments:\n"
|
|
"1. \"proTxHash\" (string, required) The hash of the initial ProRegTx.\n"
|
|
"2. \"operatorPubKey\" (string, required) The operator public key. The private key does not have to be known by you.\n"
|
|
" It has to match the private key which is later used when operating the masternode.\n"
|
|
" If set to \"0\" or an empty string, the last on-chain operator key of the masternode will be used.\n"
|
|
"3. \"votingKeyAddr\" (string, required) The voting key address. The private key does not have to be known by your wallet.\n"
|
|
" It has to match the private key which is later used when voting on proposals.\n"
|
|
" If set to \"0\" or an empty string, the last on-chain voting key of the masternode will be used.\n"
|
|
"5. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n"
|
|
" Must match \"collateralAddress\" of initial ProRegTx.\n"
|
|
" If set to \"0\" or an empty string, the last on-chain payout address of the masternode will be used.\n"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("protx", "update_registrar \"0123456701234567012345670123456701234567012345670123456701234567\" \"982eb34b7c7f614f29e5c665bc3605f1beeef85e3395ca12d3be49d2868ecfea5566f11cedfad30c51b2403f2ad95b67\" \"0\" \"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwG\"")
|
|
);
|
|
}
|
|
|
|
UniValue protx_update_registrar(const JSONRPCRequest& request)
|
|
{
|
|
if (request.fHelp || request.params.size() != 5)
|
|
protx_update_registrar_help();
|
|
|
|
CProUpRegTx ptx;
|
|
ptx.nVersion = CProRegTx::CURRENT_VERSION;
|
|
ptx.proTxHash = ParseHashV(request.params[1], "proTxHash");
|
|
|
|
auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(ptx.proTxHash);
|
|
if (!dmn) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString()));
|
|
}
|
|
ptx.pubKeyOperator = dmn->pdmnState->pubKeyOperator;
|
|
ptx.keyIDVoting = dmn->pdmnState->keyIDVoting;
|
|
ptx.scriptPayout = dmn->pdmnState->scriptPayout;
|
|
|
|
if (request.params[2].get_str() != "0" && request.params[2].get_str() != "") {
|
|
ptx.pubKeyOperator = ParseBLSPubKey(request.params[2].get_str(), "operator BLS address");
|
|
}
|
|
if (request.params[3].get_str() != "0" && request.params[3].get_str() != "") {
|
|
ptx.keyIDVoting = ParsePubKeyIDFromAddress(request.params[3].get_str(), "operator address");
|
|
}
|
|
|
|
CBitcoinAddress payoutAddress(request.params[4].get_str());
|
|
if (!payoutAddress.IsValid())
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[4].get_str()));
|
|
ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get());
|
|
|
|
CKey keyOwner;
|
|
if (!pwalletMain->GetKey(dmn->pdmnState->keyIDOwner, keyOwner)) {
|
|
throw std::runtime_error(strprintf("owner key %s not found in your wallet", dmn->pdmnState->keyIDOwner.ToString()));
|
|
}
|
|
|
|
CMutableTransaction tx;
|
|
tx.nVersion = 3;
|
|
tx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR;
|
|
|
|
FundSpecialTx(tx, ptx);
|
|
SignSpecialTxPayload(tx, ptx, keyOwner);
|
|
SetTxPayload(tx, ptx);
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
}
|
|
|
|
void protx_revoke_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx revoke \"proTxHash\"\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"
|
|
"to the masternode owner.\n"
|
|
"The operator key of the masternode must be known to your wallet.\n"
|
|
"\nArguments:\n"
|
|
"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"
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"")
|
|
);
|
|
}
|
|
|
|
UniValue protx_revoke(const JSONRPCRequest& request)
|
|
{
|
|
if (request.fHelp || (request.params.size() != 2 && request.params.size() != 3))
|
|
protx_revoke_help();
|
|
|
|
CProUpRevTx ptx;
|
|
ptx.nVersion = CProRegTx::CURRENT_VERSION;
|
|
ptx.proTxHash = ParseHashV(request.params[1], "proTxHash");
|
|
|
|
CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey");
|
|
|
|
if (request.params.size() > 3) {
|
|
int32_t nReason = ParseInt32V(request.params[3], "reason");
|
|
if (nReason < 0 || nReason >= CProUpRevTx::REASON_LAST)
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid reason %d, must be between 0 and %d", nReason, CProUpRevTx::REASON_LAST));
|
|
ptx.nReason = (uint16_t)nReason;
|
|
}
|
|
|
|
auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(ptx.proTxHash);
|
|
if (!dmn) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString()));
|
|
}
|
|
|
|
if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
|
|
}
|
|
|
|
CMutableTransaction tx;
|
|
tx.nVersion = 3;
|
|
tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
|
|
|
|
FundSpecialTx(tx, ptx);
|
|
SignSpecialTxPayload(tx, ptx, keyOperator);
|
|
SetTxPayload(tx, ptx);
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
}
|
|
|
|
void protx_list_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx list (\"type\")\n"
|
|
"\nLists all ProTxs in your wallet or on-chain, depending on the given type. If \"type\" is not\n"
|
|
"specified, it defaults to \"wallet\". All types have the optional argument \"detailed\" which if set to\n"
|
|
"\"true\" will result in a detailed list to be returned. If set to \"false\", only the hashes of the ProTx\n"
|
|
"will be returned.\n"
|
|
"\nAvailable types:\n"
|
|
" wallet (detailed) - List only ProTx which are found in your wallet. This will also include ProTx which\n"
|
|
" failed PoSe verfication\n"
|
|
" valid (height) (detailed) - List only ProTx which are active/valid at the given chain height. If height is not\n"
|
|
" specified, it defaults to the current chain-tip\n"
|
|
" registered (height) (detaileD) - List all ProTx which are registered at the given chain height. If height is not\n"
|
|
" specified, it defaults to the current chain-tip. This will also include ProTx\n"
|
|
" which failed PoSe verification at that height\n"
|
|
);
|
|
}
|
|
|
|
static bool CheckWalletOwnsScript(const CScript& script) {
|
|
CTxDestination dest;
|
|
if (ExtractDestination(script, dest)) {
|
|
if ((boost::get<CKeyID>(&dest) && pwalletMain->HaveKey(*boost::get<CKeyID>(&dest))) || (boost::get<CScriptID>(&dest) && pwalletMain->HaveCScript(*boost::get<CScriptID>(&dest)))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UniValue BuildDMNListEntry(const CDeterministicMNCPtr& dmn, bool detailed)
|
|
{
|
|
if (!detailed)
|
|
return dmn->proTxHash.ToString();
|
|
|
|
UniValue o(UniValue::VOBJ);
|
|
|
|
dmn->ToJson(o);
|
|
|
|
int confirmations = GetUTXOConfirmations(dmn->collateralOutpoint);
|
|
o.push_back(Pair("confirmations", confirmations));
|
|
|
|
bool hasOwnerKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner);
|
|
bool hasOperatorKey = false; //pwalletMain->HaveKey(dmn->pdmnState->keyIDOperator);
|
|
bool hasVotingKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting);
|
|
|
|
bool ownsCollateral = false;
|
|
CTransactionRef collateralTx;
|
|
uint256 tmpHashBlock;
|
|
if (GetTransaction(dmn->collateralOutpoint.hash, collateralTx, Params().GetConsensus(), tmpHashBlock)) {
|
|
ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->collateralOutpoint.n].scriptPubKey);
|
|
}
|
|
|
|
UniValue walletObj(UniValue::VOBJ);
|
|
walletObj.push_back(Pair("hasOwnerKey", hasOwnerKey));
|
|
walletObj.push_back(Pair("hasOperatorKey", hasOperatorKey));
|
|
walletObj.push_back(Pair("hasVotingKey", hasVotingKey));
|
|
walletObj.push_back(Pair("ownsCollateral", ownsCollateral));
|
|
walletObj.push_back(Pair("ownsPayeeScript", CheckWalletOwnsScript(dmn->pdmnState->scriptPayout)));
|
|
walletObj.push_back(Pair("ownsOperatorRewardScript", CheckWalletOwnsScript(dmn->pdmnState->scriptOperatorPayout)));
|
|
o.push_back(Pair("wallet", walletObj));
|
|
|
|
return o;
|
|
}
|
|
|
|
UniValue protx_list(const JSONRPCRequest& request)
|
|
{
|
|
if (request.fHelp)
|
|
protx_list_help();
|
|
|
|
std::string type = "wallet";
|
|
if (request.params.size() > 1)
|
|
type = request.params[1].get_str();
|
|
|
|
UniValue ret(UniValue::VARR);
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
if (type == "wallet") {
|
|
if (request.params.size() > 3)
|
|
protx_list_help();
|
|
|
|
bool detailed = request.params.size() > 2 ? ParseBoolV(request.params[2], "detailed") : false;
|
|
|
|
std::vector<COutPoint> vOutpts;
|
|
pwalletMain->ListProTxCoins(vOutpts);
|
|
std::set<COutPoint> setOutpts;
|
|
for (const auto& outpt : vOutpts) {
|
|
setOutpts.emplace(outpt);
|
|
}
|
|
|
|
deterministicMNManager->GetListAtChainTip().ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) {
|
|
if (setOutpts.count(dmn->collateralOutpoint) ||
|
|
pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner) ||
|
|
pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting) ||
|
|
CheckWalletOwnsScript(dmn->pdmnState->scriptPayout) ||
|
|
CheckWalletOwnsScript(dmn->pdmnState->scriptOperatorPayout)) {
|
|
ret.push_back(BuildDMNListEntry(dmn, detailed));
|
|
}
|
|
});
|
|
} else if (type == "valid" || type == "registered") {
|
|
if (request.params.size() > 4)
|
|
protx_list_help();
|
|
|
|
LOCK(cs_main);
|
|
|
|
int height = request.params.size() > 2 ? ParseInt32V(request.params[2], "height") : chainActive.Height();
|
|
if (height < 1 || height > chainActive.Height())
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
|
|
|
|
bool detailed = request.params.size() > 3 ? ParseBoolV(request.params[3], "detailed") : false;
|
|
|
|
CDeterministicMNList mnList = deterministicMNManager->GetListForBlock(chainActive[height]->GetBlockHash());
|
|
bool onlyValid = type == "valid";
|
|
mnList.ForEachMN(onlyValid, [&](const CDeterministicMNCPtr& dmn) {
|
|
ret.push_back(BuildDMNListEntry(dmn, detailed));
|
|
});
|
|
} else {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid type specified");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void protx_info_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx info \"proTxHash\"\n"
|
|
"\nReturns detailed information about a deterministic masternode.\n"
|
|
"\nArguments:\n"
|
|
"1. \"proTxHash\" (string, required) The hash of the initial ProRegTx.\n"
|
|
);
|
|
}
|
|
|
|
UniValue protx_info(const JSONRPCRequest& request)
|
|
{
|
|
if (request.fHelp || request.params.size() != 2)
|
|
protx_info_help();
|
|
|
|
uint256 proTxHash = ParseHashV(request.params[1], "proTxHash");
|
|
auto mnList = deterministicMNManager->GetListAtChainTip();
|
|
auto dmn = mnList.GetMN(proTxHash);
|
|
if (!dmn) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s not found", proTxHash.ToString()));
|
|
}
|
|
return BuildDMNListEntry(dmn, true);
|
|
}
|
|
|
|
void protx_diff_help()
|
|
{
|
|
throw std::runtime_error(
|
|
"protx diff \"baseBlock\" \"block\"\n"
|
|
"\nCalculates a diff between two deterministic masternode lists. The result also contains proof data.\n"
|
|
"specified, it defaults to \"wallet\". All types have the optional argument \"detailed\" which if set to\n"
|
|
"\"true\" will result in a detailed list to be returned. If set to \"false\", only the hashes of the ProTx\n"
|
|
"will be returned.\n"
|
|
"\nAvailable types:\n"
|
|
" wallet (detailed) - List only ProTx which are found in your wallet. This will also include ProTx which\n"
|
|
" failed PoSe verfication\n"
|
|
" valid (height) (detailed) - List only ProTx which are active/valid at the given chain height. If height is not\n"
|
|
" specified, it defaults to the current chain-tip\n"
|
|
" registered (height) (detaileD) - List all ProTx which are registered at the given chain height. If height is not\n"
|
|
" specified, it defaults to the current chain-tip. This will also include ProTx\n"
|
|
" which failed PoSe verification at that height\n"
|
|
);
|
|
}
|
|
|
|
static uint256 ParseBlock(const UniValue& v, std::string strName)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
|
|
try {
|
|
return ParseHashV(v, strName);
|
|
} catch (...) {
|
|
int h = ParseInt32V(v, strName);
|
|
if (h < 1 || h > chainActive.Height())
|
|
throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr()));
|
|
return *chainActive[h]->phashBlock;
|
|
}
|
|
}
|
|
|
|
UniValue protx_diff(const JSONRPCRequest& request)
|
|
{
|
|
if (request.fHelp || request.params.size() != 3)
|
|
protx_diff_help();
|
|
|
|
LOCK(cs_main);
|
|
uint256 baseBlockHash = ParseBlock(request.params[1], "baseBlock");
|
|
uint256 blockHash = ParseBlock(request.params[2], "block");
|
|
|
|
CSimplifiedMNListDiff mnListDiff;
|
|
std::string strError;
|
|
if (!BuildSimplifiedMNListDiff(baseBlockHash, blockHash, mnListDiff, strError)) {
|
|
throw std::runtime_error(strError);
|
|
}
|
|
|
|
UniValue ret;
|
|
mnListDiff.ToJson(ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
UniValue protx(const JSONRPCRequest& request)
|
|
{
|
|
if (request.params.empty()) {
|
|
throw std::runtime_error(
|
|
"protx \"command\" ...\n"
|
|
"Set of commands to execute ProTx related actions.\n"
|
|
"To get help on individual commands, use \"help protx command\".\n"
|
|
"\nArguments:\n"
|
|
"1. \"command\" (string, required) The command to execute\n"
|
|
"\nAvailable commands:\n"
|
|
" register - Create and send ProTx to network\n"
|
|
" list - List ProTxs\n"
|
|
" info - Return information about a ProTx\n"
|
|
" update_service - Create and send ProUpServTx to network\n"
|
|
" update_registrar - Create and send ProUpRegTx to network\n"
|
|
" revoke - Create and send ProUpRevTx to network\n"
|
|
" diff - Calculate a diff and a proof between two masternode lists\n"
|
|
);
|
|
}
|
|
|
|
std::string command = request.params[0].get_str();
|
|
|
|
if (command == "fund_register" || command == "register") {
|
|
return protx_register(request);
|
|
} else if (command == "list") {
|
|
return protx_list(request);
|
|
} else if (command == "info") {
|
|
return protx_info(request);
|
|
} else if (command == "update_service") {
|
|
return protx_update_service(request);
|
|
} else if (command == "update_registrar") {
|
|
return protx_update_registrar(request);
|
|
} else if (command == "revoke") {
|
|
return protx_revoke(request);
|
|
} else if (command == "diff") {
|
|
return protx_diff(request);
|
|
} else {
|
|
throw std::runtime_error("invalid command: " + command);
|
|
}
|
|
}
|
|
#endif//ENABLE_WALLET
|
|
|
|
UniValue bls_generate(const JSONRPCRequest& request)
|
|
{
|
|
CBLSSecretKey sk;
|
|
sk.MakeNewKey();
|
|
|
|
UniValue ret(UniValue::VOBJ);
|
|
ret.push_back(Pair("secret", sk.ToString()));
|
|
ret.push_back(Pair("public", sk.GetPublicKey().ToString()));
|
|
return ret;
|
|
}
|
|
|
|
UniValue _bls(const JSONRPCRequest& request)
|
|
{
|
|
if (request.params.empty()) {
|
|
throw std::runtime_error(
|
|
"bls \"command\" ...\n"
|
|
);
|
|
}
|
|
|
|
std::string command = request.params[0].get_str();
|
|
|
|
if (command == "generate") {
|
|
return bls_generate(request);
|
|
} else {
|
|
throw std::runtime_error("invalid command: " + command);
|
|
}
|
|
}
|
|
|
|
static const CRPCCommand commands[] =
|
|
{ // category name actor (function) okSafeMode
|
|
// --------------------- ------------------------ ----------------------- ----------
|
|
{ "evo", "bls", &_bls, false, {} },
|
|
#ifdef ENABLE_WALLET
|
|
// these require the wallet to be enabled to fund the transactions
|
|
{ "evo", "protx", &protx, false, {} },
|
|
#endif//ENABLE_WALLET
|
|
};
|
|
|
|
void RegisterEvoRPCCommands(CRPCTable &tableRPC)
|
|
{
|
|
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
|
|
tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
|
|
}
|