feat(rpc): added optional block height in getassetunlockstatuses (#5849)

## Issue being fixed or feature implemented
RPC `getassetunlockstatuses` is now accepting an extra optional
parameter `height`.
When a valid `height` is passed, then the RPC returns the status of
AssetUnlock indexes up to this specific block. (Requested by Platform
team)

## What was done?
Note that in order to avoid cases that can lead to deterministic result,
when `height` is passed, then the only `chainlocked` and `unknown`
outcomes are possible.

## How Has This Been Tested?
`feature_asset_locks.py` was updated.

## Breaking Changes
n/a

## Checklist:
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have added or updated relevant unit/integration/functional/e2e
tests
- [ ] 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: Konstantin Akimov <knstqq@gmail.com>
Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
Odysseas Gabrielides 2024-02-01 17:15:20 +02:00 committed by GitHub
parent 2238e03bae
commit 82310b0984
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 28 deletions

View File

@ -1,11 +1,14 @@
Added RPC 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. - `getassetunlockstatuses` RPC allows fetching of Asset Unlock txs by their withdrawal index.
The RPC accepts an array of indexes and an optional block height.
The possible outcomes per each index are: The possible outcomes per each index are:
- "chainlocked": If the Asset Unlock index is mined on a ChainLocked block. - `chainlocked`: If the Asset Unlock index is mined on a ChainLocked block or up to the given block height.
- "mined": If no ChainLock information is available, and the Asset Unlock index is mined. - `mined`: If no ChainLock information is available for the mined Asset Unlock index.
- "mempooled": If the Asset Unlock index is in the mempool. - `mempooled`: If the Asset Unlock index is in the mempool.
- "unknown": If none of the above are valid. - `unknown`: If none of the above are valid.
Note: If a specific block height is passed on request, then only `chainlocked` and `unknown` outcomes are possible.
Note: This RPC is whitelisted for the Platform RPC user. Note: This RPC is whitelisted for the Platform RPC user.

View File

@ -58,6 +58,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listreceivedbylabel", 1, "addlocked" }, { "listreceivedbylabel", 1, "addlocked" },
{ "listreceivedbylabel", 2, "include_empty" }, { "listreceivedbylabel", 2, "include_empty" },
{ "listreceivedbylabel", 3, "include_watchonly" }, { "listreceivedbylabel", 3, "include_watchonly" },
{ "getassetunlockstatuses", 0, "indexes" },
{ "getassetunlockstatuses", 1, "height" },
{ "getbalance", 1, "minconf" }, { "getbalance", 1, "minconf" },
{ "getbalance", 2, "addlocked" }, { "getbalance", 2, "addlocked" },
{ "getbalance", 3, "include_watchonly" }, { "getbalance", 3, "include_watchonly" },

View File

@ -438,13 +438,14 @@ static void getassetunlockstatuses_help(const JSONRPCRequest& request)
{ {
RPCHelpMan{ RPCHelpMan{
"getassetunlockstatuses", "getassetunlockstatuses",
"\nReturns the status of given Asset Unlock indexes.\n", "\nReturns the status of given Asset Unlock indexes at the tip of the chain or at a specific block height if specified.\n",
{ {
{"indexes", RPCArg::Type::ARR, RPCArg::Optional::NO, "The Asset Unlock indexes (no more than 100)", {"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"}, {"index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "An Asset Unlock index"},
}, },
}, },
{"height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The maximum block height to check"},
}, },
RPCResult{ RPCResult{
RPCResult::Type::ARR, "", "Response is an array with the same size as the input txids", RPCResult::Type::ARR, "", "Response is an array with the same size as the input txids",
@ -488,27 +489,43 @@ static UniValue getassetunlockstatuses(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INTERNAL_ERROR, "No blocks in chain"); throw JSONRPCError(RPC_INTERNAL_ERROR, "No blocks in chain");
} }
std::optional<CCreditPool> poolCL{std::nullopt};
std::optional<CCreditPool> poolOnTip{std::nullopt};
std::optional<int> nSpecificCoreHeight{std::nullopt};
if (!request.params[1].isNull()) {
nSpecificCoreHeight = request.params[1].get_int();
if (nSpecificCoreHeight.value() < 0 || nSpecificCoreHeight.value() > chainman.ActiveChain().Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
}
poolCL = std::make_optional(node.creditPoolManager->GetCreditPool(chainman.ActiveChain()[nSpecificCoreHeight.value()], Params().GetConsensus()));
}
else {
const auto pBlockIndexBestCL = [&]() -> const CBlockIndex* { const auto pBlockIndexBestCL = [&]() -> const CBlockIndex* {
if (llmq_ctx.clhandler->GetBestChainLock().IsNull()) { if (!llmq_ctx.clhandler->GetBestChainLock().IsNull()) {
return pTipBlockIndex->GetAncestor(llmq_ctx.clhandler->GetBestChainLock().getHeight());
}
// If no CL info is available, try to use CbTx CL information // If no CL info is available, try to use CbTx CL information
if (const auto cbtx_best_cl = GetNonNullCoinbaseChainlock(pTipBlockIndex)) { if (const auto cbtx_best_cl = GetNonNullCoinbaseChainlock(pTipBlockIndex)) {
return pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->second - 1); return pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->second - 1);
} }
} // no CL info, no CbTx CL
return nullptr; return nullptr;
}(); }();
// We need in 2 credit pools: at tip of chain and on best CL to know if tx is mined or chainlocked // 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 // Sometimes that's two different blocks, sometimes not and we need to initialize 2nd creditPoolManager
std::optional<CCreditPool> poolCL = pBlockIndexBestCL ? poolCL = pBlockIndexBestCL ?
std::make_optional(node.creditPoolManager->GetCreditPool(pBlockIndexBestCL, Params().GetConsensus())) : std::make_optional(node.creditPoolManager->GetCreditPool(pBlockIndexBestCL, Params().GetConsensus())) :
std::nullopt; std::nullopt;
auto poolOnTip = [&]() -> std::optional<CCreditPool> {
poolOnTip = [&]() -> std::optional<CCreditPool> {
if (pTipBlockIndex != pBlockIndexBestCL) { if (pTipBlockIndex != pBlockIndexBestCL) {
return std::make_optional(node.creditPoolManager->GetCreditPool(pTipBlockIndex, Params().GetConsensus())); return std::make_optional(node.creditPoolManager->GetCreditPool(pTipBlockIndex, Params().GetConsensus()));
} }
return std::nullopt; return std::nullopt;
}(); }();
}
for (const auto i : irange::range(str_indexes.size())) { for (const auto i : irange::range(str_indexes.size())) {
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
@ -537,7 +554,7 @@ static UniValue getassetunlockstatuses(const JSONRPCRequest& request)
return false; return false;
}); });
}(); }();
return is_mempooled ? "mempooled" : "unknown"; return is_mempooled && !nSpecificCoreHeight.has_value() ? "mempooled" : "unknown";
}; };
obj.pushKV("status", status_to_push()); obj.pushKV("status", status_to_push());
result_arr.push_back(obj); result_arr.push_back(obj);
@ -1975,7 +1992,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable &t)
static const CRPCCommand commands[] = static const CRPCCommand commands[] =
{ // category name actor (function) argNames { // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ---------- // --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes"} }, { "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes","height"} },
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} }, { "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "getrawtransactionmulti", &getrawtransactionmulti, {"txid_map","verbose"} }, { "rawtransactions", "getrawtransactionmulti", &getrawtransactionmulti, {"txid_map","verbose"} },
{ "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} }, { "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} },

View File

@ -349,8 +349,12 @@ class AssetLocksTest(DashTestFramework):
txid = self.send_tx(asset_unlock_tx) txid = self.send_tx(asset_unlock_tx)
assert "assetUnlockTx" in node.getrawtransaction(txid, 1) assert "assetUnlockTx" in node.getrawtransaction(txid, 1)
indexes_statuses = self.nodes[0].getassetunlockstatuses(["101", "102", "300"]) tip = self.nodes[0].getblockcount()
assert_equal([{'index': 101, 'status': 'mempooled'}, {'index': 102, 'status': 'unknown'}, {'index': 300, 'status': 'unknown'}], indexes_statuses) indexes_statuses_no_height = self.nodes[0].getassetunlockstatuses(["101", "102", "300"])
assert_equal([{'index': 101, 'status': 'mempooled'}, {'index': 102, 'status': 'unknown'}, {'index': 300, 'status': 'unknown'}], indexes_statuses_no_height)
indexes_statuses_height = self.nodes[0].getassetunlockstatuses(["101", "102", "300"], tip)
assert_equal([{'index': 101, 'status': 'unknown'}, {'index': 102, 'status': 'unknown'}, {'index': 300, 'status': 'unknown'}], indexes_statuses_height)
self.mempool_size += 1 self.mempool_size += 1
self.check_mempool_size() self.check_mempool_size()
@ -524,8 +528,12 @@ class AssetLocksTest(DashTestFramework):
node.generate(1) node.generate(1)
self.sync_all() self.sync_all()
indexes_statuses = self.nodes[0].getassetunlockstatuses(["101", "102", "103"]) tip = self.nodes[0].getblockcount()
assert_equal([{'index': 101, 'status': 'mined'}, {'index': 102, 'status': 'mined'}, {'index': 103, 'status': 'unknown'}], indexes_statuses) indexes_statuses_no_height = self.nodes[0].getassetunlockstatuses(["101", "102", "103"])
assert_equal([{'index': 101, 'status': 'mined'}, {'index': 102, 'status': 'mined'}, {'index': 103, 'status': 'unknown'}], indexes_statuses_no_height)
indexes_statuses_height = self.nodes[0].getassetunlockstatuses(["101", "102", "103"], tip)
assert_equal([{'index': 101, 'status': 'chainlocked'}, {'index': 102, 'status': 'chainlocked'}, {'index': 103, 'status': 'unknown'}], indexes_statuses_height)
self.log.info("generate many blocks to be sure that mempool is empty after expiring txes...") self.log.info("generate many blocks to be sure that mempool is empty after expiring txes...")
self.slowly_generate_batch(60) self.slowly_generate_batch(60)