diff --git a/node/src/components/binary_port.rs b/node/src/components/binary_port.rs index 05c77c10f2..59fabd9f35 100644 --- a/node/src/components/binary_port.rs +++ b/node/src/components/binary_port.rs @@ -20,9 +20,9 @@ use casper_types::{ addressable_entity::NamedKeyAddr, binary_port::{ self, BinaryRequest, BinaryRequestHeader, BinaryRequestTag, BinaryResponse, - BinaryResponseAndRequest, DbRawBytesSpec, DictionaryItemIdentifier, GetRequest, - GetTrieFullResult, GlobalStateQueryResult, GlobalStateRequest, InformationRequest, - InformationRequestTag, NodeStatus, ReactorStateName, RecordId, + BinaryResponseAndRequest, DbRawBytesSpec, DictionaryItemIdentifier, DictionaryQueryResult, + GetRequest, GetTrieFullResult, GlobalStateQueryResult, GlobalStateRequest, + InformationRequest, InformationRequestTag, NodeStatus, ReactorStateName, RecordId, TransactionWithExecutionInfo, }, bytesrepr::{self, FromBytes, ToBytes}, @@ -514,22 +514,16 @@ where seed_uref, dictionary_item_key, } => { - get_global_state_item( - effect_builder, - state_root_hash, - Key::dictionary(seed_uref, dictionary_item_key.as_bytes()), - vec![], - ) - .await + let key = Key::dictionary(seed_uref, dictionary_item_key.as_bytes()); + get_global_state_item(effect_builder, state_root_hash, key, vec![]) + .await + .map(|maybe_res| maybe_res.map(|res| DictionaryQueryResult::new(key, res))) } DictionaryItemIdentifier::DictionaryItem(addr) => { - get_global_state_item( - effect_builder, - state_root_hash, - Key::Dictionary(addr), - vec![], - ) - .await + let key = Key::Dictionary(addr); + get_global_state_item(effect_builder, state_root_hash, key, vec![]) + .await + .map(|maybe_res| maybe_res.map(|res| DictionaryQueryResult::new(key, res))) } }; match result { @@ -547,7 +541,7 @@ async fn get_dictionary_item_by_legacy_named_key( entity_key: Key, dictionary_name: String, dictionary_item_key: String, -) -> Result, binary_port::ErrorCode> +) -> Result, binary_port::ErrorCode> where REv: From + From + From, { @@ -564,8 +558,14 @@ where let Some(uref) = named_keys.get(&dictionary_name).and_then(Key::as_uref) else { return Err(binary_port::ErrorCode::DictionaryURefNotFound); }; - let dictionary_key = Key::dictionary(*uref, dictionary_item_key.as_bytes()); - get_global_state_item(effect_builder, state_root_hash, dictionary_key, vec![]).await + let key = Key::dictionary(*uref, dictionary_item_key.as_bytes()); + let Some(query_result) = + get_global_state_item(effect_builder, state_root_hash, key, vec![]).await? + else { + return Ok(None); + }; + + Ok(Some(DictionaryQueryResult::new(key, query_result))) } QueryResult::RootNotFound | QueryResult::ValueNotFound(_) => { Err(binary_port::ErrorCode::DictionaryURefNotFound) @@ -580,7 +580,7 @@ async fn get_dictionary_item_by_named_key( entity_addr: EntityAddr, dictionary_name: String, dictionary_item_key: String, -) -> Result, binary_port::ErrorCode> +) -> Result, binary_port::ErrorCode> where REv: From + From + From, { @@ -596,8 +596,13 @@ where let Ok(Key::URef(uref)) = key_val.get_key() else { return Err(binary_port::ErrorCode::DictionaryURefNotFound); }; - let dictionary_key = Key::dictionary(uref, dictionary_item_key.as_bytes()); - get_global_state_item(effect_builder, state_root_hash, dictionary_key, vec![]).await + let key = Key::dictionary(uref, dictionary_item_key.as_bytes()); + let Some(query_result) = + get_global_state_item(effect_builder, state_root_hash, key, vec![]).await? + else { + return Ok(None); + }; + Ok(Some(DictionaryQueryResult::new(key, query_result))) } QueryResult::RootNotFound | QueryResult::ValueNotFound(_) => { Err(binary_port::ErrorCode::DictionaryURefNotFound) diff --git a/node/src/reactor/main_reactor/tests/binary_port.rs b/node/src/reactor/main_reactor/tests/binary_port.rs index 9ddda50f12..9124490693 100644 --- a/node/src/reactor/main_reactor/tests/binary_port.rs +++ b/node/src/reactor/main_reactor/tests/binary_port.rs @@ -12,10 +12,10 @@ use casper_types::{ addressable_entity::{NamedKeyAddr, NamedKeyValue}, binary_port::{ BinaryRequest, BinaryRequestHeader, BinaryResponse, BinaryResponseAndRequest, - ConsensusStatus, ConsensusValidatorChanges, DictionaryItemIdentifier, ErrorCode, - GetRequest, GetTrieFullResult, GlobalStateQueryResult, GlobalStateRequest, - InformationRequest, InformationRequestTag, LastProgress, NetworkName, NodeStatus, - PayloadType, ReactorStateName, RecordId, Uptime, + ConsensusStatus, ConsensusValidatorChanges, DictionaryItemIdentifier, + DictionaryQueryResult, ErrorCode, GetRequest, GetTrieFullResult, GlobalStateQueryResult, + GlobalStateRequest, InformationRequest, InformationRequestTag, LastProgress, NetworkName, + NodeStatus, PayloadType, ReactorStateName, RecordId, Uptime, }, bytesrepr::{FromBytes, ToBytes}, execution::{Effects, TransformKindV2, TransformV2}, @@ -741,11 +741,16 @@ fn get_dictionary_item_by_addr(state_root_hash: Digest, addr: DictionaryAddr) -> state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), identifier: DictionaryItemIdentifier::DictionaryItem(addr), })), - asserter: Box::new(|response| { - assert_response::( + asserter: Box::new(move |response| { + assert_response::( response, - Some(PayloadType::GlobalStateQueryResult), - |res| matches!(res.into_inner(), (StoredValue::CLValue(_), _)), + Some(PayloadType::DictionaryQueryResult), + |res| { + matches!( + res.into_inner(), + (key, res) if key == Key::Dictionary(addr) && res.value().as_cl_value().is_some() + ) + }, ) }), } @@ -762,14 +767,20 @@ fn get_dictionary_item_by_seed_uref( state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), identifier: DictionaryItemIdentifier::URef { seed_uref, - dictionary_item_key, + dictionary_item_key: dictionary_item_key.clone(), }, })), - asserter: Box::new(|response| { - assert_response::( + asserter: Box::new(move |response| { + assert_response::( response, - Some(PayloadType::GlobalStateQueryResult), - |res| matches!(res.into_inner(), (StoredValue::CLValue(_), _)), + Some(PayloadType::DictionaryQueryResult), + |res| { + let expected_key = Key::dictionary(seed_uref, dictionary_item_key.as_bytes()); + matches!( + res.into_inner(), + (key, res) if key == expected_key && res.value().as_cl_value().is_some() + ) + }, ) }), } @@ -792,10 +803,10 @@ fn get_dictionary_item_by_legacy_named_key( }, })), asserter: Box::new(|response| { - assert_response::( + assert_response::( response, - Some(PayloadType::GlobalStateQueryResult), - |res| matches!(res.into_inner(), (StoredValue::CLValue(_), _)), + Some(PayloadType::DictionaryQueryResult), + |res| matches!(res.into_inner(),(_, res) if res.value().as_cl_value().is_some()), ) }), } @@ -818,10 +829,10 @@ fn get_dictionary_item_by_named_key( }, })), asserter: Box::new(|response| { - assert_response::( + assert_response::( response, - Some(PayloadType::GlobalStateQueryResult), - |res| matches!(res.into_inner(), (StoredValue::CLValue(_), _)), + Some(PayloadType::DictionaryQueryResult), + |res| matches!(res.into_inner(),(_, res) if res.value().as_cl_value().is_some()), ) }), } diff --git a/types/src/binary_port.rs b/types/src/binary_port.rs index 67be94403e..0d809fb905 100644 --- a/types/src/binary_port.rs +++ b/types/src/binary_port.rs @@ -33,8 +33,9 @@ pub use payload_type::{PayloadEntity, PayloadType}; pub use record_id::RecordId; pub use state_request::GlobalStateRequest; pub use type_wrappers::{ - ConsensusStatus, ConsensusValidatorChanges, GetTrieFullResult, LastProgress, NetworkName, - ReactorStateName, SpeculativeExecutionResult, TransactionWithExecutionInfo, Uptime, + ConsensusStatus, ConsensusValidatorChanges, DictionaryQueryResult, GetTrieFullResult, + LastProgress, NetworkName, ReactorStateName, SpeculativeExecutionResult, + TransactionWithExecutionInfo, Uptime, }; use alloc::vec::Vec; diff --git a/types/src/binary_port/global_state_query_result.rs b/types/src/binary_port/global_state_query_result.rs index a6592da97d..b414c388cd 100644 --- a/types/src/binary_port/global_state_query_result.rs +++ b/types/src/binary_port/global_state_query_result.rs @@ -31,6 +31,11 @@ impl GlobalStateQueryResult { } } + /// Returns the stored value. + pub fn value(&self) -> &StoredValue { + &self.value + } + /// Returns the stored value and the merkle proof. pub fn into_inner(self) -> (StoredValue, Vec>) { (self.value, self.merkle_proof) diff --git a/types/src/binary_port/payload_type.rs b/types/src/binary_port/payload_type.rs index 89d692780d..21746d5a69 100644 --- a/types/src/binary_port/payload_type.rs +++ b/types/src/binary_port/payload_type.rs @@ -31,7 +31,7 @@ use super::{ ConsensusStatus, ConsensusValidatorChanges, GetTrieFullResult, LastProgress, NetworkName, ReactorStateName, SpeculativeExecutionResult, }, - TransactionWithExecutionInfo, Uptime, + DictionaryQueryResult, TransactionWithExecutionInfo, Uptime, }; /// A type of the payload being returned in a binary response. @@ -107,6 +107,8 @@ pub enum PayloadType { GetTrieFullResult, /// Node status. NodeStatus, + /// Result of querying for a dictionary item. + DictionaryQueryResult, } impl PayloadType { @@ -192,6 +194,9 @@ impl TryFrom for PayloadType { x if x == PayloadType::StoredValues as u8 => Ok(PayloadType::StoredValues), x if x == PayloadType::GetTrieFullResult as u8 => Ok(PayloadType::GetTrieFullResult), x if x == PayloadType::NodeStatus as u8 => Ok(PayloadType::NodeStatus), + x if x == PayloadType::DictionaryQueryResult as u8 => { + Ok(PayloadType::DictionaryQueryResult) + } _ => Err(()), } } @@ -242,6 +247,7 @@ impl fmt::Display for PayloadType { PayloadType::StoredValues => write!(f, "StoredValues"), PayloadType::GetTrieFullResult => write!(f, "GetTrieFullResult"), PayloadType::NodeStatus => write!(f, "NodeStatus"), + PayloadType::DictionaryQueryResult => write!(f, "DictionaryQueryResult"), } } } @@ -280,6 +286,7 @@ const GLOBAL_STATE_QUERY_RESULT_TAG: u8 = 30; const STORED_VALUES_TAG: u8 = 31; const GET_TRIE_FULL_RESULT_TAG: u8 = 32; const NODE_STATUS_TAG: u8 = 33; +const DICTIONARY_QUERY_RESULT_TAG: u8 = 34; impl ToBytes for PayloadType { fn to_bytes(&self) -> Result, bytesrepr::Error> { @@ -324,6 +331,7 @@ impl ToBytes for PayloadType { PayloadType::StoredValues => STORED_VALUES_TAG, PayloadType::GetTrieFullResult => GET_TRIE_FULL_RESULT_TAG, PayloadType::NodeStatus => NODE_STATUS_TAG, + PayloadType::DictionaryQueryResult => DICTIONARY_QUERY_RESULT_TAG, } .write_bytes(writer) } @@ -371,6 +379,7 @@ impl FromBytes for PayloadType { STORED_VALUES_TAG => PayloadType::StoredValues, GET_TRIE_FULL_RESULT_TAG => PayloadType::GetTrieFullResult, NODE_STATUS_TAG => PayloadType::NodeStatus, + DICTIONARY_QUERY_RESULT_TAG => PayloadType::DictionaryQueryResult, _ => return Err(bytesrepr::Error::Formatting), }; Ok((record_id, remainder)) @@ -456,6 +465,10 @@ impl PayloadEntity for GlobalStateQueryResult { const PAYLOAD_TYPE: PayloadType = PayloadType::GlobalStateQueryResult; } +impl PayloadEntity for DictionaryQueryResult { + const PAYLOAD_TYPE: PayloadType = PayloadType::DictionaryQueryResult; +} + impl PayloadEntity for Vec { const PAYLOAD_TYPE: PayloadType = PayloadType::StoredValues; } diff --git a/types/src/binary_port/type_wrappers.rs b/types/src/binary_port/type_wrappers.rs index de2094741b..ff9f10afaa 100644 --- a/types/src/binary_port/type_wrappers.rs +++ b/types/src/binary_port/type_wrappers.rs @@ -12,9 +12,11 @@ use crate::{ bytesrepr::{self, Bytes, FromBytes, ToBytes}, contract_messages::Messages, execution::ExecutionResultV2, - EraId, ExecutionInfo, PublicKey, TimeDiff, Timestamp, Transaction, ValidatorChange, + EraId, ExecutionInfo, Key, PublicKey, TimeDiff, Timestamp, Transaction, ValidatorChange, }; +use super::GlobalStateQueryResult; + // `bytesrepr` implementations for type wrappers are repetitive, hence this macro helper. We should // get rid of this after we introduce the proper "bytesrepr-derive" proc macro. macro_rules! impl_bytesrepr_for_type_wrapper { @@ -298,6 +300,51 @@ impl FromBytes for TransactionWithExecutionInfo { } } +/// A query result for a dictionary item, contains the dictionary item key and a global state query +/// result. +#[derive(Debug, Clone, PartialEq)] +pub struct DictionaryQueryResult { + key: Key, + query_result: GlobalStateQueryResult, +} + +impl DictionaryQueryResult { + /// Constructs new dictionary query result. + pub fn new(key: Key, query_result: GlobalStateQueryResult) -> Self { + Self { key, query_result } + } + + /// Converts `self` into the dictionary item key and global state query result. + pub fn into_inner(self) -> (Key, GlobalStateQueryResult) { + (self.key, self.query_result) + } +} + +impl ToBytes for DictionaryQueryResult { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + self.write_bytes(&mut buffer)?; + Ok(buffer) + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + self.key.write_bytes(writer)?; + self.query_result.write_bytes(writer) + } + + fn serialized_length(&self) -> usize { + self.key.serialized_length() + self.query_result.serialized_length() + } +} + +impl FromBytes for DictionaryQueryResult { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (key, remainder) = FromBytes::from_bytes(bytes)?; + let (query_result, remainder) = FromBytes::from_bytes(remainder)?; + Ok((DictionaryQueryResult::new(key, query_result), remainder)) + } +} + impl_bytesrepr_for_type_wrapper!(Uptime); impl_bytesrepr_for_type_wrapper!(ConsensusValidatorChanges); impl_bytesrepr_for_type_wrapper!(NetworkName); @@ -312,7 +359,7 @@ mod tests { use rand::Rng; use super::*; - use crate::testing::TestRng; + use crate::{execution::ExecutionResult, testing::TestRng, BlockHash, CLValue, StoredValue}; #[test] fn uptime_roundtrip() { @@ -375,4 +422,29 @@ mod tests { Some(TimeDiff::from_millis(rng.gen())), )); } + + #[test] + fn transaction_with_execution_info_roundtrip() { + let rng = &mut TestRng::new(); + bytesrepr::test_serialization_roundtrip(&TransactionWithExecutionInfo::new( + Transaction::random(rng), + rng.gen::().then(|| ExecutionInfo { + block_hash: BlockHash::random(rng), + block_height: rng.gen(), + execution_result: rng.gen::().then(|| ExecutionResult::random(rng)), + }), + )); + } + + #[test] + fn dictionary_query_result_roundtrip() { + let rng = &mut TestRng::new(); + bytesrepr::test_serialization_roundtrip(&DictionaryQueryResult::new( + Key::Account(rng.gen()), + GlobalStateQueryResult::new( + StoredValue::CLValue(CLValue::from_t(rng.gen::()).unwrap()), + vec![], + ), + )); + } }