From 773db037a1eab0d83be0120eddfc164015056c90 Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Mon, 3 Feb 2020 20:18:59 -0300 Subject: [PATCH 1/2] Fix #2067: querying contract state --- core/primitives/src/views.rs | 3 +-- runtime/runtime/src/state_viewer.rs | 18 ++++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 21fb00dab8d..2022d88b387 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::fmt; @@ -124,7 +123,7 @@ impl From for AccessKey { #[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct ViewStateResult { - pub values: HashMap, Vec>, + pub values: Vec<(String, String)>, } #[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] diff --git a/runtime/runtime/src/state_viewer.rs b/runtime/runtime/src/state_viewer.rs index 8bd882185f5..ab96ca08ef9 100644 --- a/runtime/runtime/src/state_viewer.rs +++ b/runtime/runtime/src/state_viewer.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::str; use std::time::Instant; @@ -16,6 +15,7 @@ use near_vm_logic::{ReturnData, VMConfig, VMContext}; use crate::actions::get_code_with_cache; use crate::ext::RuntimeExt; +use near_primitives::serialize::to_base64; pub struct TrieViewer {} @@ -60,14 +60,14 @@ impl TrieViewer { if !is_valid_account_id(account_id) { return Err(format!("Account ID '{}' is not valid", account_id).into()); } - let mut values = HashMap::default(); + let mut values: Vec<(String, String)> = Default::default(); let mut query = prefix_for_data(account_id); let acc_sep_len = query.len(); query.extend_from_slice(prefix); state_update.for_keys_with_prefix(&query, |key| { // TODO error if let Ok(Some(value)) = state_update.get(key) { - values.insert(key[acc_sep_len..].to_vec(), value.to_vec()); + values.push((to_base64(&key[acc_sep_len..]), to_base64(&value))); } })?; Ok(ViewStateResult { values }) @@ -238,16 +238,10 @@ mod tests { let state_update = TrieUpdate::new(trie, new_root); let trie_viewer = TrieViewer::new(); let result = trie_viewer.view_state(&state_update, &alice_account(), b"").unwrap(); - assert_eq!( - result.values, - [(b"test123".to_vec(), b"123".to_vec())].iter().cloned().collect() - ); + assert_eq!(result.values, [("dGVzdDEyMw==".to_string(), "MTIz".to_string())]); let result = trie_viewer.view_state(&state_update, &alice_account(), b"test321").unwrap(); - assert_eq!(result.values, [].iter().cloned().collect()); + assert_eq!(result.values, []); let result = trie_viewer.view_state(&state_update, &alice_account(), b"test123").unwrap(); - assert_eq!( - result.values, - [(b"test123".to_vec(), b"123".to_vec())].iter().cloned().collect() - ) + assert_eq!(result.values, [("dGVzdDEyMw==".to_string(), "MTIz".to_string())]) } } From 0c234af20fbbbfd4f20f34a2218ca9f8ee9ad7fe Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Tue, 4 Feb 2020 18:20:59 -0300 Subject: [PATCH 2/2] Change format to {key, value, proof} for state view. Proofs are empty for now --- core/primitives/src/views.rs | 14 ++++++- core/store/src/trie/update.rs | 2 +- runtime/runtime/src/state_viewer.rs | 58 +++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 2022d88b387..cc2db1014ba 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -121,9 +121,21 @@ impl From for AccessKey { } } +/// Set of serialized TrieNodes that are encoded in base64. Represent proof of inclusion of some TrieNode in the MerkleTrie. +pub type TrieProofPath = Vec; + +/// Item of the state, key and value are serialized in base64 and proof for inclusion of given state item. +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct StateItem { + pub key: String, + pub value: String, + pub proof: TrieProofPath, +} + #[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct ViewStateResult { - pub values: Vec<(String, String)>, + pub values: Vec, + pub proof: TrieProofPath, } #[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] diff --git a/core/store/src/trie/update.rs b/core/store/src/trie/update.rs index 1e9d9e81384..d8355c11b2e 100644 --- a/core/store/src/trie/update.rs +++ b/core/store/src/trie/update.rs @@ -12,7 +12,7 @@ use crate::StorageError; /// Provides a way to access Storage and record changes with future commit. pub struct TrieUpdate { pub trie: Arc, - root: CryptoHash, + pub root: CryptoHash, committed: BTreeMap, Option>>, prospective: BTreeMap, Option>>, } diff --git a/runtime/runtime/src/state_viewer.rs b/runtime/runtime/src/state_viewer.rs index ab96ca08ef9..76d60b8c894 100644 --- a/runtime/runtime/src/state_viewer.rs +++ b/runtime/runtime/src/state_viewer.rs @@ -6,16 +6,16 @@ use borsh::BorshSerialize; use near_crypto::{KeyType, PublicKey}; use near_primitives::account::{AccessKey, Account}; use near_primitives::hash::CryptoHash; +use near_primitives::serialize::to_base64; use near_primitives::types::{AccountId, BlockHeight}; use near_primitives::utils::{is_valid_account_id, prefix_for_data}; -use near_primitives::views::ViewStateResult; +use near_primitives::views::{StateItem, ViewStateResult}; use near_runtime_fees::RuntimeFeesConfig; use near_store::{get_access_key, get_account, TrieUpdate}; use near_vm_logic::{ReturnData, VMConfig, VMContext}; use crate::actions::get_code_with_cache; use crate::ext::RuntimeExt; -use near_primitives::serialize::to_base64; pub struct TrieViewer {} @@ -60,17 +60,25 @@ impl TrieViewer { if !is_valid_account_id(account_id) { return Err(format!("Account ID '{}' is not valid", account_id).into()); } - let mut values: Vec<(String, String)> = Default::default(); + let mut values = vec![]; let mut query = prefix_for_data(account_id); let acc_sep_len = query.len(); query.extend_from_slice(prefix); - state_update.for_keys_with_prefix(&query, |key| { - // TODO error - if let Ok(Some(value)) = state_update.get(key) { - values.push((to_base64(&key[acc_sep_len..]), to_base64(&value))); + let mut iter = state_update.trie.iter(&state_update.root)?; + iter.seek(&query)?; + for item in iter { + let (key, value) = item?; + if !key.starts_with(&query) { + break; } - })?; - Ok(ViewStateResult { values }) + values.push(StateItem { + key: to_base64(&key[acc_sep_len..]), + value: to_base64(&value), + proof: vec![], + }); + } + // TODO(2076): Add proofs for the storage items. + Ok(ViewStateResult { values, proof: vec![] }) } pub fn call_function( @@ -165,6 +173,7 @@ impl TrieViewer { #[cfg(test)] mod tests { use near_primitives::utils::key_for_data; + use near_primitives::views::StateItem; use testlib::runtime_utils::{ alice_account, encode_int, get_runtime_and_trie, get_test_trie_viewer, }; @@ -232,16 +241,41 @@ mod tests { let (_, trie, root) = get_runtime_and_trie(); let mut state_update = TrieUpdate::new(trie.clone(), root); state_update.set(key_for_data(&alice_account(), b"test123"), b"123".to_vec()); + state_update.set(key_for_data(&alice_account(), b"test321"), b"321".to_vec()); + state_update.set(key_for_data(&"alina".to_string(), b"qqq"), b"321".to_vec()); + state_update.set(key_for_data(&"alex".to_string(), b"qqq"), b"321".to_vec()); let (db_changes, new_root) = state_update.finalize().unwrap().into(trie.clone()).unwrap(); db_changes.commit().unwrap(); let state_update = TrieUpdate::new(trie, new_root); let trie_viewer = TrieViewer::new(); let result = trie_viewer.view_state(&state_update, &alice_account(), b"").unwrap(); - assert_eq!(result.values, [("dGVzdDEyMw==".to_string(), "MTIz".to_string())]); - let result = trie_viewer.view_state(&state_update, &alice_account(), b"test321").unwrap(); + assert_eq!(result.proof, Vec::::new()); + assert_eq!( + result.values, + [ + StateItem { + key: "dGVzdDEyMw==".to_string(), + value: "MTIz".to_string(), + proof: vec![] + }, + StateItem { + key: "dGVzdDMyMQ==".to_string(), + value: "MzIx".to_string(), + proof: vec![] + } + ] + ); + let result = trie_viewer.view_state(&state_update, &alice_account(), b"xyz").unwrap(); assert_eq!(result.values, []); let result = trie_viewer.view_state(&state_update, &alice_account(), b"test123").unwrap(); - assert_eq!(result.values, [("dGVzdDEyMw==".to_string(), "MTIz".to_string())]) + assert_eq!( + result.values, + [StateItem { + key: "dGVzdDEyMw==".to_string(), + value: "MTIz".to_string(), + proof: vec![] + }] + ); } }