Skip to content
4 changes: 4 additions & 0 deletions doc/release-notes-6720.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Updated RPCs
------------

* A new optional field `submit` has been introduced to the `protx revoke`, `protx update_registrar`, `protx update_service` RPCs. It behaves identically to `submit` in `protx register` or `protx register_fund`.
66 changes: 49 additions & 17 deletions src/rpc/evo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxP
payload.sig = key.Sign(hash, use_legacy);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

CI failing – run clang-format

clang-format discrepancies were reported by the pipeline. Re-format this file to unblock the build.

🧰 Tools
🪛 GitHub Actions: Clang Diff Format Check

[error] 320-320: Clang format differences found. Code formatting does not match clang-format style. Please run clang-format to fix.

🤖 Prompt for AI Agents
In src/rpc/evo.cpp at line 320, the code formatting does not comply with the
project's clang-format style guidelines, causing CI failures. Run clang-format
on this file to automatically reformat the code according to the project's style
rules, then save the changes and commit the updated file to resolve the
formatting issues.

}

static std::string SignAndSendSpecialTx(const JSONRPCRequest& request, CChainstateHelper& chain_helper, const ChainstateManager& chainman, const CMutableTransaction& tx, bool fSubmit = true)
static std::string SignAndSendSpecialTx(const JSONRPCRequest& request, CChainstateHelper& chain_helper, const ChainstateManager& chainman, const CMutableTransaction& tx, bool fSubmit)
{
{
LOCK(cs_main);
Expand Down Expand Up @@ -539,9 +539,9 @@ static RPCHelpMan protx_register_fund_evo()
},
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", "register_fund_evo \"" + EXAMPLE_ADDRESS[0] + "\" \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
Expand Down Expand Up @@ -578,9 +578,9 @@ static RPCHelpMan protx_register_evo()
},
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", "register_evo \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
Expand Down Expand Up @@ -885,7 +885,7 @@ static RPCHelpMan protx_register_submit()
ptx.vchSig = opt_vchSig.value();

SetTxPayload(tx, ptx);
return SignAndSendSpecialTx(request, chain_helper, chainman, tx);
return SignAndSendSpecialTx(request, chain_helper, chainman, tx, /*fSubmit=*/true);
},
};
}
Expand All @@ -903,9 +903,13 @@ static RPCHelpMan protx_update_service()
GetRpcArg("operatorKey"),
GetRpcArg("operatorPayoutAddress"),
GetRpcArg("feeSourceAddress"),
GetRpcArg("submit"),
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id"
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", "update_service \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" 5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd")
Expand Down Expand Up @@ -935,9 +939,14 @@ static RPCHelpMan protx_update_service_evo()
GetRpcArg("platformHTTPPort"),
GetRpcArg("operatorPayoutAddress"),
GetRpcArg("feeSourceAddress"),
GetRpcArg("submit"),
},
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCExamples{
HelpExampleCli("protx", "update_service_evo \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" \"5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
Expand Down Expand Up @@ -1043,14 +1052,19 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques
}
}

bool fSubmit{true};
if (!request.params[paramIdx + 2].isNull()) {
fSubmit = ParseBoolV(request.params[paramIdx + 2], "submit");
}

FundSpecialTx(*wallet, tx, ptx, feeSource);

const bool isV19active = DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();),
Params().GetConsensus(), Consensus::DEPLOYMENT_V19);
SignSpecialTxPayloadByHash(tx, ptx, keyOperator, !isV19active);
SetTxPayload(tx, ptx);

return SignAndSendSpecialTx(request, chain_helper, chainman, tx);
return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
}

static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_scheme)
Expand All @@ -1070,9 +1084,13 @@ static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_
GetRpcArg("votingAddress_update"),
GetRpcArg("payoutAddress_update"),
GetRpcArg("feeSourceAddress"),
GetRpcArg("submit"),
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id"
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", rpc_example)
Expand Down Expand Up @@ -1149,11 +1167,16 @@ static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str());
}

bool fSubmit{true};
if (!request.params[5].isNull()) {
fSubmit = ParseBoolV(request.params[5], "submit");
}

FundSpecialTx(*wallet, tx, ptx, feeSourceDest);
SignSpecialTxPayloadByHash(tx, ptx, dmn->pdmnState->keyIDOwner, *wallet);
SetTxPayload(tx, ptx);

return SignAndSendSpecialTx(request, chain_helper, chainman, tx);
return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
},
};
}
Expand Down Expand Up @@ -1181,9 +1204,13 @@ static RPCHelpMan protx_revoke()
GetRpcArg("operatorKey"),
GetRpcArg("reason"),
GetRpcArg("feeSourceAddress"),
GetRpcArg("submit"),
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id"
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"")
Expand Down Expand Up @@ -1248,10 +1275,15 @@ static RPCHelpMan protx_revoke()
throw JSONRPCError(RPC_INTERNAL_ERROR, "No payout or fee source addresses found, can't revoke");
}

bool fSubmit{true};
if (!request.params[4].isNull()) {
fSubmit = ParseBoolV(request.params[4], "submit");
}

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

return SignAndSendSpecialTx(request, chain_helper, chainman, tx);
return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
},
};
}
Expand Down
13 changes: 7 additions & 6 deletions test/functional/feature_dip3_deterministicmns.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def run_test(self):
assert old_voting_address != new_voting_address
# also check if funds from payout address are used when no fee source address is specified
node.sendtoaddress(mn.rewards_address, 0.001)
node.protx('update_registrar' if softfork_active(node, 'v19') else 'update_registrar_legacy', mn.proTxHash, "", new_voting_address, "")
mn.update_registrar(node, submit=True, pubKeyOperator="", votingAddr=new_voting_address, rewards_address="")
self.generate(node, 1)
new_dmnState = mn.get_node(self).masternode("status")["dmnState"]
new_voting_address_from_rpc = new_dmnState["votingAddress"]
Expand All @@ -237,14 +237,15 @@ def create_mn_collateral(self, node, mn: MasternodeInfo):
# register a protx MN and also fund it (using collateral inside ProRegTx)
def register_fund_mn(self, node, mn: MasternodeInfo):
node.sendtoaddress(mn.fundsAddr, mn.get_collateral_value() + 0.001)
txid = node.protx('register_fund' if softfork_active(node, 'v19') else 'register_fund_legacy', mn.collateral_address, '127.0.0.1:%d' % mn.nodePort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
txid = mn.register_fund(node, submit=True)
assert txid is not None
vout = mn.get_collateral_vout(node, txid)
mn.set_params(proTxHash=txid, collateral_txid=txid, collateral_vout=vout)

# create a protx MN which refers to an existing collateral
def register_mn(self, node, mn: MasternodeInfo):
node.sendtoaddress(mn.fundsAddr, 0.001)
proTxHash = node.protx('register' if softfork_active(node, 'v19') else 'register_legacy', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.nodePort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr)
proTxHash = mn.register(node, submit=True)
mn.set_params(proTxHash=proTxHash)
self.generate(node, 1, sync_fun=self.no_op)

Expand All @@ -263,14 +264,14 @@ def spend_mn_collateral(self, mn: MasternodeInfo, with_dummy_input_output=False)

def update_mn_payee(self, mn: MasternodeInfo, payee):
self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001)
self.nodes[0].protx('update_registrar' if softfork_active(self.nodes[0], 'v19') else 'update_registrar_legacy', mn.proTxHash, '', '', payee, mn.fundsAddr)
mn.update_registrar(self.nodes[0], submit=True, pubKeyOperator="", votingAddr="", rewards_address=payee, fundsAddr=mn.fundsAddr)
self.generate(self.nodes[0], 1)
info = self.nodes[0].protx('info', mn.proTxHash)
assert info['state']['payoutAddress'] == payee

def test_protx_update_service(self, mn: MasternodeInfo):
self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001)
self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.2:%d' % mn.nodePort, mn.keyOperator, "", mn.fundsAddr)
mn.update_service(self.nodes[0], submit=True, ipAndPort=f'127.0.0.2:{mn.nodePort}')
self.generate(self.nodes[0], 1)
for node in self.nodes:
protx_info = node.protx('info', mn.proTxHash)
Expand All @@ -279,7 +280,7 @@ def test_protx_update_service(self, mn: MasternodeInfo):
assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.nodePort)

# undo
self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.1:%d' % mn.nodePort, mn.keyOperator, "", mn.fundsAddr)
mn.update_service(self.nodes[0], submit=True)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)

def assert_mnlists(self, mns):
Expand Down
14 changes: 6 additions & 8 deletions test/functional/feature_dip3_v19.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,8 @@ def run_test(self):
assert evo_info_3 is not None
self.dynamically_evo_update_service(evo_info_0, 9, should_be_rejected=True)

revoke_protx = self.mninfo[-1].proTxHash
revoke_keyoperator = self.mninfo[-1].keyOperator
self.log.info(f"Trying to revoke proTx:{revoke_protx}")
self.test_revoke_protx(evo_info_3.nodeIdx, revoke_protx, revoke_keyoperator)
self.log.info(f"Trying to revoke proTx:{self.mninfo[-1].proTxHash}")
self.test_revoke_protx(evo_info_3.nodeIdx, self.mninfo[-1])

self.mine_quorum(llmq_type_name='llmq_test', llmq_type=100)

Expand All @@ -115,14 +113,14 @@ def run_test(self):

self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())

def test_revoke_protx(self, node_idx, revoke_protx, revoke_keyoperator):
def test_revoke_protx(self, node_idx, revoke_mn: MasternodeInfo):
funds_address = self.nodes[0].getnewaddress()
fund_txid = self.nodes[0].sendtoaddress(funds_address, 1)
self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block
tip = self.generate(self.nodes[0], 1)[0]
assert_equal(self.nodes[0].getrawtransaction(fund_txid, 1, tip)['confirmations'], 1)

protx_result = self.nodes[0].protx('revoke', revoke_protx, revoke_keyoperator, 1, funds_address)
protx_result = revoke_mn.revoke(self.nodes[0], submit=True, reason=1, fundsAddr=funds_address)
self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block
tip = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0]
assert_equal(self.nodes[0].getrawtransaction(protx_result, 1, tip)['confirmations'], 1)
Expand All @@ -131,9 +129,9 @@ def test_revoke_protx(self, node_idx, revoke_protx, revoke_keyoperator):
self.wait_until(lambda: self.nodes[node_idx].getconnectioncount() == 0)
self.connect_nodes(node_idx, 0)
self.sync_all()
self.log.info(f"Successfully revoked={revoke_protx}")
self.log.info(f"Successfully revoked={revoke_mn.proTxHash}")
for mn in self.mninfo: # type: MasternodeInfo
if mn.proTxHash == revoke_protx:
if mn.proTxHash == revoke_mn.proTxHash:
self.mninfo.remove(mn)
return

Expand Down
2 changes: 1 addition & 1 deletion test/functional/feature_llmq_simplepose.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def repair_masternodes(self, restart):
if check_banned(self.nodes[0], mn) or check_punished(self.nodes[0], mn):
addr = self.nodes[0].getnewaddress()
self.nodes[0].sendtoaddress(addr, 0.1)
self.nodes[0].protx('update_service', mn.proTxHash, f'127.0.0.1:{mn.nodePort}', mn.keyOperator, "", addr)
mn.update_service(self.nodes[0], submit=True, fundsAddr=addr)
if restart:
self.stop_node(mn.nodeIdx)
self.start_masternode(mn)
Expand Down
Loading
Loading