diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 3f44150ce5..009e6880f1 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -275,6 +275,37 @@ static RPCHelpMan getbestchainlock() }; } +static RPCHelpMan getrawbestchainlock() +{ + return RPCHelpMan{"getrawbestchainlock", + "\nReturns the raw best ChainLock. Throws an error if there is no known ChainLock yet.", + {}, + RPCResult{ + RPCResult::Type::STR, "data", "The serialized, hex-encoded data for best ChainLock" + }, + RPCExamples{ + HelpExampleCli("getrawbestchainlock", "") + + HelpExampleRpc("getrawbestchainlock", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue result(UniValue::VOBJ); + + const NodeContext& node = EnsureAnyNodeContext(request.context); + + LLMQContext& llmq_ctx = EnsureLLMQContext(node); + llmq::CChainLockSig clsig = llmq_ctx.clhandler->GetBestChainLock(); + if (clsig.IsNull()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to find any ChainLock"); + } + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << clsig; + return HexStr(ssTx); + +}, + }; +} + void RPCNotifyBlockChange(const CBlockIndex* pindex) { if(pindex) { @@ -3106,6 +3137,7 @@ static const CRPCCommand commands[] = { "blockchain", &getblockstats, }, { "blockchain", &getbestblockhash, }, { "blockchain", &getbestchainlock, }, + { "blockchain", &getrawbestchainlock, }, { "blockchain", &getblockcount, }, { "blockchain", &getblock, }, { "blockchain", &getblockfrompeer, }, diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index e66797cd88..b80c47d10c 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -115,6 +115,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "gettransaction", 1, "include_watchonly" }, { "gettransaction", 2, "verbose" }, { "getrawtransaction", 1, "verbose" }, + { "getrawislocks", 0, "txids" }, { "getrawtransactionmulti", 0, "transactions" }, { "getrawtransactionmulti", 1, "verbose" }, { "gettxchainlocks", 0, "txids" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c62763ff7f..138182a2a6 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -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", @@ -366,6 +367,60 @@ static RPCHelpMan getrawtransactionmulti() { }; } +static RPCHelpMan getrawislocks() +{ + return RPCHelpMan{"getrawislocks", + "\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{"if InstantSend Lock is known for specified txid", + RPCResult::Type::STR, "data", "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("getrawislocks", "'[\"txid\",...]'") + + HelpExampleRpc("getrawislocks", "'[\"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"); + } + + for (const auto idx : irange::range(txids.size())) { + const uint256 txid(ParseHashV(txids[idx], "txid")); + + LLMQContext& llmq_ctx = EnsureLLMQContext(node); + llmq::CInstantSendLockPtr islock = llmq_ctx.isman->GetInstantSendLockByTxid(txid); + if (islock == nullptr) { + result_arr.push_back("None"); + } else { + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << *islock; + result_arr.push_back(HexStr(ssTx)); + } + } + return result_arr; + +}, + }; +} + static RPCHelpMan gettxchainlocks() { return RPCHelpMan{ @@ -2082,6 +2137,7 @@ static const CRPCCommand commands[] = { "rawtransactions", &getassetunlockstatuses, }, { "rawtransactions", &getrawtransaction, }, { "rawtransactions", &getrawtransactionmulti, }, + { "rawtransactions", &getrawislocks, }, { "rawtransactions", &gettxchainlocks, }, { "rawtransactions", &createrawtransaction, }, { "rawtransactions", &decoderawtransaction, }, diff --git a/test/functional/interface_zmq_dash.py b/test/functional/interface_zmq_dash.py index b1f64e445d..6c94349dad 100755 --- a/test/functional/interface_zmq_dash.py +++ b/test/functional/interface_zmq_dash.py @@ -263,6 +263,7 @@ class DashZMQTest (DashTestFramework): 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].getrawbestchainlock()) # Unsubscribe from ChainLock messages self.unsubscribe(chain_lock_publishers) @@ -285,6 +286,7 @@ class DashZMQTest (DashTestFramework): # 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].getrawislocks([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]) @@ -304,6 +306,7 @@ class DashZMQTest (DashTestFramework): 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([zmq_tx_lock.serialize().hex()], self.nodes[0].getrawislocks([rpc_raw_tx_1['txid']])) 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.