2018-02-13 13:36:59 +01:00
|
|
|
// 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"
|
2018-03-03 21:48:00 +01:00
|
|
|
#include "evo/deterministicmns.h"
|
2018-04-09 14:49:34 +02:00
|
|
|
#include "evo/simplifiedmns.h"
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
#include "bls/bls.h"
|
|
|
|
|
2018-02-13 13:36:59 +01:00
|
|
|
#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;
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!secret.SetString(strKeyOrAddress) || !secret.IsValid()) {
|
2018-02-13 13:36:59 +01:00
|
|
|
throw std::runtime_error(strprintf("invalid priv-key/address %s", strKeyOrAddress));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
return secret.GetKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress, const std::string& paramName)
|
|
|
|
{
|
|
|
|
CBitcoinAddress address(strAddress);
|
|
|
|
CKeyID keyID;
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!address.IsValid() || !address.GetKeyID(keyID)) {
|
2018-02-13 13:36:59 +01:00
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid P2PKH address, not %s", paramName, strAddress));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
return keyID;
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
|
|
|
#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;
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!pwalletMain->FundTransaction(tx, nFee, false, feeRate, nChangePos, strFailReason, false, false, setSubtractFeeFromOutputs, true, CNoDestination())) {
|
2018-02-13 13:36:59 +01:00
|
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
|
|
|
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>
|
2018-10-25 16:29:50 +02:00
|
|
|
static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload)
|
2018-02-13 13:36:59 +01:00
|
|
|
{
|
|
|
|
payload.inputsHash = CalcTxInputsHash(tx);
|
2018-10-25 16:29:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename SpecialTxPayload>
|
2018-11-01 22:57:10 +01:00
|
|
|
static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key)
|
2018-10-25 16:29:50 +02:00
|
|
|
{
|
|
|
|
UpdateSpecialTxInputsHash(tx, payload);
|
2018-02-13 13:36:59 +01:00
|
|
|
payload.vchSig.clear();
|
|
|
|
|
|
|
|
uint256 hash = ::SerializeHash(payload);
|
|
|
|
if (!CHashSigner::SignHash(hash, key, payload.vchSig)) {
|
|
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
template<typename SpecialTxPayload>
|
2018-11-01 22:57:10 +01:00
|
|
|
static void SignSpecialTxPayloadByString(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKey& key)
|
|
|
|
{
|
|
|
|
UpdateSpecialTxInputsHash(tx, payload);
|
|
|
|
payload.vchSig.clear();
|
|
|
|
|
|
|
|
std::string m = payload.MakeSignString();
|
|
|
|
if (!CMessageSigner::SignMessage(m, payload.vchSig, key)) {
|
|
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename SpecialTxPayload>
|
|
|
|
static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CBLSSecretKey& key)
|
2018-10-21 21:45:16 +02:00
|
|
|
{
|
2018-10-25 16:29:50 +02:00
|
|
|
UpdateSpecialTxInputsHash(tx, payload);
|
2018-10-21 21:45:16 +02:00
|
|
|
|
|
|
|
uint256 hash = ::SerializeHash(payload);
|
|
|
|
payload.sig = key.Sign(hash);
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:59 +01:00
|
|
|
static std::string SignAndSendSpecialTx(const CMutableTransaction& tx)
|
|
|
|
{
|
|
|
|
LOCK(cs_main);
|
2018-10-25 16:29:50 +02:00
|
|
|
|
2018-02-13 13:36:59 +01:00
|
|
|
CValidationState state;
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!CheckSpecialTx(tx, chainActive.Tip(), state)) {
|
2018-02-13 13:36:59 +01:00
|
|
|
throw std::runtime_error(FormatStateMessage(state));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2018-11-01 22:57:10 +01:00
|
|
|
void protx_register_fund_help()
|
2018-02-13 13:36:59 +01:00
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
2018-11-21 08:02:54 +01:00
|
|
|
"protx register_fund \"collateralAddress\" \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n"
|
2018-10-25 16:29:50 +02:00
|
|
|
"\nCreates, funds and sends a ProTx to the network. The resulting transaction will move 1000 Dash\n"
|
2018-02-13 13:36:59 +01:00
|
|
|
"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"
|
2018-10-25 16:29:50 +02:00
|
|
|
"2. \"ipAndPort\" (string, required) IP and port in the form \"IP:PORT\".\n"
|
2018-02-13 13:36:59 +01:00
|
|
|
" Must be unique on the network. Can be set to 0, which will require a ProUpServTx afterwards.\n"
|
2018-10-25 16:29:50 +02:00
|
|
|
"3. \"ownerKeyAddr\" (string, required) The owner key used for payee updates and proposal voting.\n"
|
2018-02-13 13:36:59 +01:00
|
|
|
" 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"
|
2018-11-21 08:02:15 +01:00
|
|
|
"4. \"operatorPubKey\" (string, required) The operator BLS public key. The private key does not have to be known.\n"
|
2018-02-13 13:36:59 +01:00
|
|
|
" It has to match the private key which is later used when operating the masternode.\n"
|
2018-10-25 16:29:50 +02:00
|
|
|
"5. \"votingKeyAddr\" (string, required) The voting key address. The private key does not have to be known by your wallet.\n"
|
2018-02-13 13:36:59 +01:00
|
|
|
" 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"
|
2018-11-27 05:12:11 +01:00
|
|
|
"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."
|
2018-02-13 13:36:59 +01:00
|
|
|
"\nExamples:\n"
|
2018-11-01 22:57:10 +01:00
|
|
|
+ HelpExampleCli("protx", "register_fund \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"")
|
2018-10-25 16:29:50 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void protx_register_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
2018-11-21 08:02:54 +01:00
|
|
|
"protx register \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n"
|
2018-11-01 22:57:10 +01:00
|
|
|
"\nSame as \"protx register_fund\", but with an externally referenced collateral.\n"
|
2018-10-25 16:29:50 +02:00
|
|
|
"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"
|
2018-11-01 22:57:10 +01:00
|
|
|
"3., 4., 5. ... See help text of \"protx register_fund\"\n"
|
2018-10-25 16:29:50 +02:00
|
|
|
"\nExamples:\n"
|
2018-11-08 18:03:50 +01:00
|
|
|
+ HelpExampleCli("protx", "register \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"")
|
2018-02-13 13:36:59 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-01 22:57:10 +01:00
|
|
|
void protx_register_prepare_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
2018-11-21 08:02:54 +01:00
|
|
|
"protx register_prepare \"collateralHash\" collateralIndex \"ipAndPort\" \"ownerKeyAddr\" \"operatorPubKey\" \"votingKeyAddr\" operatorReward \"payoutAddress\"\n"
|
2018-11-01 22:57:10 +01:00
|
|
|
"\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"
|
2018-11-08 18:03:50 +01:00
|
|
|
"1., 2., 3., ... See help text of \"protx register\".\n"
|
2018-11-01 22:57:10 +01:00
|
|
|
"\nResult:\n"
|
|
|
|
"{ (json object)\n"
|
|
|
|
" \"tx\" : (string) The serialized ProTx in hex format.\n"
|
|
|
|
" \"collateralAddress\" : (string) The collateral address.\n"
|
|
|
|
" \"signMessage\" : (string) The string message that needs to be signed with\n"
|
|
|
|
" the collateral key.\n"
|
|
|
|
"}\n"
|
|
|
|
"\nExamples:\n"
|
2018-11-08 18:03:50 +01:00
|
|
|
+ HelpExampleCli("protx", "register_prepare \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\"")
|
2018-11-01 22:57:10 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void protx_register_submit_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
|
|
|
"protx register_submit \"tx\" \"sig\"\n"
|
|
|
|
"\nSubmits the specified ProTx to the network. This command will also sign the inputs of the transaction\n"
|
|
|
|
"which were previously added by \"protx register_prepare\" to cover transaction fees\n"
|
|
|
|
"\nArguments:\n"
|
2018-11-08 18:03:50 +01:00
|
|
|
"1. \"tx\" (string, required) The serialized transaction previously returned by \"protx register_prepare\"\n"
|
2018-11-01 22:57:10 +01:00
|
|
|
"2. \"sig\" (string, required) The signature signed with the collateral key. Must be in base64 format.\n"
|
|
|
|
"\nExamples:\n"
|
|
|
|
+ HelpExampleCli("protx", "register_submit \"tx\" \"sig\"")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// handles register, register_prepare and register_fund in one method
|
2018-02-13 13:36:59 +01:00
|
|
|
UniValue protx_register(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-11-01 22:57:10 +01:00
|
|
|
bool isExternalRegister = request.params[0].get_str() == "register";
|
|
|
|
bool isFundRegister = request.params[0].get_str() == "register_fund";
|
|
|
|
bool isPrepareRegister = request.params[0].get_str() == "register_prepare";
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
if (isFundRegister && (request.fHelp || request.params.size() != 8)) {
|
2018-11-01 22:57:10 +01:00
|
|
|
protx_register_fund_help();
|
|
|
|
} else if (isExternalRegister && (request.fHelp || request.params.size() != 9)) {
|
2018-10-25 16:29:50 +02:00
|
|
|
protx_register_help();
|
2018-11-01 22:57:10 +01:00
|
|
|
} else if (isPrepareRegister && (request.fHelp || request.params.size() != 9)) {
|
|
|
|
protx_register_prepare_help();
|
2018-10-25 16:29:50 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
size_t paramIdx = 1;
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
CAmount collateralAmount = 1000 * COIN;
|
2018-02-13 13:36:59 +01:00
|
|
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
tx.nVersion = 3;
|
|
|
|
tx.nType = TRANSACTION_PROVIDER_REGISTER;
|
|
|
|
|
|
|
|
CProRegTx ptx;
|
|
|
|
ptx.nVersion = CProRegTx::CURRENT_VERSION;
|
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
if (isFundRegister) {
|
|
|
|
CBitcoinAddress collateralAddress(request.params[paramIdx].get_str());
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!collateralAddress.IsValid()) {
|
2018-10-25 16:29:50 +02:00
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[paramIdx].get_str()));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-10-25 16:29:50 +02:00
|
|
|
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() != "") {
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!Lookup(request.params[paramIdx].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) {
|
2018-10-25 16:29:50 +02:00
|
|
|
throw std::runtime_error(strprintf("invalid network address %s", request.params[paramIdx].get_str()));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
}
|
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
CKey keyOwner = ParsePrivKey(request.params[paramIdx + 1].get_str(), true);
|
|
|
|
CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address");
|
2018-02-13 13:36:59 +01:00
|
|
|
CKeyID keyIDVoting = keyOwner.GetPubKey().GetID();
|
2018-10-25 16:29:50 +02:00
|
|
|
if (request.params[paramIdx + 3].get_str() != "0" && request.params[paramIdx + 3].get_str() != "") {
|
|
|
|
keyIDVoting = ParsePubKeyIDFromAddress(request.params[paramIdx + 3].get_str(), "voting address");
|
2018-02-13 13:36:59 +01:00
|
|
|
}
|
|
|
|
|
2018-11-21 08:04:42 +01:00
|
|
|
int64_t operatorReward;
|
|
|
|
if (!ParseFixedPoint(request.params[paramIdx + 4].getValStr(), 2, &operatorReward)) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be a number");
|
|
|
|
}
|
|
|
|
if (operatorReward < 0 || operatorReward > 10000) {
|
2018-11-01 22:58:36 +01:00
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0.00 and 100.00");
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-11-21 08:04:42 +01:00
|
|
|
ptx.nOperatorReward = operatorReward;
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
CBitcoinAddress payoutAddress(request.params[paramIdx + 5].get_str());
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!payoutAddress.IsValid()) {
|
2018-10-25 16:29:50 +02:00
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str()));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
|
|
|
ptx.keyIDOwner = keyOwner.GetPubKey().GetID();
|
2018-10-21 21:45:16 +02:00
|
|
|
ptx.pubKeyOperator = pubKeyOperator;
|
2018-02-13 13:36:59 +01:00
|
|
|
ptx.keyIDVoting = keyIDVoting;
|
|
|
|
ptx.scriptPayout = GetScriptForDestination(payoutAddress.Get());
|
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
if (!isFundRegister) {
|
|
|
|
// make sure fee calculation works
|
|
|
|
ptx.vchSig.resize(65);
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:59 +01:00
|
|
|
FundSpecialTx(tx, ptx);
|
2018-11-01 22:57:10 +01:00
|
|
|
UpdateSpecialTxInputsHash(tx, ptx);
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
}
|
2018-10-25 16:29:50 +02:00
|
|
|
assert(collateralIndex != (uint32_t) -1);
|
|
|
|
ptx.collateralOutpoint.n = collateralIndex;
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-11-01 22:57:10 +01:00
|
|
|
SetTxPayload(tx, ptx);
|
|
|
|
return SignAndSendSpecialTx(tx);
|
2018-10-25 16:29:50 +02:00
|
|
|
} else {
|
2018-11-01 22:57:10 +01:00
|
|
|
// referencing external collateral
|
2018-10-25 16:29:50 +02:00
|
|
|
|
|
|
|
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()));
|
|
|
|
}
|
2018-11-01 22:57:10 +01:00
|
|
|
|
|
|
|
if (isPrepareRegister) {
|
|
|
|
// external signing with collateral key
|
|
|
|
ptx.vchSig.clear();
|
|
|
|
SetTxPayload(tx, ptx);
|
|
|
|
|
|
|
|
UniValue ret(UniValue::VOBJ);
|
|
|
|
ret.push_back(Pair("tx", EncodeHexTx(tx)));
|
|
|
|
ret.push_back(Pair("collateralAddress", CBitcoinAddress(txDest).ToString()));
|
|
|
|
ret.push_back(Pair("signMessage", ptx.MakeSignString()));
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
// lets prove we own the collateral
|
|
|
|
CKey key;
|
|
|
|
if (!pwalletMain->GetKey(keyID, key)) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral key not in wallet: %s", CBitcoinAddress(keyID).ToString()));
|
|
|
|
}
|
|
|
|
SignSpecialTxPayloadByString(tx, ptx, key);
|
|
|
|
SetTxPayload(tx, ptx);
|
|
|
|
return SignAndSendSpecialTx(tx);
|
2018-10-25 16:29:50 +02:00
|
|
|
}
|
|
|
|
}
|
2018-11-01 22:57:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
UniValue protx_register_submit(const JSONRPCRequest& request)
|
|
|
|
{
|
|
|
|
if (request.fHelp || request.params.size() != 3) {
|
|
|
|
protx_register_submit_help();
|
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-11-01 22:57:10 +01:00
|
|
|
CMutableTransaction tx;
|
|
|
|
if (!DecodeHexTx(tx, request.params[1].get_str())) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "transaction not deserializable");
|
|
|
|
}
|
|
|
|
if (tx.nType != TRANSACTION_PROVIDER_REGISTER) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "transaction not a ProRegTx");
|
|
|
|
}
|
|
|
|
CProRegTx ptx;
|
|
|
|
if (!GetTxPayload(tx, ptx)) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "transaction payload not deserializable");
|
|
|
|
}
|
|
|
|
if (!ptx.vchSig.empty()) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "payload signature not empty");
|
|
|
|
}
|
|
|
|
|
|
|
|
ptx.vchSig = DecodeBase64(request.params[2].get_str().c_str());
|
|
|
|
|
|
|
|
SetTxPayload(tx, ptx);
|
2018-02-13 13:36:59 +01:00
|
|
|
return SignAndSendSpecialTx(tx);
|
|
|
|
}
|
|
|
|
|
2018-03-14 08:57:08 +01:00
|
|
|
void protx_update_service_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
2018-10-23 13:15:38 +02:00
|
|
|
"protx update_service \"proTxHash\" \"ipAndPort\" \"operatorKey\" (\"operatorPayoutAddress\")\n"
|
|
|
|
"\nCreates and sends a ProUpServTx to the network. This will update the IP address\n"
|
2018-03-14 08:57:08 +01:00
|
|
|
"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"
|
2018-10-23 13:15:38 +02:00
|
|
|
"3. \"operatorKey\" (string, required) The operator private key belonging to the\n"
|
2018-10-21 21:45:16 +02:00
|
|
|
" registered operator public key.\n"
|
2018-10-23 13:15:38 +02:00
|
|
|
"4. \"operatorPayoutAddress\" (string, optional) The address used for operator reward payments.\n"
|
2018-03-14 08:57:08 +01:00
|
|
|
" Only allowed when the ProRegTx had a non-zero operatorReward value.\n"
|
|
|
|
"\nExamples:\n"
|
2018-10-23 13:15:38 +02:00
|
|
|
+ HelpExampleCli("protx", "update_service \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" 5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd")
|
2018-03-14 08:57:08 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
UniValue protx_update_service(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-11-14 18:57:17 +01:00
|
|
|
if (request.fHelp || (request.params.size() != 4 && request.params.size() != 5))
|
2018-03-14 08:57:08 +01:00
|
|
|
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)) {
|
2018-11-22 06:05:20 +01:00
|
|
|
throw std::runtime_error(strprintf("invalid network address %s", request.params[2].get_str()));
|
2018-03-14 08:57:08 +01:00
|
|
|
}
|
|
|
|
|
2018-10-23 13:15:38 +02:00
|
|
|
CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[3].get_str(), "operatorKey");
|
2018-10-21 21:45:16 +02:00
|
|
|
|
2018-10-23 13:15:38 +02:00
|
|
|
if (request.params.size() > 4) {
|
|
|
|
CBitcoinAddress payoutAddress(request.params[4].get_str());
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!payoutAddress.IsValid()) {
|
2018-03-14 08:57:08 +01:00
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[4].get_str()));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-14 08:57:08 +01:00
|
|
|
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()));
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
|
2018-03-14 08:57:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
tx.nVersion = 3;
|
|
|
|
tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
|
|
|
|
|
|
|
|
FundSpecialTx(tx, ptx);
|
2018-11-01 22:57:10 +01:00
|
|
|
SignSpecialTxPayloadByHash(tx, ptx, keyOperator);
|
2018-03-14 08:57:08 +01:00
|
|
|
SetTxPayload(tx, ptx);
|
|
|
|
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
|
|
}
|
|
|
|
|
2018-03-19 09:46:08 +01:00
|
|
|
void protx_update_registrar_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
2018-10-26 07:03:14 +02:00
|
|
|
"protx update_registrar \"proTxHash\" \"operatorKeyAddr\" \"votingKeyAddr\" \"payoutAddress\"\n"
|
2018-03-19 09:46:08 +01:00
|
|
|
"\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"
|
2018-10-21 21:45:16 +02:00
|
|
|
"2. \"operatorPubKey\" (string, required) The operator public key. The private key does not have to be known by you.\n"
|
2018-03-19 09:46:08 +01:00
|
|
|
" 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"
|
2018-10-26 07:03:14 +02:00
|
|
|
"4. \"payoutAddress\" (string, required) The dash address to use for masternode reward payments\n"
|
2018-03-19 09:46:08 +01:00
|
|
|
" If set to \"0\" or an empty string, the last on-chain payout address of the masternode will be used.\n"
|
|
|
|
"\nExamples:\n"
|
2018-10-26 07:03:14 +02:00
|
|
|
+ HelpExampleCli("protx", "update_registrar \"0123456701234567012345670123456701234567012345670123456701234567\" \"982eb34b7c7f614f29e5c665bc3605f1beeef85e3395ca12d3be49d2868ecfea5566f11cedfad30c51b2403f2ad95b67\" \"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwG\"")
|
2018-03-19 09:46:08 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
UniValue protx_update_registrar(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.fHelp || request.params.size() != 5) {
|
2018-03-19 09:46:08 +01:00
|
|
|
protx_update_registrar_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-19 09:46:08 +01:00
|
|
|
|
|
|
|
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()));
|
|
|
|
}
|
2018-10-21 21:45:16 +02:00
|
|
|
ptx.pubKeyOperator = dmn->pdmnState->pubKeyOperator;
|
2018-03-19 09:46:08 +01:00
|
|
|
ptx.keyIDVoting = dmn->pdmnState->keyIDVoting;
|
|
|
|
ptx.scriptPayout = dmn->pdmnState->scriptPayout;
|
|
|
|
|
|
|
|
if (request.params[2].get_str() != "0" && request.params[2].get_str() != "") {
|
2018-10-21 21:45:16 +02:00
|
|
|
ptx.pubKeyOperator = ParseBLSPubKey(request.params[2].get_str(), "operator BLS address");
|
2018-03-19 09:46:08 +01:00
|
|
|
}
|
|
|
|
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());
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!payoutAddress.IsValid()) {
|
2018-03-19 09:46:08 +01:00
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[4].get_str()));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-19 09:46:08 +01:00
|
|
|
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;
|
|
|
|
|
2018-11-15 08:05:38 +01:00
|
|
|
// make sure we get anough fees added
|
|
|
|
ptx.vchSig.resize(65);
|
|
|
|
|
2018-03-19 09:46:08 +01:00
|
|
|
FundSpecialTx(tx, ptx);
|
2018-11-01 22:57:10 +01:00
|
|
|
SignSpecialTxPayloadByHash(tx, ptx, keyOwner);
|
2018-03-19 09:46:08 +01:00
|
|
|
SetTxPayload(tx, ptx);
|
|
|
|
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
|
|
}
|
|
|
|
|
2018-03-19 12:45:15 +01:00
|
|
|
void protx_revoke_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
2018-11-21 07:57:47 +01:00
|
|
|
"protx revoke \"proTxHash\" \"operatorKey\"\n"
|
2018-03-19 12:45:15 +01:00
|
|
|
"\nCreates and sends a ProUpRevTx to the network. This will revoke the operator key of the masternode and\n"
|
2018-10-23 13:15:38 +02:00
|
|
|
"put it into the PoSe-banned state. It will also set the service field of the masternode\n"
|
2018-03-19 12:45:15 +01:00
|
|
|
"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"
|
2018-10-21 21:45:16 +02:00
|
|
|
"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"
|
2018-03-19 12:45:15 +01:00
|
|
|
"\nExamples:\n"
|
2018-10-21 21:45:16 +02:00
|
|
|
+ HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"")
|
2018-03-19 12:45:15 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
UniValue protx_revoke(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.fHelp || (request.params.size() != 2 && request.params.size() != 3)) {
|
2018-03-19 12:45:15 +01:00
|
|
|
protx_revoke_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-19 12:45:15 +01:00
|
|
|
|
|
|
|
CProUpRevTx ptx;
|
|
|
|
ptx.nVersion = CProRegTx::CURRENT_VERSION;
|
|
|
|
ptx.proTxHash = ParseHashV(request.params[1], "proTxHash");
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey");
|
|
|
|
|
|
|
|
if (request.params.size() > 3) {
|
|
|
|
int32_t nReason = ParseInt32V(request.params[3], "reason");
|
2018-10-26 07:03:14 +02:00
|
|
|
if (nReason < 0 || nReason >= CProUpRevTx::REASON_LAST) {
|
2018-03-19 12:45:15 +01:00
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid reason %d, must be between 0 and %d", nReason, CProUpRevTx::REASON_LAST));
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-19 12:45:15 +01:00
|
|
|
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()));
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator) {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
|
2018-03-19 12:45:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
tx.nVersion = 3;
|
|
|
|
tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
|
|
|
|
|
|
|
|
FundSpecialTx(tx, ptx);
|
2018-11-01 22:57:10 +01:00
|
|
|
SignSpecialTxPayloadByHash(tx, ptx, keyOperator);
|
2018-03-19 12:45:15 +01:00
|
|
|
SetTxPayload(tx, ptx);
|
|
|
|
|
|
|
|
return SignAndSendSpecialTx(tx);
|
|
|
|
}
|
|
|
|
|
2018-03-03 21:48:00 +01:00
|
|
|
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)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
if (!detailed) {
|
2018-03-03 21:48:00 +01:00
|
|
|
return dmn->proTxHash.ToString();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-03 21:48:00 +01:00
|
|
|
|
|
|
|
UniValue o(UniValue::VOBJ);
|
|
|
|
|
|
|
|
dmn->ToJson(o);
|
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
int confirmations = GetUTXOConfirmations(dmn->collateralOutpoint);
|
2018-03-03 21:48:00 +01:00
|
|
|
o.push_back(Pair("confirmations", confirmations));
|
|
|
|
|
|
|
|
bool hasOwnerKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner);
|
2018-10-21 21:45:16 +02:00
|
|
|
bool hasOperatorKey = false; //pwalletMain->HaveKey(dmn->pdmnState->keyIDOperator);
|
2018-03-03 21:48:00 +01:00
|
|
|
bool hasVotingKey = pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting);
|
|
|
|
|
|
|
|
bool ownsCollateral = false;
|
|
|
|
CTransactionRef collateralTx;
|
|
|
|
uint256 tmpHashBlock;
|
2018-10-25 16:29:50 +02:00
|
|
|
if (GetTransaction(dmn->collateralOutpoint.hash, collateralTx, Params().GetConsensus(), tmpHashBlock)) {
|
|
|
|
ownsCollateral = CheckWalletOwnsScript(collateralTx->vout[dmn->collateralOutpoint.n].scriptPubKey);
|
2018-03-03 21:48:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.fHelp) {
|
2018-03-03 21:48:00 +01:00
|
|
|
protx_list_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-03 21:48:00 +01:00
|
|
|
|
|
|
|
std::string type = "wallet";
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.params.size() > 1) {
|
2018-03-03 21:48:00 +01:00
|
|
|
type = request.params[1].get_str();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-03 21:48:00 +01:00
|
|
|
|
|
|
|
UniValue ret(UniValue::VARR);
|
|
|
|
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
|
|
|
|
if (type == "wallet") {
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.params.size() > 3) {
|
2018-03-03 21:48:00 +01:00
|
|
|
protx_list_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-03 21:48:00 +01:00
|
|
|
|
|
|
|
bool detailed = request.params.size() > 2 ? ParseBoolV(request.params[2], "detailed") : false;
|
|
|
|
|
|
|
|
std::vector<COutPoint> vOutpts;
|
|
|
|
pwalletMain->ListProTxCoins(vOutpts);
|
2018-10-25 16:29:50 +02:00
|
|
|
std::set<COutPoint> setOutpts;
|
2018-03-03 21:48:00 +01:00
|
|
|
for (const auto& outpt : vOutpts) {
|
2018-10-25 16:29:50 +02:00
|
|
|
setOutpts.emplace(outpt);
|
2018-03-03 21:48:00 +01:00
|
|
|
}
|
|
|
|
|
2018-10-02 11:03:05 +02:00
|
|
|
deterministicMNManager->GetListAtChainTip().ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) {
|
2018-10-25 16:29:50 +02:00
|
|
|
if (setOutpts.count(dmn->collateralOutpoint) ||
|
2018-10-02 11:03:05 +02:00
|
|
|
pwalletMain->HaveKey(dmn->pdmnState->keyIDOwner) ||
|
|
|
|
pwalletMain->HaveKey(dmn->pdmnState->keyIDVoting) ||
|
|
|
|
CheckWalletOwnsScript(dmn->pdmnState->scriptPayout) ||
|
|
|
|
CheckWalletOwnsScript(dmn->pdmnState->scriptOperatorPayout)) {
|
2018-03-03 21:48:00 +01:00
|
|
|
ret.push_back(BuildDMNListEntry(dmn, detailed));
|
|
|
|
}
|
2018-10-02 11:03:05 +02:00
|
|
|
});
|
2018-03-03 21:48:00 +01:00
|
|
|
} else if (type == "valid" || type == "registered") {
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.params.size() > 4) {
|
2018-03-03 21:48:00 +01:00
|
|
|
protx_list_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-03 21:48:00 +01:00
|
|
|
|
|
|
|
LOCK(cs_main);
|
|
|
|
|
|
|
|
int height = request.params.size() > 2 ? ParseInt32V(request.params[2], "height") : chainActive.Height();
|
2018-10-26 07:03:14 +02:00
|
|
|
if (height < 1 || height > chainActive.Height()) {
|
2018-03-03 21:48:00 +01:00
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-03-03 21:48:00 +01:00
|
|
|
|
|
|
|
bool detailed = request.params.size() > 3 ? ParseBoolV(request.params[3], "detailed") : false;
|
|
|
|
|
|
|
|
CDeterministicMNList mnList = deterministicMNManager->GetListForBlock(chainActive[height]->GetBlockHash());
|
2018-10-02 11:03:05 +02:00
|
|
|
bool onlyValid = type == "valid";
|
|
|
|
mnList.ForEachMN(onlyValid, [&](const CDeterministicMNCPtr& dmn) {
|
2018-03-03 21:48:00 +01:00
|
|
|
ret.push_back(BuildDMNListEntry(dmn, detailed));
|
2018-10-02 11:03:05 +02:00
|
|
|
});
|
2018-03-03 21:48:00 +01:00
|
|
|
} else {
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid type specified");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-10-25 16:29:50 +02:00
|
|
|
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)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.fHelp || request.params.size() != 2) {
|
2018-10-25 16:29:50 +02:00
|
|
|
protx_info_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-10-25 16:29:50 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-04-09 14:49:34 +02:00
|
|
|
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"
|
2018-11-02 09:18:05 +01:00
|
|
|
"\nArguments:\n"
|
|
|
|
"1. \"baseBlock\" (numeric, required) The starting block height.\n"
|
|
|
|
"2. \"block\" (numeric, required) The ending block height.\n"
|
2018-04-09 14:49:34 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
if (request.fHelp || request.params.size() != 3) {
|
2018-04-09 14:49:34 +02:00
|
|
|
protx_diff_help();
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-04-09 14:49:34 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-11-05 10:28:38 +01:00
|
|
|
[[ noreturn ]] void protx_help()
|
|
|
|
{
|
|
|
|
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"
|
|
|
|
" register_fund - Fund, create and send ProTx to network\n"
|
|
|
|
" register_prepare - Create an unsigned ProTx\n"
|
|
|
|
" register_submit - Sign and submit a ProTx\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"
|
|
|
|
);
|
|
|
|
}
|
2018-04-09 14:49:34 +02:00
|
|
|
|
2018-02-13 13:36:59 +01:00
|
|
|
UniValue protx(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-11-09 07:48:26 +01:00
|
|
|
if (request.fHelp && request.params.empty()) {
|
2018-11-05 10:28:38 +01:00
|
|
|
protx_help();
|
2018-02-13 13:36:59 +01:00
|
|
|
}
|
|
|
|
|
2018-11-13 13:23:52 +01:00
|
|
|
std::string command;
|
|
|
|
if (request.params.size() >= 1) {
|
|
|
|
command = request.params[0].get_str();
|
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
|
2018-11-01 22:57:10 +01:00
|
|
|
if (command == "register" || command == "register_fund" || command == "register_prepare") {
|
2018-02-13 13:36:59 +01:00
|
|
|
return protx_register(request);
|
2018-11-01 22:57:10 +01:00
|
|
|
} if (command == "register_submit") {
|
|
|
|
return protx_register_submit(request);
|
2018-03-03 21:48:00 +01:00
|
|
|
} else if (command == "list") {
|
|
|
|
return protx_list(request);
|
2018-10-25 16:29:50 +02:00
|
|
|
} else if (command == "info") {
|
|
|
|
return protx_info(request);
|
2018-03-14 08:57:08 +01:00
|
|
|
} else if (command == "update_service") {
|
|
|
|
return protx_update_service(request);
|
2018-03-19 09:46:08 +01:00
|
|
|
} else if (command == "update_registrar") {
|
|
|
|
return protx_update_registrar(request);
|
2018-03-19 12:45:15 +01:00
|
|
|
} else if (command == "revoke") {
|
|
|
|
return protx_revoke(request);
|
2018-04-09 14:49:34 +02:00
|
|
|
} else if (command == "diff") {
|
|
|
|
return protx_diff(request);
|
2018-02-13 13:36:59 +01:00
|
|
|
} else {
|
2018-11-05 10:28:38 +01:00
|
|
|
protx_help();
|
2018-02-13 13:36:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif//ENABLE_WALLET
|
|
|
|
|
2018-11-01 22:57:43 +01:00
|
|
|
void bls_generate_help()
|
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
|
|
|
"bls generate\n"
|
|
|
|
"\nReturns a BLS secret/public key pair.\n"
|
|
|
|
"\nResult:\n"
|
|
|
|
"{\n"
|
|
|
|
" \"secret\": \"xxxx\", (string) BLS secret key\n"
|
|
|
|
" \"public\": \"xxxx\", (string) BLS public key\n"
|
|
|
|
"}\n"
|
|
|
|
"\nExamples:\n"
|
|
|
|
+ HelpExampleCli("bls generate", "")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
UniValue bls_generate(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-11-01 22:57:43 +01:00
|
|
|
if (request.fHelp || request.params.size() != 1) {
|
|
|
|
bls_generate_help();
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-11-05 10:28:38 +01:00
|
|
|
[[ noreturn ]] void bls_help()
|
2018-11-01 22:57:43 +01:00
|
|
|
{
|
|
|
|
throw std::runtime_error(
|
|
|
|
"bls \"command\" ...\n"
|
|
|
|
"Set of commands to execute BLS related actions.\n"
|
|
|
|
"To get help on individual commands, use \"help bls command\".\n"
|
|
|
|
"\nArguments:\n"
|
|
|
|
"1. \"command\" (string, required) The command to execute\n"
|
|
|
|
"\nAvailable commands:\n"
|
|
|
|
" generate - Create a BLS secret/public key pair\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-21 21:45:16 +02:00
|
|
|
UniValue _bls(const JSONRPCRequest& request)
|
|
|
|
{
|
2018-11-13 13:23:52 +01:00
|
|
|
if (request.fHelp && request.params.empty()) {
|
2018-11-01 22:57:43 +01:00
|
|
|
bls_help();
|
2018-10-21 21:45:16 +02:00
|
|
|
}
|
|
|
|
|
2018-11-13 13:23:52 +01:00
|
|
|
std::string command;
|
|
|
|
if (request.params.size() >= 1) {
|
|
|
|
command = request.params[0].get_str();
|
|
|
|
}
|
2018-10-21 21:45:16 +02:00
|
|
|
|
|
|
|
if (command == "generate") {
|
|
|
|
return bls_generate(request);
|
|
|
|
} else {
|
2018-11-01 22:57:43 +01:00
|
|
|
bls_help();
|
2018-10-21 21:45:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-13 13:36:59 +01:00
|
|
|
static const CRPCCommand commands[] =
|
|
|
|
{ // category name actor (function) okSafeMode
|
|
|
|
// --------------------- ------------------------ ----------------------- ----------
|
2018-10-21 21:45:16 +02:00
|
|
|
{ "evo", "bls", &_bls, false, {} },
|
2018-02-13 13:36:59 +01:00
|
|
|
#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)
|
|
|
|
{
|
2018-10-26 07:03:14 +02:00
|
|
|
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
|
2018-02-13 13:36:59 +01:00
|
|
|
tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
|
2018-10-26 07:03:14 +02:00
|
|
|
}
|
2018-02-13 13:36:59 +01:00
|
|
|
}
|