Skip to content

Commit 0972dfe

Browse files
Merge #6455: feat: implement new RPC getislocks and add hex field to RPC getbestchainlock
82c0cf2 docs: release notes for new RPC getislocks and for RPC getbestchainlock changes (Konstantin Akimov) c1a861e feat: implement new rpc getislock (Konstantin Akimov) 53a5707 feat: add new field hex to RPC getbestchainlock (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented #6391 > To register an identity in the DashPlatform network, there is a required field of InstantLock or ChainLock buffer in IdentityCreateTransition. You first create and broadcast Core transaction, then wait for InstantLock or ChainLock, and then create and broadcast transaction in the Platform chain with that data. ## What was done? To retrieve information about ChainLocks has been implemented new field `hex` for RPC getbestchainlock which return information in zmq-compatible hex-encoded binary format. To retrieve information about InstantSend Lock has been implemented a new RPC `getislocks` that return information for list of txids in human-friendly JSON format and binary hex-encoded zmq-compatible format. ## How Has This Been Tested? See new checks in functional test `interface_zmq_dash.py` ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] 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 ACKs for top commit: PastaPastaPasta: utACK 82c0cf2 Tree-SHA512: 175af621bcea3d2896d629dad080bdcde3ee471be6a849fb5a21bfcf1e580926e26b42906cf636f245529a79af9acac757487c47ad3584afd879057633bef6c2
2 parents 4c166e1 + 82c0cf2 commit 0972dfe

File tree

5 files changed

+114
-5
lines changed

5 files changed

+114
-5
lines changed

doc/release-notes-6455.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## New RPCs
2+
3+
- **`getislocks`**
4+
- Retrieves the InstantSend lock data for the given transaction IDs (txids).
5+
Returns the lock information in both human-friendly JSON format and binary hex-encoded zmq-compatible format.
6+
7+
## Updated RPCs
8+
9+
- **`getbestchainlock` Changes**
10+
- A new hex field has been added to the getbestchainlock RPC, which returns the ChainLock information in zmq-compatible, hex-encoded binary format.
11+

src/rpc/blockchain.cpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -241,29 +241,38 @@ static RPCHelpMan getbestchainlock()
241241
{RPCResult::Type::NUM, "height", "The block height or index"},
242242
{RPCResult::Type::STR_HEX, "signature", "The ChainLock's BLS signature"},
243243
{RPCResult::Type::BOOL, "known_block", "True if the block is known by our node"},
244+
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for best ChainLock"},
244245
}},
245246
RPCExamples{
246247
HelpExampleCli("getbestchainlock", "")
247248
+ HelpExampleRpc("getbestchainlock", "")
248249
},
249250
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
250251
{
251-
UniValue result(UniValue::VOBJ);
252-
253252
const NodeContext& node = EnsureAnyNodeContext(request.context);
254253

255254
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
256255
const llmq::CChainLockSig clsig = llmq_ctx.clhandler->GetBestChainLock();
257256
if (clsig.IsNull()) {
258257
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to find any ChainLock");
259258
}
259+
260+
UniValue result(UniValue::VOBJ);
261+
260262
result.pushKV("blockhash", clsig.getBlockHash().GetHex());
261263
result.pushKV("height", clsig.getHeight());
262264
result.pushKV("signature", clsig.getSig().ToString());
263265

264-
const ChainstateManager& chainman = EnsureChainman(node);
265-
LOCK(cs_main);
266-
result.pushKV("known_block", chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()) != nullptr);
266+
{
267+
const ChainstateManager& chainman = EnsureChainman(node);
268+
LOCK(cs_main);
269+
result.pushKV("known_block", chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()) != nullptr);
270+
}
271+
272+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
273+
ssTx << clsig;
274+
result.pushKV("hex", HexStr(ssTx));
275+
267276
return result;
268277
},
269278
};

src/rpc/client.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
116116
{ "gettransaction", 1, "include_watchonly" },
117117
{ "gettransaction", 2, "verbose" },
118118
{ "getrawtransaction", 1, "verbose" },
119+
{ "getislocks", 0, "txids" },
119120
{ "getrawtransactionmulti", 0, "transactions" },
120121
{ "getrawtransactionmulti", 1, "verbose" },
121122
{ "gettxchainlocks", 0, "txids" },

src/rpc/rawtransaction.cpp

+82
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ static RPCHelpMan getrawtransactionmulti() {
300300
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
301301
"If false, return a string, otherwise return a json object"},
302302
},
303+
// TODO: replace RPCResults to proper annotation
303304
RPCResults{},
304305
RPCExamples{
305306
HelpExampleCli("getrawtransactionmulti",
@@ -366,6 +367,86 @@ static RPCHelpMan getrawtransactionmulti() {
366367
};
367368
}
368369

370+
static RPCHelpMan getislocks()
371+
{
372+
return RPCHelpMan{"getislocks",
373+
"\nReturns the raw InstantSend lock data for each txids. Returns Null if there is no known IS yet.",
374+
{
375+
{"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction ids (no more than 100)",
376+
{
377+
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
378+
},
379+
},
380+
},
381+
RPCResult{
382+
RPCResult::Type::ARR, "", "Response is an array with the same size as the input txids",
383+
{{RPCResult::Type::OBJ, "", "",
384+
{
385+
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
386+
{RPCResult::Type::ARR, "inputs", "The inputs",
387+
{
388+
{RPCResult::Type::OBJ, "", "",
389+
{
390+
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
391+
{RPCResult::Type::NUM, "vout", "The output number"},
392+
},
393+
},
394+
}},
395+
{RPCResult::Type::STR_HEX, "cycleHash", "The Cycle Hash"},
396+
{RPCResult::Type::STR_HEX, "signature", "The InstantSend's BLS signature"},
397+
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
398+
}},
399+
RPCResult{"if no InstantSend Lock is known for specified txid",
400+
RPCResult::Type::STR, "data", "Just 'None' string"
401+
},
402+
}},
403+
RPCExamples{
404+
HelpExampleCli("getislocks", "'[\"txid\",...]'")
405+
+ HelpExampleRpc("getislocks", "'[\"txid\",...]'")
406+
},
407+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
408+
{
409+
const NodeContext& node = EnsureAnyNodeContext(request.context);
410+
411+
UniValue result_arr(UniValue::VARR);
412+
UniValue txids = request.params[0].get_array();
413+
if (txids.size() > 100) {
414+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Up to 100 txids only");
415+
}
416+
417+
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
418+
for (const auto idx : irange::range(txids.size())) {
419+
const uint256 txid(ParseHashV(txids[idx], "txid"));
420+
421+
if (const llmq::CInstantSendLockPtr islock = llmq_ctx.isman->GetInstantSendLockByTxid(txid); islock != nullptr) {
422+
UniValue objIS(UniValue::VOBJ);
423+
objIS.pushKV("txid", islock->txid.ToString());
424+
UniValue inputs(UniValue::VARR);
425+
for (const auto out : islock->inputs) {
426+
UniValue outpoint(UniValue::VOBJ);
427+
outpoint.pushKV("txid", out.hash.ToString());
428+
outpoint.pushKV("vout", static_cast<int64_t>(out.n));
429+
inputs.push_back(outpoint);
430+
}
431+
objIS.pushKV("inputs", inputs);
432+
objIS.pushKV("cycleHash", islock->cycleHash.ToString());
433+
objIS.pushKV("signature", islock->sig.ToString());
434+
{
435+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
436+
ssTx << *islock;
437+
objIS.pushKV("hex", HexStr(ssTx));
438+
}
439+
result_arr.push_back(objIS);
440+
} else {
441+
result_arr.push_back("None");
442+
}
443+
}
444+
return result_arr;
445+
446+
},
447+
};
448+
}
449+
369450
static RPCHelpMan gettxchainlocks()
370451
{
371452
return RPCHelpMan{
@@ -2088,6 +2169,7 @@ static const CRPCCommand commands[] =
20882169
{ "rawtransactions", &getassetunlockstatuses, },
20892170
{ "rawtransactions", &getrawtransaction, },
20902171
{ "rawtransactions", &getrawtransactionmulti, },
2172+
{ "rawtransactions", &getislocks, },
20912173
{ "rawtransactions", &gettxchainlocks, },
20922174
{ "rawtransactions", &createrawtransaction, },
20932175
{ "rawtransactions", &decoderawtransaction, },

test/functional/interface_zmq_dash.py

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ def run_test(self):
136136
self.zmq_context = zmq.Context()
137137
# Initialize the network
138138
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
139+
self.log.info("Test RPC hex getbestchainlock before any CL appeared")
140+
assert_raises_rpc_error(-32603, "Unable to find any ChainLock", self.nodes[0].getbestchainlock)
139141
self.wait_for_sporks_same()
140142

141143
self.mine_cycle_quorum()
@@ -261,6 +263,7 @@ def test_chainlock_publishers(self):
261263
assert_equal(uint256_to_string(zmq_chain_lock.blockHash), rpc_chain_lock_hash)
262264
assert_equal(zmq_chain_locked_block.hash, rpc_chain_lock_hash)
263265
assert_equal(zmq_chain_lock.sig.hex(), rpc_best_chain_lock_sig)
266+
assert_equal(zmq_chain_lock.serialize().hex(), self.nodes[0].getbestchainlock()['hex'])
264267
# Unsubscribe from ChainLock messages
265268
self.unsubscribe(chain_lock_publishers)
266269

@@ -283,6 +286,7 @@ def test_instantsend_publishers(self):
283286
# Create two raw TXs, they will conflict with each other
284287
rpc_raw_tx_1 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100)
285288
rpc_raw_tx_2 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100)
289+
assert_equal(['None'], self.nodes[0].getislocks([rpc_raw_tx_1['txid']]))
286290
# Send the first transaction and wait for the InstantLock
287291
rpc_raw_tx_1_hash = self.nodes[0].sendrawtransaction(rpc_raw_tx_1['hex'])
288292
self.wait_for_instantlock(rpc_raw_tx_1_hash, self.nodes[0])
@@ -302,6 +306,8 @@ def test_instantsend_publishers(self):
302306
assert_equal(zmq_tx_lock_tx.hash, rpc_raw_tx_1['txid'])
303307
zmq_tx_lock = msg_isdlock()
304308
zmq_tx_lock.deserialize(zmq_tx_lock_sig_stream)
309+
assert_equal(rpc_raw_tx_1['txid'], self.nodes[0].getislocks([rpc_raw_tx_1['txid']])[0]['txid'])
310+
assert_equal(zmq_tx_lock.serialize().hex(), self.nodes[0].getislocks([rpc_raw_tx_1['txid']])[0]['hex'])
305311
assert_equal(uint256_to_string(zmq_tx_lock.txid), rpc_raw_tx_1['txid'])
306312
# Try to send the second transaction. This must throw an RPC error because it conflicts with rpc_raw_tx_1
307313
# which already got the InstantSend lock.

0 commit comments

Comments
 (0)