Skip to content

Commit

Permalink
feat(rpc): submit chainlock signature if needed RPC (#5765)
Browse files Browse the repository at this point in the history
## Issue being fixed or feature implemented
Once Platform is live, there could be an edge case where the CL could
arrive to an EvoNode faster through Platform quorum than regular P2P
propagation.

## What was done?
This PR introduces a new RPC `submitchainlock` with the following 3
mandatory parameters:
- `blockHash`, `signature` and `height`.

Besides some basic tests:
- If the block is unknown then the RPC returns an error (could happen if
the node is stucked)
- If the signature is not verified then the RPC return an error.
- If the node already has this CL, the RPC returns true.
- If the node doesn't have this CL, it inserts it, broadcast it through
the inv system and return true.

## How Has This Been Tested?
`feature_llmq_chainlocks.py` was modified with the following scenario:

1. node0 is isolated from the rest of the network
2. node1 mines a new block and waits for CL
3. Make sure node0 doesn't know the new block/CL (by checking
`getbestchainlock()`)
4. CL is submitted via the new RPC on node0
5. checking `getbestchainlock()` and make sure the CL was processed +
'known_block' is false
6. reconnect node0

## Breaking Changes
no

## Checklist:
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [x] I have added or updated relevant unit/integration/functional/e2e
tests
- [ ] I have made corresponding changes to the documentation
- [x] I have assigned this pull request to a milestone _(for repository
code-owners and collaborators only)_

---------

Co-authored-by: UdjinM6 <[email protected]>
Co-authored-by: thephez <[email protected]>
  • Loading branch information
3 people authored Dec 19, 2023
1 parent 714be63 commit 3bc77a6
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 0 deletions.
5 changes: 5 additions & 0 deletions doc/release-notes-5765.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added RPC
--------

- `submitchainlock` RPC allows the submission of a ChainLock signature.
Note: This RPC is whitelisted for the Platform RPC user.
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", {}},
{"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']
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()

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

0 comments on commit 3bc77a6

Please sign in to comment.