Skip to content

Commit

Permalink
Merge pull request #69 from cakedefi/rpc_mn_creation
Browse files Browse the repository at this point in the history
rpc: refactoring create/resignmasternode
  • Loading branch information
monstrobishi authored Aug 18, 2020
2 parents 823ab00 + 0ea947e commit 0abe645
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 79 deletions.
99 changes: 44 additions & 55 deletions src/masternodes/mn_rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,12 @@ UniValue createmasternode(const JSONRPCRequest& request)
CWallet* const pwallet = GetWallet(request);

RPCHelpMan{"createmasternode",
"\nCreates (and submits to local node and network) a masternode creation transaction with given metadata, spending the given inputs..\n"
"The first optional argument (may be empty array) is an array of specific UTXOs to spend." +
"\nCreates (and submits to local node and network) a masternode creation transaction with given owner and operator addresses, spending the given inputs..\n"
"The last optional argument (may be empty array) is an array of specific UTXOs to spend." +
HelpRequiringPassphrase(pwallet) + "\n",
{
{"ownerAddress", RPCArg::Type::STR, RPCArg::Optional::NO, "Any valid address for keeping collateral amount (any P2PKH or P2WKH address) - used as owner key"},
{"operatorAddress", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Optional (== ownerAddress) masternode operator auth address (P2PKH only, unique)"},
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects",
{
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
Expand All @@ -139,25 +141,13 @@ UniValue createmasternode(const JSONRPCRequest& request)
},
},
},
{"metadata", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
{
{"operatorAuthAddress", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Masternode operator auth address (P2PKH only, unique)" },
{"collateralAddress", RPCArg::Type::STR, RPCArg::Optional::NO, "Any valid address for keeping collateral amount (any P2PKH or P2WKH address) - used as owner key"},
},
},
},
RPCResult{
"\"hex\" (string) The hex-encoded raw transaction with signature(s)\n"
},
RPCExamples{
HelpExampleCli("createmasternode", "\"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\" "
"\"{\\\"operatorAuthAddress\\\":\\\"address\\\","
"\\\"collateralAddress\\\":\\\"address\\\""
"}\"")
+ HelpExampleRpc("createmasternode", "\"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\" "
"\"{\\\"operatorAuthAddress\\\":\\\"address\\\","
"\\\"collateralAddress\\\":\\\"address\\\""
"}\"")
HelpExampleCli("createmasternode", "ownerAddress operatorAddress \"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\"")
+ HelpExampleRpc("createmasternode", "ownerAddress operatorAddress \"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\"")
},
}.Check(request);

Expand All @@ -166,33 +156,25 @@ UniValue createmasternode(const JSONRPCRequest& request)
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot create Masternode while still in Initial Block Download");
}

RPCTypeCheck(request.params, { UniValue::VARR, UniValue::VOBJ }, true);
if (request.params[0].isNull() || request.params[1].isNull())
RPCTypeCheck(request.params, { UniValue::VSTR, UniValue::VSTR, UniValue::VARR }, true);
if (request.params[0].isNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters, arguments 1 and 2 must be non-null, and argument 2 expected as object with "
"{\"operatorAuthAddress\",\"collateralAddress\"}");
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters, at least argument 1 must be non-null");
}
UniValue metaObj = request.params[1].get_obj();
RPCTypeCheckObj(metaObj, {
{ "operatorAuthAddress", UniValue::VSTR },
{ "collateralAddress", UniValue::VSTR }
},
true, true);

std::string collateralAddress = metaObj["collateralAddress"].getValStr();
std::string operatorAuthAddressBase58 = metaObj["operatorAuthAddress"].getValStr();
std::string ownerAddress = request.params[0].getValStr();

CTxDestination collateralDest = DecodeDestination(collateralAddress);
if (collateralDest.which() != 1 && collateralDest.which() != 4)
CTxDestination ownerDest = DecodeDestination(ownerAddress);
if (ownerDest.which() != 1 && ownerDest.which() != 4)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "collateralAddress (" + collateralAddress + ") does not refer to a P2PKH or P2WPKH address");
throw JSONRPCError(RPC_INVALID_PARAMETER, "ownerAddress (" + ownerAddress + ") does not refer to a P2PKH or P2WPKH address");
}
CKeyID ownerAuthKey = collateralDest.which() == 1 ? CKeyID(*boost::get<PKHash>(&collateralDest)) : CKeyID(*boost::get<WitnessV0KeyHash>(&collateralDest));
CKeyID ownerAuthKey = ownerDest.which() == 1 ? CKeyID(*boost::get<PKHash>(&ownerDest)) : CKeyID(*boost::get<WitnessV0KeyHash>(&ownerDest));

CTxDestination operatorDest = operatorAuthAddressBase58 == "" ? collateralDest : DecodeDestination(operatorAuthAddressBase58);
CTxDestination operatorDest = request.params.size() > 1 ? DecodeDestination(request.params[1].getValStr()) : ownerDest;
if (operatorDest.which() != 1 && operatorDest.which() != 4)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorAuthAddress (" + operatorAuthAddressBase58 + ") does not refer to a P2PKH or P2WPKH address");
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorAddress (" + request.params[1].getValStr() + ") does not refer to a P2PKH or P2WPKH address");
}
CKeyID operatorAuthKey = operatorDest.which() == 1 ? CKeyID(*boost::get<PKHash>(&operatorDest)) : CKeyID(*boost::get<WitnessV0KeyHash>(&operatorDest)) ;

Expand All @@ -202,12 +184,12 @@ UniValue createmasternode(const JSONRPCRequest& request)
if (pmasternodesview->ExistMasternode(CMasternodesView::AuthIndex::ByOwner, ownerAuthKey) ||
pmasternodesview->ExistMasternode(CMasternodesView::AuthIndex::ByOperator, ownerAuthKey))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Masternode with collateralAddress == " + collateralAddress + " already exists");
throw JSONRPCError(RPC_INVALID_PARAMETER, "Masternode with ownerAddress == " + ownerAddress + " already exists");
}
if (pmasternodesview->ExistMasternode(CMasternodesView::AuthIndex::ByOwner, operatorAuthKey) ||
pmasternodesview->ExistMasternode(CMasternodesView::AuthIndex::ByOperator, operatorAuthKey))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Masternode with operatorAuthAddress == " + EncodeDestination(operatorDest) + " already exists");
throw JSONRPCError(RPC_INVALID_PARAMETER, "Masternode with operatorAddress == " + EncodeDestination(operatorDest) + " already exists");
}
}

Expand All @@ -220,10 +202,13 @@ UniValue createmasternode(const JSONRPCRequest& request)

CMutableTransaction rawTx;

FillInputs(request.params[0].get_array(), rawTx);
if (request.params.size() > 2)
{
FillInputs(request.params[2].get_array(), rawTx);
}

rawTx.vout.push_back(CTxOut(EstimateMnCreationFee(), scriptMeta));
rawTx.vout.push_back(CTxOut(GetMnCollateralAmount(), GetScriptForDestination(collateralDest)));
rawTx.vout.push_back(CTxOut(GetMnCollateralAmount(), GetScriptForDestination(ownerDest)));

return fundsignsend(rawTx, request, pwallet);
}
Expand All @@ -235,9 +220,10 @@ UniValue resignmasternode(const JSONRPCRequest& request)

RPCHelpMan{"resignmasternode",
"\nCreates (and submits to local node and network) a transaction resigning your masternode. Collateral will be unlocked after " + std::to_string(GetMnResignDelay()) + " blocks.\n"
"The first optional argument (may be empty array) is an array of specific UTXOs to spend. One of UTXO's must belong to the MN's owner (collateral) address" +
"The last optional argument (may be empty array) is an array of specific UTXOs to spend. One of UTXO's must belong to the MN's owner (collateral) address" +
HelpRequiringPassphrase(pwallet) + "\n",
{
{"mn_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The Masternode's ID"},
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects. Provide it if you want to spent specific UTXOs",
{
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
Expand All @@ -248,34 +234,39 @@ UniValue resignmasternode(const JSONRPCRequest& request)
},
},
},
{"mn_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The Masternode's ID"},
},
RPCResult{
"\"hex\" (string) The hex-encoded raw transaction with signature(s)\n"
},
RPCExamples{
HelpExampleCli("resignmasternode", "\"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\" \"mn_id\"")
+ HelpExampleRpc("resignmasternode", "\"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\" \"mn_id\"")
HelpExampleCli("resignmasternode", "mn_id \"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\"")
+ HelpExampleRpc("resignmasternode", "mn_id \"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\"")
},
}.Check(request);

if (pwallet->chain().isInitialBlockDownload()) {
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot resign Masternode while still in Initial Block Download");
}

RPCTypeCheck(request.params, { UniValue::VARR, UniValue::VSTR }, true);
RPCTypeCheck(request.params, { UniValue::VSTR, UniValue::VARR }, true);

std::string const nodeIdStr = request.params[1].getValStr();
std::string const nodeIdStr = request.params[0].getValStr();
uint256 nodeId = uint256S(nodeIdStr);
CTxDestination ownerDest;
{
auto locked_chain = pwallet->chain().lock();
auto optIDs = pmasternodesview->AmIOwner();
if (!optIDs)
auto nodePtr = pmasternodesview->ExistMasternode(nodeId);
if (!nodePtr)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("You are not the owner of masternode %s, or it does not exist", nodeIdStr));
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("The masternode %s does not exist", nodeIdStr));
}
auto nodePtr = pmasternodesview->ExistMasternode(nodeId);

ownerDest = nodePtr->ownerType == 1 ? CTxDestination(PKHash(nodePtr->ownerAuthAddress)) : CTxDestination(WitnessV0KeyHash(nodePtr->ownerAuthAddress));
if (!IsMine(*pwallet, ownerDest))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("You are not the owner of masternode %s", nodeIdStr));
}

if (nodePtr->banHeight != -1)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Masternode %s was criminal, banned at height %i by tx %s", nodeIdStr, nodePtr->banHeight, nodePtr->banTx.GetHex()));
Expand All @@ -285,15 +276,13 @@ UniValue resignmasternode(const JSONRPCRequest& request)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Masternode %s was resigned by tx %s; collateral can be spend at block #%d", nodeIdStr, nodePtr->resignTx.GetHex(), nodePtr->resignHeight + GetMnResignDelay()));
}
ownerDest = nodePtr->ownerType == 1 ? CTxDestination(PKHash(nodePtr->ownerAuthAddress)) : CTxDestination(WitnessV0KeyHash(nodePtr->ownerAuthAddress));
}

CMutableTransaction rawTx;

UniValue inputs = request.params[0].get_array();
if (inputs.size() > 0)
if (request.params.size() > 1)
{
FillInputs(request.params[0].get_array(), rawTx);
FillInputs(request.params[1].get_array(), rawTx);
}
else
{
Expand All @@ -309,7 +298,7 @@ UniValue resignmasternode(const JSONRPCRequest& request)

if (vecOutputs.size() == 0)
{
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Can't find any UTXO's for ownerAuthAddress (%s). Send some coins and try again!", EncodeDestination(ownerDest)));
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Can't find any UTXO's for ownerAddress (%s). Send some coins and try again!", EncodeDestination(ownerDest)));
}
rawTx.vin.push_back(CTxIn(vecOutputs[0].tx->GetHash(), vecOutputs[0].i));
}
Expand Down Expand Up @@ -448,8 +437,8 @@ UniValue listcriminalproofs(const JSONRPCRequest& request)
static const CRPCCommand commands[] =
{ // category name actor (function) params
// ----------------- ------------------------ ----------------------- ----------
{ "masternodes", "createmasternode", &createmasternode, { "inputs", "metadata" } },
{ "masternodes", "resignmasternode", &resignmasternode, { "inputs", "mn_id" } },
{ "masternodes", "createmasternode", &createmasternode, { "ownerAddress", "operatorAddress", "inputs" } },
{ "masternodes", "resignmasternode", &resignmasternode, { "mn_id", "inputs" } },
{ "masternodes", "listmasternodes", &listmasternodes, { "list", "verbose" } },
{ "masternodes", "listcriminalproofs", &listcriminalproofs, { } },
};
Expand Down
6 changes: 2 additions & 4 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 4, "avoid_reuse"},
{ "getnodeaddresses", 0, "count"},
{ "stop", 0, "wait" },
{ "createmasternode", 0, "inputs" },
{ "createmasternode", 1, "metadata" },
{ "resignmasternode", 0, "inputs" },
{ "resignmasternode", 1, "mn_id" },
{ "createmasternode", 2, "inputs" },
{ "resignmasternode", 1, "inputs" },
{ "listmasternodes", 0, "list" },
{ "listmasternodes", 1, "verbose" },

Expand Down
29 changes: 9 additions & 20 deletions test/functional/rpc_mn_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,18 @@ def run_test(self):

# Fail to create: Insufficient funds (not matured coins)
try:
idnode0 = self.nodes[0].createmasternode([], {
# "operatorAuthAddress": operator0,
"collateralAddress": collateral0
})
idnode0 = self.nodes[0].createmasternode(
collateral0
)
except JSONRPCException as e:
errorString = e.error['message']
assert("Insufficient funds" in errorString)

# Create node0
self.nodes[0].generate(1)
idnode0 = self.nodes[0].createmasternode([], {
# "operatorAuthAddress": operator0,
"collateralAddress": collateral0
})
idnode0 = self.nodes[0].createmasternode(
collateral0
)

# Create and sign (only) collateral spending tx
spendTx = self.nodes[0].createrawtransaction([{'txid':idnode0, 'vout':1}],[{collateral0:9.999}])
Expand Down Expand Up @@ -81,26 +79,17 @@ def run_test(self):

# RESIGNING:
#========================
# Fail to resign: Forget to place params in config
# Fail to resign: Have no money on ownerauth address
try:
self.nodes[0].resignmasternode([], idnode0)
except JSONRPCException as e:
errorString = e.error['message']
assert("You are not the owner" in errorString)

# Restart with new params, but have no money on ownerauth address
self.restart_node(0, extra_args=['-masternode_owner='+collateral0])
self.nodes[0].generate(1) # to broke "initial block downloading"
try:
self.nodes[0].resignmasternode([], idnode0)
self.nodes[0].resignmasternode(idnode0)
except JSONRPCException as e:
errorString = e.error['message']
assert("Can't find any UTXO's" in errorString)

# Funding auth address and successful resign
fundingTx = self.nodes[0].sendtoaddress(collateral0, 1)
self.nodes[0].generate(1)
resignTx = self.nodes[0].resignmasternode([], idnode0)
resignTx = self.nodes[0].resignmasternode(idnode0)
self.nodes[0].generate(1)
assert_equal(self.nodes[0].listmasternodes()[idnode0]['state'], "PRE_RESIGNED")
self.nodes[0].generate(10)
Expand Down

0 comments on commit 0abe645

Please sign in to comment.