Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): submit chainlock signature if needed RPC #5765

Merged
merged 11 commits into from
Dec 19, 2023
Merged
44 changes: 44 additions & 0 deletions src/rpc/quorums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,49 @@ static UniValue verifyislock(const JSONRPCRequest& request)
return llmq_ctx.sigman->VerifyRecoveredSig(llmqType, *llmq_ctx.qman, signHeight, id, txid, sig, 0) ||
llmq_ctx.sigman->VerifyRecoveredSig(llmqType, *llmq_ctx.qman, signHeight, id, txid, sig, signOffset);
}

static void submitchainlock_help(const JSONRPCRequest& request)
{
RPCHelpMan{"submitchainlock",
"Submit a ChainLock signature if needed\n",
{
{"blockHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash of the ChainLock."},
{"signature", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The signature of the ChainLock."},
{"blockHeight", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height of the ChainLock."},
},
RPCResults{},
RPCExamples{""},
}.Check(request);
}

static UniValue submitchainlock(const JSONRPCRequest& request)
{
submitchainlock_help(request);

const uint256 nBlockHash(ParseHashV(request.params[0], "blockHash"));

const int nBlockHeight = ParseInt32V(request.params[2], "blockHeight");
if (nBlockHeight <= 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid block height");
}

CBLSSignature sig;
if (!sig.SetHexStr(request.params[1].get_str(), false) && !sig.SetHexStr(request.params[1].get_str(), true)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
}


const LLMQContext& llmq_ctx = EnsureLLMQContext(EnsureAnyNodeContext(request.context));
auto clsig = llmq::CChainLockSig(nBlockHeight, nBlockHash, sig);
if (!llmq_ctx.clhandler->VerifyChainLock(clsig)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature");
}

llmq_ctx.clhandler->ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig));
return true;
}


void RegisterQuorumsRPCCommands(CRPCTable &tableRPC)
{
// clang-format off
Expand All @@ -995,6 +1038,7 @@ static const CRPCCommand commands[] =
{ "evo", "quorum", &_quorum, {} },
{ "evo", "verifychainlock", &verifychainlock, {"blockHash", "signature", "blockHeight"} },
{ "evo", "verifyislock", &verifyislock, {"id", "txid", "signature", "maxHeight"} },
{ "evo", "submitchainlock", &submitchainlock, {"blockHash", "signature", "blockHeight"} },
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
};
// clang-format on
for (const auto& command : commands) {
Expand Down
1 change: 1 addition & 0 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ void CRPCTable::InitPlatformRestrictions()
{"quorum", {"sign", static_cast<uint8_t>(Params().GetConsensus().llmqTypePlatform)}},
{"quorum", {"verify"}},
{"verifyislock", {}},
{"submitchainlock", {}},
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand Down
18 changes: 18 additions & 0 deletions test/functional/feature_llmq_chainlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ def run_test(self):
self.wait_for_chainlocked_block_all_nodes(self.nodes[1].getbestblockhash())
self.test_coinbase_best_cl(self.nodes[0])

self.log.info("Isolate node, mine on another, reconnect and submit CL via RPC")
self.isolate_node(0)
self.nodes[1].generate(1)
self.wait_for_chainlocked_block(self.nodes[1], self.nodes[1].getbestblockhash())
self.reconnect_isolated_node(0, 1)
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
time.sleep(1)
best_0 = self.nodes[0].getbestchainlock()
best_1 = self.nodes[1].getbestchainlock()
assert best_0['blockhash'] != best_1['blockhash']
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
assert best_0['height'] != best_1['height']
assert best_0['signature'] != best_1['signature']
self.log.info(self.nodes[0].submitchainlock(best_1['blockhash'], best_1['signature'], best_1['height']))
best_0 = self.nodes[0].getbestchainlock()
best_1 = self.nodes[1].getbestchainlock()
assert best_0['blockhash'] == best_1['blockhash']
assert best_0['height'] == best_1['height']
assert best_0['signature'] == best_1['signature']

self.log.info("Isolate node, mine on both parts of the network, and reconnect")
self.isolate_node(0)
bad_tip = self.nodes[0].generate(5)[-1]
Expand Down
3 changes: 2 additions & 1 deletion test/functional/rpc_platform_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ def test_command(method, params, auth, expexted_status, should_not_match=False):
"getblockcount",
"getbestchainlock",
"quorum",
"verifyislock"]
"verifyislock",
"submitchainlock"]
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved

help_output = self.nodes[0].help().split('\n')
nonwhitelisted = set()
Expand Down
Loading