Skip to content

Commit

Permalink
Merge dashpay#6455: feat: implement new RPC getislocks and add hex fi…
Browse files Browse the repository at this point in the history
…eld 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
  dashpay#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
  • Loading branch information
PastaPastaPasta committed Jan 18, 2025
2 parents 4c166e1 + 82c0cf2 commit 0972dfe
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 5 deletions.
11 changes: 11 additions & 0 deletions doc/release-notes-6455.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## New RPCs

- **`getislocks`**
- Retrieves the InstantSend lock data for the given transaction IDs (txids).
Returns the lock information in both human-friendly JSON format and binary hex-encoded zmq-compatible format.

## Updated RPCs

- **`getbestchainlock` Changes**
- A new hex field has been added to the getbestchainlock RPC, which returns the ChainLock information in zmq-compatible, hex-encoded binary format.

19 changes: 14 additions & 5 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,29 +241,38 @@ static RPCHelpMan getbestchainlock()
{RPCResult::Type::NUM, "height", "The block height or index"},
{RPCResult::Type::STR_HEX, "signature", "The ChainLock's BLS signature"},
{RPCResult::Type::BOOL, "known_block", "True if the block is known by our node"},
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for best ChainLock"},
}},
RPCExamples{
HelpExampleCli("getbestchainlock", "")
+ HelpExampleRpc("getbestchainlock", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
UniValue result(UniValue::VOBJ);

const NodeContext& node = EnsureAnyNodeContext(request.context);

const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
const llmq::CChainLockSig clsig = llmq_ctx.clhandler->GetBestChainLock();
if (clsig.IsNull()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to find any ChainLock");
}

UniValue result(UniValue::VOBJ);

result.pushKV("blockhash", clsig.getBlockHash().GetHex());
result.pushKV("height", clsig.getHeight());
result.pushKV("signature", clsig.getSig().ToString());

const ChainstateManager& chainman = EnsureChainman(node);
LOCK(cs_main);
result.pushKV("known_block", chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()) != nullptr);
{
const ChainstateManager& chainman = EnsureChainman(node);
LOCK(cs_main);
result.pushKV("known_block", chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()) != nullptr);
}

CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << clsig;
result.pushKV("hex", HexStr(ssTx));

return result;
},
};
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "gettransaction", 1, "include_watchonly" },
{ "gettransaction", 2, "verbose" },
{ "getrawtransaction", 1, "verbose" },
{ "getislocks", 0, "txids" },
{ "getrawtransactionmulti", 0, "transactions" },
{ "getrawtransactionmulti", 1, "verbose" },
{ "gettxchainlocks", 0, "txids" },
Expand Down
82 changes: 82 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ static RPCHelpMan getrawtransactionmulti() {
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
"If false, return a string, otherwise return a json object"},
},
// TODO: replace RPCResults to proper annotation
RPCResults{},
RPCExamples{
HelpExampleCli("getrawtransactionmulti",
Expand Down Expand Up @@ -366,6 +367,86 @@ static RPCHelpMan getrawtransactionmulti() {
};
}

static RPCHelpMan getislocks()
{
return RPCHelpMan{"getislocks",
"\nReturns the raw InstantSend lock data for each txids. Returns Null if there is no known IS yet.",
{
{"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction ids (no more than 100)",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
},
},
},
RPCResult{
RPCResult::Type::ARR, "", "Response is an array with the same size as the input txids",
{{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
{RPCResult::Type::ARR, "inputs", "The inputs",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
{RPCResult::Type::NUM, "vout", "The output number"},
},
},
}},
{RPCResult::Type::STR_HEX, "cycleHash", "The Cycle Hash"},
{RPCResult::Type::STR_HEX, "signature", "The InstantSend's BLS signature"},
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
}},
RPCResult{"if no InstantSend Lock is known for specified txid",
RPCResult::Type::STR, "data", "Just 'None' string"
},
}},
RPCExamples{
HelpExampleCli("getislocks", "'[\"txid\",...]'")
+ HelpExampleRpc("getislocks", "'[\"txid\",...]'")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const NodeContext& node = EnsureAnyNodeContext(request.context);

UniValue result_arr(UniValue::VARR);
UniValue txids = request.params[0].get_array();
if (txids.size() > 100) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Up to 100 txids only");
}

const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
for (const auto idx : irange::range(txids.size())) {
const uint256 txid(ParseHashV(txids[idx], "txid"));

if (const llmq::CInstantSendLockPtr islock = llmq_ctx.isman->GetInstantSendLockByTxid(txid); islock != nullptr) {
UniValue objIS(UniValue::VOBJ);
objIS.pushKV("txid", islock->txid.ToString());
UniValue inputs(UniValue::VARR);
for (const auto out : islock->inputs) {
UniValue outpoint(UniValue::VOBJ);
outpoint.pushKV("txid", out.hash.ToString());
outpoint.pushKV("vout", static_cast<int64_t>(out.n));
inputs.push_back(outpoint);
}
objIS.pushKV("inputs", inputs);
objIS.pushKV("cycleHash", islock->cycleHash.ToString());
objIS.pushKV("signature", islock->sig.ToString());
{
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << *islock;
objIS.pushKV("hex", HexStr(ssTx));
}
result_arr.push_back(objIS);
} else {
result_arr.push_back("None");
}
}
return result_arr;

},
};
}

static RPCHelpMan gettxchainlocks()
{
return RPCHelpMan{
Expand Down Expand Up @@ -2088,6 +2169,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", &getassetunlockstatuses, },
{ "rawtransactions", &getrawtransaction, },
{ "rawtransactions", &getrawtransactionmulti, },
{ "rawtransactions", &getislocks, },
{ "rawtransactions", &gettxchainlocks, },
{ "rawtransactions", &createrawtransaction, },
{ "rawtransactions", &decoderawtransaction, },
Expand Down
6 changes: 6 additions & 0 deletions test/functional/interface_zmq_dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ def run_test(self):
self.zmq_context = zmq.Context()
# Initialize the network
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
self.log.info("Test RPC hex getbestchainlock before any CL appeared")
assert_raises_rpc_error(-32603, "Unable to find any ChainLock", self.nodes[0].getbestchainlock)
self.wait_for_sporks_same()

self.mine_cycle_quorum()
Expand Down Expand Up @@ -261,6 +263,7 @@ def test_chainlock_publishers(self):
assert_equal(uint256_to_string(zmq_chain_lock.blockHash), rpc_chain_lock_hash)
assert_equal(zmq_chain_locked_block.hash, rpc_chain_lock_hash)
assert_equal(zmq_chain_lock.sig.hex(), rpc_best_chain_lock_sig)
assert_equal(zmq_chain_lock.serialize().hex(), self.nodes[0].getbestchainlock()['hex'])
# Unsubscribe from ChainLock messages
self.unsubscribe(chain_lock_publishers)

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

0 comments on commit 0972dfe

Please sign in to comment.