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): Asset Unlock status by index #5776

Merged
11 changes: 11 additions & 0 deletions doc/release-notes-5776.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Added RPC
--------

- `getassetunlockstatuses` RPC allows fetching of Asset Unlock txs by their withdrawal index. The RPC accepts an array of indexes and returns status for each index.
The possible outcomes per each index are:
- "chainlocked": If the Asset Unlock index is mined on a ChainLocked block.
- "mined": If no ChainLock information is available, and the Asset Unlock index is mined.
- "mempooled": If the Asset Unlock index is in the mempool.
- "unknown": If none of the above are valid.

Note: This RPC is whitelisted for the Platform RPC user.
5 changes: 4 additions & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ void PrepareShutdown(NodeContext& node)
llmq::quorumSnapshotManager.reset();
deterministicMNManager.reset();
creditPoolManager.reset();
node.creditPoolManager = nullptr;
node.mnhf_manager.reset();
node.evodb.reset();
}
Expand Down Expand Up @@ -1925,6 +1926,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
node.mnhf_manager.reset();
node.mnhf_manager = std::make_unique<CMNHFManager>(*node.evodb);

Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: extra \n


chainman.Reset();
chainman.InitializeChainstate(Assert(node.mempool.get()), *node.mnhf_manager, *node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
chainman.m_total_coinstip_cache = nCoinCacheUsage;
Expand All @@ -1941,7 +1943,8 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
deterministicMNManager.reset();
deterministicMNManager.reset(new CDeterministicMNManager(chainman.ActiveChainstate(), *node.connman, *node.evodb));
creditPoolManager.reset();
creditPoolManager.reset(new CCreditPoolManager(*node.evodb));
creditPoolManager = std::make_unique<CCreditPoolManager>(*node.evodb);
node.creditPoolManager = creditPoolManager.get();
llmq::quorumSnapshotManager.reset();
llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*node.evodb));

Expand Down
2 changes: 1 addition & 1 deletion src/node/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct NodeContext {
std::function<void()> rpc_interruption_point = [] {};
//! Dash
std::unique_ptr<LLMQContext> llmq_ctx;
std::unique_ptr<CCreditPoolManager> creditPoolManager;
CCreditPoolManager* creditPoolManager;
std::unique_ptr<CMNHFManager> mnhf_manager;
std::unique_ptr<CJContext> cj_ctx;

Expand Down
116 changes: 116 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <evo/creditpool.h>
#include <index/txindex.h>
#include <init.h>
#include <key_io.h>
Expand Down Expand Up @@ -40,11 +41,13 @@
#include <validationinterface.h>
#include <util/irange.h>

#include <evo/cbtx.h>
#include <evo/specialtx.h>

#include <llmq/chainlocks.h>
#include <llmq/context.h>
#include <llmq/instantsend.h>
#include <llmq/utils.h>

#include <numeric>
#include <stdint.h>
Expand Down Expand Up @@ -336,6 +339,118 @@ static UniValue gettxchainlocks(const JSONRPCRequest& request)
return result_arr;
}

static void getassetunlockstatuses_help(const JSONRPCRequest& request)
{
RPCHelpMan{
"getassetunlockstatuses",
"\nReturns the status of given Asset Unlock indexes.\n",
{
{"indexes", RPCArg::Type::ARR, RPCArg::Optional::NO, "The Asset Unlock indexes (no more than 100)",
{
{"index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "An Asset Unlock index"},
},
},
},
RPCResult{
RPCResult::Type::ARR, "", "Response is an array with the same size as the input txids",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "index", "The Asset Unlock index"},
{RPCResult::Type::STR, "status", "Status of the Asset Unlock index: {chainlocked|mined|mempooled|unknown}"},
}},
}
},
RPCExamples{
HelpExampleCli("getassetunlockstatuses", "'[\"myindex\",...]'")
+ HelpExampleRpc("getassetunlockstatuses", "[\"myindex\",...]")
},
}.Check(request);
}

static UniValue getassetunlockstatuses(const JSONRPCRequest& request)
{
getassetunlockstatuses_help(request);

const NodeContext& node = EnsureAnyNodeContext(request.context);
const CTxMemPool& mempool = EnsureMemPool(node);
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
const ChainstateManager& chainman = EnsureChainman(node);

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

if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}

const CBlockIndex* pTipBlockIndex{WITH_LOCK(cs_main, return chainman.ActiveChain().Tip())};

if (!pTipBlockIndex) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "No blocks in chain");
}

const auto pBlockIndexBestCL = [&]() -> const CBlockIndex* {
if (llmq_ctx.clhandler->GetBestChainLock().IsNull()) {
// If no CL info is available, try to use CbTx CL information
if (const auto cbtx_best_cl = GetNonNullCoinbaseChainlock(pTipBlockIndex)) {
return pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->second - 1);
}
}
return nullptr;
}();

// We need in 2 credit pools: at tip of chain and on best CL to know if tx is mined or chainlocked
// Sometimes that's two different blocks, sometimes not and we need to initialize 2nd creditPoolManager
std::optional<CCreditPool> poolCL = pBlockIndexBestCL ?
std::make_optional(node.creditPoolManager->GetCreditPool(pBlockIndexBestCL, Params().GetConsensus())) :
std::nullopt;
auto poolOnTip = [&]() -> std::optional<CCreditPool> {
if (pTipBlockIndex != pBlockIndexBestCL) {
return std::make_optional(node.creditPoolManager->GetCreditPool(pTipBlockIndex, Params().GetConsensus()));
}
return std::nullopt;
}();

for (const auto i : irange::range(str_indexes.size())) {
UniValue obj(UniValue::VOBJ);
uint64_t index{};
if (!ParseUInt64(str_indexes[i].get_str(), &index)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid index");
}
obj.pushKV("index", index);
auto status_to_push = [&]() -> std::string {
if (poolCL.has_value() && poolCL->indexes.Contains(index)) {
return "chainlocked";
}
if (poolOnTip.has_value() && poolOnTip->indexes.Contains(index)) {
return "mined";
}
bool is_mempooled = [&]() {
LOCK(mempool.cs);
return std::any_of(mempool.mapTx.begin(), mempool.mapTx.end(), [index](const CTxMemPoolEntry &e) {
if (e.GetTx().nType == CAssetUnlockPayload::SPECIALTX_TYPE) {
if (CAssetUnlockPayload assetUnlockTx; GetTxPayload(e.GetTx(), assetUnlockTx)) {
return index == assetUnlockTx.getIndex();
} else {
throw JSONRPCError(RPC_TRANSACTION_ERROR, "bad-assetunlocktx-payload");
}
}
return false;
});
}();
return is_mempooled ? "mempooled" : "unknown";
};
obj.pushKV("status", status_to_push());
result_arr.push_back(obj);
}

return result_arr;
}

static UniValue gettxoutproof(const JSONRPCRequest& request)
{
RPCHelpMan{"gettxoutproof",
Expand Down Expand Up @@ -1757,6 +1872,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable &t)
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes"} },
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} },
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} },
Expand Down
1 change: 1 addition & 0 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ std::string CRPCTable::help(const std::string& strCommand, const std::string& st
void CRPCTable::InitPlatformRestrictions()
{
mapPlatformRestrictions = {
{"getassetunlockstatuses", {}},
{"getbestblockhash", {}},
{"getblockhash", {}},
{"getblockcount", {}},
Expand Down
9 changes: 7 additions & 2 deletions src/test/util/setup_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman);
llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*m_node.evodb));
creditPoolManager = std::make_unique<CCreditPoolManager>(*m_node.evodb);
m_node.creditPoolManager = creditPoolManager.get();
static bool noui_connected = false;
if (!noui_connected) {
noui_connect();
Expand All @@ -182,6 +183,7 @@ BasicTestingSetup::~BasicTestingSetup()
connman.reset();
llmq::quorumSnapshotManager.reset();
creditPoolManager.reset();
m_node.creditPoolManager = nullptr;
m_node.mnhf_manager.reset();
m_node.evodb.reset();

Expand Down Expand Up @@ -215,7 +217,9 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
::mmetaman = std::make_unique<CMasternodeMetaMan>(/* load_cache */ false);
::netfulfilledman = std::make_unique<CNetFulfilledRequestManager>(/* load_cache */ false);

m_node.creditPoolManager = std::make_unique<CCreditPoolManager>(*m_node.evodb);
creditPoolManager = std::make_unique<CCreditPoolManager>(*m_node.evodb);
m_node.creditPoolManager = creditPoolManager.get();


// Start script-checking threads. Set g_parallel_script_checks to true so they are used.
constexpr int script_check_threads = 2;
Expand All @@ -226,7 +230,8 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
ChainTestingSetup::~ChainTestingSetup()
{
m_node.scheduler->stop();
m_node.creditPoolManager.reset();
creditPoolManager.reset();
m_node.creditPoolManager = nullptr;
StopScriptCheckWorkerThreads();
GetMainSignals().FlushBackgroundCallbacks();
GetMainSignals().UnregisterBackgroundSignalScheduler();
Expand Down
7 changes: 7 additions & 0 deletions test/functional/feature_asset_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ def run_test(self):

txid = self.send_tx(asset_unlock_tx)
assert "assetUnlockTx" in node.getrawtransaction(txid, 1)

indexes_statuses = self.nodes[0].getassetunlockstatuses(["101", "102", "300"])
assert_equal([{'index': 101, 'status': 'mempooled'}, {'index': 102, 'status': 'unknown'}, {'index': 300, 'status': 'unknown'}], indexes_statuses)

self.mempool_size += 1
self.check_mempool_size()
self.validate_credit_pool_balance(locked_1)
Expand Down Expand Up @@ -502,6 +506,9 @@ def run_test(self):
node.generate(1)
self.sync_all()

indexes_statuses = self.nodes[0].getassetunlockstatuses(["101", "102", "103"])
assert_equal([{'index': 101, 'status': 'mined'}, {'index': 102, 'status': 'mined'}, {'index': 103, 'status': 'unknown'}], indexes_statuses)

self.log.info("generate many blocks to be sure that mempool is empty after expiring txes...")
self.slowly_generate_batch(60)
self.log.info("Checking that credit pool is not changed...")
Expand Down
3 changes: 2 additions & 1 deletion test/functional/rpc_platform_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def test_command(method, params, auth, expexted_status, should_not_match=False):
assert_equal(resp.status, expexted_status)
conn.close()

whitelisted = ["getbestblockhash",
whitelisted = ["getassetunlockstatuses",
"getbestblockhash",
"getblockhash",
"getblockcount",
"getbestchainlock",
Expand Down
Loading