Skip to content

Commit

Permalink
Develop updatemasternode rpc function (#687)
Browse files Browse the repository at this point in the history
* Develop updatemasternode rpc function

* Fix cminor bug and update unittest

* Fix lint error

* Fix lint error

* Erase old Operator record and remove subnode time updates.

* Resolve char conflict

* Resolve conflicts with base branch

Co-authored-by: Peter Bushnell <[email protected]>
  • Loading branch information
ShengguangXiao and Bushstar authored Oct 20, 2021
1 parent 9a28be6 commit 3ca3988
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 14 deletions.
33 changes: 31 additions & 2 deletions src/masternodes/masternodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ CMasternode::State CMasternode::GetState(int height) const
// Special case for genesis block
int activationDelay = height < EunosPayaHeight ? GetMnActivationDelay(height) : GetMnActivationDelay(creationHeight);
if (creationHeight == 0 || height >= creationHeight + activationDelay) {
return State::ENABLED;
return State::ENABLED;
}
return State::PRE_ENABLED;
}

if (resignHeight != -1) { // pre-resigned or resigned
int resignDelay = height < EunosPayaHeight ? GetMnResignDelay(height) : GetMnResignDelay(resignHeight);
if (height < resignHeight + resignDelay) {
return State::PRE_RESIGNED;
return State::PRE_RESIGNED;
}
return State::RESIGNED;
}
Expand Down Expand Up @@ -363,6 +363,35 @@ Res CMasternodesView::RemForcedRewardAddress(uint256 const & nodeId, int height)
return Res::Ok();
}

Res CMasternodesView::UpdateMasternode(uint256 const & nodeId, char operatorType, const CKeyID& operatorAuthAddress, int height) {
// auth already checked!
auto node = GetMasternode(nodeId);
if (!node) {
return Res::Err("node %s does not exists", nodeId.ToString());
}

const auto state = node->GetState(height);
if (state != CMasternode::ENABLED) {
return Res::Err("node %s state is not 'ENABLED'", nodeId.ToString());
}

if (operatorType == node->operatorType && operatorAuthAddress == node->operatorAuthAddress) {
return Res::Err("The new operator is same as existing operator");
}

// Remove old record
EraseBy<Operator>(node->operatorAuthAddress);

node->operatorType = operatorType;
node->operatorAuthAddress = operatorAuthAddress;

// Overwrite and create new record
WriteBy<ID>(nodeId, *node);
WriteBy<Operator>(node->operatorAuthAddress, nodeId);

return Res::Ok();
}

void CMasternodesView::SetMasternodeLastBlockTime(const CKeyID & minter, const uint32_t &blockHeight, const int64_t& time)
{
auto nodeId = GetMasternodeIdByOperator(minter);
Expand Down
1 change: 1 addition & 0 deletions src/masternodes/masternodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class CMasternodesView : public virtual CStorageView
Res UnResignMasternode(uint256 const & nodeId, uint256 const & resignTx);
Res SetForcedRewardAddress(uint256 const & nodeId, const char rewardAddressType, CKeyID const & rewardAddress, int height);
Res RemForcedRewardAddress(uint256 const & nodeId, int height);
Res UpdateMasternode(uint256 const & nodeId, char operatorType, const CKeyID& operatorAuthAddress, int height);

// Get blocktimes for non-subnode and subnode with fork logic
std::vector<int64_t> GetBlockTimes(const CKeyID& keyID, const uint32_t blockHeight, const int32_t creationHeight, const uint16_t timelock);
Expand Down
14 changes: 13 additions & 1 deletion src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ std::string ToString(CustomTxType type) {
{
case CustomTxType::CreateMasternode: return "CreateMasternode";
case CustomTxType::ResignMasternode: return "ResignMasternode";
case CustomTxType::SetForcedRewardAddress: return "SetForcedRewardAddress";
case CustomTxType::SetForcedRewardAddress: return "SetForcedRewardAddress";
case CustomTxType::RemForcedRewardAddress: return "RemForcedRewardAddress";
case CustomTxType::UpdateMasternode: return "UpdateMasternode";
case CustomTxType::CreateToken: return "CreateToken";
case CustomTxType::UpdateToken: return "UpdateToken";
case CustomTxType::UpdateTokenAny: return "UpdateTokenAny";
Expand Down Expand Up @@ -124,6 +125,7 @@ CCustomTxMessage customTypeToMessage(CustomTxType txType) {
case CustomTxType::ResignMasternode: return CResignMasterNodeMessage{};
case CustomTxType::SetForcedRewardAddress: return CSetForcedRewardAddressMessage{};
case CustomTxType::RemForcedRewardAddress: return CRemForcedRewardAddressMessage{};
case CustomTxType::UpdateMasternode: return CUpdateMasterNodeMessage{};
case CustomTxType::CreateToken: return CCreateTokenMessage{};
case CustomTxType::UpdateToken: return CUpdateTokenPreAMKMessage{};
case CustomTxType::UpdateTokenAny: return CUpdateTokenMessage{};
Expand Down Expand Up @@ -259,6 +261,11 @@ class CCustomMetadataParseVisitor : public boost::static_visitor<Res>
return !res ? res : serialize(obj);
}

Res operator()(CUpdateMasterNodeMessage& obj) const {
auto res = isPostFortCanningFork();
return !res ? res : serialize(obj);
}

Res operator()(CCreateTokenMessage& obj) const {
auto res = isPostAMKFork();
return !res ? res : serialize(obj);
Expand Down Expand Up @@ -910,6 +917,11 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
return mnview.RemForcedRewardAddress(obj.nodeId, height);
}

Res operator()(const CUpdateMasterNodeMessage& obj) const {
auto res = HasCollateralAuth(obj.mnId);
return !res ? res : mnview.UpdateMasternode(obj.mnId, obj.operatorType, obj.operatorAuthAddress, height);
}

Res operator()(const CCreateTokenMessage& obj) const {
auto res = CheckTokenCreationTx();
if (!res) {
Expand Down
25 changes: 21 additions & 4 deletions src/masternodes/mn_checks.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ enum class CustomTxType : uint8_t
Reject = 1, // Invalid TX type. Returned by GuessCustomTxType on invalid custom TX.

// masternodes:
CreateMasternode = 'C',
ResignMasternode = 'R',
CreateMasternode = 'C',
ResignMasternode = 'R',
UpdateMasternode = 'm',
SetForcedRewardAddress = 'F',
RemForcedRewardAddress = 'f',
// custom tokens:
Expand Down Expand Up @@ -83,7 +84,7 @@ enum class CustomTxType : uint8_t
// Loans
LoanSetCollateralToken = 'c',
LoanSetLoanToken = 'g',
LoanUpdateLoanToken = 'f',
LoanUpdateLoanToken = 'x',
LoanScheme = 'L',
DefaultLoanScheme = 'd',
DestroyLoanScheme = 'D',
Expand All @@ -92,7 +93,7 @@ enum class CustomTxType : uint8_t
UpdateVault = 'v',
DepositToVault = 'S',
WithdrawFromVault = 'J',
LoanTakeLoan = 'F',
LoanTakeLoan = 'X',
LoanPaybackLoan = 'H',
AuctionBid = 'I'
};
Expand All @@ -104,6 +105,7 @@ inline CustomTxType CustomTxCodeToType(uint8_t ch) {
case CustomTxType::ResignMasternode:
case CustomTxType::SetForcedRewardAddress:
case CustomTxType::RemForcedRewardAddress:
case CustomTxType::UpdateMasternode:
case CustomTxType::CreateToken:
case CustomTxType::MintToken:
case CustomTxType::UpdateToken:
Expand Down Expand Up @@ -230,6 +232,20 @@ struct CRemForcedRewardAddressMessage {
}
};

struct CUpdateMasterNodeMessage {
uint256 mnId;
char operatorType;
CKeyID operatorAuthAddress;

ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(mnId);
READWRITE(operatorType);
READWRITE(operatorAuthAddress);
}
};

struct CCreateTokenMessage : public CToken {
using CToken::CToken;

Expand Down Expand Up @@ -305,6 +321,7 @@ typedef boost::variant<
CResignMasterNodeMessage,
CSetForcedRewardAddressMessage,
CRemForcedRewardAddressMessage,
CUpdateMasterNodeMessage,
CCreateTokenMessage,
CUpdateTokenPreAMKMessage,
CUpdateTokenMessage,
Expand Down
9 changes: 8 additions & 1 deletion src/masternodes/rpc_customtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class CCustomTxRpcVisitor : public boost::static_visitor<void>

void operator()(const CCreateMasterNodeMessage& obj) const {
rpcInfo.pushKV("collateralamount", ValueFromAmount(GetMnCollateralAmount(height)));
rpcInfo.pushKV("masternodeoperator", EncodeDestination(obj.operatorType == 1 ?
rpcInfo.pushKV("masternodeoperator", EncodeDestination(obj.operatorType == PKHashType ?
CTxDestination(PKHash(obj.operatorAuthAddress)) :
CTxDestination(WitnessV0KeyHash(obj.operatorAuthAddress))));
rpcInfo.pushKV("timelock", CMasternode::GetTimelockToString(static_cast<CMasternode::TimeLock>(obj.timelock)));
Expand All @@ -90,6 +90,13 @@ class CCustomTxRpcVisitor : public boost::static_visitor<void>
rpcInfo.pushKV("mc_id", obj.nodeId.GetHex());
}

void operator()(const CUpdateMasterNodeMessage& obj) const {
rpcInfo.pushKV("id", obj.mnId.GetHex());
rpcInfo.pushKV("masternodeoperator", EncodeDestination(obj.operatorType == PKHashType ?
CTxDestination(PKHash(obj.operatorAuthAddress)) :
CTxDestination(WitnessV0KeyHash(obj.operatorAuthAddress))));
}

void operator()(const CCreateTokenMessage& obj) const {
rpcInfo.pushKV("creationTx", tx.GetHash().GetHex());
tokenInfo(obj);
Expand Down
127 changes: 121 additions & 6 deletions src/masternodes/rpc_masternodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ UniValue createmasternode(const JSONRPCRequest& request)

UniValue setforcedrewardaddress(const JSONRPCRequest& request)
{
CWallet* const pwallet = GetWallet(request);
auto pwallet = GetWallet(request);

RPCHelpMan{"setforcedrewardaddress",
"\nCreates (and submits to local node and network) a set forced reward address transaction with given masternode id and reward address\n"
Expand Down Expand Up @@ -241,7 +241,6 @@ UniValue setforcedrewardaddress(const JSONRPCRequest& request)
}

pwallet->BlockUntilSyncedToCurrentChain();
LockedCoinsScopedGuard lcGuard(pwallet);

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

Expand Down Expand Up @@ -311,15 +310,15 @@ UniValue setforcedrewardaddress(const JSONRPCRequest& request)
if (optAuthTx)
AddCoins(coins, *optAuthTx, targetHeight);
auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg});
execTestTx(CTransaction(rawTx), targetHeight, metadata, CSetForcedRewardAddressMessage{}, coins);
execTestTx(CTransaction(rawTx), targetHeight, optAuthTx);
}

return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex();
}

UniValue remforcedrewardaddress(const JSONRPCRequest& request)
{
CWallet* const pwallet = GetWallet(request);
auto pwallet = GetWallet(request);

RPCHelpMan{"remforcedrewardaddress",
"\nCreates (and submits to local node and network) a remove forced reward address transaction with given masternode id\n"
Expand Down Expand Up @@ -352,7 +351,6 @@ UniValue remforcedrewardaddress(const JSONRPCRequest& request)
}

pwallet->BlockUntilSyncedToCurrentChain();
LockedCoinsScopedGuard lcGuard(pwallet);

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

Expand Down Expand Up @@ -411,7 +409,7 @@ UniValue remforcedrewardaddress(const JSONRPCRequest& request)
if (optAuthTx)
AddCoins(coins, *optAuthTx, targetHeight);
auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg});
execTestTx(CTransaction(rawTx), targetHeight, metadata, CRemForcedRewardAddressMessage{}, coins);
execTestTx(CTransaction(rawTx), targetHeight, optAuthTx);
}

return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex();
Expand Down Expand Up @@ -504,6 +502,122 @@ UniValue resignmasternode(const JSONRPCRequest& request)
return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex();
}

UniValue updatemasternode(const JSONRPCRequest& request)
{
auto pwallet = GetWallet(request);

RPCHelpMan{"updatemasternode",
"\nCreates (and submits to local node and network) a masternode update transaction which update the masternode 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",
{
{"mn_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The Masternode's ID"},
{"operatorAddress", RPCArg::Type::STR, RPCArg::Optional::NO, "The new 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, "",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
},
},
},
},
},
RPCResult{
"\"hash\" (string) The hex-encoded hash of broadcasted transaction\n"
},
RPCExamples{
HelpExampleCli("updatemasternode", "mn_id operatorAddress '[{\"txid\":\"id\",\"vout\":0}]'")
+ HelpExampleRpc("updatemasternode", "mn_id operatorAddress '[{\"txid\":\"id\",\"vout\":0}]'")
},
}.Check(request);

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

bool forkCanning;
{
LOCK(cs_main);
forkCanning = ::ChainActive().Tip()->height >= Params().GetConsensus().FortCanningHeight;
}

if (!forkCanning) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "updatemasternode cannot be called before Fortcanning hard fork");
}

RPCTypeCheck(request.params, { UniValue::VSTR, UniValue::VSTR, UniValue::VARR }, true);
if (request.params[0].isNull() || request.params[1].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters, at least argument 2 must be non-null");
}

std::string const nodeIdStr = request.params[0].getValStr();
uint256 const nodeId = uint256S(nodeIdStr);
CTxDestination ownerDest;
int targetHeight;
{
LOCK(cs_main);
auto nodePtr = pcustomcsview->GetMasternode(nodeId);
if (!nodePtr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("The masternode %s does not exist", nodeIdStr));
}
ownerDest = nodePtr->ownerType == 1 ? CTxDestination(PKHash(nodePtr->ownerAuthAddress)) : CTxDestination(WitnessV0KeyHash(nodePtr->ownerAuthAddress));

targetHeight = ::ChainActive().Height() + 1;
}

std::string operatorAddress = request.params[1].getValStr();
CTxDestination operatorDest = DecodeDestination(operatorAddress);

// check type here cause need operatorAuthKey. all other validation (for owner for ex.) in further apply/create
if (operatorDest.which() != PKHashType && operatorDest.which() != WitV0KeyHashType) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorAddress (" + operatorAddress + ") does not refer to a P2PKH or P2WPKH address");
}

const auto txVersion = GetTransactionVersion(targetHeight);
CMutableTransaction rawTx(txVersion);

CTransactionRef optAuthTx;
std::set<CScript> auths{GetScriptForDestination(ownerDest)};
rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false, optAuthTx, request.params[2]);

// Return change to owner address
CCoinControl coinControl;
if (IsValidDestination(ownerDest)) {
coinControl.destChange = ownerDest;
}

CKeyID const operatorAuthKey = operatorDest.which() == PKHashType ? CKeyID(*boost::get<PKHash>(&operatorDest)) : CKeyID(*boost::get<WitnessV0KeyHash>(&operatorDest));

CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION);
metadata << static_cast<unsigned char>(CustomTxType::UpdateMasternode)
<< nodeId
<< static_cast<char>(operatorDest.which()) << operatorAuthKey;

CScript scriptMeta;
scriptMeta << OP_RETURN << ToByteVector(metadata);

rawTx.vout.push_back(CTxOut(0, scriptMeta));

fund(rawTx, pwallet, optAuthTx, &coinControl);

// check execution
{
LOCK(cs_main);
CCoinsViewCache coins(&::ChainstateActive().CoinsTip());
if (optAuthTx)
AddCoins(coins, *optAuthTx, targetHeight);
auto stream = CDataStream{SER_NETWORK, PROTOCOL_VERSION, nodeId, static_cast<char>(operatorDest.which()), operatorAuthKey};

auto metadata = ToByteVector(stream);
execTestTx(CTransaction(rawTx), targetHeight, optAuthTx);
}
return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex();
}

UniValue listmasternodes(const JSONRPCRequest& request)
{
auto pwallet = GetWallet(request);
Expand Down Expand Up @@ -885,6 +999,7 @@ static const CRPCCommand commands[] =
// --------------- ---------------------- --------------------- ----------
{"masternodes", "createmasternode", &createmasternode, {"ownerAddress", "operatorAddress", "inputs"}},
{"masternodes", "resignmasternode", &resignmasternode, {"mn_id", "inputs"}},
{"masternodes", "updatemasternode", &updatemasternode, {"mn_id", "operatorAddress", "inputs"}},
{"masternodes", "listmasternodes", &listmasternodes, {"pagination", "verbose"}},
{"masternodes", "getmasternode", &getmasternode, {"mn_id"}},
{"masternodes", "getmasternodeblocks", &getmasternodeblocks, {"identifier", "depth"}},
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "resignmasternode", 1, "inputs" },
{ "setforcedrewardaddress", 2, "inputs" },
{ "remforcedrewardaddress", 1, "inputs" },
{ "updatemasternode", 2, "inputs" },
{ "listmasternodes", 0, "pagination" },
{ "listmasternodes", 1, "verbose" },
{ "getmasternodeblocks", 0, "identifier"},
Expand Down
Loading

0 comments on commit 3ca3988

Please sign in to comment.