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.
- null: If none of the above are valid.

Note: This RPC is whitelisted for the Platform RPC user.
2 changes: 2 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,8 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
node.evodb = std::make_unique<CEvoDB>(nEvoDbCache, false, fReset || fReindexChainState);
node.mnhf_manager.reset();
node.mnhf_manager = std::make_unique<CMNHFManager>(*node.evodb);
node.creditPoolManager.reset();
node.creditPoolManager = std::make_unique<CCreditPoolManager>(*node.evodb);
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved

chainman.Reset();
chainman.InitializeChainstate(Assert(node.mempool.get()), *node.mnhf_manager, *node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
Expand Down
119 changes: 119 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,121 @@ 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|null}"},
}},
}
},
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);
ChainstateManager& chainman = EnsureChainman(node);

UniValue result_arr(UniValue::VARR);
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();
}

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

CBlockIndex* pBlockIndexBestCL{nullptr};
const llmq::CChainLockSig bestclsig{llmq_ctx.clhandler->GetBestChainLock()};
if (bestclsig.IsNull()) {
Copy link
Member

Choose a reason for hiding this comment

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

we should create a separate feat PR which on ProcessBlock we will submit a blocks chain lock to the chain lock system

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

// If no CL info is available, try to use CbTx CL information
if (const auto cbtx_best_cl = GetNonNullCoinbaseChainlock(pTipBlockIndex); cbtx_best_cl.has_value()) {
pBlockIndexBestCL = pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->second - 1);
}
}

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

// 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;
if (pBlockIndexBestCL != nullptr) poolCL = node.creditPoolManager->GetCreditPool(pBlockIndexBestCL, Params().GetConsensus());
std::optional<CCreditPool> poolOnTip{};

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);
if (poolCL.has_value() && poolCL->indexes.Contains(index)) {
obj.pushKV("status", "chainlocked");
result_arr.push_back(obj);
continue;
}
if (pTipBlockIndex != pBlockIndexBestCL) {
if (!poolOnTip.has_value()) poolOnTip = node.creditPoolManager->GetCreditPool(pTipBlockIndex, Params().GetConsensus());
if (poolOnTip->indexes.Contains(index)) {
obj.pushKV("status", "mined");
result_arr.push_back(obj);
continue;
}
}
LOCK(mempool.cs);
bool is_mempooled = false;
for (const CTxMemPoolEntry& e : mempool.mapTx) {
if (e.GetTx().nType == CAssetUnlockPayload::SPECIALTX_TYPE) {
CAssetUnlockPayload assetUnlockTx;
if (!GetTxPayload(e.GetTx(), assetUnlockTx)) {
throw JSONRPCError(RPC_TRANSACTION_ERROR, "bad-assetunlocktx-payload");
}
if (index == assetUnlockTx.getIndex()) {
is_mempooled = true;
break;
}
}
}
if (is_mempooled)
obj.pushKV("status", "mempooled");
else {
UniValue jnull(UniValue::VNULL);
obj.pushKV("status", jnull);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: obj.pushKV("status", UniValue{}); does same, doesn't it?

}
result_arr.push_back(obj);
}

return result_arr;
}

static UniValue gettxoutproof(const JSONRPCRequest& request)
{
RPCHelpMan{"gettxoutproof",
Expand Down Expand Up @@ -1757,6 +1875,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
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': None}, {'index': 300, 'status': None}], 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': None}], 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