feat(rpc): Asset Unlock status by index (#5776)

## Issue being fixed or feature implemented
Platform in the scope of credit withdrawals, need a way to get the
status of an Asset Unlock by index.

## What was done?
A new RPC was created `getassetunlockchainlocks` that accepts Asset
Unlock indexes array as parameter and return corresponding 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.

## How Has This Been Tested?
Inserted on `feature_asset_locks.py` covering cases where Asset Unlock
txs are in mempool, mined and not present.

## Breaking Changes
no

## Checklist:
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added or updated relevant unit/integration/functional/e2e
tests
- [x] I have made corresponding changes to the documentation
- [x] I have assigned this pull request to a milestone _(for repository
code-owners and collaborators only)_

---------

Co-authored-by: thephez <thephez@users.noreply.github.com>
Co-authored-by: Konstantin Akimov <knstqq@gmail.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
Co-authored-by: pasta <pasta@dashboost.org>
This commit is contained in:
Odysseas Gabrielides 2023-12-22 22:27:00 +02:00 committed by GitHub
parent b5dc598525
commit 563cc34b4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 149 additions and 5 deletions

11
doc/release-notes-5776.md Normal file
View File

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

View File

@ -340,6 +340,7 @@ void PrepareShutdown(NodeContext& node)
llmq::quorumSnapshotManager.reset();
deterministicMNManager.reset();
creditPoolManager.reset();
node.creditPoolManager = nullptr;
node.mnhf_manager.reset();
node.evodb.reset();
}
@ -1926,6 +1927,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
node.mnhf_manager.reset();
node.mnhf_manager = std::make_unique<CMNHFManager>(*node.evodb);
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;
@ -1942,7 +1944,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));

View File

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

View File

@ -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>
@ -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>
@ -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",
@ -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"} },

View File

@ -141,6 +141,7 @@ std::string CRPCTable::help(const std::string& strCommand, const std::string& st
void CRPCTable::InitPlatformRestrictions()
{
mapPlatformRestrictions = {
{"getassetunlockstatuses", {}},
{"getbestblockhash", {}},
{"getblockhash", {}},
{"getblockcount", {}},

View File

@ -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();
@ -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();
@ -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;
@ -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();

View File

@ -330,6 +330,10 @@ class AssetLocksTest(DashTestFramework):
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)
@ -502,6 +506,9 @@ class AssetLocksTest(DashTestFramework):
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...")

View File

@ -56,7 +56,8 @@ class HTTPBasicsTest(BitcoinTestFramework):
assert_equal(resp.status, expexted_status)
conn.close()
whitelisted = ["getbestblockhash",
whitelisted = ["getassetunlockstatuses",
"getbestblockhash",
"getblockhash",
"getblockcount",
"getbestchainlock",