diff --git a/prdoc/pr_10329.prdoc b/prdoc/pr_10329.prdoc new file mode 100644 index 0000000000000..f1c4d70894279 --- /dev/null +++ b/prdoc/pr_10329.prdoc @@ -0,0 +1,10 @@ +title: 'fix: support `paginationStartKey` parameter for `archive_v1_storage`' +doc: +- audience: Node Dev + 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 diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index 0f7ceed008762..2814e12afdcb2 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -231,13 +231,29 @@ 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>>() { Ok(items) => items, Err(error) => { - let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; return }, }; @@ -246,7 +262,7 @@ where 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())); + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await; return }, }; diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 5956af3e019af..baf173511b818 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 }, - 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 +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 }, - 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 +529,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 +698,7 @@ async fn archive_storage_iterations() { vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None }] ], ) @@ -686,6 +719,7 @@ async fn archive_storage_iterations() { vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, + pagination_start_key: None }] ], ) @@ -743,6 +777,342 @@ 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"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 + .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_1), + 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_2), + 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(); 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..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,7 +400,7 @@ 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..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 }] + 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 }] + 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 }] + 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 }] + 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 }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None + }], &child_info ], ) @@ -865,11 +885,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 +938,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 +991,11 @@ 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 +1009,11 @@ 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 +1029,11 @@ 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 +1076,11 @@ 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 +1112,11 @@ 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 +1158,11 @@ 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 +1187,11 @@ 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 +1216,11 @@ 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 +1246,11 @@ 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 +1298,11 @@ 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 +2928,26 @@ 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 +3071,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 +3207,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 +3276,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 +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 }], + vec![StorageQuery { + key: key.clone(), + query_type: StorageQueryType::Hash, + pagination_start_key: None, + }], None, ) .await @@ -3536,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 }], + 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,