diff --git a/client/README.md b/client/README.md index 84563a8716..4bdc91457c 100644 --- a/client/README.md +++ b/client/README.md @@ -39,7 +39,7 @@ SUBCOMMANDS: get-block-transfers Retrieves all transfers for a block from the network list-deploys Retrieves the list of all deploy hashes in a given block get-state-root-hash Retrieves a state root hash at a given block - query-state Retrieves a stored value from the network + query-global-state Retrieves a stored value from the network get-balance Retrieves a purse's balance from the network get-auction-info Retrieves the bids and validators as of the most recently added block keygen Generates account key files in the given directory @@ -342,11 +342,11 @@ cargo run --release -- get-block-transfers \ ### Query the global state -To view data stored to global state after executing a deploy, you can use `query-state`. For example, to see the value +To view data stored to global state after executing a deploy, you can use `query-global-state`. For example, to see the value stored under our new account's public key: ``` -cargo run --release -- query-state \ +cargo run --release -- query-global-state \ --node-address=http://localhost:50101 \ --state-root-hash=242666f5959e6a51b7a75c23264f3cb326eecd6bec6dbab147f5801ec23daed6 \ --key=$PUBLIC_KEY @@ -411,7 +411,7 @@ cargo run --release -- get-balance \ Note that the system mint contract is required to retrieve the balance of any given purse. If you execute a -`query-state` specifying a purse `URef` as the `--key` argument, you'll find that the actual value stored there is a +`query-global-state` specifying a purse `URef` as the `--key` argument, you'll find that the actual value stored there is a unit value `()`. This makes the `get-balance` subcommand particularly useful. --- diff --git a/client/lib/error.rs b/client/lib/error.rs index c5c5d5fd12..daec5ece57 100644 --- a/client/lib/error.rs +++ b/client/lib/error.rs @@ -165,6 +165,10 @@ pub enum Error { #[error("Failed to parse the dictionary identifier")] FailedToParseDictionaryIdentifier, + /// Failed to identify the hash as either block hash or state root hash. + #[error("Failed to parse state identifier")] + FailedToParseStateIdentifier, + /// Must call FFI's setup function prior to making FFI calls. #[cfg(feature = "ffi")] #[error("Failed to call casper_setup_client()")] diff --git a/client/lib/ffi.rs b/client/lib/ffi.rs index 70fb6d25ca..9b789b08b9 100644 --- a/client/lib/ffi.rs +++ b/client/lib/ffi.rs @@ -54,6 +54,7 @@ pub enum casper_error_t { CASPER_CONFLICTING_ARGUMENTS = -23, CASPER_DEPLOY_SIZE_TOO_LARGE = -24, CASPER_FAILED_TO_CREATE_DICTIONARY_IDENTIFIER = -25, + CASPER_FAILED_TO_PARSE_STATE_IDENTIFIER = -26, } trait AsFFIError { @@ -92,6 +93,9 @@ impl AsFFIError for Error { Error::FailedToParseDictionaryIdentifier => { casper_error_t::CASPER_FAILED_TO_CREATE_DICTIONARY_IDENTIFIER } + Error::FailedToParseStateIdentifier => { + casper_error_t::CASPER_FAILED_TO_PARSE_STATE_IDENTIFIER + } } } } @@ -547,6 +551,7 @@ pub extern "C" fn casper_get_state_root_hash( /// /// See [get_item](super::get_item) for more details. #[no_mangle] +#[deprecated(note = "Users should use `casper_client::query_global_state` instead.")] pub extern "C" fn casper_get_item( maybe_rpc_id: *const c_char, node_address: *const c_char, @@ -565,6 +570,7 @@ pub extern "C" fn casper_get_item( let key = try_unsafe_arg!(key); let path = try_unsafe_arg!(path); runtime.block_on(async move { + #[allow(deprecated)] let result = super::get_item( maybe_rpc_id, node_address, @@ -580,6 +586,43 @@ pub extern "C" fn casper_get_item( }) } +/// Retrieves information from global state using either a Block hash or a state root hash. +/// +/// See [query_global_state](super::query_global_state) for more info. +#[no_mangle] +pub extern "C" fn casper_query_global_state( + maybe_rpc_id: *const c_char, + node_address: *const c_char, + verbosity_level: u64, + global_state_params: *const casper_global_state_params_t, + key: *const c_char, + path: *const c_char, + response_buf: *mut c_uchar, + response_buf_len: usize, +) -> casper_error_t { + let mut runtime = RUNTIME.lock().expect("should lock"); + let runtime = try_unwrap_option!(&mut *runtime, or_else => Error::FFISetupNotCalled); + let maybe_rpc_id = try_unsafe_arg!(maybe_rpc_id); + let node_address = try_unsafe_arg!(node_address); + let global_state_params = try_arg_into!(global_state_params); + let key = try_unsafe_arg!(key); + let path = try_unsafe_arg!(path); + runtime.block_on(async move { + let result = super::query_global_state( + maybe_rpc_id, + node_address, + verbosity_level, + global_state_params, + key, + path, + ) + .await; + let response = try_unwrap_rpc!(result); + copy_str_to_buf(&response, response_buf, response_buf_len); + casper_error_t::CASPER_SUCCESS + }) +} + /// Retrieves a purse's balance from the network. /// /// See [get_balance](super::get_balance) for more details. @@ -878,3 +921,27 @@ impl TryInto> for casper_session_params_t { }) } } + +/// The two ways to construct a query to global state. +/// +/// See [GlobalStateStrParams](super::GlobalStateStrParams) for more info. +#[allow(non_snake_case)] +#[repr(C)] +#[derive(Clone)] +pub struct casper_global_state_params_t { + is_block_hash: bool, + hash_value: *const c_char, +} + +impl TryInto> for casper_global_state_params_t { + type Error = Error; + + fn try_into(self) -> Result> { + let hash_value = + unsafe_str_arg(self.hash_value, "casper_global_state_params_t.hash_value")?; + Ok(super::GlobalStateStrParams { + hash_value, + is_block_hash: self.is_block_hash, + }) + } +} diff --git a/client/lib/lib.rs b/client/lib/lib.rs index 97a3431458..f218c59be6 100644 --- a/client/lib/lib.rs +++ b/client/lib/lib.rs @@ -28,7 +28,11 @@ use jsonrpc_lite::JsonRpc; use serde::Serialize; use casper_execution_engine::core::engine_state::ExecutableDeployItem; -use casper_node::{rpcs::state::DictionaryIdentifier, types::Deploy}; +use casper_node::{ + crypto::hash::Digest, + rpcs::state::{DictionaryIdentifier, GlobalStateIdentifier}, + types::{BlockHash, Deploy}, +}; use casper_types::Key; pub use cl_type::help; @@ -381,14 +385,21 @@ pub async fn get_state_root_hash( /// or [`Key`](https://docs.rs/casper-types/latest/casper-types/enum.PublicKey.html). This will /// take one of the following forms: /// ```text -/// 01c9e33693951aaac23c49bee44ad6f863eedcd38c084a3a8f11237716a3df9c2c # PublicKey +/// 01c9e33693951aaac23c49bee44ad6f863eedcd38c084a3a8f11237716a3df9c2c # PublicKey /// account-hash-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Account -/// hash-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Hash -/// uref-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20-007 # Key::URef -/// transfer-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Transfer -/// deploy-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::DeployInfo +/// hash-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Hash +/// uref-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20-007 # Key::URef +/// transfer-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Transfer +/// deploy-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::DeployInfo +/// era-1 # Key::EraInfo +/// bid-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Bid +/// withdraw-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Withdraw +/// dictionary-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Dictionary +/// The Key::SystemContractRegistry variant is unique and can only take the following value: +/// system-contract-registry-0000000000000000000000000000000000000000000000000000000000000000 /// ``` /// * `path` is comprised of components starting from the `key`, separated by `/`s. +#[deprecated(note = "Users should use `casper_client::query_global_state` instead.")] pub async fn get_item( maybe_rpc_id: &str, node_address: &str, @@ -510,6 +521,51 @@ pub async fn get_account_info( .await } +/// Retrieves information from global state using either a Block hash or a state root hash. +/// +/// * `maybe_rpc_id` is the JSON-RPC identifier, applied to the request and returned in the +/// response. If it can be parsed as an `i64` it will be used as a JSON integer. If empty, a +/// random `i64` will be assigned. Otherwise the provided string will be used verbatim. +/// * `node_address` is the hostname or IP and port of the node on which the HTTP service is +/// running, e.g. `"http://127.0.0.1:7777"`. +/// * When `verbosity_level` is `1`, the JSON-RPC request will be printed to `stdout` with long +/// string fields (e.g. hex-formatted raw Wasm bytes) shortened to a string indicating the char +/// count of the field. When `verbosity_level` is greater than `1`, the request will be printed +/// to `stdout` with no abbreviation of long fields. When `verbosity_level` is `0`, the request +/// will not be printed to `stdout`. +/// * `global_state_str_params` contains global state identifier related options for this query. See +/// [`GlobalStateStrParams`](struct.GlobalStateStrParams.html) for more details. +/// * `key` must be a formatted [`PublicKey`](https://docs.rs/casper-node/latest/casper-node/crypto/asymmetric_key/enum.PublicKey.html) +/// or [`Key`](https://docs.rs/casper-types/latest/casper-types/enum.Key.html). This will take one +/// of the following forms: +/// ```text +/// 01c9e33693951aaac23c49bee44ad6f863eedcd38c084a3a8f11237716a3df9c2c # PublicKey +/// account-hash-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Account +/// hash-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Hash +/// uref-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20-007 # Key::URef +/// transfer-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Transfer +/// deploy-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::DeployInfo +/// era-1 # Key::EraInfo +/// bid-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Bid +/// withdraw-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Withdraw +/// dictionary-0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 # Key::Dictionary +/// The Key::SystemContractRegistry variant is unique and can only take the following value: +/// system-contract-registry-0000000000000000000000000000000000000000000000000000000000000000 +/// ``` +/// * `path` is comprised of components starting from the `key`, separated by `/`s. +pub async fn query_global_state( + maybe_rpc_id: &str, + node_address: &str, + verbosity_level: u64, + global_state_str_params: GlobalStateStrParams<'_>, + key: &str, + path: &str, +) -> Result { + RpcCall::new(maybe_rpc_id, node_address, verbosity_level) + .query_global_state(global_state_str_params, key, path) + .await +} + /// Retrieves information and examples for all currently supported RPCs. /// /// * `maybe_rpc_id` is the JSON-RPC identifier, applied to the request and returned in the @@ -1088,6 +1144,33 @@ impl<'a> TryInto for DictionaryItemStrParams<'a> { } } +/// The two ways to construct a query to global state. +#[derive(Default, Debug)] +pub struct GlobalStateStrParams<'a> { + /// Identifier to mark the hash as either a Block hash or `state_root_hash` + /// When true, the hash provided is a Block hash. + pub is_block_hash: bool, + /// The hex-encoded hash value. + pub hash_value: &'a str, +} + +impl<'a> TryInto for GlobalStateStrParams<'a> { + type Error = Error; + + fn try_into(self) -> Result { + let hash = Digest::from_hex(self.hash_value).map_err(|error| Error::CryptoError { + context: "global_state_identifier", + error, + })?; + + if self.is_block_hash { + Ok(GlobalStateIdentifier::BlockHash(BlockHash::new(hash))) + } else { + Ok(GlobalStateIdentifier::StateRootHash(hash)) + } + } +} + /// When `verbosity_level` is `1`, the value will be printed to `stdout` with long string fields /// (e.g. hex-formatted raw Wasm bytes) shortened to a string indicating the char count of the /// field. When `verbosity_level` is greater than `1`, the value will be printed to `stdout` with diff --git a/client/lib/rpc.rs b/client/lib/rpc.rs index 8b237bb6e2..54ba93c69c 100644 --- a/client/lib/rpc.rs +++ b/client/lib/rpc.rs @@ -21,6 +21,7 @@ use casper_node::{ state::{ GetAccountInfo, GetAccountInfoParams, GetAuctionInfo, GetAuctionInfoParams, GetBalance, GetBalanceParams, GetDictionaryItem, GetDictionaryItemParams, GetItem, GetItemParams, + GlobalStateIdentifier, QueryGlobalState, QueryGlobalStateParams, }, RpcWithOptionalParams, RpcWithParams, RpcWithoutParams, RPC_API_PATH, }, @@ -31,7 +32,7 @@ use casper_types::{AsymmetricType, Key, PublicKey, URef}; use crate::{ deploy::{DeployExt, DeployParams, SendDeploy, Transfer}, error::{Error, Result}, - validation, DictionaryItemStrParams, + validation, DictionaryItemStrParams, GlobalStateStrParams, }; /// Struct representing a single JSON-RPC call to the casper node. @@ -289,6 +290,41 @@ impl RpcCall { GetAccountInfo::request_with_map_params(self, params).await } + pub(crate) async fn query_global_state( + self, + global_state_str_params: GlobalStateStrParams<'_>, + key: &str, + path: &str, + ) -> Result { + let global_state_identifier: GlobalStateIdentifier = global_state_str_params.try_into()?; + + let key = { + if let Ok(key) = Key::from_formatted_str(key) { + key + } else if let Ok(public_key) = PublicKey::from_hex(key) { + Key::Account(public_key.to_account_hash()) + } else { + return Err(Error::FailedToParseKey); + } + }; + + let path = if path.is_empty() { + vec![] + } else { + path.split('/').map(ToString::to_string).collect() + }; + + let params = QueryGlobalStateParams { + state_identifier: global_state_identifier.clone(), + key: key.to_formatted_string(), + path: path.clone(), + }; + + let response = QueryGlobalState::request_with_map_params(self, params).await?; + validation::validate_query_global_state(&response, global_state_identifier, &key, &path)?; + Ok(response) + } + fn block_identifier(maybe_block_identifier: &str) -> Result> { if maybe_block_identifier.is_empty() { return Ok(None); @@ -427,6 +463,10 @@ impl RpcClient for GetDictionaryItem { const RPC_METHOD: &'static str = Self::METHOD; } +impl RpcClient for QueryGlobalState { + const RPC_METHOD: &'static str = Self::METHOD; +} + pub(crate) trait IntoJsonMap: Serialize { fn into_json_map(self) -> Map where @@ -451,3 +491,4 @@ impl IntoJsonMap for ListRpcs {} impl IntoJsonMap for GetAuctionInfoParams {} impl IntoJsonMap for GetAccountInfoParams {} impl IntoJsonMap for GetDictionaryItemParams {} +impl IntoJsonMap for QueryGlobalStateParams {} diff --git a/client/lib/validation.rs b/client/lib/validation.rs index e36c76dbbb..3b949e6976 100644 --- a/client/lib/validation.rs +++ b/client/lib/validation.rs @@ -9,14 +9,20 @@ use casper_execution_engine::{ }; use casper_node::{ crypto::hash::Digest, - rpcs::chain::{BlockIdentifier, EraSummary, GetEraInfoResult}, - types::{json_compatibility, Block, BlockValidationError, JsonBlock}, + rpcs::{ + chain::{BlockIdentifier, EraSummary, GetEraInfoResult}, + state::GlobalStateIdentifier, + }, + types::{ + json_compatibility, Block, BlockHeader, BlockValidationError, JsonBlock, JsonBlockHeader, + }, }; use casper_types::{bytesrepr, Key, U512}; const GET_ITEM_RESULT_BALANCE_VALUE: &str = "balance_value"; const GET_ITEM_RESULT_STORED_VALUE: &str = "stored_value"; const GET_ITEM_RESULT_MERKLE_PROOF: &str = "merkle_proof"; +const QUERY_GLOBAL_STATE_BLOCK_HEADER: &str = "block_header"; /// Error that can be returned when validating a block returned from a JSON-RPC method. #[derive(Error, Debug)] @@ -56,6 +62,10 @@ pub enum ValidateResponseError { /// Block height was not as requested. #[error("block height was not as requested")] UnexpectedBlockHeight, + + /// An invalid combination of state identifier and block header response + #[error("Invalid combination of state identifier and block header in response")] + InvalidGlobalStateResponse, } impl From for ValidateResponseError { @@ -174,6 +184,67 @@ pub(crate) fn validate_query_response( .map_err(Into::into) } +pub(crate) fn validate_query_global_state( + response: &JsonRpc, + state_identifier: GlobalStateIdentifier, + key: &Key, + path: &[String], +) -> Result<(), ValidateResponseError> { + let value = response + .get_result() + .ok_or(ValidateResponseError::ValidateResponseFailedToParse)?; + + let object = value + .as_object() + .ok_or(ValidateResponseError::ValidateResponseFailedToParse)?; + + let proofs: Vec> = { + let proof = object + .get(GET_ITEM_RESULT_MERKLE_PROOF) + .ok_or(ValidateResponseError::ValidateResponseFailedToParse)?; + let proof_str = proof + .as_str() + .ok_or(ValidateResponseError::ValidateResponseFailedToParse)?; + let proof_bytes = hex::decode(proof_str) + .map_err(|_| ValidateResponseError::ValidateResponseFailedToParse)?; + bytesrepr::deserialize(proof_bytes)? + }; + + let proof_value: &StoredValue = { + let last_proof = proofs + .last() + .ok_or(ValidateResponseError::ValidateResponseFailedToParse)?; + last_proof.value() + }; + + let json_block_header_value = object + .get(QUERY_GLOBAL_STATE_BLOCK_HEADER) + .ok_or(ValidateResponseError::ValidateResponseFailedToParse)?; + let maybe_json_block_header: Option = + serde_json::from_value(json_block_header_value.to_owned())?; + + let state_root_hash = match (state_identifier, maybe_json_block_header) { + (GlobalStateIdentifier::BlockHash(_), None) + | (GlobalStateIdentifier::StateRootHash(_), Some(_)) => { + return Err(ValidateResponseError::InvalidGlobalStateResponse); + } + (GlobalStateIdentifier::BlockHash(_), Some(json_header)) => { + let block_header = BlockHeader::from(json_header); + *block_header.state_root_hash() + } + (GlobalStateIdentifier::StateRootHash(hash), None) => hash, + }; + + core::validate_query_proof( + &state_root_hash.to_owned().into(), + &proofs, + key, + path, + proof_value, + ) + .map_err(Into::into) +} + pub(crate) fn validate_get_balance_response( response: &JsonRpc, state_root_hash: &Digest, diff --git a/client/src/main.rs b/client/src/main.rs index e69ba39fca..b663ce51a8 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -12,7 +12,7 @@ mod get_era_info_by_switch_block; mod get_state_hash; mod keygen; mod query_dictionary; -mod query_state; +mod query_global_state; use std::process; @@ -24,7 +24,7 @@ use casper_node::rpcs::{ chain::{GetBlock, GetBlockTransfers, GetEraInfoBySwitchBlock, GetStateRootHash}, docs::ListRpcs, info::GetDeploy, - state::{GetAccountInfo, GetAuctionInfo, GetBalance, GetDictionaryItem, GetItem as QueryState}, + state::{GetAccountInfo, GetAuctionInfo, GetBalance, GetDictionaryItem, QueryGlobalState}, }; use account_address::GenerateAccountHash as AccountAddress; @@ -48,7 +48,8 @@ enum DisplayOrder { GetBlockTransfers, ListDeploys, GetStateRootHash, - QueryState, + QueryGlobalState, + GetDictionaryItem, GetBalance, GetAccountInfo, GetEraInfo, @@ -57,7 +58,6 @@ enum DisplayOrder { GenerateCompletion, GetRpcs, AccountAddress, - GetDictionaryItem, } fn cli<'a, 'b>() -> App<'a, 'b> { @@ -81,7 +81,6 @@ fn cli<'a, 'b>() -> App<'a, 'b> { .subcommand(GetStateRootHash::build( DisplayOrder::GetStateRootHash as usize, )) - .subcommand(QueryState::build(DisplayOrder::QueryState as usize)) .subcommand(GetEraInfoBySwitchBlock::build( DisplayOrder::GetEraInfo as usize, )) @@ -95,6 +94,9 @@ fn cli<'a, 'b>() -> App<'a, 'b> { .subcommand(GetDictionaryItem::build( DisplayOrder::GetDictionaryItem as usize, )) + .subcommand(QueryGlobalState::build( + DisplayOrder::QueryGlobalState as usize, + )) } #[tokio::main] @@ -116,7 +118,6 @@ async fn main() { (GetBalance::NAME, Some(matches)) => (GetBalance::run(matches).await, matches), (GetAccountInfo::NAME, Some(matches)) => (GetAccountInfo::run(matches).await, matches), (GetStateRootHash::NAME, Some(matches)) => (GetStateRootHash::run(matches).await, matches), - (QueryState::NAME, Some(matches)) => (QueryState::run(matches).await, matches), (GetEraInfoBySwitchBlock::NAME, Some(matches)) => { (GetEraInfoBySwitchBlock::run(matches).await, matches) } @@ -130,6 +131,7 @@ async fn main() { (GetDictionaryItem::NAME, Some(matches)) => { (GetDictionaryItem::run(matches).await, matches) } + (QueryGlobalState::NAME, Some(matches)) => (QueryGlobalState::run(matches).await, matches), _ => { let _ = cli().print_long_help(); diff --git a/client/src/query_global_state.rs b/client/src/query_global_state.rs new file mode 100644 index 0000000000..480be2db64 --- /dev/null +++ b/client/src/query_global_state.rs @@ -0,0 +1,217 @@ +use std::{fs, str}; + +use async_trait::async_trait; +use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand}; + +use casper_client::{Error, GlobalStateStrParams}; +use casper_node::rpcs::state::QueryGlobalState; + +use crate::{command::ClientCommand, common, Success}; + +const ARG_HEX_STRING: &str = "HEX STRING"; + +/// This struct defines the order in which the args are shown for this subcommand's help message. +enum DisplayOrder { + Verbose, + NodeAddress, + RpcId, + BlockHash, + StateRootHash, + Key, + Path, +} + +mod state_root_hash { + use super::*; + + pub(super) const ARG_NAME: &str = "state-root-hash"; + const ARG_SHORT: &str = "s"; + const ARG_VALUE_NAME: &str = ARG_HEX_STRING; + const ARG_HELP: &str = "Hex-encoded hash of the state root"; + + pub(super) fn arg() -> Arg<'static, 'static> { + Arg::with_name(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(false) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(DisplayOrder::StateRootHash as usize) + } + + pub fn get<'a>(matches: &'a ArgMatches) -> Option<&'a str> { + matches.value_of(ARG_NAME) + } +} + +mod block_hash { + use super::*; + + pub(super) const ARG_NAME: &str = "block-hash"; + const ARG_SHORT: &str = "b"; + const ARG_VALUE_NAME: &str = ARG_HEX_STRING; + const ARG_HELP: &str = "Hex-encoded hash of the block"; + + pub(super) fn arg() -> Arg<'static, 'static> { + Arg::with_name(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(false) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(DisplayOrder::BlockHash as usize) + } + + pub fn get<'a>(matches: &'a ArgMatches) -> Option<&'a str> { + matches.value_of(ARG_NAME) + } +} + +/// Handles providing the arg for and retrieval of the key. +mod key { + use casper_node::crypto::AsymmetricKeyExt; + use casper_types::{AsymmetricType, PublicKey}; + + use super::*; + + const ARG_NAME: &str = "key"; + const ARG_SHORT: &str = "k"; + const ARG_VALUE_NAME: &str = "FORMATTED STRING or PATH"; + const ARG_HELP: &str = + "The base key for the query. This must be a properly formatted public key, account hash, \ + contract address hash, URef, transfer hash, deploy-info hash,era-info number, bid, withdraw \ + or dictionary address. The format for each respectively is \"\", \ + \"account-hash-\", \"hash-\", \ + \"uref--\", \"transfer-\", \ + \"deploy-\", \"era-\", \"bid-\",\ + \"withdraw-\" or \"dictionary-\". \ + The system contract registry key is unique and can only take the value: \ + system-contract-registry-0000000000000000000000000000000000000000000000000000000000000000. \ + \nThe public key may instead be read in from a file, in which case \ + enter the path to the file as the --key argument. The file should be one of the two public \ + key files generated via the `keygen` subcommand; \"public_key_hex\" or \"public_key.pem\""; + + pub(crate) fn arg(order: usize) -> Arg<'static, 'static> { + Arg::with_name(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(true) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(order) + } + + pub(crate) fn get(matches: &ArgMatches) -> Result { + let value = matches + .value_of(ARG_NAME) + .unwrap_or_else(|| panic!("should have {} arg", ARG_NAME)); + + // Try to read as a PublicKey PEM file first. + if let Ok(public_key) = PublicKey::from_file(value) { + return Ok(public_key.to_hex()); + } + + // Try to read as a hex-encoded PublicKey file next. + if let Ok(hex_public_key) = fs::read_to_string(value) { + let _ = PublicKey::from_hex(&hex_public_key).map_err(|error| { + eprintln!( + "Can't parse the contents of {} as a public key: {}", + value, error + ); + Error::FailedToParseKey + })?; + return Ok(hex_public_key); + } + + // Just return the value. + Ok(value.to_string()) + } +} + +/// Handles providing the arg for and retrieval of the key. +mod path { + use super::*; + + const ARG_NAME: &str = "query-path"; + const ARG_SHORT: &str = "q"; + const ARG_VALUE_NAME: &str = "PATH/FROM/KEY"; + const ARG_HELP: &str = "The path from the key of the query"; + + pub(crate) fn arg(order: usize) -> Arg<'static, 'static> { + Arg::with_name(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(false) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(order) + } + + pub(crate) fn get<'a>(matches: &'a ArgMatches) -> &'a str { + matches.value_of(ARG_NAME).unwrap_or_default() + } +} + +fn global_state_str_params<'a>(matches: &'a ArgMatches) -> GlobalStateStrParams<'a> { + if let Some(state_root_hash) = state_root_hash::get(matches) { + return GlobalStateStrParams { + is_block_hash: false, + hash_value: state_root_hash, + }; + } + if let Some(block_hash) = block_hash::get(matches) { + return GlobalStateStrParams { + is_block_hash: true, + hash_value: block_hash, + }; + } + unreachable!("clap arg groups and parsing should prevent this for global state params") +} + +#[async_trait] +impl<'a, 'b> ClientCommand<'a, 'b> for QueryGlobalState { + const NAME: &'static str = "query-global-state"; + const ABOUT: &'static str = + "Retrieves a stored value from the network using either the state root hash or block hash"; + + fn build(display_order: usize) -> App<'a, 'b> { + SubCommand::with_name(Self::NAME) + .about(Self::ABOUT) + .display_order(display_order) + .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) + .arg(common::node_address::arg( + DisplayOrder::NodeAddress as usize, + )) + .arg(common::rpc_id::arg(DisplayOrder::RpcId as usize)) + .arg(key::arg(DisplayOrder::Key as usize)) + .arg(path::arg(DisplayOrder::Path as usize)) + .arg(block_hash::arg()) + .arg(state_root_hash::arg()) + .group( + ArgGroup::with_name("state-identifier") + .arg(state_root_hash::ARG_NAME) + .arg(block_hash::ARG_NAME) + .required(true), + ) + } + + async fn run(matches: &ArgMatches<'a>) -> Result { + let maybe_rpc_id = common::rpc_id::get(matches); + let node_address = common::node_address::get(matches); + let verbosity_level = common::verbose::get(matches); + let global_state_str_params = global_state_str_params(matches); + let key = key::get(matches)?; + let path = path::get(matches); + + casper_client::query_global_state( + maybe_rpc_id, + node_address, + verbosity_level, + global_state_str_params, + &key, + path, + ) + .await + .map(Success::from) + } +} diff --git a/client/src/query_state.rs b/client/src/query_state.rs deleted file mode 100644 index eeb6ba204f..0000000000 --- a/client/src/query_state.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::{fs, str}; - -use async_trait::async_trait; -use clap::{App, Arg, ArgMatches, SubCommand}; - -use casper_client::Error; -use casper_node::rpcs::state::GetItem; -use casper_types::PublicKey; - -use crate::{command::ClientCommand, common, Success}; - -/// This struct defines the order in which the args are shown for this subcommand's help message. -enum DisplayOrder { - Verbose, - NodeAddress, - RpcId, - StateRootHash, - Key, - Path, -} - -/// Handles providing the arg for and retrieval of the key. -mod key { - use casper_node::crypto::AsymmetricKeyExt; - use casper_types::AsymmetricType; - - use super::*; - - const ARG_NAME: &str = "key"; - const ARG_SHORT: &str = "k"; - const ARG_VALUE_NAME: &str = "FORMATTED STRING or PATH"; - const ARG_HELP: &str = - "The base key for the query. This must be a properly formatted public key, account hash, \ - contract address hash, URef, transfer hash or deploy-info hash. The format for each \ - respectively is \"\", \"account-hash-\", \"hash-\", \ - \"uref--\", \"transfer-\" and \ - \"deploy-\". The public key may instead be read in from a file, in which case \ - enter the path to the file as the --key argument. The file should be one of the two public \ - key files generated via the `keygen` subcommand; \"public_key_hex\" or \"public_key.pem\""; - - pub(super) fn arg() -> Arg<'static, 'static> { - Arg::with_name(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(true) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) - .display_order(DisplayOrder::Key as usize) - } - - pub(super) fn get(matches: &ArgMatches) -> Result { - let value = matches - .value_of(ARG_NAME) - .unwrap_or_else(|| panic!("should have {} arg", ARG_NAME)); - - // Try to read as a PublicKey PEM file first. - if let Ok(public_key) = PublicKey::from_file(value) { - return Ok(public_key.to_hex()); - } - - // Try to read as a hex-encoded PublicKey file next. - if let Ok(hex_public_key) = fs::read_to_string(value) { - let _ = PublicKey::from_hex(&hex_public_key).map_err(|error| { - eprintln!( - "Can't parse the contents of {} as a public key: {}", - value, error - ); - Error::FailedToParseKey - })?; - return Ok(hex_public_key); - } - - // Just return the value. - Ok(value.to_string()) - } -} - -/// Handles providing the arg for and retrieval of the key. -mod path { - use super::*; - - const ARG_NAME: &str = "query-path"; - const ARG_SHORT: &str = "q"; - const ARG_VALUE_NAME: &str = "PATH/FROM/KEY"; - const ARG_HELP: &str = "The path from the key of the query"; - - pub(super) fn arg() -> Arg<'static, 'static> { - Arg::with_name(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(false) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) - .display_order(DisplayOrder::Path as usize) - } - - pub(super) fn get<'a>(matches: &'a ArgMatches) -> &'a str { - matches.value_of(ARG_NAME).unwrap_or_default() - } -} - -#[async_trait] -impl<'a, 'b> ClientCommand<'a, 'b> for GetItem { - const NAME: &'static str = "query-state"; - const ABOUT: &'static str = "Retrieves a stored value from the network"; - - fn build(display_order: usize) -> App<'a, 'b> { - SubCommand::with_name(Self::NAME) - .about(Self::ABOUT) - .display_order(display_order) - .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) - .arg(common::node_address::arg( - DisplayOrder::NodeAddress as usize, - )) - .arg(common::rpc_id::arg(DisplayOrder::RpcId as usize)) - .arg(common::state_root_hash::arg( - DisplayOrder::StateRootHash as usize, - )) - .arg(key::arg()) - .arg(path::arg()) - } - - async fn run(matches: &ArgMatches<'a>) -> Result { - let maybe_rpc_id = common::rpc_id::get(matches); - let node_address = common::node_address::get(matches); - let verbosity_level = common::verbose::get(matches); - let state_root_hash = common::state_root_hash::get(matches); - let key = key::get(matches)?; - let path = path::get(matches); - - casper_client::get_item( - maybe_rpc_id, - node_address, - verbosity_level, - state_root_hash, - &key, - path, - ) - .await - .map(Success::from) - } -} diff --git a/client/tests/integration_test.rs b/client/tests/integration_test.rs index 0a0925210f..ed8e245b25 100644 --- a/client/tests/integration_test.rs +++ b/client/tests/integration_test.rs @@ -12,7 +12,9 @@ use warp_json_rpc::Builder; use casper_node::crypto::Error as CryptoError; use hex::FromHexError; -use casper_client::{DeployStrParams, Error, PaymentStrParams, SessionStrParams}; +use casper_client::{ + DeployStrParams, Error, GlobalStateStrParams, PaymentStrParams, SessionStrParams, +}; use casper_node::rpcs::{ account::{PutDeploy, PutDeployParams}, chain::{GetStateRootHash, GetStateRootHashParams}, @@ -143,12 +145,24 @@ impl MockServerHandle { .map(|_| ()) } + #[allow(deprecated)] async fn get_item(&self, state_root_hash: &str, key: &str, path: &str) -> Result<(), Error> { casper_client::get_item("1", &self.url(), 0, state_root_hash, key, path) .await .map(|_| ()) } + async fn query_global_state( + &self, + global_state_params: GlobalStateStrParams<'_>, + key: &str, + path: &str, + ) -> Result<(), Error> { + casper_client::query_global_state("1", &self.url(), 0, global_state_params, key, path) + .await + .map(|_| ()) + } + async fn transfer( &self, amount: &str, @@ -259,6 +273,25 @@ mod session_params { } } +/// Sample data creation methods for GlobalStateStrParams +mod global_state_params { + use super::*; + + pub fn test_params_as_state_root_hash() -> GlobalStateStrParams<'static> { + GlobalStateStrParams { + is_block_hash: false, + hash_value: VALID_STATE_ROOT_HASH, + } + } + + pub fn invalid_global_state_str_params() -> GlobalStateStrParams<'static> { + GlobalStateStrParams { + is_block_hash: false, + hash_value: "invalid state root has", + } + } +} + mod get_balance { use super::*; @@ -498,6 +531,89 @@ mod get_item { } } +mod query_global_state { + use casper_client::ValidateResponseError; + use casper_node::rpcs::state::{QueryGlobalState, QueryGlobalStateParams}; + + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn should_succeed_with_valid_global_state_params() { + let server_handle = + MockServerHandle::spawn::(QueryGlobalState::METHOD); + + // in this case, the error means that the request was sent successfully, but due to to the + // mock implementation fails to validate + + assert!(matches!( + server_handle + .query_global_state( + global_state_params::test_params_as_state_root_hash(), + VALID_PURSE_UREF, + "" + ) + .await, + Err(Error::InvalidResponse( + ValidateResponseError::ValidateResponseFailedToParse + )) + )); + } + + #[tokio::test(flavor = "multi_thread")] + async fn should_fail_with_invalid_global_state_params() { + let server_handle = + MockServerHandle::spawn::(QueryGlobalState::METHOD); + assert!(matches!( + server_handle + .query_global_state( + global_state_params::invalid_global_state_str_params(), + VALID_PURSE_UREF, + "" + ) + .await, + Err(Error::CryptoError { + context: "global_state_identifier", + error: CryptoError::FromHex(FromHexError::InvalidStringLength) + }) + )); + } + + #[tokio::test(flavor = "multi_thread")] + async fn should_fail_with_invalid_key() { + let server_handle = + MockServerHandle::spawn::(QueryGlobalState::METHOD); + assert!(matches!( + server_handle + .query_global_state( + global_state_params::test_params_as_state_root_hash(), + "invalid key", + "" + ) + .await, + Err(Error::FailedToParseKey) + )); + } + + #[tokio::test(flavor = "multi_thread")] + async fn should_fail_with_empty_key() { + let server_handle = + MockServerHandle::spawn::(QueryGlobalState::METHOD); + assert!(matches!( + server_handle + .query_global_state( + global_state_params::invalid_global_state_str_params(), + "", + "" + ) + .await, + Err(Error::CryptoError { + context: "global_state_identifier", + error: CryptoError::FromHex(FromHexError::InvalidStringLength) + }) + )); + } +} + mod get_deploy { use super::*; diff --git a/node/src/components/rpc_server/http_server.rs b/node/src/components/rpc_server/http_server.rs index 356254e238..d04263b849 100644 --- a/node/src/components/rpc_server/http_server.rs +++ b/node/src/components/rpc_server/http_server.rs @@ -59,6 +59,8 @@ pub(super) async fn run( let rpc_get_state_root_hash = rpcs::chain::GetStateRootHash::create_filter(effect_builder, api_version); let rpc_get_item = rpcs::state::GetItem::create_filter(effect_builder, api_version); + let rpc_query_global_state = + rpcs::state::QueryGlobalState::create_filter(effect_builder, api_version); let rpc_get_balance = rpcs::state::GetBalance::create_filter(effect_builder, api_version); let rpc_get_account_info = rpcs::state::GetAccountInfo::create_filter(effect_builder, api_version); @@ -97,6 +99,7 @@ pub(super) async fn run( .or(rpc_get_block_transfers) .or(rpc_get_state_root_hash) .or(rpc_get_item) + .or(rpc_query_global_state) .or(rpc_get_balance) .or(rpc_get_deploy) .or(rpc_get_peers) diff --git a/node/src/components/rpc_server/rpcs/docs.rs b/node/src/components/rpc_server/rpcs/docs.rs index 8e803dcf5a..2c8f31b0aa 100644 --- a/node/src/components/rpc_server/rpcs/docs.rs +++ b/node/src/components/rpc_server/rpcs/docs.rs @@ -30,7 +30,7 @@ use crate::{ effect::EffectBuilder, rpcs::{ chain::GetEraInfoBySwitchBlock, - state::{GetAccountInfo, GetDictionaryItem}, + state::{GetAccountInfo, GetDictionaryItem, QueryGlobalState}, }, }; @@ -77,6 +77,9 @@ pub(crate) static OPEN_RPC_SCHEMA: Lazy = Lazy::new(|| { schema.push_with_params::("returns a Deploy from the network"); schema.push_with_params::("returns an Account from the network"); schema.push_with_params::("returns an item from a Dictionary"); + schema.push_with_params::( + "a query to global state using either a Block hash or state root hash", + ); schema.push_without_params::("returns a list of peers connected to the node"); schema.push_without_params::("returns the current status of the node"); schema.push_with_optional_params::("returns a Block from the network"); @@ -86,7 +89,7 @@ pub(crate) static OPEN_RPC_SCHEMA: Lazy = Lazy::new(|| { schema.push_with_optional_params::( "returns a state root hash at a given Block", ); - schema.push_with_params::("returns a stored value from the network"); + schema.push_with_params::("returns a stored value from the network. This RPC is deprecated, use `query_global_state` instead."); schema.push_with_params::("returns a purse's balance from the network"); schema.push_with_optional_params::( "returns an EraInfo from the network", diff --git a/node/src/components/rpc_server/rpcs/state.rs b/node/src/components/rpc_server/rpcs/state.rs index 12b5ce3ef5..f17e1758d7 100644 --- a/node/src/components/rpc_server/rpcs/state.rs +++ b/node/src/components/rpc_server/rpcs/state.rs @@ -38,7 +38,7 @@ use crate::{ }, types::{ json_compatibility::{Account as JsonAccount, AuctionState, StoredValue}, - Block, + Block, BlockHash, JsonBlockHeader, }, }; @@ -100,7 +100,19 @@ static GET_DICTIONARY_ITEM_RESULT: Lazy = stored_value: StoredValue::CLValue(CLValue::from_t(1u64).unwrap()), merkle_proof: MERKLE_PROOF.clone(), }); - +static QUERY_GLOBAL_STATE_PARAMS: Lazy = + Lazy::new(|| QueryGlobalStateParams { + state_identifier: GlobalStateIdentifier::BlockHash(*Block::doc_example().hash()), + key: "deploy-af684263911154d26fa05be9963171802801a0b6aff8f199b7391eacb8edc9e1".to_string(), + path: vec![], + }); +static QUERY_GLOBAL_STATE_RESULT: Lazy = + Lazy::new(|| QueryGlobalStateResult { + api_version: DOCS_EXAMPLE_PROTOCOL_VERSION, + block_header: Some(JsonBlockHeader::doc_example().clone()), + stored_value: StoredValue::Account(JsonAccount::doc_example().clone()), + merkle_proof: MERKLE_PROOF.clone(), + }); /// Params for "state_get_item" RPC request. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(deny_unknown_fields)] @@ -830,3 +842,141 @@ impl RpcWithParamsExt for GetDictionaryItem { .boxed() } } + +/// Identifier for possible ways to query Global State +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(deny_unknown_fields)] +pub enum GlobalStateIdentifier { + /// Query using a block hash. + BlockHash(BlockHash), + /// Query using the state root hash. + StateRootHash(Digest), +} + +/// Params for "query_global_state" RPC +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct QueryGlobalStateParams { + /// The identifier used for the query. + pub state_identifier: GlobalStateIdentifier, + /// `casper_types::Key` as formatted string. + pub key: String, + /// The path components starting from the key as base. + #[serde(default)] + pub path: Vec, +} + +impl DocExample for QueryGlobalStateParams { + fn doc_example() -> &'static Self { + &*QUERY_GLOBAL_STATE_PARAMS + } +} + +/// Result for "query_global_state" RPC response. +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct QueryGlobalStateResult { + /// The RPC API version. + #[schemars(with = "String")] + pub api_version: ProtocolVersion, + /// The block header if a Block hash was provided. + pub block_header: Option, + /// The stored value. + pub stored_value: StoredValue, + /// The merkle proof. + pub merkle_proof: String, +} + +impl DocExample for QueryGlobalStateResult { + fn doc_example() -> &'static Self { + &*QUERY_GLOBAL_STATE_RESULT + } +} + +/// "query_global_state" RPC +pub struct QueryGlobalState {} + +impl RpcWithParams for QueryGlobalState { + const METHOD: &'static str = "query_global_state"; + type RequestParams = QueryGlobalStateParams; + type ResponseResult = QueryGlobalStateResult; +} + +impl RpcWithParamsExt for QueryGlobalState { + fn handle_request( + effect_builder: EffectBuilder, + response_builder: Builder, + params: Self::RequestParams, + api_version: ProtocolVersion, + ) -> BoxFuture<'static, Result, Error>> { + async move { + let (state_root_hash, maybe_block_header) = match params.state_identifier { + GlobalStateIdentifier::BlockHash(block_hash) => { + match effect_builder + .get_block_header_from_storage(block_hash) + .await + { + Some(header) => { + let json_block_header = JsonBlockHeader::from(header.clone()); + (*header.state_root_hash(), Some(json_block_header)) + } + None => { + let error_msg = + "query_global_state failed to retrieve specified block header" + .to_string(); + return Ok(response_builder.error(warp_json_rpc::Error::custom( + ErrorCode::NoSuchBlock as i64, + error_msg, + ))?); + } + } + } + GlobalStateIdentifier::StateRootHash(state_root_hash) => (state_root_hash, None), + }; + + let base_key = match Key::from_formatted_str(¶ms.key) + .map_err(|error| format!("failed to parse key: {}", error)) + { + Ok(key) => key, + Err(error_msg) => { + info!("{}", error_msg); + return Ok(response_builder.error(warp_json_rpc::Error::custom( + ErrorCode::ParseQueryKey as i64, + error_msg, + ))?); + } + }; + + let query_result = effect_builder + .make_request( + |responder| RpcRequest::QueryGlobalState { + state_root_hash, + base_key, + path: params.path, + responder, + }, + QueueKind::Api, + ) + .await; + + let (stored_value, proof_bytes) = match common::extract_query_result(query_result) { + Ok(tuple) => tuple, + Err((error_code, error_msg)) => { + info!("{}", error_msg); + return Ok(response_builder + .error(warp_json_rpc::Error::custom(error_code as i64, error_msg))?); + } + }; + + let result = Self::ResponseResult { + api_version, + block_header: maybe_block_header, + stored_value, + merkle_proof: hex::encode(proof_bytes), + }; + + Ok(response_builder.success(result)?) + } + .boxed() + } +} diff --git a/node/src/types.rs b/node/src/types.rs index 47a764d82f..5461370047 100644 --- a/node/src/types.rs +++ b/node/src/types.rs @@ -20,8 +20,9 @@ use rand::{CryptoRng, RngCore}; use rand_chacha::ChaCha20Rng; pub use block::{ - json_compatibility::JsonBlock, Block, BlockBody, BlockHash, BlockHeader, BlockSignatures, - BlockValidationError, FinalitySignature, + json_compatibility::{JsonBlock, JsonBlockHeader}, + Block, BlockBody, BlockHash, BlockHeader, BlockSignatures, BlockValidationError, + FinalitySignature, }; pub(crate) use block::{BlockByHeight, BlockHeaderWithMetadata, BlockPayload, FinalizedBlock}; pub(crate) use chainspec::ActivationPoint; diff --git a/node/src/types/block.rs b/node/src/types/block.rs index 8fc7c5351c..7e32117bd4 100644 --- a/node/src/types/block.rs +++ b/node/src/types/block.rs @@ -46,7 +46,10 @@ use crate::{ AsymmetricKeyExt, }, rpcs::docs::DocExample, - types::{error::BlockCreationError, Deploy, DeployHash, DeployOrTransferHash, JsonBlock}, + types::{ + error::BlockCreationError, Deploy, DeployHash, DeployOrTransferHash, JsonBlock, + JsonBlockHeader, + }, utils::DisplayIter, }; @@ -163,6 +166,10 @@ static JSON_BLOCK: Lazy = Lazy::new(|| { JsonBlock::new(block, Some(block_signature)) }); +static JSON_BLOCK_HEADER: Lazy = Lazy::new(|| { + let block_header = Block::doc_example().header().clone(); + JsonBlockHeader::from(block_header) +}); /// Error returned from constructing or validating a `Block`. #[derive(Debug, Error)] @@ -1428,9 +1435,10 @@ pub(crate) mod json_compatibility { } } + /// JSON representation of a block header. #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq, DataSize)] #[serde(deny_unknown_fields)] - struct JsonBlockHeader { + pub struct JsonBlockHeader { parent_hash: BlockHash, state_root_hash: Digest, body_hash: Digest, @@ -1477,6 +1485,12 @@ pub(crate) mod json_compatibility { } } + impl DocExample for JsonBlockHeader { + fn doc_example() -> &'static Self { + &*JSON_BLOCK_HEADER + } + } + /// A JSON-friendly representation of `Body` #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq, DataSize)] #[serde(deny_unknown_fields)] diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 0dc8c2f1f7..7f81fc682f 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -228,6 +228,7 @@ "additionalProperties": false }, "JsonBlockHeader": { + "description": "JSON representation of a block header.", "type": "object", "required": [ "accumulated_seed", diff --git a/utils/nctl/sh/contracts-erc20/utils.sh b/utils/nctl/sh/contracts-erc20/utils.sh index 62be2dc352..8ddc1fb9bc 100644 --- a/utils/nctl/sh/contracts-erc20/utils.sh +++ b/utils/nctl/sh/contracts-erc20/utils.sh @@ -9,7 +9,7 @@ function get_erc20_contract_hash () { local ACCOUNT_KEY=${1} - $(get_path_to_client) query-state \ + $(get_path_to_client) query-global-state \ --node-address "$(get_node_address_rpc)" \ --state-root-hash "$(get_state_root_hash)" \ --key "$ACCOUNT_KEY" \ @@ -28,7 +28,7 @@ function get_erc20_contract_key_value () local QUERY_KEY=${1} local QUERY_PATH=${2} - $(get_path_to_client) query-state \ + $(get_path_to_client) query-global-state \ --node-address "$(get_node_address_rpc)" \ --state-root-hash "$(get_state_root_hash)" \ --key "$QUERY_KEY" \ diff --git a/utils/nctl/sh/contracts-kv/utils.sh b/utils/nctl/sh/contracts-kv/utils.sh index f68bd37f75..6d3fdcbfbf 100644 --- a/utils/nctl/sh/contracts-kv/utils.sh +++ b/utils/nctl/sh/contracts-kv/utils.sh @@ -9,7 +9,7 @@ function get_kv_contract_hash () { local ACCOUNT_KEY=${1} - $(get_path_to_client) query-state \ + $(get_path_to_client) query-global-state \ --node-address "$(get_node_address_rpc)" \ --state-root-hash "$(get_state_root_hash)" \ --key "$ACCOUNT_KEY" \ @@ -28,7 +28,7 @@ function get_kv_contract_key_value () local QUERY_KEY=${1} local QUERY_PATH=${2} - $(get_path_to_client) query-state \ + $(get_path_to_client) query-global-state \ --node-address "$(get_node_address_rpc)" \ --state-root-hash "$(get_state_root_hash)" \ --key "$QUERY_KEY" \ diff --git a/utils/nctl/sh/views/view_chain_account.sh b/utils/nctl/sh/views/view_chain_account.sh index df38cded04..b4f1f0b056 100644 --- a/utils/nctl/sh/views/view_chain_account.sh +++ b/utils/nctl/sh/views/view_chain_account.sh @@ -23,7 +23,7 @@ source "$NCTL"/sh/utils/main.sh NODE_ADDRESS=$(get_node_address_rpc) STATE_ROOT_HASH=${STATE_ROOT_HASH:-$(get_state_root_hash)} -$(get_path_to_client) query-state \ +$(get_path_to_client) query-global-state \ --node-address "$NODE_ADDRESS" \ --state-root-hash "$STATE_ROOT_HASH" \ --key "$ACCOUNT_KEY" \