From ea0b7a0bdc04b7165bf7a49b98ec258479325924 Mon Sep 17 00:00:00 2001 From: "Thang X. Vu" Date: Sun, 9 Nov 2025 17:20:14 +0700 Subject: [PATCH 1/8] initial impelementation --- .../client/rpc-spec-v2/src/archive/archive.rs | 18 +++- .../client/rpc-spec-v2/src/archive/tests.rs | 22 +++-- .../rpc-spec-v2/src/chain_head/chain_head.rs | 6 +- .../rpc-spec-v2/src/chain_head/tests.rs | 84 +++++++++++-------- .../client/rpc-spec-v2/src/common/events.rs | 64 ++++++++++++-- .../client/rpc-spec-v2/src/common/storage.rs | 4 +- 6 files changed, 147 insertions(+), 51 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index 0f7ceed008762..2df21a9acd67f 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -231,7 +231,23 @@ where .into_iter() .map(|query| { let key = StorageKey(parse_hex_param(query.key)?); - Ok(StorageQuery { key, query_type: query.query_type }) + + // Validate that paginationStartKey is only used with descendant queries + if query.pagination_start_key.is_some() && + !query.query_type.is_descendant_query() + { + return Err(ArchiveError::InvalidParam( + "paginationStartKey is only valid for descendantsValues and descendantsHashes query types" + .to_string(), + )); + } + + let pagination_start_key = query + .pagination_start_key + .map(|key| parse_hex_param(key).map(StorageKey)) + .transpose()?; + + Ok(StorageQuery { key, query_type: query.query_type, pagination_start_key }) }) .collect::, ArchiveError>>() { diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 5956af3e019af..a81fe426dbf4b 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -360,10 +360,10 @@ async fn archive_storage_hashes_values() { let key = hex_string(&KEY); let items: Vec> = vec![ - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }, ]; let mut sub = api @@ -450,8 +450,8 @@ async fn archive_storage_hashes_values_child_trie() { let expected_value = hex_string(&CHILD_VALUE); let items: Vec> = vec![ - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, ]; let mut sub = api .subscribe_unbounded("archive_v1_storage", rpc_params![&genesis_hash, items, &child_info]) @@ -505,38 +505,46 @@ async fn archive_storage_closest_merkle_value() { StorageQuery { key: hex_string(b":AAAA"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AAAB"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, // Key with descendant. StorageQuery { key: hex_string(b":A"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AA"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, // Keys below this comment do not produce a result. // Key that exceed the keyspace of the trie. StorageQuery { key: hex_string(b":AAAAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AAABX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, // Key that are not part of the trie. StorageQuery { key: hex_string(b":AAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AAAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, ] ], @@ -666,6 +674,7 @@ async fn archive_storage_iterations() { vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None }] ], ) @@ -686,6 +695,7 @@ async fn archive_storage_iterations() { vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None }] ], ) diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index b949fb25402bf..34e2f3d75e913 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -400,7 +400,11 @@ where .into_iter() .map(|query| { let key = StorageKey(parse_hex_param(query.key)?); - Ok(StorageQuery { key, query_type: query.query_type }) + Ok(StorageQuery { + key, + query_type: query.query_type, + pagination_start_key: None, + }) }) .collect::, ChainHeadRpcError>>() { diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index 6edd46c8b4644..dff47f779b85a 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -699,7 +699,7 @@ async fn get_storage_hash() { rpc_params![ "invalid_sub_id", &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] ], ) .await @@ -713,7 +713,7 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] ], ) .await @@ -729,7 +729,7 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] ], ) .await @@ -772,7 +772,7 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] ], ) .await @@ -805,7 +805,7 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &genesis_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }], + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }], &child_info ], ) @@ -865,11 +865,13 @@ async fn get_storage_multi_query_iter() { vec![ StorageQuery { key: key.clone(), - query_type: StorageQueryType::DescendantsHashes + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None }, StorageQuery { key: key.clone(), - query_type: StorageQueryType::DescendantsValues + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None } ] ], @@ -916,11 +918,13 @@ async fn get_storage_multi_query_iter() { vec![ StorageQuery { key: key.clone(), - query_type: StorageQueryType::DescendantsHashes + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None }, StorageQuery { key: key.clone(), - query_type: StorageQueryType::DescendantsValues + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None } ], &child_info @@ -967,7 +971,7 @@ async fn get_storage_value() { rpc_params![ "invalid_sub_id", &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -981,7 +985,7 @@ async fn get_storage_value() { rpc_params![ &sub_id, &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -997,7 +1001,7 @@ async fn get_storage_value() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -1040,7 +1044,7 @@ async fn get_storage_value() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -1072,7 +1076,7 @@ async fn get_storage_value() { rpc_params![ &sub_id, &genesis_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }], + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }], &child_info ], ) @@ -1114,7 +1118,7 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: prefixed_key, query_type: StorageQueryType::Value }] + vec![StorageQuery { key: prefixed_key, query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -1139,7 +1143,7 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: prefixed_key, query_type: StorageQueryType::Value }] + vec![StorageQuery { key: prefixed_key, query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -1164,7 +1168,7 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }], + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }], &prefixed_key ], ) @@ -1190,7 +1194,7 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key, query_type: StorageQueryType::Value }], + vec![StorageQuery { key, query_type: StorageQueryType::Value, pagination_start_key: None }], &prefixed_key ], ) @@ -1238,7 +1242,7 @@ async fn unique_operation_ids() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }] + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] ], ) .await @@ -2864,10 +2868,10 @@ async fn ensure_operation_limits_works() { let key = hex_string(&KEY); let items = vec![ - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, ]; let response: MethodResponse = api @@ -2991,7 +2995,8 @@ async fn storage_is_backpressured() { &block_hash, vec![StorageQuery { key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None }] ], ) @@ -3126,7 +3131,8 @@ async fn stop_storage_operation() { &block_hash, vec![StorageQuery { key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None }] ], ) @@ -3194,39 +3200,47 @@ async fn storage_closest_merkle_value() { vec![ StorageQuery { key: hex_string(b":AAAA"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AAAB"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, // Key with descendant. StorageQuery { key: hex_string(b":A"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AA"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, // Keys below this comment do not produce a result. // Key that exceed the keyspace of the trie. StorageQuery { key: hex_string(b":AAAAX"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AAABX"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, // Key that are not part of the trie. StorageQuery { key: hex_string(b":AAX"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, StorageQuery { key: hex_string(b":AAAX"), - query_type: StorageQueryType::ClosestDescendantMerkleValue + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None }, ] ], @@ -3525,7 +3539,7 @@ async fn chain_head_single_connection_context() { &client, first_sub_id.clone(), finalized_hash.clone(), - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }], + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }], None, ) .await @@ -3536,7 +3550,7 @@ async fn chain_head_single_connection_context() { &second_client, first_sub_id.clone(), finalized_hash.clone(), - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }], + vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }], None, ) .await diff --git a/substrate/client/rpc-spec-v2/src/common/events.rs b/substrate/client/rpc-spec-v2/src/common/events.rs index 91530115f9218..b4c31fe375eb1 100644 --- a/substrate/client/rpc-spec-v2/src/common/events.rs +++ b/substrate/client/rpc-spec-v2/src/common/events.rs @@ -29,6 +29,11 @@ pub struct StorageQuery { /// The type of the storage query. #[serde(rename = "type")] pub query_type: StorageQueryType, + /// The optional pagination start key for descendants queries. + /// Only valid for `DescendantsValues` and `DescendantsHashes` query types. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub pagination_start_key: Option, } /// The type of the storage query. @@ -377,7 +382,11 @@ mod tests { #[test] fn storage_query() { // Item with Value. - let item = StorageQuery { key: "0x1", query_type: StorageQueryType::Value }; + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::Value, + pagination_start_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","type":"value"}"#; @@ -387,7 +396,11 @@ mod tests { assert_eq!(dec, item); // Item with Hash. - let item = StorageQuery { key: "0x1", query_type: StorageQueryType::Hash }; + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::Hash, + pagination_start_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","type":"hash"}"#; @@ -397,7 +410,11 @@ mod tests { assert_eq!(dec, item); // Item with DescendantsValues. - let item = StorageQuery { key: "0x1", query_type: StorageQueryType::DescendantsValues }; + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","type":"descendantsValues"}"#; @@ -407,7 +424,11 @@ mod tests { assert_eq!(dec, item); // Item with DescendantsHashes. - let item = StorageQuery { key: "0x1", query_type: StorageQueryType::DescendantsHashes }; + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","type":"descendantsHashes"}"#; @@ -417,8 +438,11 @@ mod tests { assert_eq!(dec, item); // Item with Merkle. - let item = - StorageQuery { key: "0x1", query_type: StorageQueryType::ClosestDescendantMerkleValue }; + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","type":"closestDescendantMerkleValue"}"#; @@ -426,5 +450,33 @@ mod tests { // Decode let dec: StorageQuery<&str> = serde_json::from_str(exp).unwrap(); assert_eq!(dec, item); + + // Item with DescendantsValues and paginationStartKey. + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: Some("0x2"), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","type":"descendantsValues","paginationStartKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: StorageQuery<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with DescendantsHashes and paginationStartKey. + let item = StorageQuery { + key: "0x1", + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: Some("0x2"), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","type":"descendantsHashes","paginationStartKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: StorageQuery<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); } } diff --git a/substrate/client/rpc-spec-v2/src/common/storage.rs b/substrate/client/rpc-spec-v2/src/common/storage.rs index a1e34d51530ec..41f4960880651 100644 --- a/substrate/client/rpc-spec-v2/src/common/storage.rs +++ b/substrate/client/rpc-spec-v2/src/common/storage.rs @@ -277,7 +277,7 @@ where let query = QueryIter { query_key: item.key, ty: IterQueryType::Value, - pagination_start_key: None, + pagination_start_key: item.pagination_start_key, }; this.client.query_iter_pagination_with_producer( query, @@ -290,7 +290,7 @@ where let query = QueryIter { query_key: item.key, ty: IterQueryType::Hash, - pagination_start_key: None, + pagination_start_key: item.pagination_start_key, }; this.client.query_iter_pagination_with_producer( query, From 828591c2c7c04c79d471082b6e981ae31a1698f7 Mon Sep 17 00:00:00 2001 From: "Thang X. Vu" Date: Sun, 9 Nov 2025 19:16:07 +0700 Subject: [PATCH 2/8] add more tests --- .../client/rpc-spec-v2/src/archive/archive.rs | 30 +- .../client/rpc-spec-v2/src/archive/tests.rs | 323 ++++++++++++++++++ 2 files changed, 338 insertions(+), 15 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index 2df21a9acd67f..40cef9364a986 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -250,22 +250,22 @@ where Ok(StorageQuery { key, query_type: query.query_type, pagination_start_key }) }) .collect::, ArchiveError>>() - { - Ok(items) => items, - Err(error) => { - let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); - return - }, - }; + { + Ok(items) => items, + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; + return + }, + }; - let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose(); - let child_trie = match child_trie { - Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec), - Err(error) => { - let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); - return - }, - }; + let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose(); + let child_trie = match child_trie { + Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec), + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; + return + }, + }; let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); let storage_fut = storage_client.generate_events(hash, items, child_trie, tx); diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index a81fe426dbf4b..7766df70d41bc 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -753,6 +753,329 @@ async fn archive_storage_iterations() { ); } +#[tokio::test] +async fn archive_storage_pagination_descendant_values() { + let (client, api) = setup_api(); + + // Import a new block with multiple storage entries. + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder.push_storage_change(b":prefix:aa".to_vec(), Some(b"value_a".to_vec())).unwrap(); + builder.push_storage_change(b":prefix:bb".to_vec(), Some(b"value_b".to_vec())).unwrap(); + builder.push_storage_change(b":prefix:cc".to_vec(), Some(b"value_c".to_vec())).unwrap(); + builder.push_storage_change(b":prefix:dd".to_vec(), Some(b"value_d".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // First request without pagination - should get all results. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":prefix:"), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None, + }] + ], + ) + .await + .unwrap(); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":prefix:aa"), + result: StorageResultType::Value(hex_string(b"value_a")), + child_trie_key: None, + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":prefix:bb"), + result: StorageResultType::Value(hex_string(b"value_b")), + child_trie_key: None, + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":prefix:cc"), + result: StorageResultType::Value(hex_string(b"value_c")), + child_trie_key: None, + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":prefix:dd"), + result: StorageResultType::Value(hex_string(b"value_d")), + child_trie_key: None, + }) + ); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); + + // Second request with pagination starting from `:prefix:bb` - should skip aa and bb. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":prefix:"), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: Some(hex_string(b":prefix:bb")), + }] + ], + ) + .await + .unwrap(); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":prefix:cc"), + result: StorageResultType::Value(hex_string(b"value_c")), + child_trie_key: None, + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":prefix:dd"), + result: StorageResultType::Value(hex_string(b"value_d")), + child_trie_key: None, + }) + ); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); +} + +#[tokio::test] +async fn archive_storage_pagination_descendant_hashes() { + let (client, api) = setup_api(); + + // Import a new block with multiple storage entries. + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder.push_storage_change(b":test:1".to_vec(), Some(b"val1".to_vec())).unwrap(); + builder.push_storage_change(b":test:2".to_vec(), Some(b"val2".to_vec())).unwrap(); + builder.push_storage_change(b":test:3".to_vec(), Some(b"val3".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // let expected_hash_1 = format!("{:?}", Blake2Hasher::hash(b"val1")); + let expected_hash_2 = format!("{:?}", Blake2Hasher::hash(b"val2")); + let expected_hash_3 = format!("{:?}", Blake2Hasher::hash(b"val3")); + + // Request with pagination starting from `:test:1` - should skip keys 1 and get 2 and 3. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":test:"), + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: Some(hex_string(b":test:1")), + }] + ], + ) + .await + .unwrap(); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":test:2"), + result: StorageResultType::Hash(expected_hash_2), + child_trie_key: None, + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":test:3"), + result: StorageResultType::Hash(expected_hash_3), + child_trie_key: None, + }) + ); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); +} + +#[tokio::test] +async fn archive_storage_pagination_invalid_query_type() { + let (client, api) = setup_api(); + let block_hash = format!("{:?}", client.genesis_hash()); + + // Test that paginationStartKey with Value query type returns an error. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":test"), + query_type: StorageQueryType::Value, + pagination_start_key: Some(hex_string(b":test:a")), + }] + ], + ) + .await + .unwrap(); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageError(err) if err.error.contains( + "paginationStartKey is only valid for descendantsValues and descendantsHashes query types" + ) + ); + + // Test that paginationStartKey with Hash query type returns an error. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":test"), + query_type: StorageQueryType::Hash, + pagination_start_key: Some(hex_string(b":test:a")), + }] + ], + ) + .await + .unwrap(); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageError(err) if err.error.contains( + "paginationStartKey is only valid for descendantsValues and descendantsHashes query types" + ) + ); + + // Test that paginationStartKey with ClosestDescendantMerkleValue query type returns an error. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":test"), + query_type: StorageQueryType::ClosestDescendantMerkleValue, + pagination_start_key: Some(hex_string(b":test:a")), + }] + ], + ) + .await + .unwrap(); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageError(err) if err.error.contains( + "paginationStartKey is only valid for descendantsValues and descendantsHashes query types" + ) + ); +} + +#[tokio::test] +async fn archive_storage_pagination_invalid_hex() { + let (client, api) = setup_api(); + let block_hash = format!("{:?}", client.genesis_hash()); + + // Test that invalid hex in pagination_start_key returns an error. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":test:"), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: Some("0xINVALID_HEX".to_string()), + }] + ], + ) + .await + .unwrap(); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageError(err) if err.error.contains("Invalid parameter") + && err.error.contains("0xINVALID_HEX") + ); + + // Test that invalid hex in key also returns an error. + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: "NOT_HEX".to_string(), + query_type: StorageQueryType::Value, + pagination_start_key: None, + }] + ], + ) + .await + .unwrap(); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageError(err) if err.error.contains("Invalid parameter") + && err.error.contains("NOT_HEX") + ); + + // Test that pagination_start_key with empty string is valid (gets converted to empty bytes). + let mut sub = api + .subscribe_unbounded( + "archive_v1_storage", + rpc_params![ + &block_hash, + vec![StorageQuery { + key: hex_string(b":test:"), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: Some("".to_string()), + }] + ], + ) + .await + .unwrap(); + + // Should complete successfully, not error + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); +} + #[tokio::test] async fn archive_storage_diff_main_trie() { let (client, api) = setup_api(); From 7e1599a21c9a131b9af99e3f87e536a62be27c70 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 17:09:04 +0000 Subject: [PATCH 3/8] Update from github-actions[bot] running command 'prdoc' --- prdoc/pr_10329.prdoc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 prdoc/pr_10329.prdoc diff --git a/prdoc/pr_10329.prdoc b/prdoc/pr_10329.prdoc new file mode 100644 index 0000000000000..b2f8250a145ea --- /dev/null +++ b/prdoc/pr_10329.prdoc @@ -0,0 +1,10 @@ +title: 'fix: support `paginationStartKey` parameter for `archive_v1_storage`' +doc: +- audience: Todo + description: |- + Fixes #10185 + + This PR is to add support for `paginationStartKey` parameter in `archive_v1_storage` JSON RPC API for query type: `descendantsValues` and `descendantsHashes` per [the latest specs](https://paritytech.github.io/json-rpc-interface-spec/api/archive_v1_storage.html). +crates: +- name: sc-rpc-spec-v2 + bump: major From 1d92f6c1d4c8ae29378da67097d4843cfab09457 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 17:09:16 +0000 Subject: [PATCH 4/8] Update from github-actions[bot] running command 'fmt' --- .../client/rpc-spec-v2/src/archive/archive.rs | 30 ++--- .../client/rpc-spec-v2/src/archive/tests.rs | 66 +++++++-- .../rpc-spec-v2/src/chain_head/chain_head.rs | 6 +- .../rpc-spec-v2/src/chain_head/tests.rs | 126 +++++++++++++++--- 4 files changed, 173 insertions(+), 55 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index 40cef9364a986..2814e12afdcb2 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -250,22 +250,22 @@ where Ok(StorageQuery { key, query_type: query.query_type, pagination_start_key }) }) .collect::, ArchiveError>>() - { - Ok(items) => items, - Err(error) => { - let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; - return - }, - }; + { + Ok(items) => items, + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; + return + }, + }; - let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose(); - let child_trie = match child_trie { - Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec), - Err(error) => { - let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; - return - }, - }; + let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose(); + let child_trie = match child_trie { + Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec), + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; + return + }, + }; let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); let storage_fut = storage_client.generate_events(hash, items, child_trie, tx); diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 7766df70d41bc..a2046379f7a0a 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -360,10 +360,26 @@ async fn archive_storage_hashes_values() { let key = hex_string(&KEY); let items: Vec> = vec![ - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None, + }, ]; let mut sub = api @@ -450,8 +466,16 @@ async fn archive_storage_hashes_values_child_trie() { let expected_value = hex_string(&CHILD_VALUE); let items: Vec> = vec![ - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None, + }, ]; let mut sub = api .subscribe_unbounded("archive_v1_storage", rpc_params![&genesis_hash, items, &child_info]) @@ -763,10 +787,18 @@ async fn archive_storage_pagination_descendant_values() { .with_parent_block_number(0) .build() .unwrap(); - builder.push_storage_change(b":prefix:aa".to_vec(), Some(b"value_a".to_vec())).unwrap(); - builder.push_storage_change(b":prefix:bb".to_vec(), Some(b"value_b".to_vec())).unwrap(); - builder.push_storage_change(b":prefix:cc".to_vec(), Some(b"value_c".to_vec())).unwrap(); - builder.push_storage_change(b":prefix:dd".to_vec(), Some(b"value_d".to_vec())).unwrap(); + builder + .push_storage_change(b":prefix:aa".to_vec(), Some(b"value_a".to_vec())) + .unwrap(); + builder + .push_storage_change(b":prefix:bb".to_vec(), Some(b"value_b".to_vec())) + .unwrap(); + builder + .push_storage_change(b":prefix:cc".to_vec(), Some(b"value_c".to_vec())) + .unwrap(); + builder + .push_storage_change(b":prefix:dd".to_vec(), Some(b"value_d".to_vec())) + .unwrap(); let block = builder.build().unwrap().block; let block_hash = format!("{:?}", block.header.hash()); client.import(BlockOrigin::Own, block.clone()).await.unwrap(); @@ -878,9 +910,15 @@ async fn archive_storage_pagination_descendant_hashes() { .with_parent_block_number(0) .build() .unwrap(); - builder.push_storage_change(b":test:1".to_vec(), Some(b"val1".to_vec())).unwrap(); - builder.push_storage_change(b":test:2".to_vec(), Some(b"val2".to_vec())).unwrap(); - builder.push_storage_change(b":test:3".to_vec(), Some(b"val3".to_vec())).unwrap(); + builder + .push_storage_change(b":test:1".to_vec(), Some(b"val1".to_vec())) + .unwrap(); + builder + .push_storage_change(b":test:2".to_vec(), Some(b"val2".to_vec())) + .unwrap(); + builder + .push_storage_change(b":test:3".to_vec(), Some(b"val3".to_vec())) + .unwrap(); let block = builder.build().unwrap().block; let block_hash = format!("{:?}", block.header.hash()); client.import(BlockOrigin::Own, block.clone()).await.unwrap(); @@ -1027,7 +1065,7 @@ async fn archive_storage_pagination_invalid_hex() { assert_matches!( get_next_event::(&mut sub).await, - ArchiveStorageEvent::StorageError(err) if err.error.contains("Invalid parameter") + ArchiveStorageEvent::StorageError(err) if err.error.contains("Invalid parameter") && err.error.contains("0xINVALID_HEX") ); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index 34e2f3d75e913..039bcf62562e3 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -400,11 +400,7 @@ where .into_iter() .map(|query| { let key = StorageKey(parse_hex_param(query.key)?); - Ok(StorageQuery { - key, - query_type: query.query_type, - pagination_start_key: None, - }) + Ok(StorageQuery { key, query_type: query.query_type, pagination_start_key: None }) }) .collect::, ChainHeadRpcError>>() { diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index dff47f779b85a..4e6cd37bcdf98 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -699,7 +699,11 @@ async fn get_storage_hash() { rpc_params![ "invalid_sub_id", &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None + }] ], ) .await @@ -713,7 +717,11 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None + }] ], ) .await @@ -729,7 +737,11 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None + }] ], ) .await @@ -772,7 +784,11 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None + }] ], ) .await @@ -805,7 +821,11 @@ async fn get_storage_hash() { rpc_params![ &sub_id, &genesis_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None + }], &child_info ], ) @@ -971,7 +991,11 @@ async fn get_storage_value() { rpc_params![ "invalid_sub_id", &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -985,7 +1009,11 @@ async fn get_storage_value() { rpc_params![ &sub_id, &invalid_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -1001,7 +1029,11 @@ async fn get_storage_value() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -1044,7 +1076,11 @@ async fn get_storage_value() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -1076,7 +1112,11 @@ async fn get_storage_value() { rpc_params![ &sub_id, &genesis_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }], &child_info ], ) @@ -1118,7 +1158,11 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: prefixed_key, query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: prefixed_key, + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -1143,7 +1187,11 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: prefixed_key, query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: prefixed_key, + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -1168,7 +1216,11 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }], &prefixed_key ], ) @@ -1194,7 +1246,11 @@ async fn get_storage_non_queryable_key() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key, query_type: StorageQueryType::Value, pagination_start_key: None }], + vec![StorageQuery { + key, + query_type: StorageQueryType::Value, + pagination_start_key: None + }], &prefixed_key ], ) @@ -1242,7 +1298,11 @@ async fn unique_operation_ids() { rpc_params![ &sub_id, &block_hash, - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value, pagination_start_key: None }] + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Value, + pagination_start_key: None + }] ], ) .await @@ -2868,10 +2928,26 @@ async fn ensure_operation_limits_works() { let key = hex_string(&KEY); let items = vec![ - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, - StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues, pagination_start_key: None }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsHashes, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None, + }, + StorageQuery { + key: key.clone(), + query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None, + }, ]; let response: MethodResponse = api @@ -3539,7 +3615,11 @@ async fn chain_head_single_connection_context() { &client, first_sub_id.clone(), finalized_hash.clone(), - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None, + }], None, ) .await @@ -3550,7 +3630,11 @@ async fn chain_head_single_connection_context() { &second_client, first_sub_id.clone(), finalized_hash.clone(), - vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash, pagination_start_key: None }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None, + }], None, ) .await From 98a092b7ad8d7910cee356e8f811e7f94e25c476 Mon Sep 17 00:00:00 2001 From: "Thang X. Vu" Date: Mon, 17 Nov 2025 00:12:16 +0700 Subject: [PATCH 5/8] adjust audience in prdoc --- prdoc/pr_10329.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_10329.prdoc b/prdoc/pr_10329.prdoc index b2f8250a145ea..a677cf486472c 100644 --- a/prdoc/pr_10329.prdoc +++ b/prdoc/pr_10329.prdoc @@ -1,6 +1,6 @@ title: 'fix: support `paginationStartKey` parameter for `archive_v1_storage`' doc: -- audience: Todo +- audience: runtime_user, node_dev description: |- Fixes #10185 From 6f0eb579893ee9cf212d92c9bbe262de5da52204 Mon Sep 17 00:00:00 2001 From: "Thang X. Vu" Date: Mon, 17 Nov 2025 10:39:52 +0700 Subject: [PATCH 6/8] Update prdoc/pr_10329.prdoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Köcher --- prdoc/pr_10329.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_10329.prdoc b/prdoc/pr_10329.prdoc index a677cf486472c..f0d04c92b6c52 100644 --- a/prdoc/pr_10329.prdoc +++ b/prdoc/pr_10329.prdoc @@ -1,6 +1,6 @@ title: 'fix: support `paginationStartKey` parameter for `archive_v1_storage`' doc: -- audience: runtime_user, node_dev +- audience: node-dev description: |- Fixes #10185 From a9d0d65f8a2a421047e7c3967afaac1c20cf3e9c Mon Sep 17 00:00:00 2001 From: "Thang X. Vu" Date: Mon, 17 Nov 2025 10:42:35 +0700 Subject: [PATCH 7/8] remove stale comment --- substrate/client/rpc-spec-v2/src/archive/tests.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index a2046379f7a0a..baf173511b818 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -923,9 +923,8 @@ async fn archive_storage_pagination_descendant_hashes() { let block_hash = format!("{:?}", block.header.hash()); client.import(BlockOrigin::Own, block.clone()).await.unwrap(); - // let expected_hash_1 = format!("{:?}", Blake2Hasher::hash(b"val1")); - let expected_hash_2 = format!("{:?}", Blake2Hasher::hash(b"val2")); - let expected_hash_3 = format!("{:?}", Blake2Hasher::hash(b"val3")); + let expected_hash_1 = format!("{:?}", Blake2Hasher::hash(b"val2")); + let expected_hash_2 = format!("{:?}", Blake2Hasher::hash(b"val3")); // Request with pagination starting from `:test:1` - should skip keys 1 and get 2 and 3. let mut sub = api @@ -947,7 +946,7 @@ async fn archive_storage_pagination_descendant_hashes() { get_next_event::(&mut sub).await, ArchiveStorageEvent::Storage(StorageResult { key: hex_string(b":test:2"), - result: StorageResultType::Hash(expected_hash_2), + result: StorageResultType::Hash(expected_hash_1), child_trie_key: None, }) ); @@ -956,7 +955,7 @@ async fn archive_storage_pagination_descendant_hashes() { get_next_event::(&mut sub).await, ArchiveStorageEvent::Storage(StorageResult { key: hex_string(b":test:3"), - result: StorageResultType::Hash(expected_hash_3), + result: StorageResultType::Hash(expected_hash_2), child_trie_key: None, }) ); From ebc3fa7698b1c5a3998d1fd8044cb4a8f9d992c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 17 Nov 2025 09:02:44 +0100 Subject: [PATCH 8/8] Apply suggestion from @bkchr --- prdoc/pr_10329.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_10329.prdoc b/prdoc/pr_10329.prdoc index f0d04c92b6c52..f1c4d70894279 100644 --- a/prdoc/pr_10329.prdoc +++ b/prdoc/pr_10329.prdoc @@ -1,6 +1,6 @@ title: 'fix: support `paginationStartKey` parameter for `archive_v1_storage`' doc: -- audience: node-dev +- audience: Node Dev description: |- Fixes #10185