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): added optional block height in getassetunlockstatuses #5849

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions doc/release-notes-5776.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
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:
- "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.
- `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 for the mined Asset Unlock index.
- `mempooled`: If the Asset Unlock index is in the mempool.
- `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.
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listreceivedbylabel", 1, "addlocked" },
{ "listreceivedbylabel", 2, "include_empty" },
{ "listreceivedbylabel", 3, "include_watchonly" },
{ "getassetunlockstatuses", 0, "indexes" },
{ "getassetunlockstatuses", 1, "height" },
{ "getbalance", 1, "minconf" },
{ "getbalance", 2, "addlocked" },
{ "getbalance", 3, "include_watchonly" },
Expand Down
57 changes: 37 additions & 20 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,14 @@ static void getassetunlockstatuses_help(const JSONRPCRequest& request)
{
RPCHelpMan{
"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)",
{
{"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::Type::ARR, "", "Response is an array with the same size as the input txids",
Expand Down Expand Up @@ -488,27 +489,43 @@ static UniValue getassetunlockstatuses(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INTERNAL_ERROR, "No blocks in chain");
}

const auto pBlockIndexBestCL = [&]() -> const CBlockIndex* {
if (llmq_ctx.clhandler->GetBestChainLock().IsNull()) {
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* {
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 (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;
}();
// no CL info, no CbTx CL
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
poolCL = pBlockIndexBestCL ?
std::make_optional(node.creditPoolManager->GetCreditPool(pBlockIndexBestCL, Params().GetConsensus())) :
std::nullopt;

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);
Expand Down Expand Up @@ -537,7 +554,7 @@ static UniValue getassetunlockstatuses(const JSONRPCRequest& request)
return false;
});
}();
return is_mempooled ? "mempooled" : "unknown";
return is_mempooled && !nSpecificCoreHeight.has_value() ? "mempooled" : "unknown";
};
obj.pushKV("status", status_to_push());
result_arr.push_back(obj);
Expand Down Expand Up @@ -1975,7 +1992,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable &t)
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes"} },
{ "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes","height"} },
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "getrawtransactionmulti", &getrawtransactionmulti, {"txid_map","verbose"} },
{ "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} },
Expand Down
16 changes: 12 additions & 4 deletions test/functional/feature_asset_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,12 @@ 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)
tip = self.nodes[0].getblockcount()
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.check_mempool_size()
Expand Down Expand Up @@ -524,8 +528,12 @@ 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)
tip = self.nodes[0].getblockcount()
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.slowly_generate_batch(60)
Expand Down
Loading