From 4a77e20060fd231b291feae41b37a18ec83fe6a3 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 25 Jul 2025 12:37:33 +0200 Subject: [PATCH 1/4] The MASP conversions query now requires a MASP epoch to be specified. (cherry picked from commit f7b17d1a37efc93fe130db6500c045ee440551a0) --- crates/apps_lib/src/client/rpc.rs | 16 ++++++++++++++-- crates/sdk/src/queries/shell.rs | 4 +++- crates/sdk/src/rpc.rs | 5 ++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 06b6a2499ef..4f4bd86cec1 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -2123,9 +2123,20 @@ pub async fn query_conversions( .wallet() .await .get_addresses_with_vp_type(AddressVpType::Token); - let conversions = rpc::query_conversions(context.client()) + + // Download conversions from all epochs to facilitate decoding asset types + let mut conversions = BTreeMap::new(); + let from = MaspEpoch::zero(); + let to = rpc::query_masp_epoch(context.client()) .await - .expect("Conversions should be defined"); + .expect("Unable to query current MASP epoch"); + for epoch in MaspEpoch::iter_bounds_inclusive(from, to) { + conversions.append( + &mut rpc::query_conversions(context.client(), &epoch) + .await + .expect("Conversions should be defined"), + ); + } if args.dump_tree { display_line!(context.io(), "Conversions: {conversions:?}"); @@ -2133,6 +2144,7 @@ pub async fn query_conversions( // Track whether any non-sentinel conversions are found let mut conversions_found = false; + for (addr, _denom, digit, epoch, amt) in conversions.values() { // If the user has specified any targets, then meet them // If we have a sentinel conversion, then skip printing diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index 48178bb70ad..6ed17d266f2 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -98,7 +98,7 @@ router! {SHELL, ( "conv" / [asset_type: AssetType] ) -> Option = read_conversion, // Conversion state access - read conversion - ( "conversions" ) -> BTreeMap = read_conversions, + ( "conversions" / [masp_epoch: MaspEpoch] ) -> BTreeMap = read_conversions, // Conversion state access - read conversion ( "masp_reward_tokens" ) -> Vec = masp_reward_tokens, @@ -211,6 +211,7 @@ where /// Query to read the conversion state fn read_conversions( ctx: RequestCtx<'_, D, H, V, T>, + masp_epoch: MaspEpoch, ) -> namada_storage::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -222,6 +223,7 @@ where .conversion_state .assets .iter() + .filter(|&(_, asset)| (asset.epoch == masp_epoch)) .map(|(&asset_type, asset)| { ( asset_type, diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 48b3119779b..924eee24d10 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -390,6 +390,7 @@ pub async fn query_conversion( /// Query conversions pub async fn query_conversions( client: &C, + masp_epoch: &MaspEpoch, ) -> Result< BTreeMap< AssetType, @@ -403,7 +404,9 @@ pub async fn query_conversions( >, error::Error, > { - convert_response::(RPC.shell().read_conversions(client).await) + convert_response::( + RPC.shell().read_conversions(client, masp_epoch).await, + ) } /// Query the total rewards minted by MASP From 75ab930f4f19df14317ca6db817daa8b12fa0812 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 17 Sep 2025 11:01:59 +0200 Subject: [PATCH 2/4] Added changelog (cherry picked from commit dca71ece84833b1b1a01d4c03f123f4a6dd3b7fd) --- .../4776-query-conversions-requires-masp-epoch.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changelog/unreleased/improvements/4776-query-conversions-requires-masp-epoch.md diff --git a/.changelog/unreleased/improvements/4776-query-conversions-requires-masp-epoch.md b/.changelog/unreleased/improvements/4776-query-conversions-requires-masp-epoch.md new file mode 100644 index 00000000000..a5d6148fc49 --- /dev/null +++ b/.changelog/unreleased/improvements/4776-query-conversions-requires-masp-epoch.md @@ -0,0 +1,12 @@ +- Querying the conversion state becomes more difficult as nodes progress + to higher and higher epochs. For instance, on a mainnet clone running on + accelerated epochs, client queries for conversions always timeout (even after + modifying timeout_broadcast_tx_commit) when the node goes beyond a certain + MASP epoch. This happens because the conversion state grows linearly with + the MASP epoch counter. This PR attempts to address this problem by making + the conversions RPC endpoint require that clients specify the MASP epoch + they want conversions from. This implies two things: first the size of the + response from the conversions RPC endpoint should now be constant (equal to + the number of tokens in the shielded rewards program), and second a client + requiring all conversions now has to do a separate query for each MASP epoch. + ([\#4776](https://github.com/namada-net/namada/pull/4776)) \ No newline at end of file From 60bc8618178154b7cc6594c5a6b37828c7683455 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Oct 2025 17:12:36 +0200 Subject: [PATCH 3/4] Parallel epoch queries (cherry picked from commit 70bc3dfe046e47e4d473073511195295b182c98c) --- crates/apps_lib/src/client/masp.rs | 3 ++- crates/apps_lib/src/client/rpc.rs | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/apps_lib/src/client/masp.rs b/crates/apps_lib/src/client/masp.rs index 6b46b9546f2..68263fe68cd 100644 --- a/crates/apps_lib/src/client/masp.rs +++ b/crates/apps_lib/src/client/masp.rs @@ -122,8 +122,9 @@ pub async fn syncing< // tests only as the cli wallet is not supposed to compile the // history of shielded transactions shielded.load_confirmed().await; + let masp_epoch = namada_sdk::rpc::query_masp_epoch(&client).await?; for (asset_type, (token, denom, position, epoch, _conv)) in - namada_sdk::rpc::query_conversions(&client).await? + namada_sdk::rpc::query_conversions(&client, &masp_epoch).await? { let pre_asset_type = AssetData { token, diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 4f4bd86cec1..bfb360acab5 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -6,6 +6,7 @@ use std::io; use color_eyre::owo_colors::OwoColorize; use data_encoding::HEXLOWER; use either::Either; +use futures::StreamExt; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; @@ -2125,18 +2126,22 @@ pub async fn query_conversions( .get_addresses_with_vp_type(AddressVpType::Token); // Download conversions from all epochs to facilitate decoding asset types - let mut conversions = BTreeMap::new(); let from = MaspEpoch::zero(); let to = rpc::query_masp_epoch(context.client()) .await .expect("Unable to query current MASP epoch"); - for epoch in MaspEpoch::iter_bounds_inclusive(from, to) { - conversions.append( - &mut rpc::query_conversions(context.client(), &epoch) - .await - .expect("Conversions should be defined"), - ); - } + let epochs: Vec<_> = MaspEpoch::iter_bounds_inclusive(from, to).collect(); + let conversion_tasks = epochs + .iter() + .map(|epoch| rpc::query_conversions(context.client(), epoch)); + let task_stream = futures::stream::iter(conversion_tasks); + let conversions = task_stream + .buffer_unordered(100) + .fold(BTreeMap::default(), async |mut acc, conversion| { + acc.append(&mut conversion.expect("Conversion should be defined")); + acc + }) + .await; if args.dump_tree { display_line!(context.io(), "Conversions: {conversions:?}"); From 234ebe0b3ed796e9a26eec4abfdfd3f2131585cb Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Oct 2025 10:25:04 +0200 Subject: [PATCH 4/4] Fixes conversion queries for integration tests (cherry picked from commit 882fbbdb08b3cbe3c0ad4d2138b10fcc2c485a47) --- crates/apps_lib/src/client/masp.rs | 29 ++++++++++++++++++++++++++--- crates/apps_lib/src/client/rpc.rs | 3 +-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/apps_lib/src/client/masp.rs b/crates/apps_lib/src/client/masp.rs index 68263fe68cd..45b1953fa5d 100644 --- a/crates/apps_lib/src/client/masp.rs +++ b/crates/apps_lib/src/client/masp.rs @@ -117,14 +117,37 @@ pub async fn syncing< #[cfg(feature = "testing")] { + use std::collections::BTreeMap; + + use futures::StreamExt; + use namada_core::masp::MaspEpoch; + // Load the confirmed context (if present) and update the conversions // for the shielded history. This is needed for integration // tests only as the cli wallet is not supposed to compile the // history of shielded transactions shielded.load_confirmed().await; - let masp_epoch = namada_sdk::rpc::query_masp_epoch(&client).await?; - for (asset_type, (token, denom, position, epoch, _conv)) in - namada_sdk::rpc::query_conversions(&client, &masp_epoch).await? + let current_masp_epoch = + namada_sdk::rpc::query_masp_epoch(&client).await?; + let epochs: Vec<_> = MaspEpoch::iter_bounds_inclusive( + MaspEpoch::zero(), + current_masp_epoch, + ) + .collect(); + let conversion_tasks = epochs + .iter() + .map(|epoch| namada_sdk::rpc::query_conversions(&client, epoch)); + let conversions = futures::stream::iter(conversion_tasks) + .buffer_unordered(100) + .fold(BTreeMap::default(), async |mut acc, conversion| { + acc.append( + &mut conversion.expect("Conversion should be defined"), + ); + acc + }) + .await; + + for (asset_type, (token, denom, position, epoch, _conv)) in conversions { let pre_asset_type = AssetData { token, diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index bfb360acab5..a53792e7956 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -2134,8 +2134,7 @@ pub async fn query_conversions( let conversion_tasks = epochs .iter() .map(|epoch| rpc::query_conversions(context.client(), epoch)); - let task_stream = futures::stream::iter(conversion_tasks); - let conversions = task_stream + let conversions = futures::stream::iter(conversion_tasks) .buffer_unordered(100) .fold(BTreeMap::default(), async |mut acc, conversion| { acc.append(&mut conversion.expect("Conversion should be defined"));