diff --git a/substrate/client/rpc-api/src/state/mod.rs b/substrate/client/rpc-api/src/state/mod.rs index b2cf8ce909b20fead05a3be7bdb9d0d93d6fade1..d29e46a4b5637cf85d0745d59a73b0ad69afde7c 100644 --- a/substrate/client/rpc-api/src/state/mod.rs +++ b/substrate/client/rpc-api/src/state/mod.rs @@ -136,6 +136,14 @@ pub trait StateApi<Hash> { hash: Option<Hash> ) -> FutureResult<Vec<StorageChangeSet<Hash>>>; + /// Query storage entries (by key) starting at block hash given as the second parameter. + #[rpc(name = "state_queryStorageAt")] + fn query_storage_at( + &self, + keys: Vec<StorageKey>, + at: Option<Hash>, + ) -> FutureResult<Vec<StorageChangeSet<Hash>>>; + /// New runtime version subscription #[pubsub( subscription = "state_runtimeVersion", diff --git a/substrate/client/rpc/src/state/mod.rs b/substrate/client/rpc/src/state/mod.rs index 82568866ee3ba4df6686d9201783f7cf8cfa673e..2747405c04fcac0c7534ea802bb5e1f08a7e1911 100644 --- a/substrate/client/rpc/src/state/mod.rs +++ b/substrate/client/rpc/src/state/mod.rs @@ -163,6 +163,13 @@ pub trait StateBackend<Block: BlockT, Client>: Send + Sync + 'static keys: Vec<StorageKey>, ) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>>; + /// Query storage entries (by key) starting at block hash given as the second parameter. + fn query_storage_at( + &self, + keys: Vec<StorageKey>, + at: Option<Block::Hash> + ) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>>; + /// New runtime version subscription fn subscribe_runtime_version( &self, @@ -357,6 +364,14 @@ impl<Block, Client> StateApi<Block::Hash> for State<Block, Client> self.backend.query_storage(from, to, keys) } + fn query_storage_at( + &self, + keys: Vec<StorageKey>, + at: Option<Block::Hash> + ) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> { + self.backend.query_storage_at(keys, at) + } + fn subscribe_storage( &self, meta: Self::Metadata, diff --git a/substrate/client/rpc/src/state/state_full.rs b/substrate/client/rpc/src/state/state_full.rs index b7589d2aefecafc9daa41e2122461c21f53c93d2..bf80820543102483ae5c41a301ad1ab0d322890d 100644 --- a/substrate/client/rpc/src/state/state_full.rs +++ b/substrate/client/rpc/src/state/state_full.rs @@ -33,7 +33,7 @@ use sp_core::{ }; use sp_version::RuntimeVersion; use sp_runtime::{ - generic::BlockId, traits::{Block as BlockT, NumberFor, SaturatedConversion}, + generic::BlockId, traits::{Block as BlockT, NumberFor, SaturatedConversion, CheckedSub}, }; use sp_api::{Metadata, ProvideRuntimeApi, CallApiAt}; @@ -94,8 +94,8 @@ impl<BE, Block: BlockT, Client> FullState<BE, Block, Client> let from_meta = self.client.header_metadata(from).map_err(invalid_block_err)?; let to_meta = self.client.header_metadata(to).map_err(invalid_block_err)?; - if from_meta.number >= to_meta.number { - return Err(invalid_block_range(&from_meta, &to_meta, "from number >= to number".to_owned())) + if from_meta.number > to_meta.number { + return Err(invalid_block_range(&from_meta, &to_meta, "from number > to number".to_owned())) } // check if we can get from `to` to `from` by going through parent_hashes. @@ -122,7 +122,10 @@ impl<BE, Block: BlockT, Client> FullState<BE, Block, Client> .max_key_changes_range(from_number, BlockId::Hash(to_meta.hash)) .map_err(client_err)?; let filtered_range_begin = changes_trie_range - .map(|(begin, _)| (begin - from_number).saturated_into::<usize>()); + .and_then(|(begin, _)| { + // avoids a corner case where begin < from_number (happens when querying genesis) + begin.checked_sub(&from_number).map(|x| x.saturated_into::<usize>()) + }); let (unfiltered_range, filtered_range) = split_range(hashes.len(), filtered_range_begin); Ok(QueryStorageRange { @@ -398,6 +401,15 @@ impl<BE, Block, Client> StateBackend<Block, Client> for FullState<BE, Block, Cli Box::new(result(call_fn())) } + fn query_storage_at( + &self, + keys: Vec<StorageKey>, + at: Option<Block::Hash> + ) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> { + let at = at.unwrap_or_else(|| self.client.info().best_hash); + self.query_storage(at, Some(at), keys) + } + fn subscribe_runtime_version( &self, _meta: crate::metadata::Metadata, diff --git a/substrate/client/rpc/src/state/state_light.rs b/substrate/client/rpc/src/state/state_light.rs index 59c0f2183cf898f0677ec9e39b843378244a7fda..092419ad0129e6f4109a19d3f916dc0eb24f5738 100644 --- a/substrate/client/rpc/src/state/state_light.rs +++ b/substrate/client/rpc/src/state/state_light.rs @@ -331,6 +331,14 @@ impl<Block, F, Client> StateBackend<Block, Client> for LightState<Block, F, Clie Box::new(result(Err(client_err(ClientError::NotAvailableOnLightClient)))) } + fn query_storage_at( + &self, + _keys: Vec<StorageKey>, + _at: Option<Block::Hash> + ) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> { + Box::new(result(Err(client_err(ClientError::NotAvailableOnLightClient)))) + } + fn subscribe_storage( &self, _meta: crate::metadata::Metadata, diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index c7b5f88215ec375cc00385af4d954157bb61a8f9..4a9b701959c8c292a3b4dba25d89762fedfd8f62 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -30,6 +30,7 @@ use substrate_test_runtime_client::{ sp_consensus::BlockOrigin, runtime, }; +use sp_runtime::generic::BlockId; const CHILD_INFO: ChildInfo<'static> = ChildInfo::new_default(b"unique_id"); @@ -212,7 +213,7 @@ fn should_send_initial_storage_changes_and_notifications() { #[test] fn should_query_storage() { - fn run_tests(mut client: Arc<TestClient>) { + fn run_tests(mut client: Arc<TestClient>, has_changes_trie_config: bool) { let core = tokio::runtime::Runtime::new().unwrap(); let api = new_full(client.clone(), Subscriptions::new(Arc::new(core.executor()))); @@ -237,6 +238,13 @@ fn should_query_storage() { let block2_hash = add_block(1); let genesis_hash = client.genesis_hash(); + if has_changes_trie_config { + assert_eq!( + client.max_key_changes_range(1, BlockId::Hash(block1_hash)).unwrap(), + Some((0, BlockId::Hash(block1_hash))), + ); + } + let mut expected = vec![ StorageChangeSet { block: genesis_hash, @@ -306,7 +314,7 @@ fn should_query_storage() { Err(Error::InvalidBlockRange { from: format!("1 ({:?})", block1_hash), to: format!("0 ({:?})", genesis_hash), - details: "from number >= to number".to_owned(), + details: "from number > to number".to_owned(), }).map_err(|e| e.to_string()) ); @@ -376,12 +384,39 @@ fn should_query_storage() { details: format!("UnknownBlock: header not found in db: {}", random_hash1), }).map_err(|e| e.to_string()), ); + + // single block range + let result = api.query_storage_at( + keys.clone(), + Some(block1_hash), + ); + + assert_eq!( + result.wait().unwrap(), + vec![ + StorageChangeSet { + block: block1_hash, + changes: vec![ + (StorageKey(vec![1_u8]), None), + (StorageKey(vec![2_u8]), Some(StorageData(vec![2_u8]))), + (StorageKey(vec![3_u8]), Some(StorageData(vec![3_u8]))), + (StorageKey(vec![4_u8]), None), + (StorageKey(vec![5_u8]), Some(StorageData(vec![0_u8]))), + ] + } + ] + ); } - run_tests(Arc::new(substrate_test_runtime_client::new())); - run_tests(Arc::new(TestClientBuilder::new() - .changes_trie_config(Some(ChangesTrieConfiguration::new(4, 2))) - .build())); + run_tests(Arc::new(substrate_test_runtime_client::new()), false); + run_tests( + Arc::new( + TestClientBuilder::new() + .changes_trie_config(Some(ChangesTrieConfiguration::new(4, 2))) + .build(), + ), + true, + ); } #[test]