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
--------

- `getassetunlockchainlocks` RPC allows to fetch 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.
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved

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
117 changes: 117 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,119 @@ 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, "xxxx", "Asset Unlock index.",
{
{RPCResult::Type::STR, "", "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* pBlockIndex{nullptr};
bool chainlock_info = false;
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 (auto cbtx_best_cl = GetNonNullCoinbaseChainlock(pTipBlockIndex); cbtx_best_cl.has_value()) {
pBlockIndex = pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->second - 1);
chainlock_info = true;
}
else {
// If no CL info available at all, build Credit pool based on Tip but chainlock_info is false.
// Found Asset Unlock indexes will be marked as "mined" instead of "chainlocked"
pBlockIndex = pTipBlockIndex;
}
}
else {
pBlockIndex = pTipBlockIndex->GetAncestor(bestclsig.getHeight());
chainlock_info = true;
}

if (!pBlockIndex) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not found");
}

CCreditPool pool = node.creditPoolManager->GetCreditPool(pBlockIndex, Params().GetConsensus());

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");
}
if (pool.indexes.Contains(index)) {
obj.pushKV(str_indexes[i].get_str(), chainlock_info ? "chainlocked" : "mined");
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
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(str_indexes[i].get_str(), "mempooled");
else {
UniValue jnull(UniValue::VNULL);
obj.pushKV(str_indexes[i].get_str(), jnull);
}
result_arr.push_back(obj);
}

return result_arr;
}

static UniValue gettxoutproof(const JSONRPCRequest& request)
{
RPCHelpMan{"gettxoutproof",
Expand Down Expand Up @@ -1757,6 +1873,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
19 changes: 19 additions & 0 deletions test/functional/feature_asset_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,16 @@ 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"])
self.log.info(f'{indexes_statuses}')
assert any('101' in d for d in indexes_statuses)
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved
assert_equal(next((d['101'] for d in indexes_statuses if '101' in d), None), "mempooled")
assert any('102' in d for d in indexes_statuses)
assert_equal(next((d['102'] for d in indexes_statuses if '102' in d), None), None)
assert any('300' in d for d in indexes_statuses)
assert_equal(next((d['300'] for d in indexes_statuses if '300' in d), None), None)
ogabrielides marked this conversation as resolved.
Show resolved Hide resolved

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

indexes_statuses = self.nodes[0].getassetunlockstatuses(["101", "102", "103"])
self.log.info(f'{indexes_statuses}')
assert any('101' in d for d in indexes_statuses)
assert_equal(next((d['101'] for d in indexes_statuses if '101' in d), None), "mined")
assert any('102' in d for d in indexes_statuses)
assert_equal(next((d['102'] for d in indexes_statuses if '102' in d), None), "mined")
assert any('103' in d for d in indexes_statuses)
assert_equal(next((d['103'] for d in indexes_statuses if '103' in d), None), None)

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