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,13 +986,57 @@ 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
static const CRPCCommand commands[] =
{ // category name actor (function)
// --------------------- ------------------------ -----------------------
{ "evo", "quorum", &_quorum, {} },
{ "evo", "submitchainlock", &submitchainlock, {"blockHash", "signature", "blockHeight"} },
{ "evo", "verifychainlock", &verifychainlock, {"blockHash", "signature", "blockHeight"} },
{ "evo", "verifyislock", &verifyislock, {"id", "txid", "signature", "maxHeight"} },
};
Expand Down
1 change: 1 addition & 0 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ void CRPCTable::InitPlatformRestrictions()
{"getbestchainlock", {}},
{"quorum", {"sign", static_cast<uint8_t>(Params().GetConsensus().llmqTypePlatform)}},
{"quorum", {"verify"}},
{"submitchainlock", {}},
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
{"verifyislock", {}},
};
}
Expand Down
19 changes: 19 additions & 0 deletions test/functional/feature_llmq_chainlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,25 @@ 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())
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']
assert_equal(best_0['known_block'], True)
self.nodes[0].submitchainlock(best_1['blockhash'], best_1['signature'], best_1['height'])
best_0 = self.nodes[0].getbestchainlock()
assert_equal(best_0['blockhash'], best_1['blockhash'])
assert_equal(best_0['height'], best_1['height'])
assert_equal(best_0['signature'], best_1['signature'])
assert_equal(best_0['known_block'], False)
self.reconnect_isolated_node(0, 1)
self.sync_all()
Copy link
Collaborator

@knst knst Jan 8, 2024

Choose a reason for hiding this comment

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

@QuantumExplorer that works not as described in the PR description.
If block is unknown -> CL is still submitted and if it succeeds we return true.

We do not return error if block is not know, we just quietly process it, verify, re-transmit to network and add to known chainlocks.
Nothing to change already.

here's even tests for it.


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
1 change: 1 addition & 0 deletions test/functional/rpc_platform_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def test_command(method, params, auth, expexted_status, should_not_match=False):
"getblockcount",
"getbestchainlock",
"quorum",
"submitchainlock",
"verifyislock"]

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