Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,8 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where

/// Get pairs of (block, extrinsic) where key has been changed at given blocks range.
/// Works only for runtimes that are supporting changes tries.
///
/// Changes are returned in descending order (i.e. last block comes first).
pub fn key_changes(
&self,
first: NumberFor<Block>,
Expand Down
30 changes: 22 additions & 8 deletions core/rpc/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,25 +258,30 @@ impl<B, E, Block: BlockT, RA> State<B, E, Block, RA> where
&self,
range: &QueryStorageRange<Block>,
keys: &[StorageKey],
last_values: &mut HashMap<StorageKey, Option<StorageData>>,
changes: &mut Vec<StorageChangeSet<Block::Hash>>,
) -> Result<()> {
let mut last_state: HashMap<_, Option<_>> = Default::default();
for block in range.unfiltered_range.start..range.unfiltered_range.end {
let block_hash = range.hashes[block].clone();
let mut block_changes = StorageChangeSet { block: block_hash.clone(), changes: Vec::new() };
let id = BlockId::hash(block_hash);
for key in keys {
let (has_changed, data) = {
let curr_data = self.client.storage(&id, key)?;
let prev_data = last_state.get(key).and_then(|x| x.as_ref());
let prev_data = last_values.get(key).and_then(|x| x.as_ref());
(curr_data.as_ref() != prev_data, curr_data)
};
if has_changed {
block_changes.changes.push((key.clone(), data.clone()));
}
last_state.insert(key.clone(), data);
last_values.insert(key.clone(), data);
}

// always insert entry for the first block, because we're claiming to provide values of

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RPC method description claims that "This first returned result contains the initial state of storage for all keys.". But actually the first entry won't have a value for the key if the value is None:

{"jsonrpc":"2.0","result":[{"block":"0x61..0c","changes":[]}],"id":1}

But if later the key has been changed to Some(_) and then back to None, the changes will look like that (mind null in the last block' changes):

{"jsonrpc":"2.0","result":[{"block":"0x61..0c","changes":[]},{"block":"0x81..8d","changes":[["0x05","0x06"]]},{"block":"0x91..9e","changes":[["0x05",null]]}],"id":1}

So we are using both absence-of-key and null to mark None values. What do you think - does it worth to use only null for that? If so, I could revert last commit, that itself reverts this fix.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a bit confusing but it seems fine for me. The alternative would be to print out all the keys with null value in the initial block, right?
While we could do that if user subscribes to specific keys (still a bit wasteful), it would not be feasible in case of a wildcard.

I'm fine with both, maybe let's hear @jacogr's opinion on this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative would be to print out all the keys with null value in the initial block, right?

Exactly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer consistency, so always showing "something" - currently on the middleware side we have to handle both undefined and null - either way, as long as the order is correct, should not break anything.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, so it'll be null everywhere (speaking of this method only, of course)

// all keys in this entry (if key has None value, it will be missing from the changes field)
if changes.is_empty() || !block_changes.changes.is_empty() {
changes.push(block_changes);
}
changes.push(block_changes);
}
Ok(())
}
Expand All @@ -286,6 +291,7 @@ impl<B, E, Block: BlockT, RA> State<B, E, Block, RA> where
&self,
range: &QueryStorageRange<Block>,
keys: &[StorageKey],
last_values: &HashMap<StorageKey, Option<StorageData>>,
changes: &mut Vec<StorageChangeSet<Block::Hash>>,
) -> Result<()> {
let (begin, end) = match range.filtered_range {
Expand All @@ -298,17 +304,24 @@ impl<B, E, Block: BlockT, RA> State<B, E, Block, RA> where
let mut changes_map: BTreeMap<NumberFor<Block>, StorageChangeSet<Block::Hash>> = BTreeMap::new();
for key in keys {
let mut last_block = None;
for (block, _) in self.client.key_changes(begin, end, key)? {
let mut last_value = last_values.get(key).cloned().unwrap_or_default();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to clone here? Can't we compare with value_at_block.as_ref()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also used as storage for the last value (it is updated in the loop). I could use the same last_values map for this, but it is single clone() call vs multiple HashMap::get() + HashMap::insert() calls. Could change that if you feel this is better.

for (block, _) in self.client.key_changes(begin, end, key)?.into_iter().rev() {
if last_block == Some(block) {
continue;
}

let block_hash = range.hashes[(block - range.first_number).saturated_into::<usize>()].clone();
let id = BlockId::Hash(block_hash);
let value_at_block = self.client.storage(&id, key)?;
if last_value == value_at_block {
continue;
}

changes_map.entry(block)
.or_insert_with(|| StorageChangeSet { block: block_hash, changes: Vec::new() })
.changes.push((key.clone(), value_at_block));
.changes.push((key.clone(), value_at_block.clone()));
last_block = Some(block);
last_value = value_at_block;
}
}
if let Some(additional_capacity) = changes_map.len().checked_sub(changes.len()) {
Expand Down Expand Up @@ -432,8 +445,9 @@ impl<B, E, Block, RA> StateApi<Block::Hash> for State<B, E, Block, RA> where
) -> Result<Vec<StorageChangeSet<Block::Hash>>> {
let range = self.split_query_storage_range(from, to)?;
let mut changes = Vec::new();
self.query_storage_unfiltered(&range, &keys, &mut changes)?;
self.query_storage_filtered(&range, &keys, &mut changes)?;
let mut last_values = HashMap::new();
self.query_storage_unfiltered(&range, &keys, &mut last_values, &mut changes)?;
self.query_storage_filtered(&range, &keys, &last_values, &mut changes)?;
Ok(changes)
}

Expand Down
44 changes: 20 additions & 24 deletions core/rpc/src/state/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,16 @@ fn should_query_storage() {

let add_block = |nonce| {
let mut builder = client.new_block(Default::default()).unwrap();
builder.push_transfer(runtime::Transfer {
from: AccountKeyring::Alice.into(),
to: AccountKeyring::Ferdie.into(),
amount: 42,
nonce,
}).unwrap();
// fake change: None -> None -> None
builder.push_storage_change(vec![1], None).unwrap();
// fake change: None -> Some(value) -> Some(value)
builder.push_storage_change(vec![2], Some(vec![2])).unwrap();
// actual change: None -> Some(value) -> None
builder.push_storage_change(vec![3], if nonce == 0 { Some(vec![3]) } else { None }).unwrap();
// actual change: None -> Some(value)
builder.push_storage_change(vec![4], if nonce == 0 { None } else { Some(vec![4]) }).unwrap();
// actual change: Some(value1) -> Some(value2)
builder.push_storage_change(vec![5], Some(vec![nonce as u8])).unwrap();
let block = builder.bake().unwrap();
let hash = block.header.hash();
client.import(BlockOrigin::Own, block).unwrap();
Expand All @@ -182,32 +186,25 @@ fn should_query_storage() {
let block2_hash = add_block(1);
let genesis_hash = client.genesis_hash();

let alice_balance_key = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into()));

let mut expected = vec![
StorageChangeSet {
block: genesis_hash,
changes: vec![
(
StorageKey(alice_balance_key.to_vec()),
Some(StorageData(vec![232, 3, 0, 0, 0, 0, 0, 0]))
),
],
changes: vec![],
},
StorageChangeSet {
block: block1_hash,
changes: vec![
(
StorageKey(alice_balance_key.to_vec()),
Some(StorageData(vec![190, 3, 0, 0, 0, 0, 0, 0]))
),
(StorageKey(vec![2]), Some(StorageData(vec![2]))),
(StorageKey(vec![3]), Some(StorageData(vec![3]))),
(StorageKey(vec![5]), Some(StorageData(vec![0]))),
],
},
];

// Query changes only up to block1
let keys = (1..6).map(|k| StorageKey(vec![k])).collect::<Vec<_>>();
let result = api.query_storage(
vec![StorageKey(alice_balance_key.to_vec())],
keys.clone(),
genesis_hash,
Some(block1_hash).into(),
);
Expand All @@ -216,18 +213,17 @@ fn should_query_storage() {

// Query all changes
let result = api.query_storage(
vec![StorageKey(alice_balance_key.to_vec())],
keys.clone(),
genesis_hash,
None.into(),
);

expected.push(StorageChangeSet {
block: block2_hash,
changes: vec![
(
StorageKey(alice_balance_key.to_vec()),
Some(StorageData(vec![148, 3, 0, 0, 0, 0, 0, 0]))
),
(StorageKey(vec![3]), None),
(StorageKey(vec![4]), Some(StorageData(vec![4]))),
(StorageKey(vec![5]), Some(StorageData(vec![1]))),
],
});
assert_eq!(result.unwrap(), expected);
Expand Down
6 changes: 6 additions & 0 deletions core/test-runtime/client/src/block_builder_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use generic_test_client::client::block_builder::api::BlockBuilder;
pub trait BlockBuilderExt {
/// Add transfer extrinsic to the block.
fn push_transfer(&mut self, transfer: runtime::Transfer) -> Result<(), client::error::Error>;
/// Add storage change extrinsic to the block.
fn push_storage_change(&mut self, key: Vec<u8>, value: Option<Vec<u8>>) -> Result<(), client::error::Error>;
}

impl<'a, A> BlockBuilderExt for client::block_builder::BlockBuilder<'a, runtime::Block, A> where
Expand All @@ -34,4 +36,8 @@ impl<'a, A> BlockBuilderExt for client::block_builder::BlockBuilder<'a, runtime:
fn push_transfer(&mut self, transfer: runtime::Transfer) -> Result<(), client::error::Error> {
self.push(transfer.into_signed_tx())
}

fn push_storage_change(&mut self, key: Vec<u8>, value: Option<Vec<u8>>) -> Result<(), client::error::Error> {
self.push(runtime::Extrinsic::StorageChange(key, value))
}
}
2 changes: 2 additions & 0 deletions core/test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub enum Extrinsic {
AuthoritiesChange(Vec<AuthorityId>),
Transfer(Transfer, AccountSignature),
IncludeData(Vec<u8>),
StorageChange(Vec<u8>, Option<Vec<u8>>),
}

#[cfg(feature = "std")]
Expand All @@ -129,6 +130,7 @@ impl BlindCheckable for Extrinsic {
}
},
Extrinsic::IncludeData(_) => Err(runtime_primitives::BAD_SIGNATURE),
Extrinsic::StorageChange(key, value) => Ok(Extrinsic::StorageChange(key, value)),
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions core/test-runtime/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ fn execute_transaction_backend(utx: &Extrinsic) -> ApplyResult {
Extrinsic::Transfer(ref transfer, _) => execute_transfer_backend(transfer),
Extrinsic::AuthoritiesChange(ref new_auth) => execute_new_authorities_backend(new_auth),
Extrinsic::IncludeData(_) => Ok(ApplyOutcome::Success),
Extrinsic::StorageChange(key, value) => execute_storage_change(key, value.as_ref().map(|v| &**v)),
}
}

Expand Down Expand Up @@ -273,6 +274,14 @@ fn execute_new_authorities_backend(new_authorities: &[AuthorityId]) -> ApplyResu
Ok(ApplyOutcome::Success)
}

fn execute_storage_change(key: &[u8], value: Option<&[u8]>) -> ApplyResult {
match value {
Some(value) => storage::unhashed::put_raw(key, value),
None => storage::unhashed::kill(key),
}
Ok(ApplyOutcome::Success)
}

#[cfg(feature = "std")]
fn info_expect_equal_hash(given: &Hash, expected: &Hash) {
use primitives::hexdisplay::HexDisplay;
Expand Down