Skip to content

Commit

Permalink
feat(rpc): added optional block height in getassetunlockstatuses (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
Co-authored-by: UdjinM6 <[email protected]>
  • Loading branch information
3 people authored Feb 1, 2024
1 parent 2238e03 commit 82310b0
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 29 deletions.
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

0 comments on commit 82310b0

Please sign in to comment.