From c8199a7f95af7fdf23d65b58379aad6ae5f2dd1b Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 22 Sep 2023 16:11:43 +0000 Subject: [PATCH 01/27] casper-types: add a new `Key` variant for contract level messages Add a new `Key::MessageTopic` variant under which message digests will be written to global state for messages emitted by contracts. Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime_context/mod.rs | 13 ++- types/src/contract_messages.rs | 93 +++++++++++++++++++++ types/src/key.rs | 76 +++++++++++++++++ types/src/lib.rs | 1 + 4 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 types/src/contract_messages.rs diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 93afebd99f..4f1bed29a3 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -330,6 +330,10 @@ where error!("should not remove the checksum registry key"); Err(Error::RemoveKeyFailure(RemoveKeyFailure::PermissionDenied)) } + Key::MessageTopic(_) => { + error!("should not remove the message keys"); + Err(Error::RemoveKeyFailure(RemoveKeyFailure::PermissionDenied)) + } bid_key @ Key::BidAddr(_) => { let _bid_kind: BidKind = self.read_gs_typed(&bid_key)?; self.named_keys.remove(name); @@ -799,7 +803,8 @@ where | Key::Unbond(_) | Key::ChainspecRegistry | Key::ChecksumRegistry - | Key::BidAddr(_) => true, + | Key::BidAddr(_) + | Key::MessageTopic(_) => true, } } @@ -821,7 +826,8 @@ where | Key::Unbond(_) | Key::ChainspecRegistry | Key::ChecksumRegistry - | Key::BidAddr(_) => false, + | Key::BidAddr(_) + | Key::MessageTopic(_) => false, } } @@ -843,7 +849,8 @@ where | Key::Unbond(_) | Key::ChainspecRegistry | Key::ChecksumRegistry - | Key::BidAddr(_) => false, + | Key::BidAddr(_) + | Key::MessageTopic(_) => false, } } diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs new file mode 100644 index 0000000000..98709cb3b2 --- /dev/null +++ b/types/src/contract_messages.rs @@ -0,0 +1,93 @@ +//! Data types for interacting with contract level messages. + +use crate::{ + bytesrepr::{self, FromBytes, ToBytes}, + HashAddr, +}; +use alloc::vec::Vec; +use core::fmt::{Display, Formatter}; + +#[cfg(feature = "datasize")] +use datasize::DataSize; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// The length in bytes of a [`MessageTopicHash`]. +pub const MESSAGE_TOPIC_HASH_LENGTH: usize = 32; + +/// The hash of the name of the message topic. +pub type MessageTopicHash = [u8; MESSAGE_TOPIC_HASH_LENGTH]; + +/// MessageAddr +#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[cfg_attr(feature = "datasize", derive(DataSize))] +pub struct MessageTopicAddr { + /// The entity addr. + entity_addr: HashAddr, + /// The hash of the name of the message topic. + topic_hash: MessageTopicHash, +} + +impl MessageTopicAddr { + /// Constructs a new [`MessageAddr`] based on the addressable entity addr and the hash of the + /// message topic name. + pub const fn new(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Self { + Self { + entity_addr, + topic_hash, + } + } +} + +impl Display for MessageTopicAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{}{}", + base16::encode_lower(&self.entity_addr), + base16::encode_lower(&self.topic_hash) + ) + } +} + +impl ToBytes for MessageTopicAddr { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.entity_addr.to_bytes()?); + buffer.append(&mut self.topic_hash.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.entity_addr.serialized_length() + self.topic_hash.serialized_length() + } +} + +impl FromBytes for MessageTopicAddr { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (entity_addr, rem) = FromBytes::from_bytes(bytes)?; + let (topic_hash, rem) = FromBytes::from_bytes(rem)?; + Ok(( + MessageTopicAddr { + entity_addr, + topic_hash, + }, + rem, + )) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> MessageTopicAddr { + MessageTopicAddr { + entity_addr: rng.gen(), + topic_hash: rng.gen(), + } + } +} diff --git a/types/src/key.rs b/types/src/key.rs index 46a81ebc05..b8ecc95c3f 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -35,6 +35,7 @@ use crate::{ addressable_entity::ContractHash, bytesrepr::{self, Error, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}, checksummed_hex, + contract_messages::{MessageTopicAddr, MessageTopicHash, MESSAGE_TOPIC_HASH_LENGTH}, contract_wasm::ContractWasmHash, package::ContractPackageHash, system::auction::{BidAddr, BidAddrTag}, @@ -56,6 +57,7 @@ const ERA_SUMMARY_PREFIX: &str = "era-summary-"; const CHAINSPEC_REGISTRY_PREFIX: &str = "chainspec-registry-"; const CHECKSUM_REGISTRY_PREFIX: &str = "checksum-registry-"; const BID_ADDR_PREFIX: &str = "bid-addr-"; +const MESSAGE_TOPIC_PREFIX: &str = "message-topic-"; /// The number of bytes in a Blake2b hash pub const BLAKE2B_DIGEST_LENGTH: usize = 32; @@ -123,6 +125,7 @@ pub enum KeyTag { ChainspecRegistry = 13, ChecksumRegistry = 14, BidAddr = 15, + MessageTopic = 16, } impl Display for KeyTag { @@ -144,6 +147,7 @@ impl Display for KeyTag { KeyTag::ChainspecRegistry => write!(f, "ChainspecRegistry"), KeyTag::ChecksumRegistry => write!(f, "ChecksumRegistry"), KeyTag::BidAddr => write!(f, "BidAddr"), + KeyTag::MessageTopic => write!(f, "MessageTopic"), } } } @@ -187,6 +191,8 @@ pub enum Key { ChecksumRegistry, /// A `Key` under which we store bid information BidAddr(BidAddr), + /// A `Key` under which a message topic is stored + MessageTopic(MessageTopicAddr), } #[cfg(feature = "json-schema")] @@ -243,6 +249,8 @@ pub enum FromStrError { ChecksumRegistry(String), /// Bid parse error. BidAddr(String), + /// Message topic parse error. + MessageTopic(String), /// Unknown prefix. UnknownPrefix, } @@ -302,6 +310,9 @@ impl Display for FromStrError { write!(f, "checksum-registry-key from string error: {}", error) } FromStrError::BidAddr(error) => write!(f, "bid-addr-key from string error: {}", error), + FromStrError::MessageTopic(error) => { + write!(f, "message-topic-key from string error: {}", error) + } FromStrError::UnknownPrefix => write!(f, "unknown prefix for key"), } } @@ -328,6 +339,7 @@ impl Key { Key::ChainspecRegistry => String::from("Key::ChainspecRegistry"), Key::ChecksumRegistry => String::from("Key::ChecksumRegistry"), Key::BidAddr(_) => String::from("Key::BidAddr"), + Key::MessageTopic(_) => String::from("Key::MessageTopic"), } } @@ -414,6 +426,9 @@ impl Key { Key::BidAddr(bid_addr) => { format!("{}{}", BID_ADDR_PREFIX, bid_addr) } + Key::MessageTopic(message_addr) => { + format!("{}{}", MESSAGE_TOPIC_PREFIX, message_addr) + } } } @@ -577,6 +592,30 @@ impl Key { return Ok(Key::ChecksumRegistry); } + if let Some(message_topic_addr) = input.strip_prefix(MESSAGE_TOPIC_PREFIX) { + let bytes = checksummed_hex::decode(message_topic_addr) + .map_err(|error| FromStrError::MessageTopic(error.to_string()))?; + + if bytes.is_empty() { + return Err(FromStrError::MessageTopic( + "bytes should not be 0 len".to_string(), + )); + } + + let entity_addr_bytes = + <[u8; KEY_HASH_LENGTH]>::try_from(bytes[0..KEY_HASH_LENGTH].as_ref()) + .map_err(|err| FromStrError::MessageTopic(err.to_string()))?; + + let topic_hash_bytes = + <[u8; MESSAGE_TOPIC_HASH_LENGTH]>::try_from(bytes[KEY_HASH_LENGTH..].as_ref()) + .map_err(|err| FromStrError::MessageTopic(err.to_string()))?; + + return Ok(Key::MessageTopic(MessageTopicAddr::new( + entity_addr_bytes, + topic_hash_bytes, + ))); + } + Err(FromStrError::UnknownPrefix) } @@ -678,6 +717,12 @@ impl Key { Key::Dictionary(addr) } + /// Creates a new [`Key::MessageTopic`] variant based on an `entity_addr` and a hash of the + /// topic name. + pub fn message_topic(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Key { + Key::MessageTopic(MessageTopicAddr::new(entity_addr, topic_hash)) + } + /// Returns true if the key is of type [`Key::Dictionary`]. pub fn is_dictionary_key(&self) -> bool { if let Key::Dictionary(_) = self { @@ -758,6 +803,7 @@ impl Display for Key { ) } Key::BidAddr(bid_addr) => write!(f, "Key::BidAddr({})", bid_addr), + Key::MessageTopic(message_addr) => write!(f, "Key::MessageTopic({})", message_addr), } } } @@ -787,6 +833,7 @@ impl Tagged for Key { Key::ChainspecRegistry => KeyTag::ChainspecRegistry, Key::ChecksumRegistry => KeyTag::ChecksumRegistry, Key::BidAddr(_) => KeyTag::BidAddr, + Key::MessageTopic(_) => KeyTag::MessageTopic, } } } @@ -866,6 +913,9 @@ impl ToBytes for Key { KEY_ID_SERIALIZED_LENGTH + bid_addr.serialized_length() } }, + Key::MessageTopic(message_addr) => { + KEY_ID_SERIALIZED_LENGTH + message_addr.serialized_length() + } } } @@ -895,6 +945,11 @@ impl ToBytes for Key { } BidAddrTag::Validator | BidAddrTag::Delegator => bid_addr.write_bytes(writer), }, + Key::MessageTopic(message_addr) => { + let bytes = message_addr.to_bytes()?; + writer.extend(&bytes); + Ok(()) + } } } } @@ -967,6 +1022,10 @@ impl FromBytes for Key { let (bid_addr, rem) = BidAddr::from_bytes(remainder)?; Ok((Key::BidAddr(bid_addr), rem)) } + tag if tag == KeyTag::MessageTopic as u8 => { + let (message_addr, rem) = MessageTopicAddr::from_bytes(remainder)?; + Ok((Key::MessageTopic(message_addr), rem)) + } _ => Err(Error::Formatting), } } @@ -993,6 +1052,7 @@ fn please_add_to_distribution_impl(key: Key) { Key::ChainspecRegistry => unimplemented!(), Key::ChecksumRegistry => unimplemented!(), Key::BidAddr(_) => unimplemented!(), + Key::MessageTopic(_) => unimplemented!(), } } @@ -1016,6 +1076,7 @@ impl Distribution for Standard { 13 => Key::ChainspecRegistry, 14 => Key::ChecksumRegistry, 15 => Key::BidAddr(rng.gen()), + 16 => Key::MessageTopic(rng.gen()), _ => unreachable!(), } } @@ -1043,6 +1104,7 @@ mod serde_helpers { ChainspecRegistry, ChecksumRegistry, BidAddr(&'a BidAddr), + MessageTopic(&'a MessageTopicAddr), } #[derive(Deserialize)] @@ -1064,6 +1126,7 @@ mod serde_helpers { ChainspecRegistry, ChecksumRegistry, BidAddr(BidAddr), + MessageTopic(MessageTopicAddr), } impl<'a> From<&'a Key> for BinarySerHelper<'a> { @@ -1085,6 +1148,7 @@ mod serde_helpers { Key::ChainspecRegistry => BinarySerHelper::ChainspecRegistry, Key::ChecksumRegistry => BinarySerHelper::ChecksumRegistry, Key::BidAddr(bid_addr) => BinarySerHelper::BidAddr(bid_addr), + Key::MessageTopic(message_addr) => BinarySerHelper::MessageTopic(message_addr), } } } @@ -1108,6 +1172,7 @@ mod serde_helpers { BinaryDeserHelper::ChainspecRegistry => Key::ChainspecRegistry, BinaryDeserHelper::ChecksumRegistry => Key::ChecksumRegistry, BinaryDeserHelper::BidAddr(bid_addr) => Key::BidAddr(bid_addr), + BinaryDeserHelper::MessageTopic(message_addr) => Key::MessageTopic(message_addr), } } } @@ -1166,6 +1231,7 @@ mod tests { const UNBOND_KEY: Key = Key::Unbond(AccountHash::new([42; 32])); const CHAINSPEC_REGISTRY_KEY: Key = Key::ChainspecRegistry; const CHECKSUM_REGISTRY_KEY: Key = Key::ChecksumRegistry; + const MESSAGE_TOPIC_KEY: Key = Key::MessageTopic(MessageTopicAddr::new([42; 32], [42; 32])); const KEYS: &[Key] = &[ ACCOUNT_KEY, HASH_KEY, @@ -1185,6 +1251,7 @@ mod tests { UNIFIED_BID_KEY, VALIDATOR_BID_KEY, DELEGATOR_BID_KEY, + MESSAGE_TOPIC_KEY, ]; const HEX_STRING: &str = "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"; const UNIFIED_HEX_STRING: &str = @@ -1319,6 +1386,10 @@ mod tests { base16::encode_lower(&PADDING_BYTES), ) ); + assert_eq!( + format!("{}", MESSAGE_TOPIC_KEY), + format!("Key::MessageTopic({}{})", HEX_STRING, HEX_STRING) + ) } #[test] @@ -1591,6 +1662,10 @@ mod tests { "{}", bid_addr_err ); + assert!(Key::from_formatted_str(MESSAGE_TOPIC_PREFIX) + .unwrap_err() + .to_string() + .starts_with("message-topic-key from string error: ")); let invalid_prefix = "a-0000000000000000000000000000000000000000000000000000000000000000"; assert_eq!( Key::from_formatted_str(invalid_prefix) @@ -1668,5 +1743,6 @@ mod tests { round_trip(&Key::Unbond(AccountHash::new(zeros))); round_trip(&Key::ChainspecRegistry); round_trip(&Key::ChecksumRegistry); + round_trip(&Key::MessageTopic(MessageTopicAddr::new(zeros, nines))) } } diff --git a/types/src/lib.rs b/types/src/lib.rs index f624cd62b6..56e8e2f6ee 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -34,6 +34,7 @@ mod chainspec; pub mod checksummed_hex; mod cl_type; mod cl_value; +pub mod contract_messages; mod contract_wasm; pub mod contracts; pub mod crypto; From 0263a79cec6941f3db1915e6c74098e765a10691 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Thu, 28 Sep 2023 14:04:47 +0000 Subject: [PATCH 02/27] ee: add support for emitting contract level messages Added 2 new FFIs that enable contracts to register message topics and emit human readable string messages. Signed-off-by: Alexandru Sardan --- Cargo.lock | 8 + .../src/engine_state/execution_result.rs | 57 ++- execution_engine/src/engine_state/mod.rs | 10 +- execution_engine/src/execution/error.rs | 11 +- execution_engine/src/execution/executor.rs | 9 + .../src/resolvers/v1_function_index.rs | 2 + execution_engine/src/resolvers/v1_resolver.rs | 8 + execution_engine/src/runtime/externals.rs | 76 +++ execution_engine/src/runtime/mod.rs | 69 +++ execution_engine/src/runtime_context/mod.rs | 61 ++- .../src/tracking_copy/byte_size.rs | 4 + execution_engine/src/tracking_copy/mod.rs | 32 ++ .../tests/src/test/contract_messages.rs | 137 ++++++ .../tests/src/test/mod.rs | 1 + .../tests/src/test/private_chain.rs | 5 +- .../tests/src/test/regression/ee_966.rs | 5 +- .../tests/src/test/regression/gh_2280.rs | 1 + .../tests/src/test/storage_costs.rs | 7 +- .../src/test/system_contracts/upgrade.rs | 8 +- .../tests/src/test/system_costs.rs | 12 +- node/src/utils/chain_specification.rs | 7 +- resources/local/chainspec.toml.in | 6 + resources/production/chainspec.toml | 6 + resources/test/rpc_schema.json | 130 ++++- .../contract/src/contract_api/runtime.rs | 39 ++ smart_contracts/contract/src/ext_ffi.rs | 29 ++ .../test/contract-messages-emitter/Cargo.toml | 16 + .../contract-messages-emitter/src/main.rs | 83 ++++ types/src/api_error.rs | 34 ++ types/src/chainspec.rs | 6 +- types/src/chainspec/vm_config.rs | 2 + .../vm_config/host_function_costs.rs | 20 + .../chainspec/vm_config/messages_limits.rs | 125 +++++ types/src/chainspec/vm_config/wasm_config.rs | 20 +- types/src/contract_messages.rs | 460 +++++++++++++++++- types/src/execution/execution_result_v2.rs | 76 ++- types/src/execution/transform_kind.rs | 10 + types/src/gens.rs | 15 +- types/src/key.rs | 115 ++--- types/src/lib.rs | 6 +- types/src/stored_value.rs | 43 ++ 41 files changed, 1644 insertions(+), 127 deletions(-) create mode 100644 execution_engine_testing/tests/src/test/contract_messages.rs create mode 100644 smart_contracts/contracts/test/contract-messages-emitter/Cargo.toml create mode 100644 smart_contracts/contracts/test/contract-messages-emitter/src/main.rs create mode 100644 types/src/chainspec/vm_config/messages_limits.rs diff --git a/Cargo.lock b/Cargo.lock index 5742e608a5..85101ada56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -936,6 +936,14 @@ dependencies = [ "casper-types", ] +[[package]] +name = "contract-messages-emitter" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "convert_case" version = "0.4.0" diff --git a/execution_engine/src/engine_state/execution_result.rs b/execution_engine/src/engine_state/execution_result.rs index b75086ec4f..6b379ef071 100644 --- a/execution_engine/src/engine_state/execution_result.rs +++ b/execution_engine/src/engine_state/execution_result.rs @@ -4,6 +4,7 @@ use std::collections::VecDeque; use casper_types::{ bytesrepr::FromBytes, + contract_messages::Message, execution::{Effects, ExecutionResultV2 as TypesExecutionResult, Transform, TransformKind}, CLTyped, CLValue, Gas, Key, Motes, StoredValue, TransferAddr, }; @@ -25,6 +26,8 @@ pub enum ExecutionResult { cost: Gas, /// Execution effects. effects: Effects, + /// Messages emitted during execution. + messages: Vec, }, /// Execution was finished successfully Success { @@ -34,6 +37,8 @@ pub enum ExecutionResult { cost: Gas, /// Execution effects. effects: Effects, + /// Messages emitted during execution. + messages: Vec, }, } @@ -60,6 +65,7 @@ impl ExecutionResult { transfers: Vec::default(), cost: Gas::default(), effects: Effects::new(), + messages: Vec::default(), } } @@ -127,19 +133,25 @@ impl ExecutionResult { error, transfers, effects, + messages, .. } => ExecutionResult::Failure { error, transfers, cost, effects, + messages, }, ExecutionResult::Success { - transfers, effects, .. + transfers, + effects, + messages, + .. } => ExecutionResult::Success { transfers, cost, effects, + messages, }, } } @@ -154,17 +166,25 @@ impl ExecutionResult { error, cost, effects, + messages, .. } => ExecutionResult::Failure { error, transfers, cost, effects, + messages, }, - ExecutionResult::Success { cost, effects, .. } => ExecutionResult::Success { + ExecutionResult::Success { + cost, + effects, + messages, + .. + } => ExecutionResult::Success { transfers, cost, effects, + messages, }, } } @@ -180,20 +200,24 @@ impl ExecutionResult { transfers, cost, effects: _, + messages, } => ExecutionResult::Failure { error, transfers, cost, effects, + messages, }, ExecutionResult::Success { transfers, cost, effects: _, + messages, } => ExecutionResult::Success { transfers, cost, effects, + messages, }, } } @@ -291,6 +315,7 @@ impl ExecutionResult { effects, transfers, cost: gas_cost, + messages: Vec::default(), }) } @@ -312,21 +337,25 @@ impl From for TypesExecutionResult { transfers, cost, effects, + messages, } => TypesExecutionResult::Success { effects, transfers, cost: cost.value(), + messages, }, ExecutionResult::Failure { error, transfers, cost, effects, + messages, } => TypesExecutionResult::Failure { effects, transfers, cost: cost.value(), error_message: error.to_string(), + messages, }, } } @@ -422,9 +451,11 @@ impl ExecutionResultBuilder { let mut transfers = self.transfers(); let cost = self.total_cost(); - let mut all_effects = match self.payment_execution_result { + let (mut all_effects, mut all_messages) = match self.payment_execution_result { Some(result @ ExecutionResult::Failure { .. }) => return Ok(result), - Some(ExecutionResult::Success { effects, .. }) => effects, + Some(ExecutionResult::Success { + effects, messages, .. + }) => (effects, messages), None => return Err(ExecutionResultBuilderError::MissingPaymentExecutionResult), }; @@ -436,11 +467,18 @@ impl ExecutionResultBuilder { transfers: session_transfers, effects: _, cost: _, + messages, }) => { error = Some(session_error); transfers = session_transfers; + all_messages.extend(messages); + } + Some(ExecutionResult::Success { + effects, messages, .. + }) => { + all_effects.append(effects); + all_messages.extend(messages); } - Some(ExecutionResult::Success { effects, .. }) => all_effects.append(effects), None => return Err(ExecutionResultBuilderError::MissingSessionExecutionResult), }; @@ -451,7 +489,12 @@ impl ExecutionResultBuilder { error::Error::Finalization, )); } - Some(ExecutionResult::Success { effects, .. }) => all_effects.append(effects), + Some(ExecutionResult::Success { + effects, messages, .. + }) => { + all_effects.append(effects); + all_messages.extend(messages); + } None => return Err(ExecutionResultBuilderError::MissingFinalizeExecutionResult), } @@ -460,12 +503,14 @@ impl ExecutionResultBuilder { transfers, cost, effects: all_effects, + messages: all_messages, }), Some(error) => Ok(ExecutionResult::Failure { error, transfers, cost, effects: all_effects, + messages: all_messages, }), } } diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index f2f3bca202..a0cd0e0ad1 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -2714,11 +2714,13 @@ fn log_execution_result(preamble: &'static str, result: &ExecutionResult) { transfers, cost, effects, + messages, } => { debug!( %cost, transfer_count = %transfers.len(), transforms_count = %effects.len(), + messages_count = %messages.len(), "{}: execution success", preamble ); @@ -2728,12 +2730,14 @@ fn log_execution_result(preamble: &'static str, result: &ExecutionResult) { transfers, cost, effects, + messages, } => { debug!( %error, %cost, transfer_count = %transfers.len(), transforms_count = %effects.len(), + messages_count = %messages.len(), "{}: execution failure", preamble ); @@ -2748,6 +2752,7 @@ fn should_charge_for_errors_in_wasm(execution_result: &ExecutionResult) -> bool transfers: _, cost: _, effects: _, + messages: _, } => match error { Error::Exec(err) => match err { ExecError::WasmPreprocessing(_) | ExecError::UnsupportedWasmStart => true, @@ -2800,7 +2805,10 @@ fn should_charge_for_errors_in_wasm(execution_result: &ExecutionResult) -> bool | ExecError::DisabledContract(_) | ExecError::UnexpectedKeyVariant(_) | ExecError::InvalidContractPackageKind(_) - | ExecError::Transform(_) => false, + | ExecError::Transform(_) + | ExecError::FailedTopicRegistration(_) + | ExecError::CannotEmitMessage(_) + | ExecError::InvalidMessageTopicOperation => false, ExecError::DisabledUnrestrictedTransfers => false, }, Error::WasmPreprocessing(_) => true, diff --git a/execution_engine/src/execution/error.rs b/execution_engine/src/execution/error.rs index 8b2ac82207..d1aba5d0eb 100644 --- a/execution_engine/src/execution/error.rs +++ b/execution_engine/src/execution/error.rs @@ -9,7 +9,7 @@ use casper_types::{ execution::TransformError, package::ContractPackageKind, system, AccessRights, ApiError, CLType, CLValueError, ContractHash, ContractPackageHash, - ContractVersionKey, ContractWasmHash, Key, StoredValueTypeMismatch, URef, + ContractVersionKey, ContractWasmHash, Key, MessagesLimitsError, StoredValueTypeMismatch, URef, }; use crate::{ @@ -191,6 +191,15 @@ pub enum Error { /// Failed to transfer tokens on a private chain. #[error("Failed to transfer with unrestricted transfers disabled")] DisabledUnrestrictedTransfers, + /// Failed to register a message topic due to config limits. + #[error("Failed to register a message topic: {0}")] + FailedTopicRegistration(MessagesLimitsError), + /// Message was not emitted. + #[error("Failed to emit a message on topic: {0}")] + CannotEmitMessage(MessagesLimitsError), + /// Invalid message topic operation. + #[error("The requested operation is invalid for a message topic")] + InvalidMessageTopicOperation, } impl From for Error { diff --git a/execution_engine/src/execution/executor.rs b/execution_engine/src/execution/executor.rs index 37418d1cee..9fad1f0a91 100644 --- a/execution_engine/src/execution/executor.rs +++ b/execution_engine/src/execution/executor.rs @@ -126,12 +126,14 @@ impl Executor { effects: runtime.context().effects(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages: runtime.context().messages(), }, Err(error) => ExecutionResult::Failure { error: error.into(), effects: runtime.context().effects(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages: runtime.context().messages(), }, } } @@ -193,6 +195,7 @@ impl Executor { ); let effects = tracking_copy.borrow().effects(); + let messages = tracking_copy.borrow().messages(); // Standard payment is executed in the calling account's context; the stack already // captures that. @@ -203,12 +206,14 @@ impl Executor { effects: runtime.context().effects(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages: runtime.context().messages(), }, Err(error) => ExecutionResult::Failure { effects, error: error.into(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages, }, } } @@ -254,6 +259,7 @@ impl Executor { // Snapshot of effects before execution, so in case of error only nonce update // can be returned. let effects = tracking_copy.borrow().effects(); + let messages = tracking_copy.borrow().messages(); let entry_point_name = direct_system_contract_call.entry_point_name(); @@ -327,6 +333,7 @@ impl Executor { effects: runtime.context().effects(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages: runtime.context().messages(), } .take_with_ret(ret), Err(error) => ExecutionResult::Failure { @@ -334,6 +341,7 @@ impl Executor { error: Error::CLValue(error).into(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages, } .take_without_ret(), }, @@ -342,6 +350,7 @@ impl Executor { error: error.into(), transfers: runtime.context().transfers().to_owned(), cost: runtime.context().gas_counter(), + messages, } .take_without_ret(), } diff --git a/execution_engine/src/resolvers/v1_function_index.rs b/execution_engine/src/resolvers/v1_function_index.rs index 56625d6e21..b6484fc81e 100644 --- a/execution_engine/src/resolvers/v1_function_index.rs +++ b/execution_engine/src/resolvers/v1_function_index.rs @@ -60,6 +60,8 @@ pub(crate) enum FunctionIndex { RandomBytes, DictionaryReadFuncIndex, EnableContractVersion, + ManageMessageTopic, + EmitMessage, } impl From for usize { diff --git a/execution_engine/src/resolvers/v1_resolver.rs b/execution_engine/src/resolvers/v1_resolver.rs index 1c7796baa9..6fb77df1d9 100644 --- a/execution_engine/src/resolvers/v1_resolver.rs +++ b/execution_engine/src/resolvers/v1_resolver.rs @@ -245,6 +245,14 @@ impl ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), FunctionIndex::EnableContractVersion.into(), ), + "casper_manage_message_topic" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), + FunctionIndex::ManageMessageTopic.into(), + ), + "casper_emit_message" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), + FunctionIndex::EmitMessage.into(), + ), _ => { return Err(InterpreterError::Function(format!( "host module doesn't export function with name {}", diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index a35379a65f..be28f973b1 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -8,6 +8,7 @@ use casper_types::{ addressable_entity::{EntryPoints, NamedKeys}, api_error, bytesrepr::{self, ToBytes}, + contract_messages::MessageTopicOperation, crypto, package::{ContractPackageKind, ContractPackageStatus}, system::auction::EraInfo, @@ -1106,6 +1107,81 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + FunctionIndex::ManageMessageTopic => { + // args(0) = pointer to the topic name in wasm memory + // args(1) = size of the topic name string in wasm memory + // args(2) = pointer to the operation to be performed for the specified topic + // args(3) = size of the operation + let (topic_name_ptr, topic_name_size, operation_ptr, operation_size) = + Args::parse(args)?; + self.charge_host_function_call( + &host_function_costs.manage_message_topic, + [ + topic_name_ptr, + topic_name_size, + operation_ptr, + operation_size, + ], + )?; + + self.context + .engine_config() + .wasm_config() + .messages_limits() + .topic_name_size_within_limits(topic_name_size) + .map_err(|e| Trap::from(Error::FailedTopicRegistration(e)))?; + let topic_name = self.string_from_mem(topic_name_ptr, topic_name_size)?; + + if operation_size as usize > MessageTopicOperation::max_serialized_len() { + return Err(Trap::from(Error::InvalidMessageTopicOperation)); + } + let topic_operation = self + .t_from_mem(operation_ptr, operation_size) + .map_err(|_e| Trap::from(Error::InvalidMessageTopicOperation))?; + + // don't allow managing topics for accounts. + if let Key::Account(_) = self.context.get_entity_address() { + return Err(Trap::from(Error::InvalidContext)); + } + + let result = match topic_operation { + MessageTopicOperation::Add => self.register_message_topic(topic_name)?, + }; + + Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) + } + FunctionIndex::EmitMessage => { + // args(0) = pointer to the message kind name in wasm memory + // args(1) = size of the name string in wasm memory + // args(2) = pointer to the message contents + // args(3) = size of the message contents + let (topic_name_ptr, topic_name_size, message_ptr, message_size) = + Args::parse(args)?; + self.charge_host_function_call( + &host_function_costs.emit_message, + [topic_name_ptr, topic_name_size, message_ptr, message_size], + )?; + + self.context + .engine_config() + .wasm_config() + .messages_limits() + .topic_name_size_within_limits(topic_name_size) + .map_err(|e| Trap::from(Error::CannotEmitMessage(e)))?; + + self.context + .engine_config() + .wasm_config() + .messages_limits() + .message_size_within_limits(message_size) + .map_err(|e| Trap::from(Error::CannotEmitMessage(e)))?; + + let topic_name = self.string_from_mem(topic_name_ptr, topic_name_size)?; + let message = self.t_from_mem(message_ptr, message_size)?; + + let result = self.emit_message(topic_name, message)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) + } } } } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 2786a8f205..930644ec37 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -31,6 +31,10 @@ use casper_types::{ DEFAULT_ENTRY_POINT_NAME, }, bytesrepr::{self, Bytes, FromBytes, ToBytes}, + contract_messages::{ + Message, MessageAddr, MessagePayload, MessageSummary, MessageTopicSummary, + }, + crypto, package::{ContractPackageKind, ContractPackageStatus}, system::{ self, @@ -3223,4 +3227,69 @@ where None => Err(Error::InvalidContract(contract_hash)), } } + + fn register_message_topic(&mut self, topic_name: String) -> Result, Trap> { + let entity_addr = self + .context + .get_entity_address() + .into_hash() + .ok_or(Error::InvalidContext)?; + + let topic_digest: [u8; 32] = crypto::blake2b(topic_name); + let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_digest)); + + // Check if topic is already registered. + if self.context.read_gs(&topic_key)?.is_some() { + return Ok(Err(ApiError::MessageTopicAlreadyRegistered)); + } + + let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0)); + self.context.metered_write_gs_unsafe(topic_key, summary)?; + + Ok(Ok(())) + } + + fn emit_message( + &mut self, + topic_name: String, + message: MessagePayload, + ) -> Result, Trap> { + let entity_addr = self + .context + .get_entity_address() + .into_hash() + .ok_or(Error::InvalidContext)?; + + let topic_digest: [u8; 32] = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); + let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_digest)); + + // Check if the topic exists and get the summary. + let current_topic_summary = if let Some(StoredValue::MessageTopic(message_summary)) = + self.context.read_gs(&topic_key)? + { + message_summary + } else { + return Ok(Err(ApiError::MessageTopicNotRegistered)); + }; + + let message_index = current_topic_summary.message_count(); + + let new_topic_summary = + StoredValue::MessageTopic(MessageTopicSummary::new(message_index + 1)); + + let message_key = Key::message(entity_addr, topic_digest, message_index); + + let message_digest = StoredValue::Message(MessageSummary(crypto::blake2b( + message.to_bytes().map_err(Error::BytesRepr)?, + ))); + + self.context.metered_emit_message( + topic_key, + new_topic_summary, + message_key, + message_digest, + Message::new(entity_addr, message, topic_name, message_index), + )?; + Ok(Ok(())) + } } diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 4f1bed29a3..f55c616240 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -22,6 +22,7 @@ use casper_types::{ UpdateKeyFailure, Weight, }, bytesrepr::ToBytes, + contract_messages::Message, execution::Effects, package::ContractPackageKind, system::auction::{BidKind, EraInfo}, @@ -330,8 +331,8 @@ where error!("should not remove the checksum registry key"); Err(Error::RemoveKeyFailure(RemoveKeyFailure::PermissionDenied)) } - Key::MessageTopic(_) => { - error!("should not remove the message keys"); + Key::Message(_) => { + error!("should not remove the message key"); Err(Error::RemoveKeyFailure(RemoveKeyFailure::PermissionDenied)) } bid_key @ Key::BidAddr(_) => { @@ -657,6 +658,11 @@ where self.tracking_copy.borrow().effects() } + /// Returns a copy of the current messages of a tracking copy. + pub fn messages(&self) -> Vec { + self.tracking_copy.borrow().messages() + } + /// Returns list of transfers. pub fn transfers(&self) -> &Vec { &self.transfers @@ -725,6 +731,8 @@ where StoredValue::BidKind(_) => Ok(()), StoredValue::Withdraw(_) => Ok(()), StoredValue::Unbonding(_) => Ok(()), + StoredValue::MessageTopic(_) => Ok(()), + StoredValue::Message(_) => Ok(()), } } @@ -804,7 +812,7 @@ where | Key::ChainspecRegistry | Key::ChecksumRegistry | Key::BidAddr(_) - | Key::MessageTopic(_) => true, + | Key::Message(_) => true, } } @@ -827,7 +835,7 @@ where | Key::ChainspecRegistry | Key::ChecksumRegistry | Key::BidAddr(_) - | Key::MessageTopic(_) => false, + | Key::Message(_) => false, } } @@ -850,7 +858,7 @@ where | Key::ChainspecRegistry | Key::ChecksumRegistry | Key::BidAddr(_) - | Key::MessageTopic(_) => false, + | Key::Message(_) => false, } } @@ -945,6 +953,49 @@ where Ok(()) } + /// Emits message and writes message summary to global state with a measurement. + pub(crate) fn metered_emit_message( + &mut self, + topic_key: Key, + topic_value: V, + message_key: Key, + message_value: V, + message: Message, + ) -> Result<(), Error> + where + V: Into, + { + let topic_value = topic_value.into(); + let message_value = message_value.into(); + + match topic_value { + StoredValue::MessageTopic(_) => {} + _ => { + return Err(Error::UnexpectedStoredValueVariant); + } + } + + match message_value { + StoredValue::Message(_) => {} + _ => { + return Err(Error::UnexpectedStoredValueVariant); + } + } + + // Charge for amount as measured by serialized length + let bytes_count = topic_value.serialized_length() + message_value.serialized_length(); + self.charge_gas_storage(bytes_count)?; + + self.tracking_copy.borrow_mut().emit_message( + topic_key, + topic_value, + message_key, + message_value, + message, + ); + Ok(()) + } + /// Writes data to a global state and charges for bytes stored. /// /// This method performs full validation of the key to be written. diff --git a/execution_engine/src/tracking_copy/byte_size.rs b/execution_engine/src/tracking_copy/byte_size.rs index 9cabd62aac..cb76d2d178 100644 --- a/execution_engine/src/tracking_copy/byte_size.rs +++ b/execution_engine/src/tracking_copy/byte_size.rs @@ -40,6 +40,10 @@ impl ByteSize for StoredValue { StoredValue::BidKind(bid_kind) => bid_kind.serialized_length(), StoredValue::Withdraw(withdraw_purses) => withdraw_purses.serialized_length(), StoredValue::Unbonding(unbonding_purses) => unbonding_purses.serialized_length(), + StoredValue::MessageTopic(message_topic_summary) => { + message_topic_summary.serialized_length() + } + StoredValue::Message(message_summary) => message_summary.serialized_length(), } } } diff --git a/execution_engine/src/tracking_copy/mod.rs b/execution_engine/src/tracking_copy/mod.rs index e15beb8eb7..c00139e9c8 100644 --- a/execution_engine/src/tracking_copy/mod.rs +++ b/execution_engine/src/tracking_copy/mod.rs @@ -19,6 +19,7 @@ use casper_storage::global_state::{state::StateReader, trie::merkle_proof::TrieM use casper_types::{ addressable_entity::NamedKeys, bytesrepr::{self}, + contract_messages::Message, execution::{Effects, Transform, TransformError, TransformInstruction, TransformKind}, CLType, CLValue, CLValueError, Digest, Key, KeyTag, StoredValue, StoredValueTypeMismatch, Tagged, U512, @@ -223,6 +224,7 @@ pub struct TrackingCopy { reader: R, cache: TrackingCopyCache, effects: Effects, + messages: Vec, } /// Result of executing an "add" operation on a value in the state. @@ -261,6 +263,7 @@ impl> TrackingCopy { // TODO: Should `max_cache_size` be a fraction of wasm memory limit? cache: TrackingCopyCache::new(1024 * 16, HeapSize), effects: Effects::new(), + messages: Vec::new(), } } @@ -342,6 +345,24 @@ impl> TrackingCopy { self.effects.push(transform); } + /// Caches the emitted message and writes the message topic summary under the specified key. + /// + /// This function does not check the types for the key and the value so the caller should + /// correctly set the type. The `message_topic_key` should be of the `Key::MessageTopic` + /// variant and the `message_topic_summary` should be of the `StoredValue::Message` variant. + pub fn emit_message( + &mut self, + message_topic_key: Key, + message_topic_summary: StoredValue, + message_key: Key, + message_value: StoredValue, + message: Message, + ) { + self.write(message_key, message_value); + self.write(message_topic_key, message_topic_summary); + self.messages.push(message); + } + /// Prunes a `key`. pub(crate) fn prune(&mut self, key: Key) { let normalized_key = key.normalize(); @@ -435,6 +456,11 @@ impl> TrackingCopy { self.effects.clone() } + /// Returns a copy of the messages cached by this instance. + pub fn messages(&self) -> Vec { + self.messages.clone() + } + /// Calling `query()` avoids calling into `self.cache`, so this will not return any values /// written or mutated in this `TrackingCopy` via previous calls to `write()` or `add()`, since /// these updates are only held in `self.cache`. @@ -563,6 +589,12 @@ impl> TrackingCopy { StoredValue::Unbonding(_) => { return Ok(query.into_not_found_result("UnbondingPurses value found.")); } + StoredValue::MessageTopic(_) => { + return Ok(query.into_not_found_result("MessageTopic value found.")); + } + StoredValue::Message(_) => { + return Ok(query.into_not_found_result("Message value found.")); + } } } } diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs new file mode 100644 index 0000000000..3a100030eb --- /dev/null +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -0,0 +1,137 @@ +use casper_engine_test_support::{ + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, + PRODUCTION_RUN_GENESIS_REQUEST, +}; +use casper_types::{ + bytesrepr::ToBytes, + contract_messages::{MessagePayload, MessageTopicHash, MessageTopicSummary}, + crypto, runtime_args, ContractHash, Key, RuntimeArgs, StoredValue, +}; + +const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; +const MESSAGE_EMITTER_PACKAGE_NAME: &str = "messages_emitter_package_name"; +const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; +const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; + +const EMITTER_MESSAGE_PREFIX: &str = "generic message: "; + +fn query_message_topic( + builder: &mut LmdbWasmTestBuilder, + contract_hash: &ContractHash, + message_topic_hash: MessageTopicHash, +) -> MessageTopicSummary { + let query_result = builder + .query( + None, + Key::message_topic(contract_hash.value(), message_topic_hash), + &[], + ) + .expect("should query"); + + match query_result { + StoredValue::MessageTopic(summary) => summary, + _ => { + panic!( + "Stored value is not a message topic summary: {:?}", + query_result + ); + } + } +} + +#[ignore] +#[test] +fn should_emit_messages() { + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + // Request to install the contract that will be emitting messages. + let install_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + MESSAGE_EMITTER_INSTALLER_WASM, + RuntimeArgs::default(), + ) + .build(); + + // Execute the request to install the message emitting contract. + // This will also register a topic for the contract to emit messages on. + builder.exec(install_request).expect_success().commit(); + + // Get the contract package for the messages_emitter. + let query_result = builder + .query( + None, + Key::from(*DEFAULT_ACCOUNT_ADDR), + &[MESSAGE_EMITTER_PACKAGE_NAME.into()], + ) + .expect("should query"); + + let message_emitter_package = if let StoredValue::ContractPackage(package) = query_result { + package + } else { + panic!("Stored value is not a contract package: {:?}", query_result); + }; + + // Get the contract hash of the messages_emitter contract. + let message_emitter_contract_hash = message_emitter_package + .versions() + .contract_hashes() + .last() + .expect("Should have contract hash"); + + // Check that the topic exists for the installed contract. + let message_topic_hash = crypto::blake2b(MESSAGE_EMITTER_GENERIC_TOPIC); + assert_eq!( + query_message_topic( + &mut builder, + message_emitter_contract_hash, + message_topic_hash + ) + .message_count(), + 0 + ); + + // Now call the entry point to emit some messages. + let emit_message_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + *message_emitter_contract_hash, + ENTRY_POINT_EMIT_MESSAGE, + runtime_args! { + ARG_MESSAGE_SUFFIX_NAME => "test", + }, + ) + .build(); + + builder.exec(emit_message_request).expect_success().commit(); + + let query_result = builder + .query( + None, + Key::message(message_emitter_contract_hash.value(), message_topic_hash, 0), + &[], + ) + .expect("should query"); + + let queried_message_summary = if let StoredValue::Message(summary) = query_result { + summary.value() + } else { + panic!("Stored value is not a message summary: {:?}", query_result); + }; + + let expected_message = + MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test")); + let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); + + assert_eq!(expected_message_hash, queried_message_summary); + + assert_eq!( + query_message_topic( + &mut builder, + message_emitter_contract_hash, + message_topic_hash + ) + .message_count(), + 1 + ) +} diff --git a/execution_engine_testing/tests/src/test/mod.rs b/execution_engine_testing/tests/src/test/mod.rs index 263169bc60..88b90a4e11 100644 --- a/execution_engine_testing/tests/src/test/mod.rs +++ b/execution_engine_testing/tests/src/test/mod.rs @@ -3,6 +3,7 @@ mod chainspec_registry; mod check_transfer_success; mod contract_api; mod contract_context; +mod contract_messages; mod counter_factory; mod deploy; mod explorer; diff --git a/execution_engine_testing/tests/src/test/private_chain.rs b/execution_engine_testing/tests/src/test/private_chain.rs index 0f1c5746d9..2d8fb3e9d3 100644 --- a/execution_engine_testing/tests/src/test/private_chain.rs +++ b/execution_engine_testing/tests/src/test/private_chain.rs @@ -21,8 +21,8 @@ use once_cell::sync::Lazy; use casper_types::{ account::AccountHash, system::auction::DELEGATION_RATE_DENOMINATOR, AdministratorAccount, - FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, Motes, - OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, WasmConfig, + FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessagesLimits, + Motes, OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; use tempfile::TempDir; @@ -208,6 +208,7 @@ fn make_wasm_config() -> WasmConfig { OpcodeCosts::default(), StorageCosts::default(), host_functions, + MessagesLimits::default(), ) } diff --git a/execution_engine_testing/tests/src/test/regression/ee_966.rs b/execution_engine_testing/tests/src/test/regression/ee_966.rs index 644d20192b..e6b60feaa2 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_966.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_966.rs @@ -13,8 +13,8 @@ use casper_execution_engine::{ }; use casper_types::{ addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, ApiError, EraId, HostFunctionCosts, - OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, - DEFAULT_WASM_MAX_MEMORY, + MessagesLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, + DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, }; const CONTRACT_EE_966_REGRESSION: &str = "ee_966_regression.wasm"; @@ -28,6 +28,7 @@ static DOUBLED_WASM_MEMORY_LIMIT: Lazy = Lazy::new(|| { OpcodeCosts::default(), StorageCosts::default(), HostFunctionCosts::default(), + MessagesLimits::default(), ) }); static NEW_PROTOCOL_VERSION: Lazy = Lazy::new(|| { diff --git a/execution_engine_testing/tests/src/test/regression/gh_2280.rs b/execution_engine_testing/tests/src/test/regression/gh_2280.rs index 75f2f0fa1a..e7a5852d38 100644 --- a/execution_engine_testing/tests/src/test/regression/gh_2280.rs +++ b/execution_engine_testing/tests/src/test/regression/gh_2280.rs @@ -758,6 +758,7 @@ fn make_wasm_config( old_wasm_config.opcode_costs(), old_wasm_config.storage_costs(), new_host_function_costs, + old_wasm_config.messages_limits(), ) } diff --git a/execution_engine_testing/tests/src/test/storage_costs.rs b/execution_engine_testing/tests/src/test/storage_costs.rs index 36b31cf23e..1dc6a7082d 100644 --- a/execution_engine_testing/tests/src/test/storage_costs.rs +++ b/execution_engine_testing/tests/src/test/storage_costs.rs @@ -13,8 +13,8 @@ use casper_types::DEFAULT_ADD_BID_COST; use casper_types::{ bytesrepr::{Bytes, ToBytes}, BrTableCost, CLValue, ContractHash, ControlFlowCosts, EraId, HostFunction, HostFunctionCosts, - OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, StoredValue, WasmConfig, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + MessagesLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, StoredValue, + WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; #[cfg(not(feature = "use-as-wasm"))] use casper_types::{ @@ -133,6 +133,8 @@ static NEW_HOST_FUNCTION_COSTS: Lazy = Lazy::new(|| HostFunct blake2b: HostFunction::fixed(0), random_bytes: HostFunction::fixed(0), enable_contract_version: HostFunction::fixed(0), + manage_message_topic: HostFunction::fixed(0), + emit_message: HostFunction::fixed(0), }); static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { WasmConfig::new( @@ -141,6 +143,7 @@ static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { NEW_OPCODE_COSTS, StorageCosts::default(), *NEW_HOST_FUNCTION_COSTS, + MessagesLimits::default(), ) }); diff --git a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs index fb1b9927b4..8f2ff53c15 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs @@ -17,9 +17,9 @@ use casper_types::{ }, mint::ROUND_SEIGNIORAGE_RATE_KEY, }, - BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, OpcodeCosts, ProtocolVersion, - StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, DEFAULT_BIT_COST, DEFAULT_CONST_COST, - DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, + BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessagesLimits, OpcodeCosts, + ProtocolVersion, StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, DEFAULT_BIT_COST, + DEFAULT_CONST_COST, DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, DEFAULT_CONTROL_FLOW_BR_OPCODE, DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER, DEFAULT_CONTROL_FLOW_BR_TABLE_OPCODE, DEFAULT_CONTROL_FLOW_CALL_INDIRECT_OPCODE, DEFAULT_CONTROL_FLOW_CALL_OPCODE, DEFAULT_CONTROL_FLOW_DROP_OPCODE, @@ -74,12 +74,14 @@ fn get_upgraded_wasm_config() -> WasmConfig { }; let storage_costs = StorageCosts::default(); let host_function_costs = HostFunctionCosts::default(); + let messages_limits = MessagesLimits::default(); WasmConfig::new( DEFAULT_WASM_MAX_MEMORY, DEFAULT_MAX_STACK_HEIGHT * 2, opcode_cost, storage_costs, host_function_costs, + messages_limits, ) } diff --git a/execution_engine_testing/tests/src/test/system_costs.rs b/execution_engine_testing/tests/src/test/system_costs.rs index d7ae56e707..9f22ba91e9 100644 --- a/execution_engine_testing/tests/src/test/system_costs.rs +++ b/execution_engine_testing/tests/src/test/system_costs.rs @@ -16,10 +16,11 @@ use casper_types::{ handle_payment, mint, AUCTION, }, AuctionCosts, BrTableCost, ControlFlowCosts, EraId, Gas, GenesisAccount, GenesisValidator, - HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MintCosts, Motes, - OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, StandardPaymentCosts, - StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, DEFAULT_MAX_STACK_HEIGHT, - DEFAULT_TRANSFER_COST, DEFAULT_WASMLESS_TRANSFER_COST, DEFAULT_WASM_MAX_MEMORY, U512, + HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessagesLimits, + MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, + StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, + DEFAULT_MAX_STACK_HEIGHT, DEFAULT_TRANSFER_COST, DEFAULT_WASMLESS_TRANSFER_COST, + DEFAULT_WASM_MAX_MEMORY, U512, }; use crate::wasm_utils; @@ -968,6 +969,8 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { blake2b: HostFunction::fixed(0), random_bytes: HostFunction::fixed(0), enable_contract_version: HostFunction::fixed(0), + manage_message_topic: HostFunction::fixed(0), + emit_message: HostFunction::fixed(0), }; let new_wasm_config = WasmConfig::new( @@ -976,6 +979,7 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { new_opcode_costs, new_storage_costs, new_host_function_costs, + MessagesLimits::default(), ); let new_wasmless_transfer_cost = 0; diff --git a/node/src/utils/chain_specification.rs b/node/src/utils/chain_specification.rs index 2f309f7880..cce255931d 100644 --- a/node/src/utils/chain_specification.rs +++ b/node/src/utils/chain_specification.rs @@ -129,8 +129,8 @@ mod tests { use casper_types::{ bytesrepr::FromBytes, ActivationPoint, BrTableCost, ChainspecRawBytes, ControlFlowCosts, CoreConfig, EraId, GlobalStateUpdate, HighwayConfig, HostFunction, HostFunctionCosts, - Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, StoredValue, - TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, WasmConfig, U512, + MessagesLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, + StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, WasmConfig, U512, }; use super::*; @@ -221,6 +221,8 @@ mod tests { blake2b: HostFunction::new(133, [0, 1, 2, 3]), random_bytes: HostFunction::new(123, [0, 1]), enable_contract_version: HostFunction::new(142, [0, 1, 2, 3]), + manage_message_topic: HostFunction::new(100, [0, 1, 2, 4]), + emit_message: HostFunction::new(100, [0, 1, 2, 3]), }); static EXPECTED_GENESIS_WASM_COSTS: Lazy = Lazy::new(|| { WasmConfig::new( @@ -229,6 +231,7 @@ mod tests { EXPECTED_GENESIS_COSTS, EXPECTED_GENESIS_STORAGE_COSTS, *EXPECTED_GENESIS_HOST_FUNCTION_COSTS, + MessagesLimits::default(), ) }); diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 6008870bce..0390431196 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -253,6 +253,12 @@ transfer_to_account = { cost = 2_500_000_000, arguments = [0, 0, 0, 0, 0, 0, 0] update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } write_local = { cost = 9_500, arguments = [0, 1_800, 0, 520] } +manage_message_topic = { cost = 200, arguments = [0, 0, 0, 0] } +emit_message = { cost = 200, arguments = [0, 0, 0, 0] } + +[wasm.messages_limits] +max_topic_name_size = 256 +max_message_size = 1_024 [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 02bb1b692f..8c53e274cf 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -264,6 +264,12 @@ update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } write_local = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } +manage_message_topic = { cost = 200, arguments = [0, 0, 0, 0] } +emit_message = { cost = 200, arguments = [0, 0, 0, 0] } + +[wasm.messages_limits] +max_topic_name_size = 256 +max_message_size = 1_024 [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index 2a51409dc0..48b73ffc85 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -277,7 +277,8 @@ "transfer-5959595959595959595959595959595959595959595959595959595959595959", "transfer-8282828282828282828282828282828282828282828282828282828282828282" ], - "cost": "123456" + "cost": "123456", + "messages": [] } } } @@ -3571,6 +3572,7 @@ "cost", "effects", "error_message", + "messages", "transfers" ], "properties": { @@ -3600,6 +3602,13 @@ "error_message": { "description": "The error message associated with executing the deploy.", "type": "string" + }, + "messages": { + "description": "Messages that were emitted during execution.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + } } }, "additionalProperties": false @@ -3619,6 +3628,7 @@ "required": [ "cost", "effects", + "messages", "transfers" ], "properties": { @@ -3644,6 +3654,13 @@ "$ref": "#/components/schemas/U512" } ] + }, + "messages": { + "description": "Messages that were emitted during execution.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + } } }, "additionalProperties": false @@ -3660,6 +3677,72 @@ "$ref": "#/components/schemas/Transform" } }, + "Message": { + "description": "Message that was emitted by an addressable entity during execution.", + "type": "object", + "required": [ + "entity_addr", + "index", + "message", + "topic" + ], + "properties": { + "entity_addr": { + "description": "The identity of the entity that produced the message.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32 + }, + "message": { + "description": "Message payload", + "allOf": [ + { + "$ref": "#/components/schemas/MessagePayload" + } + ] + }, + "topic": { + "description": "Topic name", + "type": "string" + }, + "index": { + "description": "Message index in the topic", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "MessagePayload": { + "description": "The payload of the message emitted by an addressable entity during execution.", + "oneOf": [ + { + "description": "Empty message.", + "type": "string", + "enum": [ + "Empty" + ] + }, + { + "description": "Human readable string message.", + "type": "object", + "required": [ + "String" + ], + "properties": { + "String": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, "AccountIdentifier": { "description": "Identifier of an account.", "anyOf": [ @@ -4115,6 +4198,32 @@ } }, "additionalProperties": false + }, + { + "description": "Variant that stores a message topic.", + "type": "object", + "required": [ + "MessageTopic" + ], + "properties": { + "MessageTopic": { + "$ref": "#/components/schemas/MessageTopicSummary" + } + }, + "additionalProperties": false + }, + { + "description": "Variant that stores a message digest.", + "type": "object", + "required": [ + "Message" + ], + "properties": { + "Message": { + "$ref": "#/components/schemas/MessageSummary" + } + }, + "additionalProperties": false } ] }, @@ -4594,6 +4703,25 @@ } } }, + "MessageTopicSummary": { + "description": "Summary of a message topic that will be stored in global state.", + "type": "object", + "required": [ + "message_count" + ], + "properties": { + "message_count": { + "description": "Number of messages in this topic.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "MessageSummary": { + "description": "Message summary as a formatted string.", + "type": "string" + }, "GlobalStateIdentifier": { "description": "Identifier for possible ways to query Global State", "oneOf": [ diff --git a/smart_contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs index 155463039b..f9ed92b4b9 100644 --- a/smart_contracts/contract/src/contract_api/runtime.rs +++ b/smart_contracts/contract/src/contract_api/runtime.rs @@ -8,6 +8,7 @@ use casper_types::{ addressable_entity::NamedKeys, api_error, bytesrepr::{self, FromBytes}, + contract_messages::{MessagePayload, MessageTopicOperation}, package::ContractVersion, system::CallStackElement, ApiError, BlockTime, CLTyped, CLValue, ContractHash, ContractPackageHash, Key, Phase, @@ -404,6 +405,44 @@ pub fn get_call_stack() -> Vec { bytesrepr::deserialize(bytes).unwrap_or_revert() } +/// Manages a message topic. +pub fn manage_message_topic( + topic_name: &str, + operation: MessageTopicOperation, +) -> Result<(), ApiError> { + if topic_name.is_empty() { + return Err(ApiError::InvalidArgument); + } + + let (topic_name_ptr, topic_name_size, _bytes) = contract_api::to_ptr(topic_name); + let (operation_ptr, operation_size, _bytes) = contract_api::to_ptr(operation); + let result = unsafe { + ext_ffi::casper_manage_message_topic( + topic_name_ptr, + topic_name_size, + operation_ptr, + operation_size, + ) + }; + api_error::result_from(result) +} + +/// Emits a message on a topic. +pub fn emit_message(topic_name: &str, message: &MessagePayload) -> Result<(), ApiError> { + if topic_name.is_empty() { + return Err(ApiError::InvalidArgument); + } + + let (topic_name_ptr, topic_name_size, _bytes) = contract_api::to_ptr(topic_name); + let (message_ptr, message_size, _bytes) = contract_api::to_ptr(message); + + let result = unsafe { + ext_ffi::casper_emit_message(topic_name_ptr, topic_name_size, message_ptr, message_size) + }; + + api_error::result_from(result) +} + #[cfg(feature = "test-support")] /// Prints a debug message pub fn print(text: &str) { diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index 7bf2bce38b..d7cca0fc2d 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -805,4 +805,33 @@ extern "C" { contract_hash_ptr: *const u8, contract_hash_size: usize, ) -> i32; + /// Manages a message topic. + /// + /// # Arguments + /// + /// * `topic_name_ptr` - pointer to a `str` containing the name of the message topic. + /// * `topic_name_size` - size of the topic string. + /// * `operation_ptr` - pointer to the management operation to be performed for the specified + /// topic. + /// * `operation_ptr_size` - size of the operation. + pub fn casper_manage_message_topic( + topic_name_ptr: *const u8, + topic_name_size: usize, + operation_ptr: *const u8, + operation_size: usize, + ) -> i32; + /// Emits a new message on the specified topic. + /// + /// # Arguments + /// + /// * `topic_name_ptr` - pointer to a `str` containing the name of the topic to be registered. + /// * `topic_name_size` - size of the topic string. + /// * `message_ptr` - pointer pointer to serialized message payload. + /// * `message_size` - size of the serialized message payload. + pub fn casper_emit_message( + topic_name_ptr: *const u8, + topic_name_size: usize, + message_ptr: *const u8, + message_size: usize, + ) -> i32; } diff --git a/smart_contracts/contracts/test/contract-messages-emitter/Cargo.toml b/smart_contracts/contracts/test/contract-messages-emitter/Cargo.toml new file mode 100644 index 0000000000..4594ca4c88 --- /dev/null +++ b/smart_contracts/contracts/test/contract-messages-emitter/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "contract-messages-emitter" +version = "0.1.0" +authors = ["Alexandru Sardan "] +edition = "2018" + +[[bin]] +name = "contract_messages_emitter" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs new file mode 100644 index 0000000000..12690156b1 --- /dev/null +++ b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs @@ -0,0 +1,83 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; + +use casper_contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; + +use casper_types::{ + addressable_entity::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys}, + api_error::ApiError, + contract_messages::{MessagePayload, MessageTopicOperation}, + CLType, CLTyped, Parameter, RuntimeArgs, +}; + +pub const ENTRY_POINT_INIT: &str = "init"; +pub const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +pub const MESSAGE_EMITTER_INITIALIZED: &str = "message_emitter_initialized"; +pub const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; + +pub const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; +pub const MESSAGE_PREFIX: &str = "generic message: "; + +#[no_mangle] +pub extern "C" fn emit_message() { + let suffix: String = runtime::get_named_arg(ARG_MESSAGE_SUFFIX_NAME); + + runtime::emit_message( + MESSAGE_EMITTER_GENERIC_TOPIC, + &MessagePayload::from_string(format!("{}{}", MESSAGE_PREFIX, suffix)), + ); +} + +#[no_mangle] +pub extern "C" fn init() { + if runtime::has_key(MESSAGE_EMITTER_INITIALIZED) { + runtime::revert(ApiError::User(0)); + } + + runtime::manage_message_topic(MESSAGE_EMITTER_GENERIC_TOPIC, MessageTopicOperation::Add); + + runtime::put_key(MESSAGE_EMITTER_INITIALIZED, storage::new_uref(()).into()); +} + +#[no_mangle] +pub extern "C" fn call() { + let mut emitter_entry_points = EntryPoints::new(); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_INIT, + Vec::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_EMIT_MESSAGE, + vec![Parameter::new(ARG_MESSAGE_SUFFIX_NAME, String::cl_type())], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); + + let (stored_contract_hash, _contract_version) = storage::new_contract( + emitter_entry_points, + Some(NamedKeys::new()), + Some("messages_emitter_package_name".to_string()), + Some("messages_emitter_access_uref".to_string()), + ); + + // Call contract to initialize it + runtime::call_contract::<()>( + stored_contract_hash, + ENTRY_POINT_INIT, + RuntimeArgs::default(), + ); +} diff --git a/types/src/api_error.rs b/types/src/api_error.rs index 75bb0db216..2f424ce8f1 100644 --- a/types/src/api_error.rs +++ b/types/src/api_error.rs @@ -396,6 +396,24 @@ pub enum ApiError { /// } /// ``` User(u16), + /// The message topic is already registered. + /// ``` + /// # use casper_types::ApiError; + /// assert_eq!(ApiError::from(41), ApiError::MessageTopicAlreadyRegistered); + /// ``` + MessageTopicAlreadyRegistered, + /// The message topic is not registered. + /// ``` + /// # use casper_types::ApiError; + /// assert_eq!(ApiError::from(42), ApiError::MessageTopicNotRegistered); + /// ``` + MessageTopicNotRegistered, + /// The message topic is full and cannot accept new messages. + /// ``` + /// # use casper_types::ApiError; + /// assert_eq!(ApiError::from(43), ApiError::MessageTopicFull); + /// ``` + MessageTopicFull, } impl From for ApiError { @@ -542,6 +560,9 @@ impl From for u32 { ApiError::MissingSystemContractHash => 38, ApiError::ExceededRecursionDepth => 39, ApiError::NonRepresentableSerialization => 40, + ApiError::MessageTopicAlreadyRegistered => 41, + ApiError::MessageTopicNotRegistered => 42, + ApiError::MessageTopicFull => 43, ApiError::AuctionError(value) => AUCTION_ERROR_OFFSET + u32::from(value), ApiError::ContractHeader(value) => HEADER_ERROR_OFFSET + u32::from(value), ApiError::Mint(value) => MINT_ERROR_OFFSET + u32::from(value), @@ -594,6 +615,9 @@ impl From for ApiError { 38 => ApiError::MissingSystemContractHash, 39 => ApiError::ExceededRecursionDepth, 40 => ApiError::NonRepresentableSerialization, + 41 => ApiError::MessageTopicAlreadyRegistered, + 42 => ApiError::MessageTopicNotRegistered, + 43 => ApiError::MessageTopicFull, USER_ERROR_MIN..=USER_ERROR_MAX => ApiError::User(value as u16), HP_ERROR_MIN..=HP_ERROR_MAX => ApiError::HandlePayment(value as u8), MINT_ERROR_MIN..=MINT_ERROR_MAX => ApiError::Mint(value as u8), @@ -652,6 +676,13 @@ impl Debug for ApiError { ApiError::NonRepresentableSerialization => { write!(f, "ApiError::NonRepresentableSerialization")? } + ApiError::MessageTopicAlreadyRegistered => { + write!(f, "ApiError::MessageTopicAlreadyRegistered")? + } + ApiError::MessageTopicNotRegistered => { + write!(f, "ApiError::MessageTopicNotRegistered")? + } + ApiError::MessageTopicFull => write!(f, "ApiError::MessageTopicFull")?, ApiError::ExceededRecursionDepth => write!(f, "ApiError::ExceededRecursionDepth")?, ApiError::AuctionError(value) => write!( f, @@ -871,5 +902,8 @@ mod tests { round_trip(Err(ApiError::User(u16::MAX))); round_trip(Err(ApiError::AuctionError(0))); round_trip(Err(ApiError::AuctionError(u8::MAX))); + round_trip(Err(ApiError::MessageTopicAlreadyRegistered)); + round_trip(Err(ApiError::MessageTopicNotRegistered)); + round_trip(Err(ApiError::MessageTopicFull)); } } diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index 4d802253c2..64ffc07dee 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -47,9 +47,9 @@ pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Confi pub use transaction_config::{DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES}; pub use vm_config::{ AuctionCosts, BrTableCost, ChainspecRegistry, ControlFlowCosts, HandlePaymentCosts, - HostFunction, HostFunctionCost, HostFunctionCosts, MintCosts, OpcodeCosts, - StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, - DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, + HostFunction, HostFunctionCost, HostFunctionCosts, MessagesLimits, MessagesLimitsError, + MintCosts, OpcodeCosts, StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, + WasmConfig, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, }; #[cfg(any(feature = "testing", test))] pub use vm_config::{ diff --git a/types/src/chainspec/vm_config.rs b/types/src/chainspec/vm_config.rs index ff934a33d5..212976f306 100644 --- a/types/src/chainspec/vm_config.rs +++ b/types/src/chainspec/vm_config.rs @@ -2,6 +2,7 @@ mod auction_costs; mod chainspec_registry; mod handle_payment_costs; mod host_function_costs; +mod messages_limits; mod mint_costs; mod opcode_costs; mod standard_payment_costs; @@ -17,6 +18,7 @@ pub use host_function_costs::{ Cost as HostFunctionCost, HostFunction, HostFunctionCosts, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_NEW_DICTIONARY_COST, }; +pub use messages_limits::{Error as MessagesLimitsError, MessagesLimits}; pub use mint_costs::{MintCosts, DEFAULT_TRANSFER_COST}; pub use opcode_costs::{BrTableCost, ControlFlowCosts, OpcodeCosts}; #[cfg(any(feature = "testing", test))] diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index 3037f4b53f..92e42ed78f 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -295,6 +295,10 @@ pub struct HostFunctionCosts { pub random_bytes: HostFunction<[Cost; 2]>, /// Cost of calling the `enable_contract_version` host function. pub enable_contract_version: HostFunction<[Cost; 4]>, + /// Cost of calling the `casper_manage_message_topic` host function. + pub manage_message_topic: HostFunction<[Cost; 4]>, + /// Cost of calling the `casper_emit_message` host function. + pub emit_message: HostFunction<[Cost; 4]>, } impl Default for HostFunctionCosts { @@ -427,6 +431,8 @@ impl Default for HostFunctionCosts { blake2b: HostFunction::default(), random_bytes: HostFunction::default(), enable_contract_version: HostFunction::default(), + manage_message_topic: HostFunction::default(), + emit_message: HostFunction::default(), } } } @@ -478,6 +484,8 @@ impl ToBytes for HostFunctionCosts { ret.append(&mut self.blake2b.to_bytes()?); ret.append(&mut self.random_bytes.to_bytes()?); ret.append(&mut self.enable_contract_version.to_bytes()?); + ret.append(&mut self.manage_message_topic.to_bytes()?); + ret.append(&mut self.emit_message.to_bytes()?); Ok(ret) } @@ -526,6 +534,8 @@ impl ToBytes for HostFunctionCosts { + self.blake2b.serialized_length() + self.random_bytes.serialized_length() + self.enable_contract_version.serialized_length() + + self.manage_message_topic.serialized_length() + + self.emit_message.serialized_length() } } @@ -575,6 +585,8 @@ impl FromBytes for HostFunctionCosts { let (blake2b, rem) = FromBytes::from_bytes(rem)?; let (random_bytes, rem) = FromBytes::from_bytes(rem)?; let (enable_contract_version, rem) = FromBytes::from_bytes(rem)?; + let (manage_message_topic, rem) = FromBytes::from_bytes(rem)?; + let (emit_message, rem) = FromBytes::from_bytes(rem)?; Ok(( HostFunctionCosts { read_value, @@ -621,6 +633,8 @@ impl FromBytes for HostFunctionCosts { blake2b, random_bytes, enable_contract_version, + manage_message_topic, + emit_message, }, rem, )) @@ -674,6 +688,8 @@ impl Distribution for Standard { blake2b: rng.gen(), random_bytes: rng.gen(), enable_contract_version: rng.gen(), + manage_message_topic: rng.gen(), + emit_message: rng.gen(), } } } @@ -737,6 +753,8 @@ pub mod gens { blake2b in host_function_cost_arb(), random_bytes in host_function_cost_arb(), enable_contract_version in host_function_cost_arb(), + manage_message_topic in host_function_cost_arb(), + emit_message in host_function_cost_arb(), ) -> HostFunctionCosts { HostFunctionCosts { read_value, @@ -783,6 +801,8 @@ pub mod gens { blake2b, random_bytes, enable_contract_version, + manage_message_topic, + emit_message, } } } diff --git a/types/src/chainspec/vm_config/messages_limits.rs b/types/src/chainspec/vm_config/messages_limits.rs new file mode 100644 index 0000000000..03ffe45e58 --- /dev/null +++ b/types/src/chainspec/vm_config/messages_limits.rs @@ -0,0 +1,125 @@ +#[cfg(feature = "datasize")] +use datasize::DataSize; +use rand::{distributions::Standard, prelude::*, Rng}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::bytesrepr::{self, FromBytes, ToBytes}; + +/// Configuration for messages limits. +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[serde(deny_unknown_fields)] +pub struct MessagesLimits { + /// Maximum size (in bytes) of a topic name. + max_topic_name_size: u32, + /// Maximum message size in bytes. + max_message_size: u32, +} + +impl MessagesLimits { + /// Check if a specified topic `name_size` exceeds the configured value. + pub fn topic_name_size_within_limits(&self, name_size: u32) -> Result<(), Error> { + if name_size > self.max_topic_name_size { + Err(Error::TopicNameSizeExceeded( + self.max_topic_name_size, + name_size, + )) + } else { + Ok(()) + } + } + + /// Check if a specified message size exceeds the configured max value. + pub fn message_size_within_limits(&self, message_size: u32) -> Result<(), Error> { + if message_size > self.max_message_size { + Err(Error::MessageTooLarge(self.max_message_size, message_size)) + } else { + Ok(()) + } + } +} + +impl Default for MessagesLimits { + fn default() -> Self { + Self { + max_topic_name_size: 256, + max_message_size: 1024, + } + } +} + +impl ToBytes for MessagesLimits { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + + ret.append(&mut self.max_topic_name_size.to_bytes()?); + ret.append(&mut self.max_message_size.to_bytes()?); + + Ok(ret) + } + + fn serialized_length(&self) -> usize { + self.max_topic_name_size.serialized_length() + self.max_message_size.serialized_length() + } +} + +impl FromBytes for MessagesLimits { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (max_topic_name_size, rem) = FromBytes::from_bytes(bytes)?; + let (max_message_size, rem) = FromBytes::from_bytes(rem)?; + + Ok(( + MessagesLimits { + max_topic_name_size, + max_message_size, + }, + rem, + )) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> MessagesLimits { + MessagesLimits { + max_topic_name_size: rng.gen(), + max_message_size: rng.gen(), + } + } +} + +/// Possible execution errors. +#[derive(Error, Debug, Clone)] +#[non_exhaustive] +pub enum Error { + /// Topic name size exceeded. + #[error( + "Topic name size is too large: expected less then {} bytes, got {} bytes", + _0, + _1 + )] + TopicNameSizeExceeded(u32, u32), + /// Message size exceeded. + #[error("Message size cannot exceed {} bytes; actual size {}", _0, _1)] + MessageTooLarge(u32, u32), +} + +#[doc(hidden)] +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::{num, prop_compose}; + + use super::MessagesLimits; + + prop_compose! { + pub fn message_limits_arb()( + max_topic_name_size in num::u32::ANY, + max_message_size in num::u32::ANY, + ) -> MessagesLimits { + MessagesLimits { + max_topic_name_size, + max_message_size, + } + } + } +} diff --git a/types/src/chainspec/vm_config/wasm_config.rs b/types/src/chainspec/vm_config/wasm_config.rs index bfc9452496..9535b32418 100644 --- a/types/src/chainspec/vm_config/wasm_config.rs +++ b/types/src/chainspec/vm_config/wasm_config.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - chainspec::vm_config::{HostFunctionCosts, OpcodeCosts, StorageCosts}, + chainspec::vm_config::{HostFunctionCosts, MessagesLimits, OpcodeCosts, StorageCosts}, }; /// Default maximum number of pages of the Wasm memory. @@ -32,6 +32,8 @@ pub struct WasmConfig { storage_costs: StorageCosts, /// Host function costs table. host_function_costs: HostFunctionCosts, + /// Messages limits. + messages_limits: MessagesLimits, } impl WasmConfig { @@ -42,6 +44,7 @@ impl WasmConfig { opcode_costs: OpcodeCosts, storage_costs: StorageCosts, host_function_costs: HostFunctionCosts, + messages_limits: MessagesLimits, ) -> Self { Self { max_memory, @@ -49,6 +52,7 @@ impl WasmConfig { opcode_costs, storage_costs, host_function_costs, + messages_limits, } } @@ -66,6 +70,11 @@ impl WasmConfig { pub fn take_host_function_costs(self) -> HostFunctionCosts { self.host_function_costs } + + /// Returns the limits config for messages. + pub fn messages_limits(&self) -> MessagesLimits { + self.messages_limits + } } impl Default for WasmConfig { @@ -76,6 +85,7 @@ impl Default for WasmConfig { opcode_costs: OpcodeCosts::default(), storage_costs: StorageCosts::default(), host_function_costs: HostFunctionCosts::default(), + messages_limits: MessagesLimits::default(), } } } @@ -109,6 +119,7 @@ impl FromBytes for WasmConfig { let (opcode_costs, rem) = FromBytes::from_bytes(rem)?; let (storage_costs, rem) = FromBytes::from_bytes(rem)?; let (host_function_costs, rem) = FromBytes::from_bytes(rem)?; + let (messages_limits, rem) = FromBytes::from_bytes(rem)?; Ok(( WasmConfig { @@ -117,6 +128,7 @@ impl FromBytes for WasmConfig { opcode_costs, storage_costs, host_function_costs, + messages_limits, }, rem, )) @@ -131,6 +143,7 @@ impl Distribution for Standard { opcode_costs: rng.gen(), storage_costs: rng.gen(), host_function_costs: rng.gen(), + messages_limits: rng.gen(), } } } @@ -143,7 +156,8 @@ pub mod gens { use crate::{ chainspec::vm_config::{ host_function_costs::gens::host_function_costs_arb, - opcode_costs::gens::opcode_costs_arb, storage_costs::gens::storage_costs_arb, + messages_limits::gens::message_limits_arb, opcode_costs::gens::opcode_costs_arb, + storage_costs::gens::storage_costs_arb, }, WasmConfig, }; @@ -155,6 +169,7 @@ pub mod gens { opcode_costs in opcode_costs_arb(), storage_costs in storage_costs_arb(), host_function_costs in host_function_costs_arb(), + messages_limits in message_limits_arb(), ) -> WasmConfig { WasmConfig { max_memory, @@ -162,6 +177,7 @@ pub mod gens { opcode_costs, storage_costs, host_function_costs, + messages_limits, } } } diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index 98709cb3b2..0106b16afb 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -1,11 +1,17 @@ //! Data types for interacting with contract level messages. - use crate::{ - bytesrepr::{self, FromBytes, ToBytes}, - HashAddr, + alloc::string::ToString, + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + checksummed_hex, HashAddr, KEY_HASH_LENGTH, +}; + +use core::convert::TryFrom; + +use alloc::{string::String, vec::Vec}; +use core::{ + fmt::{self, Display, Formatter}, + num::ParseIntError, }; -use alloc::vec::Vec; -use core::fmt::{Display, Formatter}; #[cfg(feature = "datasize")] use datasize::DataSize; @@ -20,74 +26,478 @@ use serde::{Deserialize, Serialize}; /// The length in bytes of a [`MessageTopicHash`]. pub const MESSAGE_TOPIC_HASH_LENGTH: usize = 32; +/// The length of a message digest +pub const MESSAGE_DIGEST_LENGTH: usize = 32; + /// The hash of the name of the message topic. pub type MessageTopicHash = [u8; MESSAGE_TOPIC_HASH_LENGTH]; -/// MessageAddr -#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] +const TOPIC_FORMATTED_STRING_PREFIX: &str = "topic-"; + +/// A newtype wrapping an array which contains the raw bytes of +/// the hash of the message emitted +#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars(description = "Message summary as a formatted string.") +)] +pub struct MessageSummary( + #[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] + pub [u8; MESSAGE_DIGEST_LENGTH], +); + +impl MessageSummary { + /// Returns inner value of the message summary + pub fn value(&self) -> [u8; MESSAGE_DIGEST_LENGTH] { + self.0 + } +} + +impl ToBytes for MessageSummary { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.0.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for MessageSummary { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (summary, rem) = FromBytes::from_bytes(bytes)?; + Ok((MessageSummary(summary), rem)) + } +} + +/// Error while parsing a `[MessageTopicAddr]` from string. +#[derive(Debug)] +#[non_exhaustive] +pub enum FromStrError { + /// No message index at the end of the string. + MissingMessageIndex, + /// Cannot parse entity hash. + EntityHashParseError(String), + /// Cannot parse message topic hash. + MessageTopicParseError(String), + /// Failed to decode address portion of URef. + Hex(base16::DecodeError), + /// Failed to parse an int. + Int(ParseIntError), +} + +impl From for FromStrError { + fn from(error: base16::DecodeError) -> Self { + FromStrError::Hex(error) + } +} + +impl From for FromStrError { + fn from(error: ParseIntError) -> Self { + FromStrError::Int(error) + } +} + +impl Display for FromStrError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + FromStrError::MissingMessageIndex => { + write!(f, "no message index found at the end of the string") + } + FromStrError::EntityHashParseError(err) => { + write!(f, "could not parse entity hash: {}", err) + } + FromStrError::MessageTopicParseError(err) => { + write!(f, "could not parse topic hash: {}", err) + } + FromStrError::Hex(error) => { + write!(f, "failed to decode address portion from hex: {}", error) + } + FromStrError::Int(error) => write!(f, "failed to parse an int: {}", error), + } + } +} + +/// MessageTopicAddr +#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "json-schema", derive(JsonSchema))] #[cfg_attr(feature = "datasize", derive(DataSize))] -pub struct MessageTopicAddr { +pub struct MessageAddr { /// The entity addr. entity_addr: HashAddr, /// The hash of the name of the message topic. topic_hash: MessageTopicHash, + /// The message index. + message_index: Option, } -impl MessageTopicAddr { - /// Constructs a new [`MessageAddr`] based on the addressable entity addr and the hash of the +impl MessageAddr { + /// Constructs a new topic address based on the addressable entity addr and the hash of the /// message topic name. - pub const fn new(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Self { + pub const fn new_topic_addr(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Self { + Self { + entity_addr, + topic_hash, + message_index: None, + } + } + + /// Constructs a new message address based on the addressable entity addr, the hash of the + /// message topic name and the message index in the topic. + pub const fn new_message_addr( + entity_addr: HashAddr, + topic_hash: MessageTopicHash, + message_index: u32, + ) -> Self { Self { entity_addr, topic_hash, + message_index: Some(message_index), } } + + /// Parses a formatted string into a [`MessageAddr`]. + pub fn from_formatted_str(input: &str) -> Result { + let (remainder, message_index) = match input.strip_prefix(TOPIC_FORMATTED_STRING_PREFIX) { + Some(topic_string) => (topic_string, None), + None => { + let parts = input.splitn(2, '-').collect::>(); + if parts.len() != 2 { + return Err(FromStrError::MissingMessageIndex); + } + (parts[0], Some(u32::from_str_radix(parts[1], 16)?)) + } + }; + + let bytes = checksummed_hex::decode(remainder)?; + + let entity_addr = <[u8; KEY_HASH_LENGTH]>::try_from(bytes[0..KEY_HASH_LENGTH].as_ref()) + .map_err(|err| FromStrError::EntityHashParseError(err.to_string()))?; + + let topic_hash = + <[u8; MESSAGE_TOPIC_HASH_LENGTH]>::try_from(bytes[KEY_HASH_LENGTH..].as_ref()) + .map_err(|err| FromStrError::MessageTopicParseError(err.to_string()))?; + + Ok(MessageAddr { + entity_addr, + topic_hash, + message_index, + }) + } + + /// Returns the entity addr of this message topic. + pub fn entity_addr(&self) -> HashAddr { + self.entity_addr + } } -impl Display for MessageTopicAddr { +impl Display for MessageAddr { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{}{}", - base16::encode_lower(&self.entity_addr), - base16::encode_lower(&self.topic_hash) - ) + match self.message_index { + Some(index) => { + write!( + f, + "{}{}-{:x}", + base16::encode_lower(&self.entity_addr), + base16::encode_lower(&self.topic_hash), + index, + ) + } + None => { + write!( + f, + "{}{}{}", + TOPIC_FORMATTED_STRING_PREFIX, + base16::encode_lower(&self.entity_addr), + base16::encode_lower(&self.topic_hash), + ) + } + } } } -impl ToBytes for MessageTopicAddr { +impl ToBytes for MessageAddr { fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut buffer = bytesrepr::allocate_buffer(self)?; buffer.append(&mut self.entity_addr.to_bytes()?); buffer.append(&mut self.topic_hash.to_bytes()?); + buffer.append(&mut self.message_index.to_bytes()?); Ok(buffer) } fn serialized_length(&self) -> usize { - self.entity_addr.serialized_length() + self.topic_hash.serialized_length() + self.entity_addr.serialized_length() + + self.topic_hash.serialized_length() + + self.message_index.serialized_length() } } -impl FromBytes for MessageTopicAddr { +impl FromBytes for MessageAddr { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (entity_addr, rem) = FromBytes::from_bytes(bytes)?; let (topic_hash, rem) = FromBytes::from_bytes(rem)?; + let (message_index, rem) = FromBytes::from_bytes(rem)?; Ok(( - MessageTopicAddr { + MessageAddr { entity_addr, topic_hash, + message_index, }, rem, )) } } -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> MessageTopicAddr { - MessageTopicAddr { +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> MessageAddr { + MessageAddr { entity_addr: rng.gen(), topic_hash: rng.gen(), + message_index: rng.gen(), + } + } +} + +/// Summary of a message topic that will be stored in global state. +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub struct MessageTopicSummary { + /// Number of messages in this topic. + pub(crate) message_count: u32, +} + +impl MessageTopicSummary { + /// Creates a new topic summary. + pub fn new(message_count: u32) -> Self { + Self { message_count } + } + + /// Returns the number of messages that were sent on this topic. + pub fn message_count(&self) -> u32 { + self.message_count + } +} + +impl ToBytes for MessageTopicSummary { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.message_count.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.message_count.serialized_length() + } +} + +impl FromBytes for MessageTopicSummary { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (message_count, rem) = FromBytes::from_bytes(bytes)?; + Ok((MessageTopicSummary { message_count }, rem)) + } +} + +const MESSAGE_PAYLOAD_TAG_LENGTH: usize = U8_SERIALIZED_LENGTH; + +/// Tag for an empty message payload. +pub const MESSAGE_PAYLOAD_EMPTY_TAG: u8 = 0; +/// Tag for a message payload that contains a human readable string. +pub const MESSAGE_PAYLOAD_STRING_TAG: u8 = 1; + +/// The payload of the message emitted by an addressable entity during execution. +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub enum MessagePayload { + /// Empty message. + Empty, + /// Human readable string message. + String(String), +} + +impl MessagePayload { + /// Creates a new [`MessagePayload`] from a [`String`]. + pub fn from_string(message: String) -> Self { + Self::String(message) + } +} + +impl ToBytes for MessagePayload { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + match self { + MessagePayload::Empty => { + buffer.insert(0, MESSAGE_PAYLOAD_EMPTY_TAG); + } + MessagePayload::String(message_string) => { + buffer.insert(0, MESSAGE_PAYLOAD_STRING_TAG); + buffer.extend(message_string.to_bytes()?); + } + } + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + MESSAGE_PAYLOAD_TAG_LENGTH + + match self { + MessagePayload::Empty => 0, + MessagePayload::String(message_string) => message_string.serialized_length(), + } + } +} + +impl FromBytes for MessagePayload { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + match tag { + MESSAGE_PAYLOAD_EMPTY_TAG => Ok((Self::Empty, remainder)), + MESSAGE_PAYLOAD_STRING_TAG => { + let (message, remainder): (String, _) = FromBytes::from_bytes(remainder)?; + Ok((Self::String(message), remainder)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +/// Message that was emitted by an addressable entity during execution. +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub struct Message { + /// The identity of the entity that produced the message. + entity_addr: HashAddr, + /// Message payload + message: MessagePayload, + /// Topic name + topic: String, + /// Message index in the topic + index: u32, +} + +impl Message { + /// Creates new instance of [`Message`] with the specified source and message payload. + pub fn new(source: HashAddr, message: MessagePayload, topic: String, index: u32) -> Self { + Self { + entity_addr: source, + message, + topic, + index, + } + } +} + +impl ToBytes for Message { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.entity_addr.to_bytes()?); + buffer.append(&mut self.message.to_bytes()?); + buffer.append(&mut self.topic.to_bytes()?); + buffer.append(&mut self.index.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.entity_addr.serialized_length() + + self.message.serialized_length() + + self.topic.serialized_length() + + self.index.serialized_length() + } +} + +impl FromBytes for Message { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (entity_addr, rem) = FromBytes::from_bytes(bytes)?; + let (message, rem) = FromBytes::from_bytes(rem)?; + let (topic, rem) = FromBytes::from_bytes(rem)?; + let (index, rem) = FromBytes::from_bytes(rem)?; + Ok(( + Message { + entity_addr, + message, + topic, + index, + }, + rem, + )) + } +} + +const TOPIC_OPERATION_ADD_TAG: u8 = 0; +const OPERATION_MAX_SERIALIZED_LEN: usize = 1; + +/// Operations that can be performed on message topics. +pub enum MessageTopicOperation { + /// Add a new message topic. + Add, +} + +impl MessageTopicOperation { + /// Maximum serialized length of a message topic operation. + pub const fn max_serialized_len() -> usize { + OPERATION_MAX_SERIALIZED_LEN + } +} + +impl ToBytes for MessageTopicOperation { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + match self { + MessageTopicOperation::Add => buffer.push(TOPIC_OPERATION_ADD_TAG), + } + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + match self { + MessageTopicOperation::Add => 1, + } + } +} + +impl FromBytes for MessageTopicOperation { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; + match tag { + TOPIC_OPERATION_ADD_TAG => Ok((MessageTopicOperation::Add, remainder)), + _ => Err(bytesrepr::Error::Formatting), } } } + +#[cfg(test)] +mod tests { + use crate::{bytesrepr, KEY_HASH_LENGTH}; + + use super::*; + + #[test] + fn serialization_roundtrip() { + let message_addr = + MessageAddr::new_topic_addr([1; KEY_HASH_LENGTH], [2; MESSAGE_TOPIC_HASH_LENGTH]); + bytesrepr::test_serialization_roundtrip(&message_addr); + + let topic_summary = MessageTopicSummary::new(100); + bytesrepr::test_serialization_roundtrip(&topic_summary); + + let string_message_payload = + MessagePayload::from_string("new contract message".to_string()); + bytesrepr::test_serialization_roundtrip(&string_message_payload); + + let empty_message_payload = MessagePayload::Empty; + bytesrepr::test_serialization_roundtrip(&empty_message_payload); + + let message = Message::new( + [1; KEY_HASH_LENGTH], + string_message_payload, + "new contract message".to_string(), + 32, + ); + bytesrepr::test_serialization_roundtrip(&message); + } +} diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index a3065c95ab..08ac0de2bb 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -10,10 +10,16 @@ use alloc::{string::String, vec::Vec}; #[cfg(feature = "datasize")] use datasize::DataSize; +#[cfg(any(feature = "testing", test))] +use itertools::Itertools; #[cfg(feature = "json-schema")] use once_cell::sync::Lazy; #[cfg(any(feature = "testing", test))] -use rand::{distributions::Standard, prelude::Distribution, Rng}; +use rand::{ + distributions::{Alphanumeric, DistString, Standard}, + prelude::Distribution, + Rng, +}; #[cfg(feature = "json-schema")] use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -21,12 +27,13 @@ use serde::{Deserialize, Serialize}; use super::Effects; #[cfg(feature = "json-schema")] use super::{Transform, TransformKind}; -#[cfg(any(feature = "testing", test))] -use crate::testing::TestRng; use crate::{ bytesrepr::{self, FromBytes, ToBytes, RESULT_ERR_TAG, RESULT_OK_TAG, U8_SERIALIZED_LENGTH}, + contract_messages::Message, TransferAddr, U512, }; +#[cfg(any(feature = "testing", test))] +use crate::{contract_messages::MessagePayload, testing::TestRng}; #[cfg(feature = "json-schema")] use crate::{Key, KEY_HASH_LENGTH}; @@ -53,6 +60,7 @@ static EXECUTION_RESULT: Lazy = Lazy::new(|| { effects, transfers, cost: U512::from(123_456), + messages: Vec::default(), } }); @@ -72,6 +80,8 @@ pub enum ExecutionResultV2 { cost: U512, /// The error message associated with executing the deploy. error_message: String, + /// Messages that were emitted during execution. + messages: Vec, }, /// The result of a successful execution. Success { @@ -81,6 +91,8 @@ pub enum ExecutionResultV2 { transfers: Vec, /// The cost in Motes of executing the deploy. cost: U512, + /// Messages that were emitted during execution. + messages: Vec, }, } @@ -93,18 +105,38 @@ impl Distribution for Standard { transfers.push(TransferAddr::new(rng.gen())) } + let effects = Effects::random(rng); + let messages = effects + .transforms() + .iter() + .filter_map(|transform| { + if let Key::Message(addr) = transform.key() { + Some(Message::new( + addr.entity_addr(), + MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), + Alphanumeric.sample_string(rng, 32), + rng.gen::(), + )) + } else { + None + } + }) + .collect_vec(); + if rng.gen() { ExecutionResultV2::Failure { - effects: Effects::random(rng), + effects, transfers, cost: rng.gen::().into(), error_message: format!("Error message {}", rng.gen::()), + messages, } } else { ExecutionResultV2::Success { - effects: Effects::random(rng), + effects, transfers, cost: rng.gen::().into(), + messages, } } } @@ -122,6 +154,22 @@ impl ExecutionResultV2 { #[cfg(any(feature = "testing", test))] pub fn random(rng: &mut TestRng) -> Self { let effects = Effects::random(rng); + let messages = effects + .transforms() + .iter() + .filter_map(|transform| { + if let Key::Message(addr) = transform.key() { + Some(Message::new( + addr.entity_addr(), + MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), + Alphanumeric.sample_string(rng, 32), + rng.gen::(), + )) + } else { + None + } + }) + .collect_vec(); let transfer_count = rng.gen_range(0..6); let mut transfers = vec![]; @@ -137,12 +185,14 @@ impl ExecutionResultV2 { transfers, cost, error_message: format!("Error message {}", rng.gen::()), + messages, } } else { ExecutionResultV2::Success { effects, transfers, cost, + messages, } } } @@ -156,22 +206,26 @@ impl ToBytes for ExecutionResultV2 { transfers, cost, error_message, + messages, } => { RESULT_ERR_TAG.write_bytes(writer)?; effects.write_bytes(writer)?; transfers.write_bytes(writer)?; cost.write_bytes(writer)?; - error_message.write_bytes(writer) + error_message.write_bytes(writer)?; + messages.write_bytes(writer) } ExecutionResultV2::Success { effects, transfers, cost, + messages, } => { RESULT_OK_TAG.write_bytes(writer)?; effects.write_bytes(writer)?; transfers.write_bytes(writer)?; - cost.write_bytes(writer) + cost.write_bytes(writer)?; + messages.write_bytes(writer) } } } @@ -190,20 +244,24 @@ impl ToBytes for ExecutionResultV2 { transfers, cost, error_message, + messages, } => { effects.serialized_length() + transfers.serialized_length() + cost.serialized_length() + error_message.serialized_length() + + messages.serialized_length() } ExecutionResultV2::Success { effects, transfers, cost, + messages, } => { effects.serialized_length() + transfers.serialized_length() + cost.serialized_length() + + messages.serialized_length() } } } @@ -218,11 +276,13 @@ impl FromBytes for ExecutionResultV2 { let (transfers, remainder) = Vec::::from_bytes(remainder)?; let (cost, remainder) = U512::from_bytes(remainder)?; let (error_message, remainder) = String::from_bytes(remainder)?; + let (messages, remainder) = Vec::::from_bytes(remainder)?; let execution_result = ExecutionResultV2::Failure { effects, transfers, cost, error_message, + messages, }; Ok((execution_result, remainder)) } @@ -230,10 +290,12 @@ impl FromBytes for ExecutionResultV2 { let (effects, remainder) = Effects::from_bytes(remainder)?; let (transfers, remainder) = Vec::::from_bytes(remainder)?; let (cost, remainder) = U512::from_bytes(remainder)?; + let (messages, remainder) = Vec::::from_bytes(remainder)?; let execution_result = ExecutionResultV2::Success { effects, transfers, cost, + messages, }; Ok((execution_result, remainder)) } diff --git a/types/src/execution/transform_kind.rs b/types/src/execution/transform_kind.rs index de9ada8835..55eb41217b 100644 --- a/types/src/execution/transform_kind.rs +++ b/types/src/execution/transform_kind.rs @@ -162,6 +162,16 @@ impl TransformKind { let found = "Unbonding".to_string(); Err(StoredValueTypeMismatch::new(expected, found).into()) } + StoredValue::MessageTopic(_) => { + let expected = "Contract or Account".to_string(); + let found = "MessageTopic".to_string(); + Err(StoredValueTypeMismatch::new(expected, found).into()) + } + StoredValue::Message(_) => { + let expected = "Contract or Account".to_string(); + let found = "Message".to_string(); + Err(StoredValueTypeMismatch::new(expected, found).into()) + } }, TransformKind::Failure(error) => Err(error), } diff --git a/types/src/gens.rs b/types/src/gens.rs index 16837be4ee..84a0c2d7f7 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -15,6 +15,7 @@ use proptest::{ use crate::{ account::{self, action_thresholds::gens::account_action_thresholds_arb, AccountHash}, addressable_entity::{NamedKeys, Parameters, Weight}, + contract_messages::{MessageSummary, MessageTopicSummary}, crypto::gens::public_key_arb_no_system, package::{ContractPackageStatus, ContractVersionKey, ContractVersions, Groups}, system::auction::{ @@ -620,6 +621,14 @@ fn unbondings_arb(size: impl Into) -> impl Strategy impl Strategy { + any::().prop_map(|message_count| MessageTopicSummary { message_count }) +} + +fn message_summary_arb() -> impl Strategy { + u8_slice_32().prop_map(MessageSummary) +} + pub fn stored_value_arb() -> impl Strategy { prop_oneof![ cl_value_arb().prop_map(StoredValue::CLValue), @@ -635,7 +644,9 @@ pub fn stored_value_arb() -> impl Strategy { validator_bid_arb().prop_map(StoredValue::BidKind), delegator_bid_arb().prop_map(StoredValue::BidKind), withdraws_arb(1..50).prop_map(StoredValue::Withdraw), - unbondings_arb(1..50).prop_map(StoredValue::Unbonding) + unbondings_arb(1..50).prop_map(StoredValue::Unbonding), + message_topic_summary_arb().prop_map(StoredValue::MessageTopic), + message_summary_arb().prop_map(StoredValue::Message), ] .prop_map(|stored_value| // The following match statement is here only to make sure @@ -654,5 +665,7 @@ pub fn stored_value_arb() -> impl Strategy { StoredValue::Unbonding(_) => stored_value, StoredValue::AddressableEntity(_) => stored_value, StoredValue::BidKind(_) => stored_value, + StoredValue::MessageTopic(_) => stored_value, + StoredValue::Message(_) => stored_value, }) } diff --git a/types/src/key.rs b/types/src/key.rs index b8ecc95c3f..1854a8b03f 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -35,7 +35,7 @@ use crate::{ addressable_entity::ContractHash, bytesrepr::{self, Error, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}, checksummed_hex, - contract_messages::{MessageTopicAddr, MessageTopicHash, MESSAGE_TOPIC_HASH_LENGTH}, + contract_messages::{self, MessageAddr, MessageTopicHash}, contract_wasm::ContractWasmHash, package::ContractPackageHash, system::auction::{BidAddr, BidAddrTag}, @@ -57,7 +57,7 @@ const ERA_SUMMARY_PREFIX: &str = "era-summary-"; const CHAINSPEC_REGISTRY_PREFIX: &str = "chainspec-registry-"; const CHECKSUM_REGISTRY_PREFIX: &str = "checksum-registry-"; const BID_ADDR_PREFIX: &str = "bid-addr-"; -const MESSAGE_TOPIC_PREFIX: &str = "message-topic-"; +const MESSAGE_PREFIX: &str = "message-"; /// The number of bytes in a Blake2b hash pub const BLAKE2B_DIGEST_LENGTH: usize = 32; @@ -125,7 +125,7 @@ pub enum KeyTag { ChainspecRegistry = 13, ChecksumRegistry = 14, BidAddr = 15, - MessageTopic = 16, + Message = 16, } impl Display for KeyTag { @@ -147,7 +147,7 @@ impl Display for KeyTag { KeyTag::ChainspecRegistry => write!(f, "ChainspecRegistry"), KeyTag::ChecksumRegistry => write!(f, "ChecksumRegistry"), KeyTag::BidAddr => write!(f, "BidAddr"), - KeyTag::MessageTopic => write!(f, "MessageTopic"), + KeyTag::Message => write!(f, "Message"), } } } @@ -191,8 +191,8 @@ pub enum Key { ChecksumRegistry, /// A `Key` under which we store bid information BidAddr(BidAddr), - /// A `Key` under which a message topic is stored - MessageTopic(MessageTopicAddr), + /// A `Key` under which a message is stored + Message(MessageAddr), } #[cfg(feature = "json-schema")] @@ -249,8 +249,8 @@ pub enum FromStrError { ChecksumRegistry(String), /// Bid parse error. BidAddr(String), - /// Message topic parse error. - MessageTopic(String), + /// Message parse error. + Message(contract_messages::FromStrError), /// Unknown prefix. UnknownPrefix, } @@ -273,6 +273,12 @@ impl From for FromStrError { } } +impl From for FromStrError { + fn from(error: contract_messages::FromStrError) -> Self { + FromStrError::Message(error) + } +} + impl Display for FromStrError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { @@ -310,8 +316,8 @@ impl Display for FromStrError { write!(f, "checksum-registry-key from string error: {}", error) } FromStrError::BidAddr(error) => write!(f, "bid-addr-key from string error: {}", error), - FromStrError::MessageTopic(error) => { - write!(f, "message-topic-key from string error: {}", error) + FromStrError::Message(error) => { + write!(f, "message-key from string error: {}", error) } FromStrError::UnknownPrefix => write!(f, "unknown prefix for key"), } @@ -339,7 +345,7 @@ impl Key { Key::ChainspecRegistry => String::from("Key::ChainspecRegistry"), Key::ChecksumRegistry => String::from("Key::ChecksumRegistry"), Key::BidAddr(_) => String::from("Key::BidAddr"), - Key::MessageTopic(_) => String::from("Key::MessageTopic"), + Key::Message(_) => String::from("Key::Message"), } } @@ -426,8 +432,8 @@ impl Key { Key::BidAddr(bid_addr) => { format!("{}{}", BID_ADDR_PREFIX, bid_addr) } - Key::MessageTopic(message_addr) => { - format!("{}{}", MESSAGE_TOPIC_PREFIX, message_addr) + Key::Message(message_addr) => { + format!("{}{}", MESSAGE_PREFIX, message_addr) } } } @@ -592,28 +598,10 @@ impl Key { return Ok(Key::ChecksumRegistry); } - if let Some(message_topic_addr) = input.strip_prefix(MESSAGE_TOPIC_PREFIX) { - let bytes = checksummed_hex::decode(message_topic_addr) - .map_err(|error| FromStrError::MessageTopic(error.to_string()))?; - - if bytes.is_empty() { - return Err(FromStrError::MessageTopic( - "bytes should not be 0 len".to_string(), - )); - } - - let entity_addr_bytes = - <[u8; KEY_HASH_LENGTH]>::try_from(bytes[0..KEY_HASH_LENGTH].as_ref()) - .map_err(|err| FromStrError::MessageTopic(err.to_string()))?; + if let Some(message_addr) = input.strip_prefix(MESSAGE_PREFIX) { + let message_addr = MessageAddr::from_formatted_str(message_addr)?; - let topic_hash_bytes = - <[u8; MESSAGE_TOPIC_HASH_LENGTH]>::try_from(bytes[KEY_HASH_LENGTH..].as_ref()) - .map_err(|err| FromStrError::MessageTopic(err.to_string()))?; - - return Ok(Key::MessageTopic(MessageTopicAddr::new( - entity_addr_bytes, - topic_hash_bytes, - ))); + return Ok(Key::Message(message_addr)); } Err(FromStrError::UnknownPrefix) @@ -717,10 +705,20 @@ impl Key { Key::Dictionary(addr) } - /// Creates a new [`Key::MessageTopic`] variant based on an `entity_addr` and a hash of the - /// topic name. + /// Creates a new [`Key::Message`] variant that identifies an indexed message based on an + /// `entity_addr` `topic_hash` and message `index`. + pub fn message(entity_addr: HashAddr, topic_hash: MessageTopicHash, index: u32) -> Key { + Key::Message(MessageAddr::new_message_addr( + entity_addr, + topic_hash, + index, + )) + } + + /// Creates a new [`Key::Message`] variant that identifies a message topic based on an + /// `entity_addr` and a hash of the topic name. pub fn message_topic(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Key { - Key::MessageTopic(MessageTopicAddr::new(entity_addr, topic_hash)) + Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_hash)) } /// Returns true if the key is of type [`Key::Dictionary`]. @@ -803,7 +801,9 @@ impl Display for Key { ) } Key::BidAddr(bid_addr) => write!(f, "Key::BidAddr({})", bid_addr), - Key::MessageTopic(message_addr) => write!(f, "Key::MessageTopic({})", message_addr), + Key::Message(message_addr) => { + write!(f, "Key::Message({})", message_addr) + } } } } @@ -833,7 +833,7 @@ impl Tagged for Key { Key::ChainspecRegistry => KeyTag::ChainspecRegistry, Key::ChecksumRegistry => KeyTag::ChecksumRegistry, Key::BidAddr(_) => KeyTag::BidAddr, - Key::MessageTopic(_) => KeyTag::MessageTopic, + Key::Message(_) => KeyTag::Message, } } } @@ -913,7 +913,7 @@ impl ToBytes for Key { KEY_ID_SERIALIZED_LENGTH + bid_addr.serialized_length() } }, - Key::MessageTopic(message_addr) => { + Key::Message(message_addr) => { KEY_ID_SERIALIZED_LENGTH + message_addr.serialized_length() } } @@ -945,11 +945,7 @@ impl ToBytes for Key { } BidAddrTag::Validator | BidAddrTag::Delegator => bid_addr.write_bytes(writer), }, - Key::MessageTopic(message_addr) => { - let bytes = message_addr.to_bytes()?; - writer.extend(&bytes); - Ok(()) - } + Key::Message(message_addr) => message_addr.write_bytes(writer), } } } @@ -1022,9 +1018,9 @@ impl FromBytes for Key { let (bid_addr, rem) = BidAddr::from_bytes(remainder)?; Ok((Key::BidAddr(bid_addr), rem)) } - tag if tag == KeyTag::MessageTopic as u8 => { - let (message_addr, rem) = MessageTopicAddr::from_bytes(remainder)?; - Ok((Key::MessageTopic(message_addr), rem)) + tag if tag == KeyTag::Message as u8 => { + let (message_addr, rem) = MessageAddr::from_bytes(remainder)?; + Ok((Key::Message(message_addr), rem)) } _ => Err(Error::Formatting), } @@ -1052,7 +1048,7 @@ fn please_add_to_distribution_impl(key: Key) { Key::ChainspecRegistry => unimplemented!(), Key::ChecksumRegistry => unimplemented!(), Key::BidAddr(_) => unimplemented!(), - Key::MessageTopic(_) => unimplemented!(), + Key::Message(_) => unimplemented!(), } } @@ -1076,7 +1072,7 @@ impl Distribution for Standard { 13 => Key::ChainspecRegistry, 14 => Key::ChecksumRegistry, 15 => Key::BidAddr(rng.gen()), - 16 => Key::MessageTopic(rng.gen()), + 16 => Key::Message(rng.gen()), _ => unreachable!(), } } @@ -1104,7 +1100,7 @@ mod serde_helpers { ChainspecRegistry, ChecksumRegistry, BidAddr(&'a BidAddr), - MessageTopic(&'a MessageTopicAddr), + Message(&'a MessageAddr), } #[derive(Deserialize)] @@ -1126,7 +1122,7 @@ mod serde_helpers { ChainspecRegistry, ChecksumRegistry, BidAddr(BidAddr), - MessageTopic(MessageTopicAddr), + Message(MessageAddr), } impl<'a> From<&'a Key> for BinarySerHelper<'a> { @@ -1148,7 +1144,7 @@ mod serde_helpers { Key::ChainspecRegistry => BinarySerHelper::ChainspecRegistry, Key::ChecksumRegistry => BinarySerHelper::ChecksumRegistry, Key::BidAddr(bid_addr) => BinarySerHelper::BidAddr(bid_addr), - Key::MessageTopic(message_addr) => BinarySerHelper::MessageTopic(message_addr), + Key::Message(message_addr) => BinarySerHelper::Message(message_addr), } } } @@ -1172,7 +1168,7 @@ mod serde_helpers { BinaryDeserHelper::ChainspecRegistry => Key::ChainspecRegistry, BinaryDeserHelper::ChecksumRegistry => Key::ChecksumRegistry, BinaryDeserHelper::BidAddr(bid_addr) => Key::BidAddr(bid_addr), - BinaryDeserHelper::MessageTopic(message_addr) => Key::MessageTopic(message_addr), + BinaryDeserHelper::Message(message_addr) => Key::Message(message_addr), } } } @@ -1231,7 +1227,8 @@ mod tests { const UNBOND_KEY: Key = Key::Unbond(AccountHash::new([42; 32])); const CHAINSPEC_REGISTRY_KEY: Key = Key::ChainspecRegistry; const CHECKSUM_REGISTRY_KEY: Key = Key::ChecksumRegistry; - const MESSAGE_TOPIC_KEY: Key = Key::MessageTopic(MessageTopicAddr::new([42; 32], [42; 32])); + const MESSAGE_TOPIC_KEY: Key = Key::Message(MessageAddr::new_topic_addr([42; 32], [42; 32])); + const MESSAGE_KEY: Key = Key::Message(MessageAddr::new_message_addr([42; 32], [2; 32], 9)); const KEYS: &[Key] = &[ ACCOUNT_KEY, HASH_KEY, @@ -1252,6 +1249,7 @@ mod tests { VALIDATOR_BID_KEY, DELEGATOR_BID_KEY, MESSAGE_TOPIC_KEY, + MESSAGE_KEY, ]; const HEX_STRING: &str = "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"; const UNIFIED_HEX_STRING: &str = @@ -1662,7 +1660,7 @@ mod tests { "{}", bid_addr_err ); - assert!(Key::from_formatted_str(MESSAGE_TOPIC_PREFIX) + assert!(Key::from_formatted_str(MESSAGE_PREFIX) .unwrap_err() .to_string() .starts_with("message-topic-key from string error: ")); @@ -1743,6 +1741,9 @@ mod tests { round_trip(&Key::Unbond(AccountHash::new(zeros))); round_trip(&Key::ChainspecRegistry); round_trip(&Key::ChecksumRegistry); - round_trip(&Key::MessageTopic(MessageTopicAddr::new(zeros, nines))) + round_trip(&Key::Message(MessageAddr::new_topic_addr(zeros, nines))); + round_trip(&Key::Message(MessageAddr::new_message_addr( + zeros, nines, 1, + ))); } } diff --git a/types/src/lib.rs b/types/src/lib.rs index 56e8e2f6ee..f53bc22143 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -105,9 +105,9 @@ pub use chainspec::{ ControlFlowCosts, CoreConfig, DelegatorConfig, DeployConfig, FeeHandling, GenesisAccount, GenesisValidator, GlobalStateUpdate, GlobalStateUpdateConfig, GlobalStateUpdateError, HandlePaymentCosts, HighwayConfig, HostFunction, HostFunctionCost, HostFunctionCosts, - LegacyRequiredFinality, MintCosts, NetworkConfig, OpcodeCosts, ProtocolConfig, RefundHandling, - StandardPaymentCosts, StorageCosts, SystemConfig, TransactionConfig, TransactionV1Config, - UpgradeConfig, ValidatorConfig, WasmConfig, + LegacyRequiredFinality, MessagesLimits, MessagesLimitsError, MintCosts, NetworkConfig, + OpcodeCosts, ProtocolConfig, RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, + TransactionConfig, TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, }; #[cfg(any(all(feature = "std", feature = "testing"), test))] pub use chainspec::{ diff --git a/types/src/stored_value.rs b/types/src/stored_value.rs index 2cc34a508d..9697c9a7fb 100644 --- a/types/src/stored_value.rs +++ b/types/src/stored_value.rs @@ -17,6 +17,7 @@ use serde_bytes::ByteBuf; use crate::{ account::Account, bytesrepr::{self, Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + contract_messages::{MessageSummary, MessageTopicSummary}, contracts::Contract, package::Package, system::auction::{Bid, BidKind, EraInfo, UnbondingPurse, WithdrawPurse}, @@ -40,6 +41,8 @@ enum Tag { Unbonding = 10, AddressableEntity = 11, BidKind = 12, + MessageTopic = 13, + Message = 14, } /// A value stored in Global State. @@ -78,6 +81,10 @@ pub enum StoredValue { AddressableEntity(AddressableEntity), /// Variant that stores [`BidKind`]. BidKind(BidKind), + /// Variant that stores a message topic. + MessageTopic(MessageTopicSummary), + /// Variant that stores a message digest. + Message(MessageSummary), } impl StoredValue { @@ -309,6 +316,8 @@ impl StoredValue { StoredValue::Unbonding(_) => "Unbonding".to_string(), StoredValue::AddressableEntity(_) => "AddressableEntity".to_string(), StoredValue::BidKind(_) => "BidKind".to_string(), + StoredValue::MessageTopic(_) => "MessageTopic".to_string(), + StoredValue::Message(_) => "Message".to_string(), } } @@ -327,6 +336,8 @@ impl StoredValue { StoredValue::Unbonding(_) => Tag::Unbonding, StoredValue::AddressableEntity(_) => Tag::AddressableEntity, StoredValue::BidKind(_) => Tag::BidKind, + StoredValue::MessageTopic(_) => Tag::MessageTopic, + StoredValue::Message(_) => Tag::Message, } } } @@ -544,6 +555,10 @@ impl ToBytes for StoredValue { StoredValue::Unbonding(unbonding_purses) => unbonding_purses.serialized_length(), StoredValue::AddressableEntity(entity) => entity.serialized_length(), StoredValue::BidKind(bid_kind) => bid_kind.serialized_length(), + StoredValue::MessageTopic(message_topic_summary) => { + message_topic_summary.serialized_length() + } + StoredValue::Message(message_digest) => message_digest.serialized_length(), } } @@ -565,6 +580,10 @@ impl ToBytes for StoredValue { StoredValue::Unbonding(unbonding_purses) => unbonding_purses.write_bytes(writer)?, StoredValue::AddressableEntity(entity) => entity.write_bytes(writer)?, StoredValue::BidKind(bid_kind) => bid_kind.write_bytes(writer)?, + StoredValue::MessageTopic(message_topic_summary) => { + message_topic_summary.write_bytes(writer)? + } + StoredValue::Message(message_digest) => message_digest.write_bytes(writer)?, }; Ok(()) } @@ -612,6 +631,15 @@ impl FromBytes for StoredValue { } tag if tag == Tag::AddressableEntity as u8 => AddressableEntity::from_bytes(remainder) .map(|(entity, remainder)| (StoredValue::AddressableEntity(entity), remainder)), + tag if tag == Tag::MessageTopic as u8 => MessageTopicSummary::from_bytes(remainder) + .map(|(message_summary, remainder)| { + (StoredValue::MessageTopic(message_summary), remainder) + }), + tag if tag == Tag::Message as u8 => { + MessageSummary::from_bytes(remainder).map(|(message_digest, remainder)| { + (StoredValue::Message(message_digest), remainder) + }) + } _ => Err(Error::Formatting), } } @@ -648,6 +676,9 @@ mod serde_helpers { AddressableEntity(&'a AddressableEntity), /// Variant that stores [`BidKind`]. BidKind(&'a BidKind), + /// Variant that stores [`MessageSummary`]. + MessageTopic(&'a MessageTopicSummary), + Message(&'a MessageSummary), } #[derive(Deserialize)] @@ -678,6 +709,10 @@ mod serde_helpers { AddressableEntity(AddressableEntity), /// Variant that stores [`BidKind`]. BidKind(BidKind), + /// Variant that stores [`MessageSummary`]. + MessageTopic(MessageTopicSummary), + /// Variant that stores [`MessageDigest`]. + Message(MessageSummary), } impl<'a> From<&'a StoredValue> for BinarySerHelper<'a> { @@ -698,6 +733,10 @@ mod serde_helpers { BinarySerHelper::AddressableEntity(payload) } StoredValue::BidKind(payload) => BinarySerHelper::BidKind(payload), + StoredValue::MessageTopic(message_topic_summary) => { + BinarySerHelper::MessageTopic(message_topic_summary) + } + StoredValue::Message(message_digest) => BinarySerHelper::Message(message_digest), } } } @@ -722,6 +761,10 @@ mod serde_helpers { StoredValue::AddressableEntity(payload) } BinaryDeserHelper::BidKind(payload) => StoredValue::BidKind(payload), + BinaryDeserHelper::MessageTopic(message_topic_summary) => { + StoredValue::MessageTopic(message_topic_summary) + } + BinaryDeserHelper::Message(message_digest) => StoredValue::Message(message_digest), } } } From 94f14da7127883e1b7ab452ca2f48733ff753847 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Wed, 4 Oct 2023 15:35:25 +0000 Subject: [PATCH 03/27] ee: prune old contract messages from GS with every new execution in a new block Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/mod.rs | 20 +- .../tests/src/test/contract_messages.rs | 240 +++++++++++++----- resources/test/rpc_schema.json | 24 +- resources/test/sse_data_schema.json | 75 ++++++ types/src/block_time.rs | 10 +- types/src/contract_messages.rs | 31 ++- types/src/gens.rs | 11 +- 7 files changed, 319 insertions(+), 92 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 930644ec37..e39368988a 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -3243,7 +3243,8 @@ where return Ok(Err(ApiError::MessageTopicAlreadyRegistered)); } - let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0)); + let summary = + StoredValue::MessageTopic(MessageTopicSummary::new(0, self.context.get_blocktime())); self.context.metered_write_gs_unsafe(topic_key, summary)?; Ok(Ok(())) @@ -3272,10 +3273,21 @@ where return Ok(Err(ApiError::MessageTopicNotRegistered)); }; - let message_index = current_topic_summary.message_count(); + let current_blocktime = self.context.get_blocktime(); + let message_index = if current_topic_summary.blocktime() != current_blocktime { + for index in 1..current_topic_summary.message_count() { + self.context + .prune_gs_unsafe(Key::message(entity_addr, topic_digest, index)); + } + 0 + } else { + current_topic_summary.message_count() + }; - let new_topic_summary = - StoredValue::MessageTopic(MessageTopicSummary::new(message_index + 1)); + let new_topic_summary = StoredValue::MessageTopic(MessageTopicSummary::new( + message_index + 1, + current_blocktime, + )); let message_key = Key::message(entity_addr, topic_digest, message_index); diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 3a100030eb..7d8a017bb6 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -1,11 +1,13 @@ +use std::cell::RefCell; + use casper_engine_test_support::{ - ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_BLOCK_TIME, PRODUCTION_RUN_GENESIS_REQUEST, }; use casper_types::{ bytesrepr::ToBytes, - contract_messages::{MessagePayload, MessageTopicHash, MessageTopicSummary}, - crypto, runtime_args, ContractHash, Key, RuntimeArgs, StoredValue, + contract_messages::{MessagePayload, MessageSummary, MessageTopicHash, MessageTopicSummary}, + crypto, runtime_args, ContractHash, Digest, Key, RuntimeArgs, StoredValue, }; const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; @@ -16,36 +18,9 @@ const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; const EMITTER_MESSAGE_PREFIX: &str = "generic message: "; -fn query_message_topic( - builder: &mut LmdbWasmTestBuilder, - contract_hash: &ContractHash, - message_topic_hash: MessageTopicHash, -) -> MessageTopicSummary { - let query_result = builder - .query( - None, - Key::message_topic(contract_hash.value(), message_topic_hash), - &[], - ) - .expect("should query"); - - match query_result { - StoredValue::MessageTopic(summary) => summary, - _ => { - panic!( - "Stored value is not a message topic summary: {:?}", - query_result - ); - } - } -} - -#[ignore] -#[test] -fn should_emit_messages() { - let mut builder = LmdbWasmTestBuilder::default(); - builder.run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); - +fn install_messages_emitter_contract<'a>( + builder: &'a RefCell, +) -> (ContractHash, [u8; 32]) { // Request to install the contract that will be emitting messages. let install_request = ExecuteRequestBuilder::standard( *DEFAULT_ACCOUNT_ADDR, @@ -56,10 +31,15 @@ fn should_emit_messages() { // Execute the request to install the message emitting contract. // This will also register a topic for the contract to emit messages on. - builder.exec(install_request).expect_success().commit(); + builder + .borrow_mut() + .exec(install_request) + .expect_success() + .commit(); // Get the contract package for the messages_emitter. let query_result = builder + .borrow_mut() .query( None, Key::from(*DEFAULT_ACCOUNT_ADDR), @@ -80,58 +60,180 @@ fn should_emit_messages() { .last() .expect("Should have contract hash"); - // Check that the topic exists for the installed contract. - let message_topic_hash = crypto::blake2b(MESSAGE_EMITTER_GENERIC_TOPIC); - assert_eq!( - query_message_topic( - &mut builder, - message_emitter_contract_hash, - message_topic_hash - ) - .message_count(), - 0 - ); + ( + *message_emitter_contract_hash, + crypto::blake2b(MESSAGE_EMITTER_GENERIC_TOPIC), + ) +} - // Now call the entry point to emit some messages. +fn emit_message_with_suffix<'a>( + builder: &'a RefCell, + suffix: &str, + contract_hash: &ContractHash, + block_time: u64, +) { let emit_message_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - *message_emitter_contract_hash, + *contract_hash, ENTRY_POINT_EMIT_MESSAGE, runtime_args! { - ARG_MESSAGE_SUFFIX_NAME => "test", + ARG_MESSAGE_SUFFIX_NAME => suffix, }, ) + .with_block_time(block_time) .build(); - builder.exec(emit_message_request).expect_success().commit(); + builder + .borrow_mut() + .exec(emit_message_request) + .expect_success() + .commit(); +} - let query_result = builder - .query( - None, - Key::message(message_emitter_contract_hash.value(), message_topic_hash, 0), +struct MessageEmitterQueryView<'a> { + builder: &'a RefCell, + contract_hash: ContractHash, + message_topic_hash: MessageTopicHash, +} + +impl<'a> MessageEmitterQueryView<'a> { + fn new( + builder: &'a RefCell, + contract_hash: ContractHash, + message_topic_hash: MessageTopicHash, + ) -> Self { + Self { + builder, + contract_hash, + message_topic_hash, + } + } + + fn message_topic(&self) -> MessageTopicSummary { + let query_result = self + .builder + .borrow_mut() + .query( + None, + Key::message_topic(self.contract_hash.value(), self.message_topic_hash), + &[], + ) + .expect("should query"); + + match query_result { + StoredValue::MessageTopic(summary) => summary, + _ => { + panic!( + "Stored value is not a message topic summary: {:?}", + query_result + ); + } + } + } + + fn message_summary( + &self, + message_index: u32, + state_hash: Option, + ) -> Result { + let query_result = self.builder.borrow_mut().query( + state_hash, + Key::message( + self.contract_hash.value(), + self.message_topic_hash, + message_index, + ), &[], - ) - .expect("should query"); + )?; - let queried_message_summary = if let StoredValue::Message(summary) = query_result { - summary.value() - } else { - panic!("Stored value is not a message summary: {:?}", query_result); - }; + match query_result { + StoredValue::Message(summary) => Ok(summary), + _ => panic!("Stored value is not a message summary: {:?}", query_result), + } + } +} + +#[ignore] +#[test] +fn should_emit_messages() { + let builder = RefCell::new(LmdbWasmTestBuilder::default()); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let (contract_hash, message_topic_hash) = install_messages_emitter_contract(&builder); + let query_view = MessageEmitterQueryView::new(&builder, contract_hash, message_topic_hash); + // Check that the topic exists for the installed contract. + assert_eq!(query_view.message_topic().message_count(), 0); + + // Now call the entry point to emit some messages. + emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); let expected_message = MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test")); let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); + let queried_message_summary = query_view + .message_summary(0, None) + .expect("should have value") + .value(); + assert_eq!(expected_message_hash, queried_message_summary); + assert_eq!(query_view.message_topic().message_count(), 1); + + // call again to emit a new message and check that the index in the topic incremented. + emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); + let queried_message_summary = query_view + .message_summary(1, None) + .expect("should have value") + .value(); + assert_eq!(expected_message_hash, queried_message_summary); + assert_eq!(query_view.message_topic().message_count(), 2); + let first_block_state_hash = builder.borrow().get_post_state_hash(); + + // call to emit a new message but in another block. + emit_message_with_suffix( + &builder, + "new block time", + &contract_hash, + DEFAULT_BLOCK_TIME + 1, + ); + let expected_message = + MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "new block time")); + let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); + let queried_message_summary = query_view + .message_summary(0, None) + .expect("should have value") + .value(); assert_eq!(expected_message_hash, queried_message_summary); + assert_eq!(query_view.message_topic().message_count(), 1); - assert_eq!( - query_message_topic( - &mut builder, - message_emitter_contract_hash, - message_topic_hash - ) - .message_count(), - 1 - ) + // old messages should be pruned from tip and inaccessible at the latest state hash. + assert!(query_view.message_summary(1, None).is_err()); + + // old messages should still be discoverable at a state hash before pruning. + assert!(query_view + .message_summary(1, Some(first_block_state_hash)) + .is_ok()); +} + +#[ignore] +#[test] +fn should_emit_message_on_empty_topic_in_new_block() { + let builder = RefCell::new(LmdbWasmTestBuilder::default()); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let (contract_hash, message_topic_hash) = install_messages_emitter_contract(&builder); + + let query_view = MessageEmitterQueryView::new(&builder, contract_hash, message_topic_hash); + assert_eq!(query_view.message_topic().message_count(), 0); + + emit_message_with_suffix( + &builder, + "new block time", + &contract_hash, + DEFAULT_BLOCK_TIME + 1, + ); + assert_eq!(query_view.message_topic().message_count(), 1); } diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index 48b73ffc85..ad8f788f00 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -3689,14 +3689,7 @@ "properties": { "entity_addr": { "description": "The identity of the entity that produced the message.", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "maxItems": 32, - "minItems": 32 + "type": "string" }, "message": { "description": "Message payload", @@ -4707,6 +4700,7 @@ "description": "Summary of a message topic that will be stored in global state.", "type": "object", "required": [ + "blocktime", "message_count" ], "properties": { @@ -4715,9 +4709,23 @@ "type": "integer", "format": "uint32", "minimum": 0.0 + }, + "blocktime": { + "description": "Block timestamp in which these messages were emitted.", + "allOf": [ + { + "$ref": "#/components/schemas/BlockTime" + } + ] } } }, + "BlockTime": { + "description": "A newtype wrapping a [`u64`] which represents the block time.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "MessageSummary": { "description": "Message summary as a formatted string.", "type": "string" diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 655da2932f..099d13bba5 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -2450,6 +2450,7 @@ "cost", "effects", "error_message", + "messages", "transfers" ], "properties": { @@ -2479,6 +2480,13 @@ "error_message": { "description": "The error message associated with executing the deploy.", "type": "string" + }, + "messages": { + "description": "Messages that were emitted during execution.", + "type": "array", + "items": { + "$ref": "#/definitions/Message" + } } }, "additionalProperties": false @@ -2498,6 +2506,7 @@ "required": [ "cost", "effects", + "messages", "transfers" ], "properties": { @@ -2523,6 +2532,13 @@ "$ref": "#/definitions/U512" } ] + }, + "messages": { + "description": "Messages that were emitted during execution.", + "type": "array", + "items": { + "$ref": "#/definitions/Message" + } } }, "additionalProperties": false @@ -2539,6 +2555,65 @@ "$ref": "#/definitions/Transform" } }, + "Message": { + "description": "Message that was emitted by an addressable entity during execution.", + "type": "object", + "required": [ + "entity_addr", + "index", + "message", + "topic" + ], + "properties": { + "entity_addr": { + "description": "The identity of the entity that produced the message.", + "type": "string" + }, + "message": { + "description": "Message payload", + "allOf": [ + { + "$ref": "#/definitions/MessagePayload" + } + ] + }, + "topic": { + "description": "Topic name", + "type": "string" + }, + "index": { + "description": "Message index in the topic", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "MessagePayload": { + "description": "The payload of the message emitted by an addressable entity during execution.", + "oneOf": [ + { + "description": "Empty message.", + "type": "string", + "enum": [ + "Empty" + ] + }, + { + "description": "Human readable string message.", + "type": "object", + "required": [ + "String" + ], + "properties": { + "String": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, "FinalitySignature": { "description": "A validator's signature of a block, confirming it is finalized.", "type": "object", diff --git a/types/src/block_time.rs b/types/src/block_time.rs index 4122f7ca94..f278a36b87 100644 --- a/types/src/block_time.rs +++ b/types/src/block_time.rs @@ -2,11 +2,19 @@ use alloc::vec::Vec; use crate::bytesrepr::{Error, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + /// The number of bytes in a serialized [`BlockTime`]. pub const BLOCKTIME_SERIALIZED_LENGTH: usize = U64_SERIALIZED_LENGTH; /// A newtype wrapping a [`u64`] which represents the block time. -#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] pub struct BlockTime(u64); impl BlockTime { diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index 0106b16afb..2978ee0259 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -2,7 +2,7 @@ use crate::{ alloc::string::ToString, bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - checksummed_hex, HashAddr, KEY_HASH_LENGTH, + checksummed_hex, BlockTime, HashAddr, KEY_HASH_LENGTH, }; use core::convert::TryFrom; @@ -269,36 +269,54 @@ impl Distribution for Standard { pub struct MessageTopicSummary { /// Number of messages in this topic. pub(crate) message_count: u32, + /// Block timestamp in which these messages were emitted. + pub(crate) blocktime: BlockTime, } impl MessageTopicSummary { /// Creates a new topic summary. - pub fn new(message_count: u32) -> Self { - Self { message_count } + pub fn new(message_count: u32, blocktime: BlockTime) -> Self { + Self { + message_count, + blocktime, + } } /// Returns the number of messages that were sent on this topic. pub fn message_count(&self) -> u32 { self.message_count } + + /// Returns the block time. + pub fn blocktime(&self) -> BlockTime { + self.blocktime + } } impl ToBytes for MessageTopicSummary { fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut buffer = bytesrepr::allocate_buffer(self)?; buffer.append(&mut self.message_count.to_bytes()?); + buffer.append(&mut self.blocktime.to_bytes()?); Ok(buffer) } fn serialized_length(&self) -> usize { - self.message_count.serialized_length() + self.message_count.serialized_length() + self.blocktime.serialized_length() } } impl FromBytes for MessageTopicSummary { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (message_count, rem) = FromBytes::from_bytes(bytes)?; - Ok((MessageTopicSummary { message_count }, rem)) + let (blocktime, rem) = FromBytes::from_bytes(rem)?; + Ok(( + MessageTopicSummary { + message_count, + blocktime, + }, + rem, + )) } } @@ -371,6 +389,7 @@ impl FromBytes for MessagePayload { #[cfg_attr(feature = "json-schema", derive(JsonSchema))] pub struct Message { /// The identity of the entity that produced the message. + #[cfg_attr(feature = "json-schema", schemars(with = "String"))] entity_addr: HashAddr, /// Message payload message: MessagePayload, @@ -482,7 +501,7 @@ mod tests { MessageAddr::new_topic_addr([1; KEY_HASH_LENGTH], [2; MESSAGE_TOPIC_HASH_LENGTH]); bytesrepr::test_serialization_roundtrip(&message_addr); - let topic_summary = MessageTopicSummary::new(100); + let topic_summary = MessageTopicSummary::new(100, BlockTime::new(1000)); bytesrepr::test_serialization_roundtrip(&topic_summary); let string_message_payload = diff --git a/types/src/gens.rs b/types/src/gens.rs index 84a0c2d7f7..e96e3e5f74 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -23,9 +23,9 @@ use crate::{ DELEGATION_RATE_DENOMINATOR, }, transfer::TransferAddr, - AccessRights, AddressableEntity, CLType, CLValue, ContractHash, ContractWasm, EntryPoint, - EntryPointAccess, EntryPointType, EntryPoints, EraId, Group, Key, NamedArg, Package, Parameter, - Phase, ProtocolVersion, SemVer, StoredValue, URef, U128, U256, U512, + AccessRights, AddressableEntity, BlockTime, CLType, CLValue, ContractHash, ContractWasm, + EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, EraId, Group, Key, NamedArg, + Package, Parameter, Phase, ProtocolVersion, SemVer, StoredValue, URef, U128, U256, U512, }; use crate::{ @@ -622,7 +622,10 @@ fn unbondings_arb(size: impl Into) -> impl Strategy impl Strategy { - any::().prop_map(|message_count| MessageTopicSummary { message_count }) + (any::(), any::()).prop_map(|(message_count, blocktime)| MessageTopicSummary { + message_count, + blocktime: BlockTime::new(blocktime), + }) } fn message_summary_arb() -> impl Strategy { From 5a1f0ec808678c97e0b4a125e9a7865f07e3accb Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Thu, 5 Oct 2023 16:20:29 +0000 Subject: [PATCH 04/27] ee: add message topics to the addressable entity record Add the message topic names along with the hashes of their names to the addressable entity to enable easy discovery. Also migrate topics to new contract on upgrade. Signed-off-by: Alexandru Sardan --- execution_engine/src/engine_state/genesis.rs | 3 +- execution_engine/src/engine_state/mod.rs | 4 +- execution_engine/src/engine_state/upgrade.rs | 4 +- execution_engine/src/execution/error.rs | 3 - execution_engine/src/runtime/externals.rs | 31 +- execution_engine/src/runtime/mod.rs | 81 ++--- execution_engine/src/runtime_context/mod.rs | 46 ++- execution_engine/src/runtime_context/tests.rs | 6 +- execution_engine/src/tracking_copy/ext.rs | 2 + execution_engine/src/tracking_copy/tests.rs | 16 +- .../tests/src/test/contract_messages.rs | 293 +++++++++++++++--- resources/local/chainspec.toml.in | 1 + resources/production/chainspec.toml | 1 + resources/test/rpc_schema.json | 32 ++ .../contract-messages-emitter/src/main.rs | 23 +- types/benches/bytesrepr_bench.rs | 5 +- types/src/addressable_entity.rs | 137 +++++++- types/src/api_error.rs | 46 ++- .../chainspec/vm_config/messages_limits.rs | 41 ++- types/src/gens.rs | 27 +- .../src/generic/state_tracker.rs | 3 +- .../src/generic/testing.rs | 3 +- utils/validation/src/generators.rs | 5 +- 23 files changed, 675 insertions(+), 138 deletions(-) diff --git a/execution_engine/src/engine_state/genesis.rs b/execution_engine/src/engine_state/genesis.rs index d51dc48271..b067b6d536 100644 --- a/execution_engine/src/engine_state/genesis.rs +++ b/execution_engine/src/engine_state/genesis.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; use casper_storage::global_state::state::StateProvider; use casper_types::{ - addressable_entity::{ActionThresholds, NamedKeys}, + addressable_entity::{ActionThresholds, MessageTopics, NamedKeys}, execution::Effects, package::{ContractPackageKind, ContractPackageStatus, ContractVersions, Groups}, system::{ @@ -1166,6 +1166,7 @@ where main_purse, associated_keys, ActionThresholds::default(), + MessageTopics::default(), ); let access_key = self diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index a0cd0e0ad1..006456da0f 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -45,7 +45,7 @@ use casper_storage::{ }; use casper_types::{ account::{Account, AccountHash}, - addressable_entity::{AssociatedKeys, NamedKeys}, + addressable_entity::{AssociatedKeys, MessageTopics, NamedKeys}, bytesrepr::ToBytes, execution::Effects, package::{ContractPackageKind, ContractPackageStatus, ContractVersions, Groups}, @@ -875,6 +875,7 @@ where account.main_purse(), associated_keys, account.action_thresholds().clone().into(), + MessageTopics::default(), ); let access_key = generator.new_uref(AccessRights::READ_ADD_WRITE); @@ -2806,7 +2807,6 @@ fn should_charge_for_errors_in_wasm(execution_result: &ExecutionResult) -> bool | ExecError::UnexpectedKeyVariant(_) | ExecError::InvalidContractPackageKind(_) | ExecError::Transform(_) - | ExecError::FailedTopicRegistration(_) | ExecError::CannotEmitMessage(_) | ExecError::InvalidMessageTopicOperation => false, ExecError::DisabledUnrestrictedTransfers => false, diff --git a/execution_engine/src/engine_state/upgrade.rs b/execution_engine/src/engine_state/upgrade.rs index 4f09dd678e..c1f5f312e7 100644 --- a/execution_engine/src/engine_state/upgrade.rs +++ b/execution_engine/src/engine_state/upgrade.rs @@ -5,7 +5,7 @@ use thiserror::Error; use casper_storage::global_state::state::StateProvider; use casper_types::{ - addressable_entity::{ActionThresholds, AssociatedKeys, NamedKeys, Weight}, + addressable_entity::{ActionThresholds, AssociatedKeys, MessageTopics, NamedKeys, Weight}, bytesrepr::{self, ToBytes}, execution::Effects, package::{ContractPackageKind, ContractPackageStatus, ContractVersions, Groups}, @@ -183,6 +183,7 @@ where URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), ); self.tracking_copy.borrow_mut().write( contract_hash.into(), @@ -268,6 +269,7 @@ where main_purse, associated_keys, ActionThresholds::default(), + MessageTopics::default(), ); let access_key = address_generator.new_uref(AccessRights::READ_ADD_WRITE); diff --git a/execution_engine/src/execution/error.rs b/execution_engine/src/execution/error.rs index d1aba5d0eb..316ad952b7 100644 --- a/execution_engine/src/execution/error.rs +++ b/execution_engine/src/execution/error.rs @@ -191,9 +191,6 @@ pub enum Error { /// Failed to transfer tokens on a private chain. #[error("Failed to transfer with unrestricted transfers disabled")] DisabledUnrestrictedTransfers, - /// Failed to register a message topic due to config limits. - #[error("Failed to register a message topic: {0}")] - FailedTopicRegistration(MessagesLimitsError), /// Message was not emitted. #[error("Failed to emit a message on topic: {0}")] CannotEmitMessage(MessagesLimitsError), diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index be28f973b1..6cfbf9e5e7 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -1124,12 +1124,14 @@ where ], )?; - self.context - .engine_config() - .wasm_config() - .messages_limits() - .topic_name_size_within_limits(topic_name_size) - .map_err(|e| Trap::from(Error::FailedTopicRegistration(e)))?; + let limits = self.context.engine_config().wasm_config().messages_limits(); + + if topic_name_size > limits.max_topic_name_size() { + return Ok(Some(RuntimeValue::I32(api_error::i32_from(Err( + ApiError::MaxTopicNameSizeExceeded, + ))))); + } + let topic_name = self.string_from_mem(topic_name_ptr, topic_name_size)?; if operation_size as usize > MessageTopicOperation::max_serialized_len() { @@ -1145,7 +1147,9 @@ where } let result = match topic_operation { - MessageTopicOperation::Add => self.register_message_topic(topic_name)?, + MessageTopicOperation::Add => { + self.add_message_topic(topic_name).map_err(Trap::from)? + } }; Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) @@ -1162,12 +1166,13 @@ where [topic_name_ptr, topic_name_size, message_ptr, message_size], )?; - self.context - .engine_config() - .wasm_config() - .messages_limits() - .topic_name_size_within_limits(topic_name_size) - .map_err(|e| Trap::from(Error::CannotEmitMessage(e)))?; + let limits = self.context.engine_config().wasm_config().messages_limits(); + + if topic_name_size > limits.max_topic_name_size() { + return Ok(Some(RuntimeValue::I32(api_error::i32_from(Err( + ApiError::MaxTopicNameSizeExceeded, + ))))); + } self.context .engine_config() diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index e39368988a..ffc61e763d 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -26,8 +26,8 @@ use casper_types::{ account::{Account, AccountHash}, addressable_entity::{ self, ActionThresholds, ActionType, AddKeyFailure, AddressableEntity, AssociatedKeys, - ContractHash, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys, - Parameter, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure, Weight, + ContractHash, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, MessageTopics, + NamedKeys, Parameter, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure, Weight, DEFAULT_ENTRY_POINT_NAME, }, bytesrepr::{self, Bytes, FromBytes, ToBytes}, @@ -1724,25 +1724,27 @@ where named_keys.append(previous_named_keys); } - let (main_purse, associated_keys, action_thresholds) = match package.current_contract_hash() - { - Some(previous_contract_hash) => { - let previous_contract: AddressableEntity = - self.context.read_gs_typed(&previous_contract_hash.into())?; - let previous_named_keys = previous_contract.named_keys().clone(); - named_keys.append(previous_named_keys); - ( - previous_contract.main_purse(), - previous_contract.associated_keys().clone(), - previous_contract.action_thresholds().clone(), - ) - } - None => ( - self.create_purse()?, - AssociatedKeys::default(), - ActionThresholds::default(), - ), - }; + let (main_purse, associated_keys, action_thresholds, message_topics) = + match package.current_contract_hash() { + Some(previous_contract_hash) => { + let previous_contract: AddressableEntity = + self.context.read_gs_typed(&previous_contract_hash.into())?; + let previous_named_keys = previous_contract.named_keys().clone(); + named_keys.append(previous_named_keys); + ( + previous_contract.main_purse(), + previous_contract.associated_keys().clone(), + previous_contract.action_thresholds().clone(), + previous_contract.message_topics().clone(), + ) + } + None => ( + self.create_purse()?, + AssociatedKeys::default(), + ActionThresholds::default(), + MessageTopics::default(), + ), + }; let entity = AddressableEntity::new( package_hash, @@ -1753,6 +1755,7 @@ where main_purse, associated_keys, action_thresholds, + message_topics.clone(), ); let insert_contract_result = package.insert_contract_version(major, entity_hash.into()); @@ -1764,6 +1767,15 @@ where self.context .metered_write_gs_unsafe(package_hash, package)?; + for (_, topic_hash) in message_topics.iter() { + let topic_key = Key::message_topic(entity_hash, *topic_hash); + let summary = StoredValue::MessageTopic(MessageTopicSummary::new( + 0, + self.context.get_blocktime(), + )); + self.context.metered_write_gs_unsafe(topic_key, summary)?; + } + // return contract key to caller { let key_bytes = match entity_hash.to_bytes() { @@ -2330,6 +2342,7 @@ where let associated_keys = AssociatedKeys::new(target, Weight::new(1)); let named_keys = NamedKeys::new(); let entry_points = EntryPoints::new(); + let message_topics = MessageTopics::default(); let contract = AddressableEntity::new( contract_package_hash, @@ -2340,6 +2353,7 @@ where main_purse, associated_keys, ActionThresholds::default(), + message_topics, ); let access_key = self.context.new_unit_uref()?; @@ -3218,6 +3232,7 @@ where entity_main_purse, AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), ); self.context.metered_write_gs(contract_key, updated_entity) @@ -3228,26 +3243,12 @@ where } } - fn register_message_topic(&mut self, topic_name: String) -> Result, Trap> { - let entity_addr = self - .context - .get_entity_address() - .into_hash() - .ok_or(Error::InvalidContext)?; - - let topic_digest: [u8; 32] = crypto::blake2b(topic_name); - let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_digest)); - - // Check if topic is already registered. - if self.context.read_gs(&topic_key)?.is_some() { - return Ok(Err(ApiError::MessageTopicAlreadyRegistered)); - } + fn add_message_topic(&mut self, topic_name: String) -> Result, Error> { + let topic_hash: [u8; 32] = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); - let summary = - StoredValue::MessageTopic(MessageTopicSummary::new(0, self.context.get_blocktime())); - self.context.metered_write_gs_unsafe(topic_key, summary)?; - - Ok(Ok(())) + self.context + .add_message_topic(topic_name, topic_hash) + .map(|ret| ret.map_err(ApiError::from)) } fn emit_message( diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index f55c616240..ebc7818250 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -18,11 +18,11 @@ use casper_storage::global_state::state::StateReader; use casper_types::{ account::{Account, AccountHash}, addressable_entity::{ - ActionType, AddKeyFailure, NamedKeys, RemoveKeyFailure, SetThresholdFailure, - UpdateKeyFailure, Weight, + ActionType, AddKeyFailure, MessageTopicError, NamedKeys, RemoveKeyFailure, + SetThresholdFailure, UpdateKeyFailure, Weight, }, bytesrepr::ToBytes, - contract_messages::Message, + contract_messages::{Message, MessageAddr, MessageTopicHash, MessageTopicSummary}, execution::Effects, package::ContractPackageKind, system::auction::{BidKind, EraInfo}, @@ -1385,4 +1385,44 @@ where pub(crate) fn set_remaining_spending_limit(&mut self, amount: U512) { self.remaining_spending_limit = amount; } + + /// Adds new message topic. + pub(crate) fn add_message_topic( + &mut self, + topic_name: String, + topic_hash: MessageTopicHash, + ) -> Result, Error> { + let entity_key: Key = self.get_entity_address(); + let entity_addr = entity_key.into_hash().ok_or(Error::InvalidContext)?; + + // Take the addressable entity out of the global state + let entity = { + let mut entity: AddressableEntity = self.read_gs_typed(&entity_key)?; + + let max_topics_per_contract = self + .engine_config + .wasm_config() + .messages_limits() + .max_topics_per_contract(); + + if entity.message_topics().len() >= max_topics_per_contract as usize { + return Ok(Err(MessageTopicError::MaxTopicsExceeded)); + } + + if let Err(e) = entity.add_message_topic(topic_name, topic_hash) { + return Ok(Err(e)); + } + entity + }; + + let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_hash)); + let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, self.get_blocktime())); + + let entity_value = self.addressable_entity_to_validated_value(entity)?; + + self.metered_write_gs_unsafe(entity_key, entity_value)?; + self.metered_write_gs_unsafe(topic_key, summary)?; + + Ok(Ok(())) + } } diff --git a/execution_engine/src/runtime_context/tests.rs b/execution_engine/src/runtime_context/tests.rs index b1214374d3..5f7fffe453 100644 --- a/execution_engine/src/runtime_context/tests.rs +++ b/execution_engine/src/runtime_context/tests.rs @@ -13,8 +13,8 @@ use casper_storage::global_state::state::{self, lmdb::LmdbGlobalStateView, State use casper_types::{ account::{AccountHash, ACCOUNT_HASH_LENGTH}, addressable_entity::{ - ActionThresholds, ActionType, AddKeyFailure, AssociatedKeys, NamedKeys, RemoveKeyFailure, - SetThresholdFailure, Weight, + ActionThresholds, ActionType, AddKeyFailure, AssociatedKeys, MessageTopics, NamedKeys, + RemoveKeyFailure, SetThresholdFailure, Weight, }, bytesrepr::ToBytes, execution::TransformKind, @@ -83,6 +83,7 @@ fn new_addressable_entity_with_purse( URef::new(purse, AccessRights::READ_ADD_WRITE), associated_keys, Default::default(), + MessageTopics::default(), ); let account_key = Key::Account(account_hash); let contract_key = contract_hash.into(); @@ -447,6 +448,7 @@ fn contract_key_addable_valid() { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); let read_contract = runtime_context.read_gs(&contract_key).unwrap(); diff --git a/execution_engine/src/tracking_copy/ext.rs b/execution_engine/src/tracking_copy/ext.rs index 963498c50d..87970c8f90 100644 --- a/execution_engine/src/tracking_copy/ext.rs +++ b/execution_engine/src/tracking_copy/ext.rs @@ -3,6 +3,7 @@ use std::{collections::BTreeSet, convert::TryInto}; use casper_storage::global_state::{state::StateReader, trie::merkle_proof::TrieMerkleProof}; use casper_types::{ account::AccountHash, + addressable_entity::MessageTopics, package::{ContractPackageKind, ContractPackageStatus, ContractVersions, Groups}, AccessRights, AddressableEntity, CLValue, ContractHash, ContractPackageHash, ContractWasm, ContractWasmHash, EntryPoints, Key, Motes, Package, Phase, ProtocolVersion, StoredValue, @@ -146,6 +147,7 @@ where account.main_purse(), account.associated_keys().clone().into(), account.action_thresholds().clone().into(), + MessageTopics::default(), ); let access_key = generator.new_uref(AccessRights::READ_ADD_WRITE); diff --git a/execution_engine/src/tracking_copy/tests.rs b/execution_engine/src/tracking_copy/tests.rs index e249f632d0..f09c7be68a 100644 --- a/execution_engine/src/tracking_copy/tests.rs +++ b/execution_engine/src/tracking_copy/tests.rs @@ -9,7 +9,9 @@ use casper_storage::global_state::{ }; use casper_types::{ account::{AccountHash, ACCOUNT_HASH_LENGTH}, - addressable_entity::{ActionThresholds, AssociatedKeys, ContractHash, NamedKeys, Weight}, + addressable_entity::{ + ActionThresholds, AssociatedKeys, ContractHash, MessageTopics, NamedKeys, Weight, + }, execution::{Effects, Transform, TransformKind}, gens::*, package::ContractPackageHash, @@ -196,6 +198,7 @@ fn tracking_copy_add_named_key() { URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), associated_keys, Default::default(), + MessageTopics::default(), ); let db = CountingDb::new_init(StoredValue::AddressableEntity(contract)); @@ -355,6 +358,7 @@ proptest! { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); let contract_key = Key::Hash(hash); @@ -398,7 +402,8 @@ proptest! { ProtocolVersion::V1_0_0, purse, associated_keys, - ActionThresholds::default() + ActionThresholds::default(), + MessageTopics::default(), ); @@ -448,6 +453,7 @@ proptest! { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); let contract_key = Key::Hash(hash); @@ -551,6 +557,7 @@ fn query_for_circular_references_should_fail() { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); let (global_state, root_hash, _tempdir) = state::lmdb::make_temporary_global_state([ @@ -604,6 +611,7 @@ fn validate_query_proof_should_work() { fake_purse, AssociatedKeys::new(account_hash, Weight::new(1)), ActionThresholds::default(), + MessageTopics::default(), )); // create contract that refers to that account @@ -623,6 +631,7 @@ fn validate_query_proof_should_work() { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); let contract_key = Key::Hash([5; 32]); @@ -649,6 +658,7 @@ fn validate_query_proof_should_work() { fake_purse, AssociatedKeys::new(account_hash, Weight::new(1)), ActionThresholds::default(), + MessageTopics::default(), )); let main_account_value = StoredValue::CLValue(cl_value_2); @@ -1072,6 +1082,7 @@ fn query_with_large_depth_with_fixed_path_should_fail() { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); pairs.push((contract_key, contract)); contract_keys.push(contract_key); @@ -1134,6 +1145,7 @@ fn query_with_large_depth_with_urefs_should_fail() { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), )); let contract_key = Key::Hash([0; 32]); pairs.push((contract_key, contract)); diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 7d8a017bb6..e4b3623369 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -4,23 +4,27 @@ use casper_engine_test_support::{ ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_BLOCK_TIME, PRODUCTION_RUN_GENESIS_REQUEST, }; +use casper_execution_engine::engine_state::EngineConfigBuilder; use casper_types::{ bytesrepr::ToBytes, contract_messages::{MessagePayload, MessageSummary, MessageTopicHash, MessageTopicSummary}, - crypto, runtime_args, ContractHash, Digest, Key, RuntimeArgs, StoredValue, + crypto, runtime_args, AddressableEntity, ContractHash, Digest, Key, MessagesLimits, + RuntimeArgs, StoredValue, WasmConfig, }; const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; const MESSAGE_EMITTER_PACKAGE_NAME: &str = "messages_emitter_package_name"; const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +const ARG_TOPIC_NAME: &str = "topic_name"; +const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; const EMITTER_MESSAGE_PREFIX: &str = "generic message: "; fn install_messages_emitter_contract<'a>( builder: &'a RefCell, -) -> (ContractHash, [u8; 32]) { +) -> ContractHash { // Request to install the contract that will be emitting messages. let install_request = ExecuteRequestBuilder::standard( *DEFAULT_ACCOUNT_ADDR, @@ -54,16 +58,11 @@ fn install_messages_emitter_contract<'a>( }; // Get the contract hash of the messages_emitter contract. - let message_emitter_contract_hash = message_emitter_package + *message_emitter_package .versions() .contract_hashes() .last() - .expect("Should have contract hash"); - - ( - *message_emitter_contract_hash, - crypto::blake2b(MESSAGE_EMITTER_GENERIC_TOPIC), - ) + .expect("Should have contract hash") } fn emit_message_with_suffix<'a>( @@ -90,32 +89,45 @@ fn emit_message_with_suffix<'a>( .commit(); } -struct MessageEmitterQueryView<'a> { +struct ContractQueryView<'a> { builder: &'a RefCell, contract_hash: ContractHash, - message_topic_hash: MessageTopicHash, } -impl<'a> MessageEmitterQueryView<'a> { - fn new( - builder: &'a RefCell, - contract_hash: ContractHash, - message_topic_hash: MessageTopicHash, - ) -> Self { +impl<'a> ContractQueryView<'a> { + fn new(builder: &'a RefCell, contract_hash: ContractHash) -> Self { Self { builder, contract_hash, - message_topic_hash, } } - fn message_topic(&self) -> MessageTopicSummary { + fn entity(&self) -> AddressableEntity { + let query_result = self + .builder + .borrow_mut() + .query(None, Key::from(self.contract_hash), &[]) + .expect("should query"); + + let entity = if let StoredValue::AddressableEntity(entity) = query_result { + entity + } else { + panic!( + "Stored value is not an adressable entity: {:?}", + query_result + ); + }; + + entity + } + + fn message_topic(&self, message_topic_hash: MessageTopicHash) -> MessageTopicSummary { let query_result = self .builder .borrow_mut() .query( None, - Key::message_topic(self.contract_hash.value(), self.message_topic_hash), + Key::message_topic(self.contract_hash.value(), message_topic_hash), &[], ) .expect("should query"); @@ -133,6 +145,7 @@ impl<'a> MessageEmitterQueryView<'a> { fn message_summary( &self, + message_topic_hash: MessageTopicHash, message_index: u32, state_hash: Option, ) -> Result { @@ -140,7 +153,7 @@ impl<'a> MessageEmitterQueryView<'a> { state_hash, Key::message( self.contract_hash.value(), - self.message_topic_hash, + message_topic_hash, message_index, ), &[], @@ -161,11 +174,24 @@ fn should_emit_messages() { .borrow_mut() .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); - let (contract_hash, message_topic_hash) = install_messages_emitter_contract(&builder); - let query_view = MessageEmitterQueryView::new(&builder, contract_hash, message_topic_hash); + let contract_hash = install_messages_emitter_contract(&builder); + let query_view = ContractQueryView::new(&builder, contract_hash); + let entity = query_view.entity(); + + let (topic_name, message_topic_hash) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); + assert_eq!(topic_name, &MESSAGE_EMITTER_GENERIC_TOPIC.to_string()); // Check that the topic exists for the installed contract. - assert_eq!(query_view.message_topic().message_count(), 0); + assert_eq!( + query_view + .message_topic(*message_topic_hash) + .message_count(), + 0 + ); // Now call the entry point to emit some messages. emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); @@ -173,20 +199,30 @@ fn should_emit_messages() { MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test")); let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); let queried_message_summary = query_view - .message_summary(0, None) + .message_summary(*message_topic_hash, 0, None) .expect("should have value") .value(); assert_eq!(expected_message_hash, queried_message_summary); - assert_eq!(query_view.message_topic().message_count(), 1); + assert_eq!( + query_view + .message_topic(*message_topic_hash) + .message_count(), + 1 + ); // call again to emit a new message and check that the index in the topic incremented. emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); let queried_message_summary = query_view - .message_summary(1, None) + .message_summary(*message_topic_hash, 1, None) .expect("should have value") .value(); assert_eq!(expected_message_hash, queried_message_summary); - assert_eq!(query_view.message_topic().message_count(), 2); + assert_eq!( + query_view + .message_topic(*message_topic_hash) + .message_count(), + 2 + ); let first_block_state_hash = builder.borrow().get_post_state_hash(); @@ -201,18 +237,25 @@ fn should_emit_messages() { MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "new block time")); let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); let queried_message_summary = query_view - .message_summary(0, None) + .message_summary(*message_topic_hash, 0, None) .expect("should have value") .value(); assert_eq!(expected_message_hash, queried_message_summary); - assert_eq!(query_view.message_topic().message_count(), 1); + assert_eq!( + query_view + .message_topic(*message_topic_hash) + .message_count(), + 1 + ); // old messages should be pruned from tip and inaccessible at the latest state hash. - assert!(query_view.message_summary(1, None).is_err()); + assert!(query_view + .message_summary(*message_topic_hash, 1, None) + .is_err()); // old messages should still be discoverable at a state hash before pruning. assert!(query_view - .message_summary(1, Some(first_block_state_hash)) + .message_summary(*message_topic_hash, 1, Some(first_block_state_hash)) .is_ok()); } @@ -224,10 +267,22 @@ fn should_emit_message_on_empty_topic_in_new_block() { .borrow_mut() .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); - let (contract_hash, message_topic_hash) = install_messages_emitter_contract(&builder); + let contract_hash = install_messages_emitter_contract(&builder); + let query_view = ContractQueryView::new(&builder, contract_hash); + let entity = query_view.entity(); - let query_view = MessageEmitterQueryView::new(&builder, contract_hash, message_topic_hash); - assert_eq!(query_view.message_topic().message_count(), 0); + let (_, message_topic_hash) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); + + assert_eq!( + query_view + .message_topic(*message_topic_hash) + .message_count(), + 0 + ); emit_message_with_suffix( &builder, @@ -235,5 +290,171 @@ fn should_emit_message_on_empty_topic_in_new_block() { &contract_hash, DEFAULT_BLOCK_TIME + 1, ); - assert_eq!(query_view.message_topic().message_count(), 1); + assert_eq!( + query_view + .message_topic(*message_topic_hash) + .message_count(), + 1 + ); +} + +#[ignore] +#[test] +fn should_add_topics() { + let builder = RefCell::new(LmdbWasmTestBuilder::default()); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + let contract_hash = install_messages_emitter_contract(&builder); + let query_view = ContractQueryView::new(&builder, contract_hash); + + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => "topic_1", + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_success() + .commit(); + + let topic_1_hash = *query_view + .entity() + .message_topics() + .get("topic_1") + .expect("should have added topic `topic_1"); + assert_eq!(query_view.message_topic(topic_1_hash).message_count(), 0); + + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => "topic_2", + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_success() + .commit(); + + let topic_2_hash = *query_view + .entity() + .message_topics() + .get("topic_2") + .expect("should have added topic `topic_2"); + + assert!(query_view + .entity() + .message_topics() + .get("topic_1") + .is_some()); + assert_eq!(query_view.message_topic(topic_1_hash).message_count(), 0); + assert_eq!(query_view.message_topic(topic_2_hash).message_count(), 0); +} + +#[ignore] +#[test] +fn should_not_add_duplicate_topics() { + let builder = RefCell::new(LmdbWasmTestBuilder::default()); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let contract_hash = install_messages_emitter_contract(&builder); + let query_view = ContractQueryView::new(&builder, contract_hash); + + let entity = query_view.entity(); + let (first_topic_name, _) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); + + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => first_topic_name, + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_failure() + .commit(); +} + +#[ignore] +#[test] +fn should_not_exceed_configured_limits() { + let default_wasm_config = WasmConfig::default(); + let custom_engine_config = EngineConfigBuilder::default() + .with_wasm_config(WasmConfig::new( + default_wasm_config.max_memory, + default_wasm_config.max_stack_height, + default_wasm_config.opcode_costs(), + default_wasm_config.storage_costs(), + default_wasm_config.take_host_function_costs(), + MessagesLimits { + max_topic_name_size: 32, + max_message_size: 100, + max_topics_per_contract: 1, + } + )) + .build(); + + let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(custom_engine_config)); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let contract_hash = install_messages_emitter_contract(&builder); + + // Check that the max number of topics limit is enforced. + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => "topic_1", + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_failure() + .commit(); + + // Check topic name size limit is respected. + let large_topic_name = std::str::from_utf8(&[0x4du8; 100]).unwrap(); + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => large_topic_name, + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_failure() + .commit(); } diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 0390431196..343d714dfc 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -258,6 +258,7 @@ emit_message = { cost = 200, arguments = [0, 0, 0, 0] } [wasm.messages_limits] max_topic_name_size = 256 +max_topics_per_contract = 128 max_message_size = 1_024 [system_costs] diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 8c53e274cf..f56713248d 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -269,6 +269,7 @@ emit_message = { cost = 200, arguments = [0, 0, 0, 0] } [wasm.messages_limits] max_topic_name_size = 256 +max_topics_per_contract = 128 max_message_size = 1_024 [system_costs] diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index ad8f788f00..c3f8f854cd 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -4666,6 +4666,7 @@ "contract_wasm_hash", "entry_points", "main_purse", + "message_topics", "named_keys", "protocol_version" ], @@ -4693,6 +4694,37 @@ }, "action_thresholds": { "$ref": "#/components/schemas/ActionThresholds" + }, + "message_topics": { + "$ref": "#/components/schemas/Array_of_MessageTopic" + } + } + }, + "Array_of_MessageTopic": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageTopic" + } + }, + "MessageTopic": { + "type": "object", + "required": [ + "topic_hash", + "topic_name" + ], + "properties": { + "topic_name": { + "type": "string" + }, + "topic_hash": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32 } } }, diff --git a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs index 12690156b1..ec1c8c425c 100644 --- a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs +++ b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs @@ -22,8 +22,10 @@ use casper_types::{ pub const ENTRY_POINT_INIT: &str = "init"; pub const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +pub const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; pub const MESSAGE_EMITTER_INITIALIZED: &str = "message_emitter_initialized"; pub const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; +pub const ARG_TOPIC_NAME: &str = "topic_name"; pub const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; pub const MESSAGE_PREFIX: &str = "generic message: "; @@ -35,7 +37,16 @@ pub extern "C" fn emit_message() { runtime::emit_message( MESSAGE_EMITTER_GENERIC_TOPIC, &MessagePayload::from_string(format!("{}{}", MESSAGE_PREFIX, suffix)), - ); + ) + .unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn add_topic() { + let topic_name: String = runtime::get_named_arg(ARG_TOPIC_NAME); + + runtime::manage_message_topic(topic_name.as_str(), MessageTopicOperation::Add) + .unwrap_or_revert(); } #[no_mangle] @@ -44,7 +55,8 @@ pub extern "C" fn init() { runtime::revert(ApiError::User(0)); } - runtime::manage_message_topic(MESSAGE_EMITTER_GENERIC_TOPIC, MessageTopicOperation::Add); + runtime::manage_message_topic(MESSAGE_EMITTER_GENERIC_TOPIC, MessageTopicOperation::Add) + .unwrap_or_revert(); runtime::put_key(MESSAGE_EMITTER_INITIALIZED, storage::new_uref(()).into()); } @@ -66,6 +78,13 @@ pub extern "C" fn call() { EntryPointAccess::Public, EntryPointType::Contract, )); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_ADD_TOPIC, + vec![Parameter::new(ARG_TOPIC_NAME, String::cl_type())], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); let (stored_contract_hash, _contract_version) = storage::new_contract( emitter_entry_points, diff --git a/types/benches/bytesrepr_bench.rs b/types/benches/bytesrepr_bench.rs index 5648e984fe..6a163cc9c5 100644 --- a/types/benches/bytesrepr_bench.rs +++ b/types/benches/bytesrepr_bench.rs @@ -7,7 +7,9 @@ use std::{ use casper_types::{ account::AccountHash, - addressable_entity::{ActionThresholds, AddressableEntity, AssociatedKeys, NamedKeys}, + addressable_entity::{ + ActionThresholds, AddressableEntity, AssociatedKeys, MessageTopics, NamedKeys, + }, bytesrepr::{self, Bytes, FromBytes, ToBytes}, package::{ContractPackageKind, ContractPackageStatus}, system::auction::{Bid, Delegator, EraInfo, SeigniorageAllocation}, @@ -502,6 +504,7 @@ fn sample_contract(named_keys_len: u8, entry_points_len: u8) -> AddressableEntit URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), ) } diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index a367b6f8fc..c61fbffa48 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -10,7 +10,7 @@ mod named_keys; mod weight; use alloc::{ - collections::{BTreeMap, BTreeSet}, + collections::{btree_map::Entry, BTreeMap, BTreeSet}, format, string::{String, ToString}, vec::Vec, @@ -49,6 +49,7 @@ use crate::{ account::{Account, AccountHash}, bytesrepr::{self, FromBytes, ToBytes}, checksummed_hex, + contract_messages::MessageTopicHash, contract_wasm::ContractWasmHash, contracts::Contract, uref::{self, URef}, @@ -608,6 +609,114 @@ impl KeyValueJsonSchema for EntryPointLabels { const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("NamedEntryPoint"); } +/// Collection of named message topics. +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[serde(transparent, deny_unknown_fields)] +pub struct MessageTopics( + #[serde(with = "BTreeMapToArray::")] + BTreeMap, +); + +impl ToBytes for MessageTopics { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + self.0.write_bytes(writer) + } +} + +impl FromBytes for MessageTopics { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (message_topics_map, remainder) = + BTreeMap::::from_bytes(bytes)?; + Ok((MessageTopics(message_topics_map), remainder)) + } +} + +impl MessageTopics { + /// Adds new message topic by topic name. + pub fn add_topic( + &mut self, + topic_name: String, + topic_hash: MessageTopicHash, + ) -> Result<(), MessageTopicError> { + if self.0.len() > u32::MAX as usize { + return Err(MessageTopicError::MaxTopicsExceeded); + } + + match self.0.entry(topic_name) { + Entry::Vacant(entry) => { + entry.insert(topic_hash); + Ok(()) + } + Entry::Occupied(_) => Err(MessageTopicError::DuplicateTopic), + } + } + + /// Checks if given topic name exists. + pub fn has_topic(&self, topic_name: &str) -> bool { + self.0.contains_key(topic_name) + } + + /// Gets the topic hash from the collection by its topic name. + pub fn get(&self, topic_name: &str) -> Option<&MessageTopicHash> { + self.0.get(topic_name) + } + + /// Returns the length of the message topics. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns true if no message topics are registered. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns an iterator over the account hash and the weights. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } +} + +struct MessageTopicLabels; + +impl KeyValueLabels for MessageTopicLabels { + const KEY: &'static str = "topic_name"; + const VALUE: &'static str = "topic_hash"; +} + +#[cfg(feature = "json-schema")] +impl KeyValueJsonSchema for MessageTopicLabels { + const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("MessageTopic"); +} + +impl From> for MessageTopics { + fn from(topics: BTreeMap) -> MessageTopics { + MessageTopics(topics) + } +} + +/// Errors that can occur while adding a new topic. +#[derive(PartialEq, Eq, Debug, Clone)] +#[non_exhaustive] +pub enum MessageTopicError { + /// Topic already exists. + DuplicateTopic, + /// Maximum number of topics exceeded. + MaxTopicsExceeded, + /// Topic name size exceeded. + TopicNameSizeExceeded, +} + /// Methods and type signatures supported by a contract. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "datasize", derive(DataSize))] @@ -621,6 +730,7 @@ pub struct AddressableEntity { main_purse: URef, associated_keys: AssociatedKeys, action_thresholds: ActionThresholds, + message_topics: MessageTopics, } impl From @@ -661,6 +771,7 @@ impl AddressableEntity { main_purse: URef, associated_keys: AssociatedKeys, action_thresholds: ActionThresholds, + message_topics: MessageTopics, ) -> Self { AddressableEntity { contract_package_hash, @@ -671,6 +782,7 @@ impl AddressableEntity { main_purse, action_thresholds, associated_keys, + message_topics, } } @@ -876,6 +988,20 @@ impl AddressableEntity { &self.entry_points } + /// Returns a reference to the message topics + pub fn message_topics(&self) -> &MessageTopics { + &self.message_topics + } + + /// Adds a new message topic to the entity + pub fn add_message_topic( + &mut self, + topic_name: String, + topic_hash: MessageTopicHash, + ) -> Result<(), MessageTopicError> { + self.message_topics.add_topic(topic_name, topic_hash) + } + /// Takes `named_keys` pub fn take_named_keys(self) -> NamedKeys { self.named_keys @@ -928,6 +1054,7 @@ impl ToBytes for AddressableEntity { self.main_purse().write_bytes(&mut result)?; self.associated_keys().write_bytes(&mut result)?; self.action_thresholds().write_bytes(&mut result)?; + self.message_topics().write_bytes(&mut result)?; Ok(result) } @@ -940,6 +1067,7 @@ impl ToBytes for AddressableEntity { + ToBytes::serialized_length(&self.main_purse) + ToBytes::serialized_length(&self.associated_keys) + ToBytes::serialized_length(&self.action_thresholds) + + ToBytes::serialized_length(&self.message_topics) } fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { @@ -951,6 +1079,7 @@ impl ToBytes for AddressableEntity { self.main_purse().write_bytes(writer)?; self.associated_keys().write_bytes(writer)?; self.action_thresholds().write_bytes(writer)?; + self.message_topics().write_bytes(writer)?; Ok(()) } } @@ -965,6 +1094,7 @@ impl FromBytes for AddressableEntity { let (main_purse, bytes) = URef::from_bytes(bytes)?; let (associated_keys, bytes) = AssociatedKeys::from_bytes(bytes)?; let (action_thresholds, bytes) = ActionThresholds::from_bytes(bytes)?; + let (message_topics, bytes) = MessageTopics::from_bytes(bytes)?; Ok(( AddressableEntity { contract_package_hash, @@ -975,6 +1105,7 @@ impl FromBytes for AddressableEntity { main_purse, associated_keys, action_thresholds, + message_topics, }, bytes, )) @@ -992,6 +1123,7 @@ impl Default for AddressableEntity { main_purse: URef::default(), action_thresholds: ActionThresholds::default(), associated_keys: AssociatedKeys::default(), + message_topics: MessageTopics::default(), } } } @@ -1007,6 +1139,7 @@ impl From for AddressableEntity { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), ) } } @@ -1022,6 +1155,7 @@ impl From for AddressableEntity { value.main_purse(), value.associated_keys().clone().into(), value.action_thresholds().clone().into(), + MessageTopics::default(), ) } } @@ -1462,6 +1596,7 @@ mod tests { associated_keys, ActionThresholds::new(Weight::new(1), Weight::new(1)) .expect("should create thresholds"), + MessageTopics::default(), ); let access_rights = contract.extract_access_rights(contract_hash); let expected_uref = URef::new([42; UREF_ADDR_LENGTH], AccessRights::READ_ADD_WRITE); diff --git a/types/src/api_error.rs b/types/src/api_error.rs index 2f424ce8f1..08085890d2 100644 --- a/types/src/api_error.rs +++ b/types/src/api_error.rs @@ -7,8 +7,8 @@ use core::{ use crate::{ addressable_entity::{ - self, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, TryFromIntError, - TryFromSliceForAccountHashError, UpdateKeyFailure, + self, AddKeyFailure, MessageTopicError, RemoveKeyFailure, SetThresholdFailure, + TryFromIntError, TryFromSliceForAccountHashError, UpdateKeyFailure, }, bytesrepr, system::{auction, handle_payment, mint}, @@ -402,16 +402,28 @@ pub enum ApiError { /// assert_eq!(ApiError::from(41), ApiError::MessageTopicAlreadyRegistered); /// ``` MessageTopicAlreadyRegistered, + /// The maximum number of allowed message topics was exceeded. + /// ``` + /// # use casper_types::ApiError; + /// assert_eq!(ApiError::from(42), ApiError::MaxTopicsNumberExceeded); + /// ``` + MaxTopicsNumberExceeded, + /// The maximum size for the topic name was exceeded. + /// ``` + /// # use casper_types::ApiError; + /// assert_eq!(ApiError::from(43), ApiError::MaxTopicNameSizeExceeded); + /// ``` + MaxTopicNameSizeExceeded, /// The message topic is not registered. /// ``` /// # use casper_types::ApiError; - /// assert_eq!(ApiError::from(42), ApiError::MessageTopicNotRegistered); + /// assert_eq!(ApiError::from(44), ApiError::MessageTopicNotRegistered); /// ``` MessageTopicNotRegistered, /// The message topic is full and cannot accept new messages. /// ``` /// # use casper_types::ApiError; - /// assert_eq!(ApiError::from(43), ApiError::MessageTopicFull); + /// assert_eq!(ApiError::from(45), ApiError::MessageTopicFull); /// ``` MessageTopicFull, } @@ -517,6 +529,16 @@ impl From for ApiError { } } +impl From for ApiError { + fn from(error: MessageTopicError) -> Self { + match error { + MessageTopicError::DuplicateTopic => ApiError::MessageTopicAlreadyRegistered, + MessageTopicError::MaxTopicsExceeded => ApiError::MaxTopicsNumberExceeded, + MessageTopicError::TopicNameSizeExceeded => ApiError::MaxTopicNameSizeExceeded, + } + } +} + impl From for u32 { fn from(error: ApiError) -> Self { match error { @@ -561,8 +583,10 @@ impl From for u32 { ApiError::ExceededRecursionDepth => 39, ApiError::NonRepresentableSerialization => 40, ApiError::MessageTopicAlreadyRegistered => 41, - ApiError::MessageTopicNotRegistered => 42, - ApiError::MessageTopicFull => 43, + ApiError::MaxTopicsNumberExceeded => 42, + ApiError::MaxTopicNameSizeExceeded => 43, + ApiError::MessageTopicNotRegistered => 44, + ApiError::MessageTopicFull => 45, ApiError::AuctionError(value) => AUCTION_ERROR_OFFSET + u32::from(value), ApiError::ContractHeader(value) => HEADER_ERROR_OFFSET + u32::from(value), ApiError::Mint(value) => MINT_ERROR_OFFSET + u32::from(value), @@ -616,8 +640,10 @@ impl From for ApiError { 39 => ApiError::ExceededRecursionDepth, 40 => ApiError::NonRepresentableSerialization, 41 => ApiError::MessageTopicAlreadyRegistered, - 42 => ApiError::MessageTopicNotRegistered, - 43 => ApiError::MessageTopicFull, + 42 => ApiError::MaxTopicsNumberExceeded, + 43 => ApiError::MaxTopicNameSizeExceeded, + 44 => ApiError::MessageTopicNotRegistered, + 45 => ApiError::MessageTopicFull, USER_ERROR_MIN..=USER_ERROR_MAX => ApiError::User(value as u16), HP_ERROR_MIN..=HP_ERROR_MAX => ApiError::HandlePayment(value as u8), MINT_ERROR_MIN..=MINT_ERROR_MAX => ApiError::Mint(value as u8), @@ -679,6 +705,8 @@ impl Debug for ApiError { ApiError::MessageTopicAlreadyRegistered => { write!(f, "ApiError::MessageTopicAlreadyRegistered")? } + ApiError::MaxTopicsNumberExceeded => write!(f, "ApiError::MaxTopicsNumberExceeded")?, + ApiError::MaxTopicNameSizeExceeded => write!(f, "ApiError::MaxTopicNameSizeExceeded")?, ApiError::MessageTopicNotRegistered => { write!(f, "ApiError::MessageTopicNotRegistered")? } @@ -903,6 +931,8 @@ mod tests { round_trip(Err(ApiError::AuctionError(0))); round_trip(Err(ApiError::AuctionError(u8::MAX))); round_trip(Err(ApiError::MessageTopicAlreadyRegistered)); + round_trip(Err(ApiError::MaxTopicsNumberExceeded)); + round_trip(Err(ApiError::MaxTopicNameSizeExceeded)); round_trip(Err(ApiError::MessageTopicNotRegistered)); round_trip(Err(ApiError::MessageTopicFull)); } diff --git a/types/src/chainspec/vm_config/messages_limits.rs b/types/src/chainspec/vm_config/messages_limits.rs index 03ffe45e58..4894510b1c 100644 --- a/types/src/chainspec/vm_config/messages_limits.rs +++ b/types/src/chainspec/vm_config/messages_limits.rs @@ -11,25 +11,15 @@ use crate::bytesrepr::{self, FromBytes, ToBytes}; #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct MessagesLimits { - /// Maximum size (in bytes) of a topic name. - max_topic_name_size: u32, + /// Maximum size (in bytes) of a topic name string. + pub max_topic_name_size: u32, /// Maximum message size in bytes. - max_message_size: u32, + pub max_message_size: u32, + /// Maximum number of topics that a contract can register. + pub max_topics_per_contract: u32, } impl MessagesLimits { - /// Check if a specified topic `name_size` exceeds the configured value. - pub fn topic_name_size_within_limits(&self, name_size: u32) -> Result<(), Error> { - if name_size > self.max_topic_name_size { - Err(Error::TopicNameSizeExceeded( - self.max_topic_name_size, - name_size, - )) - } else { - Ok(()) - } - } - /// Check if a specified message size exceeds the configured max value. pub fn message_size_within_limits(&self, message_size: u32) -> Result<(), Error> { if message_size > self.max_message_size { @@ -38,6 +28,16 @@ impl MessagesLimits { Ok(()) } } + + /// Returns the max number of topics a contract can register. + pub fn max_topics_per_contract(&self) -> u32 { + self.max_topics_per_contract + } + + /// Returns the maximum allowed size for the topic name string. + pub fn max_topic_name_size(&self) -> u32 { + self.max_topic_name_size + } } impl Default for MessagesLimits { @@ -45,6 +45,7 @@ impl Default for MessagesLimits { Self { max_topic_name_size: 256, max_message_size: 1024, + max_topics_per_contract: 128, } } } @@ -55,12 +56,15 @@ impl ToBytes for MessagesLimits { ret.append(&mut self.max_topic_name_size.to_bytes()?); ret.append(&mut self.max_message_size.to_bytes()?); + ret.append(&mut self.max_topics_per_contract.to_bytes()?); Ok(ret) } fn serialized_length(&self) -> usize { - self.max_topic_name_size.serialized_length() + self.max_message_size.serialized_length() + self.max_topic_name_size.serialized_length() + + self.max_message_size.serialized_length() + + self.max_topics_per_contract.serialized_length() } } @@ -68,11 +72,13 @@ impl FromBytes for MessagesLimits { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (max_topic_name_size, rem) = FromBytes::from_bytes(bytes)?; let (max_message_size, rem) = FromBytes::from_bytes(rem)?; + let (max_topics_per_contract, rem) = FromBytes::from_bytes(rem)?; Ok(( MessagesLimits { max_topic_name_size, max_message_size, + max_topics_per_contract, }, rem, )) @@ -84,6 +90,7 @@ impl Distribution for Standard { MessagesLimits { max_topic_name_size: rng.gen(), max_message_size: rng.gen(), + max_topics_per_contract: rng.gen(), } } } @@ -115,10 +122,12 @@ pub mod gens { pub fn message_limits_arb()( max_topic_name_size in num::u32::ANY, max_message_size in num::u32::ANY, + max_topics_per_contract in num::u32::ANY, ) -> MessagesLimits { MessagesLimits { max_topic_name_size, max_message_size, + max_topics_per_contract, } } } diff --git a/types/src/gens.rs b/types/src/gens.rs index e96e3e5f74..1220aa41b3 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -2,7 +2,12 @@ //! [`Proptest`](https://crates.io/crates/proptest). #![allow(missing_docs)] -use alloc::{boxed::Box, collections::BTreeSet, string::String, vec}; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + string::String, + vec, +}; use proptest::{ array, bits, bool, @@ -14,9 +19,9 @@ use proptest::{ use crate::{ account::{self, action_thresholds::gens::account_action_thresholds_arb, AccountHash}, - addressable_entity::{NamedKeys, Parameters, Weight}, - contract_messages::{MessageSummary, MessageTopicSummary}, - crypto::gens::public_key_arb_no_system, + addressable_entity::{MessageTopics, NamedKeys, Parameters, Weight}, + contract_messages::{MessageSummary, MessageTopicHash, MessageTopicSummary}, + crypto::{self, gens::public_key_arb_no_system}, package::{ContractPackageStatus, ContractVersionKey, ContractVersions, Groups}, system::auction::{ gens::era_info_arb, DelegationRate, Delegator, UnbondingPurse, WithdrawPurse, @@ -340,6 +345,17 @@ pub fn entry_points_arb() -> impl Strategy { collection::vec(entry_point_arb(), 1..10).prop_map(EntryPoints::from) } +pub fn message_topics_arb() -> impl Strategy { + collection::vec(any::(), 1..100).prop_map(|topic_names| { + MessageTopics::from( + topic_names + .into_iter() + .map(|name| (name.clone(), crypto::blake2b(name))) + .collect::>(), + ) + }) +} + pub fn account_arb() -> impl Strategy { ( account_hash_arb(), @@ -398,6 +414,7 @@ pub fn addressable_entity_arb() -> impl Strategy { uref_arb(), associated_keys_arb(), action_thresholds_arb(), + message_topics_arb(), ) .prop_map( |( @@ -409,6 +426,7 @@ pub fn addressable_entity_arb() -> impl Strategy { main_purse, associated_keys, action_thresholds, + message_topics, )| { AddressableEntity::new( contract_package_hash_arb.into(), @@ -419,6 +437,7 @@ pub fn addressable_entity_arb() -> impl Strategy { main_purse, associated_keys, action_thresholds, + message_topics, ) }, ) diff --git a/utils/global-state-update-gen/src/generic/state_tracker.rs b/utils/global-state-update-gen/src/generic/state_tracker.rs index eec3465d51..4f12a46be7 100644 --- a/utils/global-state-update-gen/src/generic/state_tracker.rs +++ b/utils/global-state-update-gen/src/generic/state_tracker.rs @@ -8,7 +8,7 @@ use rand::Rng; use casper_types::{ account::AccountHash, - addressable_entity::{ActionThresholds, AssociatedKeys, NamedKeys, Weight}, + addressable_entity::{ActionThresholds, AssociatedKeys, MessageTopics, NamedKeys, Weight}, package::{ContractPackageKind, ContractPackageStatus, ContractVersions, Groups}, system::auction::{BidAddr, BidKind, BidsExt, SeigniorageRecipientsSnapshot, UnbondingPurse}, AccessRights, AddressableEntity, CLValue, ContractHash, ContractPackageHash, ContractWasmHash, @@ -176,6 +176,7 @@ impl StateTracker { main_purse, associated_keys, ActionThresholds::default(), + MessageTopics::default(), ); let mut contract_package = Package::new( diff --git a/utils/global-state-update-gen/src/generic/testing.rs b/utils/global-state-update-gen/src/generic/testing.rs index d6969c13ce..0cd290f1b2 100644 --- a/utils/global-state-update-gen/src/generic/testing.rs +++ b/utils/global-state-update-gen/src/generic/testing.rs @@ -5,7 +5,7 @@ use rand::Rng; use casper_types::{ account::AccountHash, - addressable_entity::{ActionThresholds, AssociatedKeys, NamedKeys, Weight}, + addressable_entity::{ActionThresholds, AssociatedKeys, MessageTopics, NamedKeys, Weight}, system::auction::{ BidKind, BidsExt, Delegator, SeigniorageRecipient, SeigniorageRecipients, SeigniorageRecipientsSnapshot, UnbondingPurse, UnbondingPurses, ValidatorBid, @@ -68,6 +68,7 @@ impl MockStateReader { main_purse, AssociatedKeys::new(account_hash, Weight::new(1)), ActionThresholds::default(), + MessageTopics::default(), ); self.purses.insert(main_purse.addr(), balance); diff --git a/utils/validation/src/generators.rs b/utils/validation/src/generators.rs index 00dae3f3a7..2b0b09037d 100644 --- a/utils/validation/src/generators.rs +++ b/utils/validation/src/generators.rs @@ -8,7 +8,9 @@ use casper_types::{ Account, AccountHash, ActionThresholds as AccountActionThresholds, AssociatedKeys as AccountAssociatedKeys, Weight as AccountWeight, }, - addressable_entity::{ActionThresholds, AddressableEntity, AssociatedKeys, NamedKeys}, + addressable_entity::{ + ActionThresholds, AddressableEntity, AssociatedKeys, MessageTopics, NamedKeys, + }, package::{ContractPackageKind, ContractPackageStatus, ContractVersions, Groups, Package}, system::auction::{ Bid, BidAddr, BidKind, Delegator, EraInfo, SeigniorageAllocation, UnbondingPurse, @@ -376,6 +378,7 @@ pub fn make_abi_test_fixtures() -> Result { URef::default(), AssociatedKeys::default(), ActionThresholds::default(), + MessageTopics::default(), ); stored_value.insert( "AddressableEntity".to_string(), From 24fe54db0b1e27dd0145ba05e66478adb975f83d Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Thu, 5 Oct 2023 16:36:43 +0000 Subject: [PATCH 05/27] tests/contract_messages: fix lint errors Signed-off-by: Alexandru Sardan --- .../tests/src/test/contract_messages.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index e4b3623369..f4ff083d1b 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -412,11 +412,13 @@ fn should_not_exceed_configured_limits() { max_topic_name_size: 32, max_message_size: 100, max_topics_per_contract: 1, - } + }, )) .build(); - let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(custom_engine_config)); + let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config( + custom_engine_config, + )); builder .borrow_mut() .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); From 27af40bc8d422ebdfc033b9b31bf7d48804ca44c Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 6 Oct 2023 12:33:02 +0000 Subject: [PATCH 06/27] ee/contract_messages: fix JSON schema for topic name hash Also re-organize the types used for contract level messages in multiple modules and refactor naming for better consistency. Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/mod.rs | 16 +- execution_engine/src/runtime_context/mod.rs | 8 +- .../tests/src/test/contract_messages.rs | 24 +- resources/test/rpc_schema.json | 42 +- resources/test/sse_data_schema.json | 13 +- types/src/addressable_entity.rs | 27 +- types/src/contract_messages.rs | 445 +++--------------- types/src/contract_messages/error.rs | 74 +++ types/src/contract_messages/messages.rs | 174 +++++++ types/src/contract_messages/topics.rs | 234 +++++++++ types/src/gens.rs | 13 +- types/src/key.rs | 47 +- types/src/stored_value.rs | 15 +- 13 files changed, 650 insertions(+), 482 deletions(-) create mode 100644 types/src/contract_messages/error.rs create mode 100644 types/src/contract_messages/messages.rs create mode 100644 types/src/contract_messages/topics.rs diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index ffc61e763d..dd097b36b4 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -32,7 +32,7 @@ use casper_types::{ }, bytesrepr::{self, Bytes, FromBytes, ToBytes}, contract_messages::{ - Message, MessageAddr, MessagePayload, MessageSummary, MessageTopicSummary, + Message, MessageAddr, MessageChecksum, MessagePayload, MessageTopicSummary, }, crypto, package::{ContractPackageKind, ContractPackageStatus}, @@ -3244,7 +3244,7 @@ where } fn add_message_topic(&mut self, topic_name: String) -> Result, Error> { - let topic_hash: [u8; 32] = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); + let topic_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)).into(); self.context .add_message_topic(topic_name, topic_hash) @@ -3262,8 +3262,8 @@ where .into_hash() .ok_or(Error::InvalidContext)?; - let topic_digest: [u8; 32] = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); - let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_digest)); + let topic_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)).into(); + let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_hash)); // Check if the topic exists and get the summary. let current_topic_summary = if let Some(StoredValue::MessageTopic(message_summary)) = @@ -3278,7 +3278,7 @@ where let message_index = if current_topic_summary.blocktime() != current_blocktime { for index in 1..current_topic_summary.message_count() { self.context - .prune_gs_unsafe(Key::message(entity_addr, topic_digest, index)); + .prune_gs_unsafe(Key::message(entity_addr, topic_hash, index)); } 0 } else { @@ -3290,9 +3290,9 @@ where current_blocktime, )); - let message_key = Key::message(entity_addr, topic_digest, message_index); + let message_key = Key::message(entity_addr, topic_hash, message_index); - let message_digest = StoredValue::Message(MessageSummary(crypto::blake2b( + let message_checksum = StoredValue::Message(MessageChecksum(crypto::blake2b( message.to_bytes().map_err(Error::BytesRepr)?, ))); @@ -3300,7 +3300,7 @@ where topic_key, new_topic_summary, message_key, - message_digest, + message_checksum, Message::new(entity_addr, message, topic_name, message_index), )?; Ok(Ok(())) diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index ebc7818250..7f0d43b0e7 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -22,7 +22,7 @@ use casper_types::{ SetThresholdFailure, UpdateKeyFailure, Weight, }, bytesrepr::ToBytes, - contract_messages::{Message, MessageAddr, MessageTopicHash, MessageTopicSummary}, + contract_messages::{Message, MessageAddr, MessageTopicSummary, TopicNameHash}, execution::Effects, package::ContractPackageKind, system::auction::{BidKind, EraInfo}, @@ -1390,7 +1390,7 @@ where pub(crate) fn add_message_topic( &mut self, topic_name: String, - topic_hash: MessageTopicHash, + topic_name_hash: TopicNameHash, ) -> Result, Error> { let entity_key: Key = self.get_entity_address(); let entity_addr = entity_key.into_hash().ok_or(Error::InvalidContext)?; @@ -1409,13 +1409,13 @@ where return Ok(Err(MessageTopicError::MaxTopicsExceeded)); } - if let Err(e) = entity.add_message_topic(topic_name, topic_hash) { + if let Err(e) = entity.add_message_topic(topic_name, topic_name_hash) { return Ok(Err(e)); } entity }; - let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_hash)); + let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)); let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, self.get_blocktime())); let entity_value = self.addressable_entity_to_validated_value(entity)?; diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index f4ff083d1b..930cdd7547 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -7,7 +7,7 @@ use casper_engine_test_support::{ use casper_execution_engine::engine_state::EngineConfigBuilder; use casper_types::{ bytesrepr::ToBytes, - contract_messages::{MessagePayload, MessageSummary, MessageTopicHash, MessageTopicSummary}, + contract_messages::{MessageChecksum, MessagePayload, MessageTopicSummary, TopicNameHash}, crypto, runtime_args, AddressableEntity, ContractHash, Digest, Key, MessagesLimits, RuntimeArgs, StoredValue, WasmConfig, }; @@ -22,9 +22,7 @@ const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; const EMITTER_MESSAGE_PREFIX: &str = "generic message: "; -fn install_messages_emitter_contract<'a>( - builder: &'a RefCell, -) -> ContractHash { +fn install_messages_emitter_contract(builder: &RefCell) -> ContractHash { // Request to install the contract that will be emitting messages. let install_request = ExecuteRequestBuilder::standard( *DEFAULT_ACCOUNT_ADDR, @@ -65,8 +63,8 @@ fn install_messages_emitter_contract<'a>( .expect("Should have contract hash") } -fn emit_message_with_suffix<'a>( - builder: &'a RefCell, +fn emit_message_with_suffix( + builder: &RefCell, suffix: &str, contract_hash: &ContractHash, block_time: u64, @@ -121,13 +119,13 @@ impl<'a> ContractQueryView<'a> { entity } - fn message_topic(&self, message_topic_hash: MessageTopicHash) -> MessageTopicSummary { + fn message_topic(&self, topic_name_hash: TopicNameHash) -> MessageTopicSummary { let query_result = self .builder .borrow_mut() .query( None, - Key::message_topic(self.contract_hash.value(), message_topic_hash), + Key::message_topic(self.contract_hash.value(), topic_name_hash), &[], ) .expect("should query"); @@ -145,17 +143,13 @@ impl<'a> ContractQueryView<'a> { fn message_summary( &self, - message_topic_hash: MessageTopicHash, + topic_name_hash: TopicNameHash, message_index: u32, state_hash: Option, - ) -> Result { + ) -> Result { let query_result = self.builder.borrow_mut().query( state_hash, - Key::message( - self.contract_hash.value(), - message_topic_hash, - message_index, - ), + Key::message(self.contract_hash.value(), topic_name_hash, message_index), &[], )?; diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index c3f8f854cd..c10c456ed5 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -3692,7 +3692,7 @@ "type": "string" }, "message": { - "description": "Message payload", + "description": "The payload of the message.", "allOf": [ { "$ref": "#/components/schemas/MessagePayload" @@ -3700,11 +3700,11 @@ ] }, "topic": { - "description": "Topic name", + "description": "The name of the topic on which the message was emitted on.", "type": "string" }, "index": { - "description": "Message index in the topic", + "description": "Message index in the topic.", "type": "integer", "format": "uint32", "minimum": 0.0 @@ -3714,13 +3714,6 @@ "MessagePayload": { "description": "The payload of the message emitted by an addressable entity during execution.", "oneOf": [ - { - "description": "Empty message.", - "type": "string", - "enum": [ - "Empty" - ] - }, { "description": "Human readable string message.", "type": "object", @@ -4213,7 +4206,7 @@ ], "properties": { "Message": { - "$ref": "#/components/schemas/MessageSummary" + "$ref": "#/components/schemas/MessageChecksum" } }, "additionalProperties": false @@ -4709,25 +4702,26 @@ "MessageTopic": { "type": "object", "required": [ - "topic_hash", - "topic_name" + "topic_name", + "topic_name_hash" ], "properties": { "topic_name": { "type": "string" }, - "topic_hash": { - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "maxItems": 32, - "minItems": 32 + "topic_name_hash": { + "allOf": [ + { + "$ref": "#/components/schemas/TopicNameHash" + } + ] } } }, + "TopicNameHash": { + "description": "The hash of the name of the message topic.", + "type": "string" + }, "MessageTopicSummary": { "description": "Summary of a message topic that will be stored in global state.", "type": "object", @@ -4758,8 +4752,8 @@ "format": "uint64", "minimum": 0.0 }, - "MessageSummary": { - "description": "Message summary as a formatted string.", + "MessageChecksum": { + "description": "Message checksum as a formatted string.", "type": "string" }, "GlobalStateIdentifier": { diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 099d13bba5..71a9b574ba 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -2570,7 +2570,7 @@ "type": "string" }, "message": { - "description": "Message payload", + "description": "The payload of the message.", "allOf": [ { "$ref": "#/definitions/MessagePayload" @@ -2578,11 +2578,11 @@ ] }, "topic": { - "description": "Topic name", + "description": "The name of the topic on which the message was emitted on.", "type": "string" }, "index": { - "description": "Message index in the topic", + "description": "Message index in the topic.", "type": "integer", "format": "uint32", "minimum": 0.0 @@ -2592,13 +2592,6 @@ "MessagePayload": { "description": "The payload of the message emitted by an addressable entity during execution.", "oneOf": [ - { - "description": "Empty message.", - "type": "string", - "enum": [ - "Empty" - ] - }, { "description": "Human readable string message.", "type": "object", diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index c61fbffa48..fcbe3abbf9 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -49,7 +49,7 @@ use crate::{ account::{Account, AccountHash}, bytesrepr::{self, FromBytes, ToBytes}, checksummed_hex, - contract_messages::MessageTopicHash, + contract_messages::TopicNameHash, contract_wasm::ContractWasmHash, contracts::Contract, uref::{self, URef}, @@ -615,8 +615,8 @@ impl KeyValueJsonSchema for EntryPointLabels { #[cfg_attr(feature = "json-schema", derive(JsonSchema))] #[serde(transparent, deny_unknown_fields)] pub struct MessageTopics( - #[serde(with = "BTreeMapToArray::")] - BTreeMap, + #[serde(with = "BTreeMapToArray::")] + BTreeMap, ); impl ToBytes for MessageTopics { @@ -635,8 +635,7 @@ impl ToBytes for MessageTopics { impl FromBytes for MessageTopics { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (message_topics_map, remainder) = - BTreeMap::::from_bytes(bytes)?; + let (message_topics_map, remainder) = BTreeMap::::from_bytes(bytes)?; Ok((MessageTopics(message_topics_map), remainder)) } } @@ -646,7 +645,7 @@ impl MessageTopics { pub fn add_topic( &mut self, topic_name: String, - topic_hash: MessageTopicHash, + topic_name_hash: TopicNameHash, ) -> Result<(), MessageTopicError> { if self.0.len() > u32::MAX as usize { return Err(MessageTopicError::MaxTopicsExceeded); @@ -654,7 +653,7 @@ impl MessageTopics { match self.0.entry(topic_name) { Entry::Vacant(entry) => { - entry.insert(topic_hash); + entry.insert(topic_name_hash); Ok(()) } Entry::Occupied(_) => Err(MessageTopicError::DuplicateTopic), @@ -667,7 +666,7 @@ impl MessageTopics { } /// Gets the topic hash from the collection by its topic name. - pub fn get(&self, topic_name: &str) -> Option<&MessageTopicHash> { + pub fn get(&self, topic_name: &str) -> Option<&TopicNameHash> { self.0.get(topic_name) } @@ -682,7 +681,7 @@ impl MessageTopics { } /// Returns an iterator over the account hash and the weights. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } } @@ -691,7 +690,7 @@ struct MessageTopicLabels; impl KeyValueLabels for MessageTopicLabels { const KEY: &'static str = "topic_name"; - const VALUE: &'static str = "topic_hash"; + const VALUE: &'static str = "topic_name_hash"; } #[cfg(feature = "json-schema")] @@ -699,8 +698,8 @@ impl KeyValueJsonSchema for MessageTopicLabels { const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("MessageTopic"); } -impl From> for MessageTopics { - fn from(topics: BTreeMap) -> MessageTopics { +impl From> for MessageTopics { + fn from(topics: BTreeMap) -> MessageTopics { MessageTopics(topics) } } @@ -997,9 +996,9 @@ impl AddressableEntity { pub fn add_message_topic( &mut self, topic_name: String, - topic_hash: MessageTopicHash, + topic_name_hash: TopicNameHash, ) -> Result<(), MessageTopicError> { - self.message_topics.add_topic(topic_name, topic_hash) + self.message_topics.add_topic(topic_name, topic_name_hash) } /// Takes `named_keys` diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index 2978ee0259..b530b66bed 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -1,17 +1,23 @@ //! Data types for interacting with contract level messages. + +mod error; +mod messages; +mod topics; + +pub use error::FromStrError; +pub use messages::{Message, MessageChecksum, MessagePayload}; +pub use topics::{MessageTopicOperation, MessageTopicSummary, TopicNameHash}; + use crate::{ alloc::string::ToString, - bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - checksummed_hex, BlockTime, HashAddr, KEY_HASH_LENGTH, + bytesrepr::{self, FromBytes, ToBytes}, + checksummed_hex, HashAddr, KEY_HASH_LENGTH, }; use core::convert::TryFrom; use alloc::{string::String, vec::Vec}; -use core::{ - fmt::{self, Display, Formatter}, - num::ParseIntError, -}; +use core::fmt::{Debug, Display, Formatter}; #[cfg(feature = "datasize")] use datasize::DataSize; @@ -23,104 +29,8 @@ use rand::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The length in bytes of a [`MessageTopicHash`]. -pub const MESSAGE_TOPIC_HASH_LENGTH: usize = 32; - -/// The length of a message digest -pub const MESSAGE_DIGEST_LENGTH: usize = 32; - -/// The hash of the name of the message topic. -pub type MessageTopicHash = [u8; MESSAGE_TOPIC_HASH_LENGTH]; - const TOPIC_FORMATTED_STRING_PREFIX: &str = "topic-"; - -/// A newtype wrapping an array which contains the raw bytes of -/// the hash of the message emitted -#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Message summary as a formatted string.") -)] -pub struct MessageSummary( - #[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] - pub [u8; MESSAGE_DIGEST_LENGTH], -); - -impl MessageSummary { - /// Returns inner value of the message summary - pub fn value(&self) -> [u8; MESSAGE_DIGEST_LENGTH] { - self.0 - } -} - -impl ToBytes for MessageSummary { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - buffer.append(&mut self.0.to_bytes()?); - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - self.0.serialized_length() - } -} - -impl FromBytes for MessageSummary { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (summary, rem) = FromBytes::from_bytes(bytes)?; - Ok((MessageSummary(summary), rem)) - } -} - -/// Error while parsing a `[MessageTopicAddr]` from string. -#[derive(Debug)] -#[non_exhaustive] -pub enum FromStrError { - /// No message index at the end of the string. - MissingMessageIndex, - /// Cannot parse entity hash. - EntityHashParseError(String), - /// Cannot parse message topic hash. - MessageTopicParseError(String), - /// Failed to decode address portion of URef. - Hex(base16::DecodeError), - /// Failed to parse an int. - Int(ParseIntError), -} - -impl From for FromStrError { - fn from(error: base16::DecodeError) -> Self { - FromStrError::Hex(error) - } -} - -impl From for FromStrError { - fn from(error: ParseIntError) -> Self { - FromStrError::Int(error) - } -} - -impl Display for FromStrError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - FromStrError::MissingMessageIndex => { - write!(f, "no message index found at the end of the string") - } - FromStrError::EntityHashParseError(err) => { - write!(f, "could not parse entity hash: {}", err) - } - FromStrError::MessageTopicParseError(err) => { - write!(f, "could not parse topic hash: {}", err) - } - FromStrError::Hex(error) => { - write!(f, "failed to decode address portion from hex: {}", error) - } - FromStrError::Int(error) => write!(f, "failed to parse an int: {}", error), - } - } -} +const MESSAGE_ADDR_PREFIX: &str = "message-"; /// MessageTopicAddr #[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, Debug)] @@ -130,7 +40,7 @@ pub struct MessageAddr { /// The entity addr. entity_addr: HashAddr, /// The hash of the name of the message topic. - topic_hash: MessageTopicHash, + topic_name_hash: TopicNameHash, /// The message index. message_index: Option, } @@ -138,10 +48,10 @@ pub struct MessageAddr { impl MessageAddr { /// Constructs a new topic address based on the addressable entity addr and the hash of the /// message topic name. - pub const fn new_topic_addr(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Self { + pub const fn new_topic_addr(entity_addr: HashAddr, topic_name_hash: TopicNameHash) -> Self { Self { entity_addr, - topic_hash, + topic_name_hash, message_index: None, } } @@ -150,41 +60,71 @@ impl MessageAddr { /// message topic name and the message index in the topic. pub const fn new_message_addr( entity_addr: HashAddr, - topic_hash: MessageTopicHash, + topic_name_hash: TopicNameHash, message_index: u32, ) -> Self { Self { entity_addr, - topic_hash, + topic_name_hash, message_index: Some(message_index), } } + /// Formats the [`MessageAddr`] as a prefixed, hex-encoded string. + pub fn to_formatted_string(self) -> String { + match self.message_index { + Some(index) => { + format!( + "{}{}-{}-{:x}", + MESSAGE_ADDR_PREFIX, + base16::encode_lower(&self.entity_addr), + self.topic_name_hash.to_formatted_string(), + index, + ) + } + None => { + format!( + "{}{}{}-{}", + MESSAGE_ADDR_PREFIX, + TOPIC_FORMATTED_STRING_PREFIX, + base16::encode_lower(&self.entity_addr), + self.topic_name_hash.to_formatted_string(), + ) + } + } + } + /// Parses a formatted string into a [`MessageAddr`]. pub fn from_formatted_str(input: &str) -> Result { - let (remainder, message_index) = match input.strip_prefix(TOPIC_FORMATTED_STRING_PREFIX) { + let remainder = input + .strip_prefix(MESSAGE_ADDR_PREFIX) + .ok_or(FromStrError::InvalidPrefix)?; + + let (remainder, message_index) = match remainder.strip_prefix(TOPIC_FORMATTED_STRING_PREFIX) + { Some(topic_string) => (topic_string, None), None => { - let parts = input.splitn(2, '-').collect::>(); + let parts = input.rsplitn(2, '-').collect::>(); if parts.len() != 2 { return Err(FromStrError::MissingMessageIndex); } - (parts[0], Some(u32::from_str_radix(parts[1], 16)?)) + (parts[1], Some(u32::from_str_radix(parts[0], 16)?)) } }; - let bytes = checksummed_hex::decode(remainder)?; + let parts = remainder.splitn(2, '-').collect::>(); + if parts.len() != 2 { + return Err(FromStrError::MissingMessageIndex); + } + let bytes = checksummed_hex::decode(parts[0])?; let entity_addr = <[u8; KEY_HASH_LENGTH]>::try_from(bytes[0..KEY_HASH_LENGTH].as_ref()) .map_err(|err| FromStrError::EntityHashParseError(err.to_string()))?; - let topic_hash = - <[u8; MESSAGE_TOPIC_HASH_LENGTH]>::try_from(bytes[KEY_HASH_LENGTH..].as_ref()) - .map_err(|err| FromStrError::MessageTopicParseError(err.to_string()))?; - + let topic_name_hash = TopicNameHash::from_formatted_str(parts[1])?; Ok(MessageAddr { entity_addr, - topic_hash, + topic_name_hash, message_index, }) } @@ -201,19 +141,18 @@ impl Display for MessageAddr { Some(index) => { write!( f, - "{}{}-{:x}", + "{}-{}-{:x}", base16::encode_lower(&self.entity_addr), - base16::encode_lower(&self.topic_hash), + self.topic_name_hash, index, ) } None => { write!( f, - "{}{}{}", - TOPIC_FORMATTED_STRING_PREFIX, + "{}-{}", base16::encode_lower(&self.entity_addr), - base16::encode_lower(&self.topic_hash), + self.topic_name_hash, ) } } @@ -224,14 +163,14 @@ impl ToBytes for MessageAddr { fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut buffer = bytesrepr::allocate_buffer(self)?; buffer.append(&mut self.entity_addr.to_bytes()?); - buffer.append(&mut self.topic_hash.to_bytes()?); + buffer.append(&mut self.topic_name_hash.to_bytes()?); buffer.append(&mut self.message_index.to_bytes()?); Ok(buffer) } fn serialized_length(&self) -> usize { self.entity_addr.serialized_length() - + self.topic_hash.serialized_length() + + self.topic_name_hash.serialized_length() + self.message_index.serialized_length() } } @@ -244,7 +183,7 @@ impl FromBytes for MessageAddr { Ok(( MessageAddr { entity_addr, - topic_hash, + topic_name_hash: topic_hash, message_index, }, rem, @@ -256,267 +195,29 @@ impl Distribution for Standard { fn sample(&self, rng: &mut R) -> MessageAddr { MessageAddr { entity_addr: rng.gen(), - topic_hash: rng.gen(), + topic_name_hash: rng.gen(), message_index: rng.gen(), } } } -/// Summary of a message topic that will be stored in global state. -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] -pub struct MessageTopicSummary { - /// Number of messages in this topic. - pub(crate) message_count: u32, - /// Block timestamp in which these messages were emitted. - pub(crate) blocktime: BlockTime, -} - -impl MessageTopicSummary { - /// Creates a new topic summary. - pub fn new(message_count: u32, blocktime: BlockTime) -> Self { - Self { - message_count, - blocktime, - } - } - - /// Returns the number of messages that were sent on this topic. - pub fn message_count(&self) -> u32 { - self.message_count - } - - /// Returns the block time. - pub fn blocktime(&self) -> BlockTime { - self.blocktime - } -} - -impl ToBytes for MessageTopicSummary { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - buffer.append(&mut self.message_count.to_bytes()?); - buffer.append(&mut self.blocktime.to_bytes()?); - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - self.message_count.serialized_length() + self.blocktime.serialized_length() - } -} - -impl FromBytes for MessageTopicSummary { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (message_count, rem) = FromBytes::from_bytes(bytes)?; - let (blocktime, rem) = FromBytes::from_bytes(rem)?; - Ok(( - MessageTopicSummary { - message_count, - blocktime, - }, - rem, - )) - } -} - -const MESSAGE_PAYLOAD_TAG_LENGTH: usize = U8_SERIALIZED_LENGTH; - -/// Tag for an empty message payload. -pub const MESSAGE_PAYLOAD_EMPTY_TAG: u8 = 0; -/// Tag for a message payload that contains a human readable string. -pub const MESSAGE_PAYLOAD_STRING_TAG: u8 = 1; - -/// The payload of the message emitted by an addressable entity during execution. -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] -pub enum MessagePayload { - /// Empty message. - Empty, - /// Human readable string message. - String(String), -} - -impl MessagePayload { - /// Creates a new [`MessagePayload`] from a [`String`]. - pub fn from_string(message: String) -> Self { - Self::String(message) - } -} - -impl ToBytes for MessagePayload { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - match self { - MessagePayload::Empty => { - buffer.insert(0, MESSAGE_PAYLOAD_EMPTY_TAG); - } - MessagePayload::String(message_string) => { - buffer.insert(0, MESSAGE_PAYLOAD_STRING_TAG); - buffer.extend(message_string.to_bytes()?); - } - } - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - MESSAGE_PAYLOAD_TAG_LENGTH - + match self { - MessagePayload::Empty => 0, - MessagePayload::String(message_string) => message_string.serialized_length(), - } - } -} - -impl FromBytes for MessagePayload { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (tag, remainder) = u8::from_bytes(bytes)?; - match tag { - MESSAGE_PAYLOAD_EMPTY_TAG => Ok((Self::Empty, remainder)), - MESSAGE_PAYLOAD_STRING_TAG => { - let (message, remainder): (String, _) = FromBytes::from_bytes(remainder)?; - Ok((Self::String(message), remainder)) - } - _ => Err(bytesrepr::Error::Formatting), - } - } -} - -/// Message that was emitted by an addressable entity during execution. -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] -pub struct Message { - /// The identity of the entity that produced the message. - #[cfg_attr(feature = "json-schema", schemars(with = "String"))] - entity_addr: HashAddr, - /// Message payload - message: MessagePayload, - /// Topic name - topic: String, - /// Message index in the topic - index: u32, -} - -impl Message { - /// Creates new instance of [`Message`] with the specified source and message payload. - pub fn new(source: HashAddr, message: MessagePayload, topic: String, index: u32) -> Self { - Self { - entity_addr: source, - message, - topic, - index, - } - } -} - -impl ToBytes for Message { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - buffer.append(&mut self.entity_addr.to_bytes()?); - buffer.append(&mut self.message.to_bytes()?); - buffer.append(&mut self.topic.to_bytes()?); - buffer.append(&mut self.index.to_bytes()?); - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - self.entity_addr.serialized_length() - + self.message.serialized_length() - + self.topic.serialized_length() - + self.index.serialized_length() - } -} - -impl FromBytes for Message { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (entity_addr, rem) = FromBytes::from_bytes(bytes)?; - let (message, rem) = FromBytes::from_bytes(rem)?; - let (topic, rem) = FromBytes::from_bytes(rem)?; - let (index, rem) = FromBytes::from_bytes(rem)?; - Ok(( - Message { - entity_addr, - message, - topic, - index, - }, - rem, - )) - } -} - -const TOPIC_OPERATION_ADD_TAG: u8 = 0; -const OPERATION_MAX_SERIALIZED_LEN: usize = 1; - -/// Operations that can be performed on message topics. -pub enum MessageTopicOperation { - /// Add a new message topic. - Add, -} - -impl MessageTopicOperation { - /// Maximum serialized length of a message topic operation. - pub const fn max_serialized_len() -> usize { - OPERATION_MAX_SERIALIZED_LEN - } -} - -impl ToBytes for MessageTopicOperation { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - match self { - MessageTopicOperation::Add => buffer.push(TOPIC_OPERATION_ADD_TAG), - } - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - match self { - MessageTopicOperation::Add => 1, - } - } -} - -impl FromBytes for MessageTopicOperation { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (tag, remainder): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; - match tag { - TOPIC_OPERATION_ADD_TAG => Ok((MessageTopicOperation::Add, remainder)), - _ => Err(bytesrepr::Error::Formatting), - } - } -} - #[cfg(test)] mod tests { use crate::{bytesrepr, KEY_HASH_LENGTH}; - use super::*; + use super::{topics::TOPIC_NAME_HASH_LENGTH, *}; #[test] fn serialization_roundtrip() { - let message_addr = - MessageAddr::new_topic_addr([1; KEY_HASH_LENGTH], [2; MESSAGE_TOPIC_HASH_LENGTH]); - bytesrepr::test_serialization_roundtrip(&message_addr); - - let topic_summary = MessageTopicSummary::new(100, BlockTime::new(1000)); - bytesrepr::test_serialization_roundtrip(&topic_summary); - - let string_message_payload = - MessagePayload::from_string("new contract message".to_string()); - bytesrepr::test_serialization_roundtrip(&string_message_payload); + let topic_addr = + MessageAddr::new_topic_addr([1; KEY_HASH_LENGTH], [2; TOPIC_NAME_HASH_LENGTH].into()); + bytesrepr::test_serialization_roundtrip(&topic_addr); - let empty_message_payload = MessagePayload::Empty; - bytesrepr::test_serialization_roundtrip(&empty_message_payload); - - let message = Message::new( + let message_addr = MessageAddr::new_message_addr( [1; KEY_HASH_LENGTH], - string_message_payload, - "new contract message".to_string(), - 32, + [2; TOPIC_NAME_HASH_LENGTH].into(), + 3, ); - bytesrepr::test_serialization_roundtrip(&message); + bytesrepr::test_serialization_roundtrip(&message_addr); } } diff --git a/types/src/contract_messages/error.rs b/types/src/contract_messages/error.rs new file mode 100644 index 0000000000..ba7f2cd383 --- /dev/null +++ b/types/src/contract_messages/error.rs @@ -0,0 +1,74 @@ +use core::array::TryFromSliceError; + +use alloc::string::String; +use core::{ + fmt::{self, Debug, Display, Formatter}, + num::ParseIntError, +}; + +/// Error while parsing message hashes from string. +#[derive(Debug)] +#[non_exhaustive] +pub enum FromStrError { + /// The prefix is invalid. + InvalidPrefix, + /// No message index at the end of the string. + MissingMessageIndex, + /// String not formatted correctly. + Formatting, + /// Cannot parse entity hash. + EntityHashParseError(String), + /// Cannot parse message topic hash. + MessageTopicParseError(String), + /// Failed to decode address portion of URef. + Hex(base16::DecodeError), + /// Failed to parse an int. + Int(ParseIntError), + /// The slice is the wrong length. + Length(TryFromSliceError), +} + +impl From for FromStrError { + fn from(error: base16::DecodeError) -> Self { + FromStrError::Hex(error) + } +} + +impl From for FromStrError { + fn from(error: ParseIntError) -> Self { + FromStrError::Int(error) + } +} + +impl From for FromStrError { + fn from(error: TryFromSliceError) -> Self { + FromStrError::Length(error) + } +} + +impl Display for FromStrError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + FromStrError::InvalidPrefix => { + write!(f, "prefix is invalid") + } + FromStrError::MissingMessageIndex => { + write!(f, "no message index found at the end of the string") + } + FromStrError::Formatting => { + write!(f, "string not properly formatted") + } + FromStrError::EntityHashParseError(err) => { + write!(f, "could not parse entity hash: {}", err) + } + FromStrError::MessageTopicParseError(err) => { + write!(f, "could not parse topic hash: {}", err) + } + FromStrError::Hex(error) => { + write!(f, "failed to decode address portion from hex: {}", error) + } + FromStrError::Int(error) => write!(f, "failed to parse an int: {}", error), + FromStrError::Length(error) => write!(f, "address portion is wrong length: {}", error), + } + } +} diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs new file mode 100644 index 0000000000..5a8506af70 --- /dev/null +++ b/types/src/contract_messages/messages.rs @@ -0,0 +1,174 @@ +use crate::{ + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + HashAddr, +}; + +use alloc::{string::String, vec::Vec}; +use core::fmt::Debug; + +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// The length of a message digest +pub const MESSAGE_CHECKSUM_LENGTH: usize = 32; + +/// A newtype wrapping an array which contains the raw bytes of +/// the hash of the message emitted. +#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars(description = "Message checksum as a formatted string.") +)] +pub struct MessageChecksum( + #[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] + pub [u8; MESSAGE_CHECKSUM_LENGTH], +); + +impl MessageChecksum { + /// Returns inner value of the message checksum. + pub fn value(&self) -> [u8; MESSAGE_CHECKSUM_LENGTH] { + self.0 + } +} + +impl ToBytes for MessageChecksum { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.0.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for MessageChecksum { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (checksum, rem) = FromBytes::from_bytes(bytes)?; + Ok((MessageChecksum(checksum), rem)) + } +} + +const MESSAGE_PAYLOAD_TAG_LENGTH: usize = U8_SERIALIZED_LENGTH; + +/// Tag for a message payload that contains a human readable string. +pub const MESSAGE_PAYLOAD_STRING_TAG: u8 = 0; + +/// The payload of the message emitted by an addressable entity during execution. +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub enum MessagePayload { + /// Human readable string message. + String(String), +} + +impl MessagePayload { + /// Creates a new [`MessagePayload`] from a [`String`]. + pub fn from_string(message: String) -> Self { + Self::String(message) + } +} + +impl ToBytes for MessagePayload { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + match self { + MessagePayload::String(message_string) => { + buffer.insert(0, MESSAGE_PAYLOAD_STRING_TAG); + buffer.extend(message_string.to_bytes()?); + } + } + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + MESSAGE_PAYLOAD_TAG_LENGTH + + match self { + MessagePayload::String(message_string) => message_string.serialized_length(), + } + } +} + +impl FromBytes for MessagePayload { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + match tag { + MESSAGE_PAYLOAD_STRING_TAG => { + let (message, remainder): (String, _) = FromBytes::from_bytes(remainder)?; + Ok((Self::String(message), remainder)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +/// Message that was emitted by an addressable entity during execution. +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub struct Message { + /// The identity of the entity that produced the message. + #[cfg_attr(feature = "json-schema", schemars(with = "String"))] + entity_addr: HashAddr, + /// The payload of the message. + message: MessagePayload, + /// The name of the topic on which the message was emitted on. + topic: String, + /// Message index in the topic. + index: u32, +} + +impl Message { + /// Creates new instance of [`Message`] with the specified source and message payload. + pub fn new(source: HashAddr, message: MessagePayload, topic: String, index: u32) -> Self { + Self { + entity_addr: source, + message, + topic, + index, + } + } +} + +impl ToBytes for Message { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.entity_addr.to_bytes()?); + buffer.append(&mut self.message.to_bytes()?); + buffer.append(&mut self.topic.to_bytes()?); + buffer.append(&mut self.index.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.entity_addr.serialized_length() + + self.message.serialized_length() + + self.topic.serialized_length() + + self.index.serialized_length() + } +} + +impl FromBytes for Message { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (entity_addr, rem) = FromBytes::from_bytes(bytes)?; + let (message, rem) = FromBytes::from_bytes(rem)?; + let (topic, rem) = FromBytes::from_bytes(rem)?; + let (index, rem) = FromBytes::from_bytes(rem)?; + Ok(( + Message { + entity_addr, + message, + topic, + index, + }, + rem, + )) + } +} diff --git a/types/src/contract_messages/topics.rs b/types/src/contract_messages/topics.rs new file mode 100644 index 0000000000..85350b40da --- /dev/null +++ b/types/src/contract_messages/topics.rs @@ -0,0 +1,234 @@ +use crate::{ + bytesrepr::{self, FromBytes, ToBytes}, + checksummed_hex, BlockTime, +}; + +use core::convert::TryFrom; + +use alloc::{string::String, vec::Vec}; +use core::fmt::{Debug, Display, Formatter}; + +#[cfg(feature = "datasize")] +use datasize::DataSize; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer}; + +use super::error::FromStrError; + +/// The length in bytes of a topic name hash. +pub const TOPIC_NAME_HASH_LENGTH: usize = 32; +const MESSAGE_TOPIC_NAME_HASH: &str = "topic-name-"; + +/// The hash of the name of the message topic. +#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars(description = "The hash of the name of the message topic.") +)] +pub struct TopicNameHash( + #[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] + pub [u8; TOPIC_NAME_HASH_LENGTH], +); + +impl TopicNameHash { + /// Returns a new [`TopicNameHash`] based on the specified value. + pub const fn new(topic_name_hash: [u8; TOPIC_NAME_HASH_LENGTH]) -> TopicNameHash { + TopicNameHash(topic_name_hash) + } + + /// Returns inner value of the topic hash. + pub fn value(&self) -> [u8; TOPIC_NAME_HASH_LENGTH] { + self.0 + } + + /// Formats the [`TopicNameHash`] as a prefixed, hex-encoded string. + pub fn to_formatted_string(self) -> String { + format!( + "{}{}", + MESSAGE_TOPIC_NAME_HASH, + base16::encode_lower(&self.0), + ) + } + + /// Parses a string formatted as per `Self::to_formatted_string()` into a [`TopicNameHash`]. + pub fn from_formatted_str(input: &str) -> Result { + let remainder = input + .strip_prefix(MESSAGE_TOPIC_NAME_HASH) + .ok_or(FromStrError::InvalidPrefix)?; + let bytes = + <[u8; TOPIC_NAME_HASH_LENGTH]>::try_from(checksummed_hex::decode(remainder)?.as_ref())?; + Ok(TopicNameHash(bytes)) + } +} + +impl ToBytes for TopicNameHash { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.0.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for TopicNameHash { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (hash, rem) = FromBytes::from_bytes(bytes)?; + Ok((TopicNameHash(hash), rem)) + } +} + +impl Serialize for TopicNameHash { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.to_formatted_string().serialize(serializer) + } else { + self.0.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for TopicNameHash { + fn deserialize>(deserializer: D) -> Result { + if deserializer.is_human_readable() { + let formatted_string = String::deserialize(deserializer)?; + TopicNameHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom) + } else { + let bytes = <[u8; TOPIC_NAME_HASH_LENGTH]>::deserialize(deserializer)?; + Ok(TopicNameHash(bytes)) + } + } +} + +impl Display for TopicNameHash { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", base16::encode_lower(&self.0)) + } +} + +impl Debug for TopicNameHash { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + write!(f, "MessageTopicHash({})", base16::encode_lower(&self.0)) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> TopicNameHash { + TopicNameHash(rng.gen()) + } +} + +impl From<[u8; TOPIC_NAME_HASH_LENGTH]> for TopicNameHash { + fn from(value: [u8; TOPIC_NAME_HASH_LENGTH]) -> Self { + TopicNameHash(value) + } +} + +/// Summary of a message topic that will be stored in global state. +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub struct MessageTopicSummary { + /// Number of messages in this topic. + pub(crate) message_count: u32, + /// Block timestamp in which these messages were emitted. + pub(crate) blocktime: BlockTime, +} + +impl MessageTopicSummary { + /// Creates a new topic summary. + pub fn new(message_count: u32, blocktime: BlockTime) -> Self { + Self { + message_count, + blocktime, + } + } + + /// Returns the number of messages that were sent on this topic. + pub fn message_count(&self) -> u32 { + self.message_count + } + + /// Returns the block time. + pub fn blocktime(&self) -> BlockTime { + self.blocktime + } +} + +impl ToBytes for MessageTopicSummary { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + buffer.append(&mut self.message_count.to_bytes()?); + buffer.append(&mut self.blocktime.to_bytes()?); + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + self.message_count.serialized_length() + self.blocktime.serialized_length() + } +} + +impl FromBytes for MessageTopicSummary { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (message_count, rem) = FromBytes::from_bytes(bytes)?; + let (blocktime, rem) = FromBytes::from_bytes(rem)?; + Ok(( + MessageTopicSummary { + message_count, + blocktime, + }, + rem, + )) + } +} + +const TOPIC_OPERATION_ADD_TAG: u8 = 0; +const OPERATION_MAX_SERIALIZED_LEN: usize = 1; + +/// Operations that can be performed on message topics. +pub enum MessageTopicOperation { + /// Add a new message topic. + Add, +} + +impl MessageTopicOperation { + /// Maximum serialized length of a message topic operation. + pub const fn max_serialized_len() -> usize { + OPERATION_MAX_SERIALIZED_LEN + } +} + +impl ToBytes for MessageTopicOperation { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + match self { + MessageTopicOperation::Add => buffer.push(TOPIC_OPERATION_ADD_TAG), + } + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + match self { + MessageTopicOperation::Add => 1, + } + } +} + +impl FromBytes for MessageTopicOperation { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; + match tag { + TOPIC_OPERATION_ADD_TAG => Ok((MessageTopicOperation::Add, remainder)), + _ => Err(bytesrepr::Error::Formatting), + } + } +} diff --git a/types/src/gens.rs b/types/src/gens.rs index 1220aa41b3..98ceee9c1d 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -20,7 +20,7 @@ use proptest::{ use crate::{ account::{self, action_thresholds::gens::account_action_thresholds_arb, AccountHash}, addressable_entity::{MessageTopics, NamedKeys, Parameters, Weight}, - contract_messages::{MessageSummary, MessageTopicHash, MessageTopicSummary}, + contract_messages::{MessageChecksum, MessageTopicSummary, TopicNameHash}, crypto::{self, gens::public_key_arb_no_system}, package::{ContractPackageStatus, ContractVersionKey, ContractVersions, Groups}, system::auction::{ @@ -350,8 +350,11 @@ pub fn message_topics_arb() -> impl Strategy { MessageTopics::from( topic_names .into_iter() - .map(|name| (name.clone(), crypto::blake2b(name))) - .collect::>(), + .map(|name| { + let name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&name)).into(); + (name, name_hash) + }) + .collect::>(), ) }) } @@ -647,8 +650,8 @@ fn message_topic_summary_arb() -> impl Strategy { }) } -fn message_summary_arb() -> impl Strategy { - u8_slice_32().prop_map(MessageSummary) +fn message_summary_arb() -> impl Strategy { + u8_slice_32().prop_map(MessageChecksum) } pub fn stored_value_arb() -> impl Strategy { diff --git a/types/src/key.rs b/types/src/key.rs index 1854a8b03f..0355e93be2 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -35,7 +35,7 @@ use crate::{ addressable_entity::ContractHash, bytesrepr::{self, Error, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}, checksummed_hex, - contract_messages::{self, MessageAddr, MessageTopicHash}, + contract_messages::{self, MessageAddr, TopicNameHash}, contract_wasm::ContractWasmHash, package::ContractPackageHash, system::auction::{BidAddr, BidAddrTag}, @@ -57,7 +57,6 @@ const ERA_SUMMARY_PREFIX: &str = "era-summary-"; const CHAINSPEC_REGISTRY_PREFIX: &str = "chainspec-registry-"; const CHECKSUM_REGISTRY_PREFIX: &str = "checksum-registry-"; const BID_ADDR_PREFIX: &str = "bid-addr-"; -const MESSAGE_PREFIX: &str = "message-"; /// The number of bytes in a Blake2b hash pub const BLAKE2B_DIGEST_LENGTH: usize = 32; @@ -432,9 +431,7 @@ impl Key { Key::BidAddr(bid_addr) => { format!("{}{}", BID_ADDR_PREFIX, bid_addr) } - Key::Message(message_addr) => { - format!("{}{}", MESSAGE_PREFIX, message_addr) - } + Key::Message(message_addr) => message_addr.to_formatted_string(), } } @@ -598,10 +595,10 @@ impl Key { return Ok(Key::ChecksumRegistry); } - if let Some(message_addr) = input.strip_prefix(MESSAGE_PREFIX) { - let message_addr = MessageAddr::from_formatted_str(message_addr)?; - - return Ok(Key::Message(message_addr)); + match MessageAddr::from_formatted_str(input) { + Ok(message_addr) => return Ok(Key::Message(message_addr)), + Err(contract_messages::FromStrError::InvalidPrefix) => {} + Err(error) => return Err(error.into()), } Err(FromStrError::UnknownPrefix) @@ -707,18 +704,18 @@ impl Key { /// Creates a new [`Key::Message`] variant that identifies an indexed message based on an /// `entity_addr` `topic_hash` and message `index`. - pub fn message(entity_addr: HashAddr, topic_hash: MessageTopicHash, index: u32) -> Key { + pub fn message(entity_addr: HashAddr, topic_name_hash: TopicNameHash, index: u32) -> Key { Key::Message(MessageAddr::new_message_addr( entity_addr, - topic_hash, + topic_name_hash, index, )) } /// Creates a new [`Key::Message`] variant that identifies a message topic based on an /// `entity_addr` and a hash of the topic name. - pub fn message_topic(entity_addr: HashAddr, topic_hash: MessageTopicHash) -> Key { - Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_hash)) + pub fn message_topic(entity_addr: HashAddr, topic_name_hash: TopicNameHash) -> Key { + Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)) } /// Returns true if the key is of type [`Key::Dictionary`]. @@ -1227,8 +1224,15 @@ mod tests { const UNBOND_KEY: Key = Key::Unbond(AccountHash::new([42; 32])); const CHAINSPEC_REGISTRY_KEY: Key = Key::ChainspecRegistry; const CHECKSUM_REGISTRY_KEY: Key = Key::ChecksumRegistry; - const MESSAGE_TOPIC_KEY: Key = Key::Message(MessageAddr::new_topic_addr([42; 32], [42; 32])); - const MESSAGE_KEY: Key = Key::Message(MessageAddr::new_message_addr([42; 32], [2; 32], 9)); + const MESSAGE_TOPIC_KEY: Key = Key::Message(MessageAddr::new_topic_addr( + [42; 32], + TopicNameHash::new([42; 32]), + )); + const MESSAGE_KEY: Key = Key::Message(MessageAddr::new_message_addr( + [42; 32], + TopicNameHash::new([2; 32]), + 9, + )); const KEYS: &[Key] = &[ ACCOUNT_KEY, HASH_KEY, @@ -1660,10 +1664,6 @@ mod tests { "{}", bid_addr_err ); - assert!(Key::from_formatted_str(MESSAGE_PREFIX) - .unwrap_err() - .to_string() - .starts_with("message-topic-key from string error: ")); let invalid_prefix = "a-0000000000000000000000000000000000000000000000000000000000000000"; assert_eq!( Key::from_formatted_str(invalid_prefix) @@ -1741,9 +1741,14 @@ mod tests { round_trip(&Key::Unbond(AccountHash::new(zeros))); round_trip(&Key::ChainspecRegistry); round_trip(&Key::ChecksumRegistry); - round_trip(&Key::Message(MessageAddr::new_topic_addr(zeros, nines))); + round_trip(&Key::Message(MessageAddr::new_topic_addr( + zeros, + nines.into(), + ))); round_trip(&Key::Message(MessageAddr::new_message_addr( - zeros, nines, 1, + zeros, + nines.into(), + 1, ))); } } diff --git a/types/src/stored_value.rs b/types/src/stored_value.rs index 9697c9a7fb..8d2ffea2f2 100644 --- a/types/src/stored_value.rs +++ b/types/src/stored_value.rs @@ -17,7 +17,7 @@ use serde_bytes::ByteBuf; use crate::{ account::Account, bytesrepr::{self, Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - contract_messages::{MessageSummary, MessageTopicSummary}, + contract_messages::{MessageChecksum, MessageTopicSummary}, contracts::Contract, package::Package, system::auction::{Bid, BidKind, EraInfo, UnbondingPurse, WithdrawPurse}, @@ -84,7 +84,7 @@ pub enum StoredValue { /// Variant that stores a message topic. MessageTopic(MessageTopicSummary), /// Variant that stores a message digest. - Message(MessageSummary), + Message(MessageChecksum), } impl StoredValue { @@ -635,11 +635,8 @@ impl FromBytes for StoredValue { .map(|(message_summary, remainder)| { (StoredValue::MessageTopic(message_summary), remainder) }), - tag if tag == Tag::Message as u8 => { - MessageSummary::from_bytes(remainder).map(|(message_digest, remainder)| { - (StoredValue::Message(message_digest), remainder) - }) - } + tag if tag == Tag::Message as u8 => MessageChecksum::from_bytes(remainder) + .map(|(checksum, remainder)| (StoredValue::Message(checksum), remainder)), _ => Err(Error::Formatting), } } @@ -678,7 +675,7 @@ mod serde_helpers { BidKind(&'a BidKind), /// Variant that stores [`MessageSummary`]. MessageTopic(&'a MessageTopicSummary), - Message(&'a MessageSummary), + Message(&'a MessageChecksum), } #[derive(Deserialize)] @@ -712,7 +709,7 @@ mod serde_helpers { /// Variant that stores [`MessageSummary`]. MessageTopic(MessageTopicSummary), /// Variant that stores [`MessageDigest`]. - Message(MessageSummary), + Message(MessageChecksum), } impl<'a> From<&'a StoredValue> for BinarySerHelper<'a> { From 0d4571b131172057a937b8119e0bd2a72b7739f6 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 6 Oct 2023 12:59:07 +0000 Subject: [PATCH 07/27] ee/contract_messages: add topic name hash to the message sent out on the SSE Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/mod.rs | 27 ++++++++++++-------- resources/test/rpc_schema.json | 21 +++++++++++----- resources/test/sse_data_schema.json | 17 +++++++++++-- types/src/contract_messages/messages.rs | 29 ++++++++++++++++------ types/src/execution/execution_result_v2.rs | 12 ++++++--- 5 files changed, 77 insertions(+), 29 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index dd097b36b4..b7df7773bc 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -3262,11 +3262,11 @@ where .into_hash() .ok_or(Error::InvalidContext)?; - let topic_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)).into(); - let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_hash)); + let topic_name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)).into(); + let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)); // Check if the topic exists and get the summary. - let current_topic_summary = if let Some(StoredValue::MessageTopic(message_summary)) = + let prev_topic_summary = if let Some(StoredValue::MessageTopic(message_summary)) = self.context.read_gs(&topic_key)? { message_summary @@ -3275,23 +3275,22 @@ where }; let current_blocktime = self.context.get_blocktime(); - let message_index = if current_topic_summary.blocktime() != current_blocktime { - for index in 1..current_topic_summary.message_count() { + let message_index = if prev_topic_summary.blocktime() != current_blocktime { + for index in 1..prev_topic_summary.message_count() { self.context - .prune_gs_unsafe(Key::message(entity_addr, topic_hash, index)); + .prune_gs_unsafe(Key::message(entity_addr, topic_name_hash, index)); } 0 } else { - current_topic_summary.message_count() + prev_topic_summary.message_count() }; let new_topic_summary = StoredValue::MessageTopic(MessageTopicSummary::new( - message_index + 1, + message_index + 1, //TODO[AS]: need checked add here current_blocktime, )); - let message_key = Key::message(entity_addr, topic_hash, message_index); - + let message_key = Key::message(entity_addr, topic_name_hash, message_index); let message_checksum = StoredValue::Message(MessageChecksum(crypto::blake2b( message.to_bytes().map_err(Error::BytesRepr)?, ))); @@ -3301,7 +3300,13 @@ where new_topic_summary, message_key, message_checksum, - Message::new(entity_addr, message, topic_name, message_index), + Message::new( + entity_addr, + message, + topic_name, + topic_name_hash, + message_index, + ), )?; Ok(Ok(())) } diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index c10c456ed5..d951c3cfad 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -3684,7 +3684,8 @@ "entity_addr", "index", "message", - "topic" + "topic_name", + "topic_name_hash" ], "properties": { "entity_addr": { @@ -3699,10 +3700,18 @@ } ] }, - "topic": { + "topic_name": { "description": "The name of the topic on which the message was emitted on.", "type": "string" }, + "topic_name_hash": { + "description": "The hash of the name of the topic.", + "allOf": [ + { + "$ref": "#/components/schemas/TopicNameHash" + } + ] + }, "index": { "description": "Message index in the topic.", "type": "integer", @@ -3729,6 +3738,10 @@ } ] }, + "TopicNameHash": { + "description": "The hash of the name of the message topic.", + "type": "string" + }, "AccountIdentifier": { "description": "Identifier of an account.", "anyOf": [ @@ -4718,10 +4731,6 @@ } } }, - "TopicNameHash": { - "description": "The hash of the name of the message topic.", - "type": "string" - }, "MessageTopicSummary": { "description": "Summary of a message topic that will be stored in global state.", "type": "object", diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 71a9b574ba..549b6d7a9f 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -2562,7 +2562,8 @@ "entity_addr", "index", "message", - "topic" + "topic_name", + "topic_name_hash" ], "properties": { "entity_addr": { @@ -2577,10 +2578,18 @@ } ] }, - "topic": { + "topic_name": { "description": "The name of the topic on which the message was emitted on.", "type": "string" }, + "topic_name_hash": { + "description": "The hash of the name of the topic.", + "allOf": [ + { + "$ref": "#/definitions/TopicNameHash" + } + ] + }, "index": { "description": "Message index in the topic.", "type": "integer", @@ -2607,6 +2616,10 @@ } ] }, + "TopicNameHash": { + "description": "The hash of the name of the message topic.", + "type": "string" + }, "FinalitySignature": { "description": "A validator's signature of a block, confirming it is finalized.", "type": "object", diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs index 5a8506af70..c462152b80 100644 --- a/types/src/contract_messages/messages.rs +++ b/types/src/contract_messages/messages.rs @@ -12,6 +12,8 @@ use datasize::DataSize; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use super::TopicNameHash; + /// The length of a message digest pub const MESSAGE_CHECKSUM_LENGTH: usize = 32; @@ -120,18 +122,27 @@ pub struct Message { /// The payload of the message. message: MessagePayload, /// The name of the topic on which the message was emitted on. - topic: String, + topic_name: String, + /// The hash of the name of the topic. + topic_name_hash: TopicNameHash, /// Message index in the topic. index: u32, } impl Message { /// Creates new instance of [`Message`] with the specified source and message payload. - pub fn new(source: HashAddr, message: MessagePayload, topic: String, index: u32) -> Self { + pub fn new( + source: HashAddr, + message: MessagePayload, + topic_name: String, + topic_name_hash: TopicNameHash, + index: u32, + ) -> Self { Self { entity_addr: source, message, - topic, + topic_name, + topic_name_hash, index, } } @@ -142,7 +153,8 @@ impl ToBytes for Message { let mut buffer = bytesrepr::allocate_buffer(self)?; buffer.append(&mut self.entity_addr.to_bytes()?); buffer.append(&mut self.message.to_bytes()?); - buffer.append(&mut self.topic.to_bytes()?); + buffer.append(&mut self.topic_name.to_bytes()?); + buffer.append(&mut self.topic_name_hash.to_bytes()?); buffer.append(&mut self.index.to_bytes()?); Ok(buffer) } @@ -150,7 +162,8 @@ impl ToBytes for Message { fn serialized_length(&self) -> usize { self.entity_addr.serialized_length() + self.message.serialized_length() - + self.topic.serialized_length() + + self.topic_name.serialized_length() + + self.topic_name_hash.serialized_length() + self.index.serialized_length() } } @@ -159,13 +172,15 @@ impl FromBytes for Message { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (entity_addr, rem) = FromBytes::from_bytes(bytes)?; let (message, rem) = FromBytes::from_bytes(rem)?; - let (topic, rem) = FromBytes::from_bytes(rem)?; + let (topic_name, rem) = FromBytes::from_bytes(rem)?; + let (topic_name_hash, rem) = FromBytes::from_bytes(rem)?; let (index, rem) = FromBytes::from_bytes(rem)?; Ok(( Message { entity_addr, message, - topic, + topic_name, + topic_name_hash, index, }, rem, diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 08ac0de2bb..7d71430e78 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -30,7 +30,7 @@ use super::{Transform, TransformKind}; use crate::{ bytesrepr::{self, FromBytes, ToBytes, RESULT_ERR_TAG, RESULT_OK_TAG, U8_SERIALIZED_LENGTH}, contract_messages::Message, - TransferAddr, U512, + crypto, TransferAddr, U512, }; #[cfg(any(feature = "testing", test))] use crate::{contract_messages::MessagePayload, testing::TestRng}; @@ -111,10 +111,13 @@ impl Distribution for Standard { .iter() .filter_map(|transform| { if let Key::Message(addr) = transform.key() { + let topic_name = Alphanumeric.sample_string(rng, 32); + let topic_name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); Some(Message::new( addr.entity_addr(), MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), - Alphanumeric.sample_string(rng, 32), + topic_name, + topic_name_hash.into(), rng.gen::(), )) } else { @@ -159,10 +162,13 @@ impl ExecutionResultV2 { .iter() .filter_map(|transform| { if let Key::Message(addr) = transform.key() { + let topic_name = Alphanumeric.sample_string(rng, 32); + let topic_name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); Some(Message::new( addr.entity_addr(), MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), - Alphanumeric.sample_string(rng, 32), + topic_name, + topic_name_hash.into(), rng.gen::(), )) } else { From c8214ec91407678b66840abdbe7139146756f7d2 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 6 Oct 2023 13:05:09 +0000 Subject: [PATCH 08/27] types/execution_results: fix lint issue Signed-off-by: Alexandru Sardan --- types/src/execution/execution_result_v2.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 7d71430e78..0e98531127 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -27,10 +27,12 @@ use serde::{Deserialize, Serialize}; use super::Effects; #[cfg(feature = "json-schema")] use super::{Transform, TransformKind}; +#[cfg(any(feature = "testing", test))] +use crate::crypto; use crate::{ bytesrepr::{self, FromBytes, ToBytes, RESULT_ERR_TAG, RESULT_OK_TAG, U8_SERIALIZED_LENGTH}, contract_messages::Message, - crypto, TransferAddr, U512, + TransferAddr, U512, }; #[cfg(any(feature = "testing", test))] use crate::{contract_messages::MessagePayload, testing::TestRng}; From 12526439aef7d8e16949f66909948d858034549c Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 6 Oct 2023 14:53:03 +0000 Subject: [PATCH 09/27] ee/contract_messages: minor cleanups Remove unnecessary trap error, fix comments and add new test case for checking topic name limits. Signed-off-by: Alexandru Sardan --- execution_engine/src/engine_state/mod.rs | 1 - execution_engine/src/execution/error.rs | 5 +-- execution_engine/src/runtime/externals.rs | 23 ++++++------- .../tests/src/test/contract_messages.rs | 34 +++++++++++++++---- smart_contracts/contract/src/ext_ffi.rs | 11 +++--- types/src/api_error.rs | 10 ++++++ types/src/chainspec.rs | 6 ++-- types/src/chainspec/vm_config.rs | 2 +- .../chainspec/vm_config/messages_limits.rs | 31 +++-------------- types/src/lib.rs | 6 ++-- 10 files changed, 68 insertions(+), 61 deletions(-) diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index 006456da0f..2b237517bd 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -2807,7 +2807,6 @@ fn should_charge_for_errors_in_wasm(execution_result: &ExecutionResult) -> bool | ExecError::UnexpectedKeyVariant(_) | ExecError::InvalidContractPackageKind(_) | ExecError::Transform(_) - | ExecError::CannotEmitMessage(_) | ExecError::InvalidMessageTopicOperation => false, ExecError::DisabledUnrestrictedTransfers => false, }, diff --git a/execution_engine/src/execution/error.rs b/execution_engine/src/execution/error.rs index 316ad952b7..88a3a19c37 100644 --- a/execution_engine/src/execution/error.rs +++ b/execution_engine/src/execution/error.rs @@ -9,7 +9,7 @@ use casper_types::{ execution::TransformError, package::ContractPackageKind, system, AccessRights, ApiError, CLType, CLValueError, ContractHash, ContractPackageHash, - ContractVersionKey, ContractWasmHash, Key, MessagesLimitsError, StoredValueTypeMismatch, URef, + ContractVersionKey, ContractWasmHash, Key, StoredValueTypeMismatch, URef, }; use crate::{ @@ -191,9 +191,6 @@ pub enum Error { /// Failed to transfer tokens on a private chain. #[error("Failed to transfer with unrestricted transfers disabled")] DisabledUnrestrictedTransfers, - /// Message was not emitted. - #[error("Failed to emit a message on topic: {0}")] - CannotEmitMessage(MessagesLimitsError), /// Invalid message topic operation. #[error("The requested operation is invalid for a message topic")] InvalidMessageTopicOperation, diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index 6cfbf9e5e7..878bd24012 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -1108,8 +1108,8 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } FunctionIndex::ManageMessageTopic => { - // args(0) = pointer to the topic name in wasm memory - // args(1) = size of the topic name string in wasm memory + // args(0) = pointer to the serialized topic name string in wasm memory + // args(1) = size of the serialized topic name string in wasm memory // args(2) = pointer to the operation to be performed for the specified topic // args(3) = size of the operation let (topic_name_ptr, topic_name_size, operation_ptr, operation_size) = @@ -1155,10 +1155,10 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } FunctionIndex::EmitMessage => { - // args(0) = pointer to the message kind name in wasm memory - // args(1) = size of the name string in wasm memory - // args(2) = pointer to the message contents - // args(3) = size of the message contents + // args(0) = pointer to the serialized topic name string in wasm memory + // args(1) = size of the serialized name string in wasm memory + // args(2) = pointer to the serialized message payload in wasm memory + // args(3) = size of the serialized message payload in wasm memory let (topic_name_ptr, topic_name_size, message_ptr, message_size) = Args::parse(args)?; self.charge_host_function_call( @@ -1174,12 +1174,11 @@ where ))))); } - self.context - .engine_config() - .wasm_config() - .messages_limits() - .message_size_within_limits(message_size) - .map_err(|e| Trap::from(Error::CannotEmitMessage(e)))?; + if message_size > limits.max_message_size() { + return Ok(Some(RuntimeValue::I32(api_error::i32_from(Err( + ApiError::MessageTooLarge, + ))))); + } let topic_name = self.string_from_mem(topic_name_ptr, topic_name_size)?; let message = self.t_from_mem(message_ptr, message_size)?; diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 930cdd7547..33259fe1a9 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -405,7 +405,7 @@ fn should_not_exceed_configured_limits() { MessagesLimits { max_topic_name_size: 32, max_message_size: 100, - max_topics_per_contract: 1, + max_topics_per_contract: 2, }, )) .build(); @@ -419,13 +419,15 @@ fn should_not_exceed_configured_limits() { let contract_hash = install_messages_emitter_contract(&builder); - // Check that the max number of topics limit is enforced. + // if the topic larger than the limit, registering should fail. + // string is 29 bytes + 4 bytes for size = 33 bytes > limit established above + let too_large_topic_name = std::str::from_utf8(&[0x4du8; 29]).unwrap(); let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, contract_hash, ENTRY_POINT_ADD_TOPIC, runtime_args! { - ARG_TOPIC_NAME => "topic_1", + ARG_TOPIC_NAME => too_large_topic_name, }, ) .build(); @@ -436,14 +438,34 @@ fn should_not_exceed_configured_limits() { .expect_failure() .commit(); - // Check topic name size limit is respected. - let large_topic_name = std::str::from_utf8(&[0x4du8; 100]).unwrap(); + // if the topic name is equal to the limit, registering should work. + // string is 28 bytes + 4 bytes for size = 32 bytes == limit established above + let topic_name_at_limit = std::str::from_utf8(&[0x4du8; 28]).unwrap(); let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, contract_hash, ENTRY_POINT_ADD_TOPIC, runtime_args! { - ARG_TOPIC_NAME => large_topic_name, + ARG_TOPIC_NAME => topic_name_at_limit, + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_success() + .commit(); + + // Check that the max number of topics limit is enforced. + // 2 topics are already registered, so registering another topic should + // fail since the limit is already reached. + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => "topic_1", }, ) .build(); diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index d7cca0fc2d..09e04d1c18 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -809,8 +809,8 @@ extern "C" { /// /// # Arguments /// - /// * `topic_name_ptr` - pointer to a `str` containing the name of the message topic. - /// * `topic_name_size` - size of the topic string. + /// * `topic_name_ptr` - pointer to the serialized topic name string. + /// * `topic_name_size` - size of the serialized name string. /// * `operation_ptr` - pointer to the management operation to be performed for the specified /// topic. /// * `operation_ptr_size` - size of the operation. @@ -824,9 +824,10 @@ extern "C" { /// /// # Arguments /// - /// * `topic_name_ptr` - pointer to a `str` containing the name of the topic to be registered. - /// * `topic_name_size` - size of the topic string. - /// * `message_ptr` - pointer pointer to serialized message payload. + /// * `topic_name_ptr` - pointer to the serialized topic name string where the message will be + /// emitted. + /// * `topic_name_size` - size of the serialized name string. + /// * `message_ptr` - pointer to the serialized message payload to be emitted. /// * `message_size` - size of the serialized message payload. pub fn casper_emit_message( topic_name_ptr: *const u8, diff --git a/types/src/api_error.rs b/types/src/api_error.rs index 08085890d2..6315ad5074 100644 --- a/types/src/api_error.rs +++ b/types/src/api_error.rs @@ -426,6 +426,12 @@ pub enum ApiError { /// assert_eq!(ApiError::from(45), ApiError::MessageTopicFull); /// ``` MessageTopicFull, + /// The message topic is full and cannot accept new messages. + /// ``` + /// # use casper_types::ApiError; + /// assert_eq!(ApiError::from(46), ApiError::MessageTooLarge); + /// ``` + MessageTooLarge, } impl From for ApiError { @@ -587,6 +593,7 @@ impl From for u32 { ApiError::MaxTopicNameSizeExceeded => 43, ApiError::MessageTopicNotRegistered => 44, ApiError::MessageTopicFull => 45, + ApiError::MessageTooLarge => 46, ApiError::AuctionError(value) => AUCTION_ERROR_OFFSET + u32::from(value), ApiError::ContractHeader(value) => HEADER_ERROR_OFFSET + u32::from(value), ApiError::Mint(value) => MINT_ERROR_OFFSET + u32::from(value), @@ -644,6 +651,7 @@ impl From for ApiError { 43 => ApiError::MaxTopicNameSizeExceeded, 44 => ApiError::MessageTopicNotRegistered, 45 => ApiError::MessageTopicFull, + 46 => ApiError::MessageTooLarge, USER_ERROR_MIN..=USER_ERROR_MAX => ApiError::User(value as u16), HP_ERROR_MIN..=HP_ERROR_MAX => ApiError::HandlePayment(value as u8), MINT_ERROR_MIN..=MINT_ERROR_MAX => ApiError::Mint(value as u8), @@ -711,6 +719,7 @@ impl Debug for ApiError { write!(f, "ApiError::MessageTopicNotRegistered")? } ApiError::MessageTopicFull => write!(f, "ApiError::MessageTopicFull")?, + ApiError::MessageTooLarge => write!(f, "ApiError::MessageTooLarge")?, ApiError::ExceededRecursionDepth => write!(f, "ApiError::ExceededRecursionDepth")?, ApiError::AuctionError(value) => write!( f, @@ -935,5 +944,6 @@ mod tests { round_trip(Err(ApiError::MaxTopicNameSizeExceeded)); round_trip(Err(ApiError::MessageTopicNotRegistered)); round_trip(Err(ApiError::MessageTopicFull)); + round_trip(Err(ApiError::MessageTooLarge)); } } diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index 64ffc07dee..ceeae0148f 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -47,9 +47,9 @@ pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Confi pub use transaction_config::{DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES}; pub use vm_config::{ AuctionCosts, BrTableCost, ChainspecRegistry, ControlFlowCosts, HandlePaymentCosts, - HostFunction, HostFunctionCost, HostFunctionCosts, MessagesLimits, MessagesLimitsError, - MintCosts, OpcodeCosts, StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, - WasmConfig, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, + HostFunction, HostFunctionCost, HostFunctionCosts, MessagesLimits, MintCosts, OpcodeCosts, + StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, + DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, }; #[cfg(any(feature = "testing", test))] pub use vm_config::{ diff --git a/types/src/chainspec/vm_config.rs b/types/src/chainspec/vm_config.rs index 212976f306..82d575a9f8 100644 --- a/types/src/chainspec/vm_config.rs +++ b/types/src/chainspec/vm_config.rs @@ -18,7 +18,7 @@ pub use host_function_costs::{ Cost as HostFunctionCost, HostFunction, HostFunctionCosts, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_NEW_DICTIONARY_COST, }; -pub use messages_limits::{Error as MessagesLimitsError, MessagesLimits}; +pub use messages_limits::MessagesLimits; pub use mint_costs::{MintCosts, DEFAULT_TRANSFER_COST}; pub use opcode_costs::{BrTableCost, ControlFlowCosts, OpcodeCosts}; #[cfg(any(feature = "testing", test))] diff --git a/types/src/chainspec/vm_config/messages_limits.rs b/types/src/chainspec/vm_config/messages_limits.rs index 4894510b1c..e66afc0f52 100644 --- a/types/src/chainspec/vm_config/messages_limits.rs +++ b/types/src/chainspec/vm_config/messages_limits.rs @@ -2,7 +2,6 @@ use datasize::DataSize; use rand::{distributions::Standard, prelude::*, Rng}; use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::bytesrepr::{self, FromBytes, ToBytes}; @@ -20,15 +19,6 @@ pub struct MessagesLimits { } impl MessagesLimits { - /// Check if a specified message size exceeds the configured max value. - pub fn message_size_within_limits(&self, message_size: u32) -> Result<(), Error> { - if message_size > self.max_message_size { - Err(Error::MessageTooLarge(self.max_message_size, message_size)) - } else { - Ok(()) - } - } - /// Returns the max number of topics a contract can register. pub fn max_topics_per_contract(&self) -> u32 { self.max_topics_per_contract @@ -38,6 +28,11 @@ impl MessagesLimits { pub fn max_topic_name_size(&self) -> u32 { self.max_topic_name_size } + + /// Returns the maximum allowed size (in bytes) of the serialized message payload. + pub fn max_message_size(&self) -> u32 { + self.max_message_size + } } impl Default for MessagesLimits { @@ -95,22 +90,6 @@ impl Distribution for Standard { } } -/// Possible execution errors. -#[derive(Error, Debug, Clone)] -#[non_exhaustive] -pub enum Error { - /// Topic name size exceeded. - #[error( - "Topic name size is too large: expected less then {} bytes, got {} bytes", - _0, - _1 - )] - TopicNameSizeExceeded(u32, u32), - /// Message size exceeded. - #[error("Message size cannot exceed {} bytes; actual size {}", _0, _1)] - MessageTooLarge(u32, u32), -} - #[doc(hidden)] #[cfg(any(feature = "gens", test))] pub mod gens { diff --git a/types/src/lib.rs b/types/src/lib.rs index f53bc22143..a670f7d618 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -105,9 +105,9 @@ pub use chainspec::{ ControlFlowCosts, CoreConfig, DelegatorConfig, DeployConfig, FeeHandling, GenesisAccount, GenesisValidator, GlobalStateUpdate, GlobalStateUpdateConfig, GlobalStateUpdateError, HandlePaymentCosts, HighwayConfig, HostFunction, HostFunctionCost, HostFunctionCosts, - LegacyRequiredFinality, MessagesLimits, MessagesLimitsError, MintCosts, NetworkConfig, - OpcodeCosts, ProtocolConfig, RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, - TransactionConfig, TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, + LegacyRequiredFinality, MessagesLimits, MintCosts, NetworkConfig, OpcodeCosts, ProtocolConfig, + RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, TransactionConfig, + TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, }; #[cfg(any(all(feature = "std", feature = "testing"), test))] pub use chainspec::{ From 566b3534a29355dd56ec8375f4c42d0a2ca0f24c Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Mon, 9 Oct 2023 17:31:23 +0000 Subject: [PATCH 10/27] ee/contract_level_messages: address CR comments Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/mod.rs | 19 +++++------- execution_engine/src/runtime_context/mod.rs | 33 ++++++--------------- types/src/contract_messages.rs | 20 ++++++------- 3 files changed, 26 insertions(+), 46 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index b7df7773bc..b6ffc41832 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -3244,7 +3244,7 @@ where } fn add_message_topic(&mut self, topic_name: String) -> Result, Error> { - let topic_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)).into(); + let topic_hash = crypto::blake2b(&topic_name).into(); self.context .add_message_topic(topic_name, topic_hash) @@ -3262,15 +3262,12 @@ where .into_hash() .ok_or(Error::InvalidContext)?; - let topic_name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)).into(); + let topic_name_hash = crypto::blake2b(&topic_name).into(); let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)); // Check if the topic exists and get the summary. - let prev_topic_summary = if let Some(StoredValue::MessageTopic(message_summary)) = - self.context.read_gs(&topic_key)? - { - message_summary - } else { + let Some(StoredValue::MessageTopic(prev_topic_summary)) = + self.context.read_gs(&topic_key)? else { return Ok(Err(ApiError::MessageTopicNotRegistered)); }; @@ -3285,15 +3282,15 @@ where prev_topic_summary.message_count() }; - let new_topic_summary = StoredValue::MessageTopic(MessageTopicSummary::new( + let new_topic_summary = MessageTopicSummary::new( message_index + 1, //TODO[AS]: need checked add here current_blocktime, - )); + ); let message_key = Key::message(entity_addr, topic_name_hash, message_index); - let message_checksum = StoredValue::Message(MessageChecksum(crypto::blake2b( + let message_checksum = MessageChecksum(crypto::blake2b( message.to_bytes().map_err(Error::BytesRepr)?, - ))); + )); self.context.metered_emit_message( topic_key, diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 7f0d43b0e7..312f09a558 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -22,7 +22,9 @@ use casper_types::{ SetThresholdFailure, UpdateKeyFailure, Weight, }, bytesrepr::ToBytes, - contract_messages::{Message, MessageAddr, MessageTopicSummary, TopicNameHash}, + contract_messages::{ + Message, MessageAddr, MessageChecksum, MessageTopicSummary, TopicNameHash, + }, execution::Effects, package::ContractPackageKind, system::auction::{BidKind, EraInfo}, @@ -954,33 +956,16 @@ where } /// Emits message and writes message summary to global state with a measurement. - pub(crate) fn metered_emit_message( + pub(crate) fn metered_emit_message( &mut self, topic_key: Key, - topic_value: V, + topic_value: MessageTopicSummary, message_key: Key, - message_value: V, + message_value: MessageChecksum, message: Message, - ) -> Result<(), Error> - where - V: Into, - { - let topic_value = topic_value.into(); - let message_value = message_value.into(); - - match topic_value { - StoredValue::MessageTopic(_) => {} - _ => { - return Err(Error::UnexpectedStoredValueVariant); - } - } - - match message_value { - StoredValue::Message(_) => {} - _ => { - return Err(Error::UnexpectedStoredValueVariant); - } - } + ) -> Result<(), Error> { + let topic_value = StoredValue::MessageTopic(topic_value); + let message_value = StoredValue::Message(message_value); // Charge for amount as measured by serialized length let bytes_count = topic_value.serialized_length() + message_value.serialized_length(); diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index b530b66bed..36af634516 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -104,24 +104,22 @@ impl MessageAddr { { Some(topic_string) => (topic_string, None), None => { - let parts = input.rsplitn(2, '-').collect::>(); - if parts.len() != 2 { - return Err(FromStrError::MissingMessageIndex); - } - (parts[1], Some(u32::from_str_radix(parts[0], 16)?)) + let (remainder, message_index_str) = input + .rsplit_once('-') + .ok_or(FromStrError::MissingMessageIndex)?; + (remainder, Some(u32::from_str_radix(message_index_str, 16)?)) } }; - let parts = remainder.splitn(2, '-').collect::>(); - if parts.len() != 2 { - return Err(FromStrError::MissingMessageIndex); - } + let (entity_addr_str, topic_name_hash_str) = remainder + .split_once('-') + .ok_or(FromStrError::MissingMessageIndex)?; - let bytes = checksummed_hex::decode(parts[0])?; + let bytes = checksummed_hex::decode(entity_addr_str)?; let entity_addr = <[u8; KEY_HASH_LENGTH]>::try_from(bytes[0..KEY_HASH_LENGTH].as_ref()) .map_err(|err| FromStrError::EntityHashParseError(err.to_string()))?; - let topic_name_hash = TopicNameHash::from_formatted_str(parts[1])?; + let topic_name_hash = TopicNameHash::from_formatted_str(topic_name_hash_str)?; Ok(MessageAddr { entity_addr, topic_name_hash, From 3190647ec0012448907208e3a8406244433c5486 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Tue, 10 Oct 2023 12:32:06 +0000 Subject: [PATCH 11/27] ee/contract_messages: fix further CR comments Signed-off-by: Alexandru Sardan --- .../tests/src/test/contract_messages.rs | 24 +++++++++-- .../tests/src/test/private_chain.rs | 4 +- .../tests/src/test/regression/ee_966.rs | 4 +- .../tests/src/test/storage_costs.rs | 4 +- .../src/test/system_contracts/upgrade.rs | 4 +- .../tests/src/test/system_costs.rs | 4 +- node/src/utils/chain_specification.rs | 4 +- types/src/addressable_entity.rs | 4 +- types/src/chainspec.rs | 2 +- types/src/chainspec/vm_config.rs | 4 +- .../{messages_limits.rs => message_limits.rs} | 42 +++++++++++++------ types/src/chainspec/vm_config/wasm_config.rs | 12 +++--- types/src/contract_messages/messages.rs | 25 +++++++++++ types/src/contract_messages/topics.rs | 20 +++++++++ types/src/execution/execution_result_v2.rs | 2 +- types/src/gens.rs | 2 +- types/src/key.rs | 2 +- types/src/lib.rs | 2 +- types/src/stored_value.rs | 7 ++-- 19 files changed, 127 insertions(+), 45 deletions(-) rename types/src/chainspec/vm_config/{messages_limits.rs => message_limits.rs} (79%) diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 33259fe1a9..0d5a2a6ade 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -8,8 +8,8 @@ use casper_execution_engine::engine_state::EngineConfigBuilder; use casper_types::{ bytesrepr::ToBytes, contract_messages::{MessageChecksum, MessagePayload, MessageTopicSummary, TopicNameHash}, - crypto, runtime_args, AddressableEntity, ContractHash, Digest, Key, MessagesLimits, - RuntimeArgs, StoredValue, WasmConfig, + crypto, runtime_args, AddressableEntity, ContractHash, Digest, Key, MessageLimits, RuntimeArgs, + StoredValue, WasmConfig, }; const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; @@ -402,7 +402,7 @@ fn should_not_exceed_configured_limits() { default_wasm_config.opcode_costs(), default_wasm_config.storage_costs(), default_wasm_config.take_host_function_costs(), - MessagesLimits { + MessageLimits { max_topic_name_size: 32, max_message_size: 100, max_topics_per_contract: 2, @@ -475,4 +475,22 @@ fn should_not_exceed_configured_limits() { .exec(add_topic_request) .expect_failure() .commit(); + + // Check message size limit + let large_message = std::str::from_utf8(&[0x4du8; 128]).unwrap(); + let emit_message_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_EMIT_MESSAGE, + runtime_args! { + ARG_MESSAGE_SUFFIX_NAME => large_message, + }, + ) + .build(); + + builder + .borrow_mut() + .exec(emit_message_request) + .expect_failure() + .commit(); } diff --git a/execution_engine_testing/tests/src/test/private_chain.rs b/execution_engine_testing/tests/src/test/private_chain.rs index 2d8fb3e9d3..e428d24bf4 100644 --- a/execution_engine_testing/tests/src/test/private_chain.rs +++ b/execution_engine_testing/tests/src/test/private_chain.rs @@ -21,7 +21,7 @@ use once_cell::sync::Lazy; use casper_types::{ account::AccountHash, system::auction::DELEGATION_RATE_DENOMINATOR, AdministratorAccount, - FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessagesLimits, + FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessageLimits, Motes, OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; @@ -208,7 +208,7 @@ fn make_wasm_config() -> WasmConfig { OpcodeCosts::default(), StorageCosts::default(), host_functions, - MessagesLimits::default(), + MessageLimits::default(), ) } diff --git a/execution_engine_testing/tests/src/test/regression/ee_966.rs b/execution_engine_testing/tests/src/test/regression/ee_966.rs index e6b60feaa2..4c92f52d87 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_966.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_966.rs @@ -13,7 +13,7 @@ use casper_execution_engine::{ }; use casper_types::{ addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, ApiError, EraId, HostFunctionCosts, - MessagesLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, + MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, }; @@ -28,7 +28,7 @@ static DOUBLED_WASM_MEMORY_LIMIT: Lazy = Lazy::new(|| { OpcodeCosts::default(), StorageCosts::default(), HostFunctionCosts::default(), - MessagesLimits::default(), + MessageLimits::default(), ) }); static NEW_PROTOCOL_VERSION: Lazy = Lazy::new(|| { diff --git a/execution_engine_testing/tests/src/test/storage_costs.rs b/execution_engine_testing/tests/src/test/storage_costs.rs index 1dc6a7082d..af469f421d 100644 --- a/execution_engine_testing/tests/src/test/storage_costs.rs +++ b/execution_engine_testing/tests/src/test/storage_costs.rs @@ -13,7 +13,7 @@ use casper_types::DEFAULT_ADD_BID_COST; use casper_types::{ bytesrepr::{Bytes, ToBytes}, BrTableCost, CLValue, ContractHash, ControlFlowCosts, EraId, HostFunction, HostFunctionCosts, - MessagesLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, StoredValue, + MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, StoredValue, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; #[cfg(not(feature = "use-as-wasm"))] @@ -143,7 +143,7 @@ static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { NEW_OPCODE_COSTS, StorageCosts::default(), *NEW_HOST_FUNCTION_COSTS, - MessagesLimits::default(), + MessageLimits::default(), ) }); diff --git a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs index 8f2ff53c15..7386d35399 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs @@ -17,7 +17,7 @@ use casper_types::{ }, mint::ROUND_SEIGNIORAGE_RATE_KEY, }, - BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessagesLimits, OpcodeCosts, + BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessageLimits, OpcodeCosts, ProtocolVersion, StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, DEFAULT_BIT_COST, DEFAULT_CONST_COST, DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, DEFAULT_CONTROL_FLOW_BR_OPCODE, DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER, @@ -74,7 +74,7 @@ fn get_upgraded_wasm_config() -> WasmConfig { }; let storage_costs = StorageCosts::default(); let host_function_costs = HostFunctionCosts::default(); - let messages_limits = MessagesLimits::default(); + let messages_limits = MessageLimits::default(); WasmConfig::new( DEFAULT_WASM_MAX_MEMORY, DEFAULT_MAX_STACK_HEIGHT * 2, diff --git a/execution_engine_testing/tests/src/test/system_costs.rs b/execution_engine_testing/tests/src/test/system_costs.rs index 9f22ba91e9..db34b022e3 100644 --- a/execution_engine_testing/tests/src/test/system_costs.rs +++ b/execution_engine_testing/tests/src/test/system_costs.rs @@ -16,7 +16,7 @@ use casper_types::{ handle_payment, mint, AUCTION, }, AuctionCosts, BrTableCost, ControlFlowCosts, EraId, Gas, GenesisAccount, GenesisValidator, - HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessagesLimits, + HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_TRANSFER_COST, DEFAULT_WASMLESS_TRANSFER_COST, @@ -979,7 +979,7 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { new_opcode_costs, new_storage_costs, new_host_function_costs, - MessagesLimits::default(), + MessageLimits::default(), ); let new_wasmless_transfer_cost = 0; diff --git a/node/src/utils/chain_specification.rs b/node/src/utils/chain_specification.rs index cce255931d..a387f8cb6e 100644 --- a/node/src/utils/chain_specification.rs +++ b/node/src/utils/chain_specification.rs @@ -129,7 +129,7 @@ mod tests { use casper_types::{ bytesrepr::FromBytes, ActivationPoint, BrTableCost, ChainspecRawBytes, ControlFlowCosts, CoreConfig, EraId, GlobalStateUpdate, HighwayConfig, HostFunction, HostFunctionCosts, - MessagesLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, + MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, WasmConfig, U512, }; @@ -231,7 +231,7 @@ mod tests { EXPECTED_GENESIS_COSTS, EXPECTED_GENESIS_STORAGE_COSTS, *EXPECTED_GENESIS_HOST_FUNCTION_COSTS, - MessagesLimits::default(), + MessageLimits::default(), ) }); diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index fcbe3abbf9..d48e41dc5f 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -647,7 +647,7 @@ impl MessageTopics { topic_name: String, topic_name_hash: TopicNameHash, ) -> Result<(), MessageTopicError> { - if self.0.len() > u32::MAX as usize { + if self.0.len() >= u32::MAX as usize { return Err(MessageTopicError::MaxTopicsExceeded); } @@ -680,7 +680,7 @@ impl MessageTopics { self.0.is_empty() } - /// Returns an iterator over the account hash and the weights. + /// Returns an iterator over the topic name and its hash. pub fn iter(&self) -> impl Iterator { self.0.iter() } diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index ceeae0148f..6e8727c8ff 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -47,7 +47,7 @@ pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Confi pub use transaction_config::{DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES}; pub use vm_config::{ AuctionCosts, BrTableCost, ChainspecRegistry, ControlFlowCosts, HandlePaymentCosts, - HostFunction, HostFunctionCost, HostFunctionCosts, MessagesLimits, MintCosts, OpcodeCosts, + HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, MintCosts, OpcodeCosts, StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, }; diff --git a/types/src/chainspec/vm_config.rs b/types/src/chainspec/vm_config.rs index 82d575a9f8..34bb856e63 100644 --- a/types/src/chainspec/vm_config.rs +++ b/types/src/chainspec/vm_config.rs @@ -2,7 +2,7 @@ mod auction_costs; mod chainspec_registry; mod handle_payment_costs; mod host_function_costs; -mod messages_limits; +mod message_limits; mod mint_costs; mod opcode_costs; mod standard_payment_costs; @@ -18,7 +18,7 @@ pub use host_function_costs::{ Cost as HostFunctionCost, HostFunction, HostFunctionCosts, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_NEW_DICTIONARY_COST, }; -pub use messages_limits::MessagesLimits; +pub use message_limits::MessageLimits; pub use mint_costs::{MintCosts, DEFAULT_TRANSFER_COST}; pub use opcode_costs::{BrTableCost, ControlFlowCosts, OpcodeCosts}; #[cfg(any(feature = "testing", test))] diff --git a/types/src/chainspec/vm_config/messages_limits.rs b/types/src/chainspec/vm_config/message_limits.rs similarity index 79% rename from types/src/chainspec/vm_config/messages_limits.rs rename to types/src/chainspec/vm_config/message_limits.rs index e66afc0f52..9363515357 100644 --- a/types/src/chainspec/vm_config/messages_limits.rs +++ b/types/src/chainspec/vm_config/message_limits.rs @@ -9,7 +9,7 @@ use crate::bytesrepr::{self, FromBytes, ToBytes}; #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] -pub struct MessagesLimits { +pub struct MessageLimits { /// Maximum size (in bytes) of a topic name string. pub max_topic_name_size: u32, /// Maximum message size in bytes. @@ -18,7 +18,7 @@ pub struct MessagesLimits { pub max_topics_per_contract: u32, } -impl MessagesLimits { +impl MessageLimits { /// Returns the max number of topics a contract can register. pub fn max_topics_per_contract(&self) -> u32 { self.max_topics_per_contract @@ -35,7 +35,7 @@ impl MessagesLimits { } } -impl Default for MessagesLimits { +impl Default for MessageLimits { fn default() -> Self { Self { max_topic_name_size: 256, @@ -45,7 +45,7 @@ impl Default for MessagesLimits { } } -impl ToBytes for MessagesLimits { +impl ToBytes for MessageLimits { fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut ret = bytesrepr::unchecked_allocate_buffer(self); @@ -63,14 +63,14 @@ impl ToBytes for MessagesLimits { } } -impl FromBytes for MessagesLimits { +impl FromBytes for MessageLimits { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (max_topic_name_size, rem) = FromBytes::from_bytes(bytes)?; let (max_message_size, rem) = FromBytes::from_bytes(rem)?; let (max_topics_per_contract, rem) = FromBytes::from_bytes(rem)?; Ok(( - MessagesLimits { + MessageLimits { max_topic_name_size, max_message_size, max_topics_per_contract, @@ -80,9 +80,9 @@ impl FromBytes for MessagesLimits { } } -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> MessagesLimits { - MessagesLimits { +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> MessageLimits { + MessageLimits { max_topic_name_size: rng.gen(), max_message_size: rng.gen(), max_topics_per_contract: rng.gen(), @@ -95,15 +95,15 @@ impl Distribution for Standard { pub mod gens { use proptest::{num, prop_compose}; - use super::MessagesLimits; + use super::MessageLimits; prop_compose! { pub fn message_limits_arb()( max_topic_name_size in num::u32::ANY, max_message_size in num::u32::ANY, max_topics_per_contract in num::u32::ANY, - ) -> MessagesLimits { - MessagesLimits { + ) -> MessageLimits { + MessageLimits { max_topic_name_size, max_message_size, max_topics_per_contract, @@ -111,3 +111,21 @@ pub mod gens { } } } + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use crate::bytesrepr; + + use super::gens; + + proptest! { + #[test] + fn should_serialize_and_deserialize_with_arbitrary_values( + message_limits in gens::message_limits_arb() + ) { + bytesrepr::test_serialization_roundtrip(&message_limits); + } + } +} diff --git a/types/src/chainspec/vm_config/wasm_config.rs b/types/src/chainspec/vm_config/wasm_config.rs index 9535b32418..36b2a391f1 100644 --- a/types/src/chainspec/vm_config/wasm_config.rs +++ b/types/src/chainspec/vm_config/wasm_config.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - chainspec::vm_config::{HostFunctionCosts, MessagesLimits, OpcodeCosts, StorageCosts}, + chainspec::vm_config::{HostFunctionCosts, MessageLimits, OpcodeCosts, StorageCosts}, }; /// Default maximum number of pages of the Wasm memory. @@ -33,7 +33,7 @@ pub struct WasmConfig { /// Host function costs table. host_function_costs: HostFunctionCosts, /// Messages limits. - messages_limits: MessagesLimits, + messages_limits: MessageLimits, } impl WasmConfig { @@ -44,7 +44,7 @@ impl WasmConfig { opcode_costs: OpcodeCosts, storage_costs: StorageCosts, host_function_costs: HostFunctionCosts, - messages_limits: MessagesLimits, + messages_limits: MessageLimits, ) -> Self { Self { max_memory, @@ -72,7 +72,7 @@ impl WasmConfig { } /// Returns the limits config for messages. - pub fn messages_limits(&self) -> MessagesLimits { + pub fn messages_limits(&self) -> MessageLimits { self.messages_limits } } @@ -85,7 +85,7 @@ impl Default for WasmConfig { opcode_costs: OpcodeCosts::default(), storage_costs: StorageCosts::default(), host_function_costs: HostFunctionCosts::default(), - messages_limits: MessagesLimits::default(), + messages_limits: MessageLimits::default(), } } } @@ -156,7 +156,7 @@ pub mod gens { use crate::{ chainspec::vm_config::{ host_function_costs::gens::host_function_costs_arb, - messages_limits::gens::message_limits_arb, opcode_costs::gens::opcode_costs_arb, + message_limits::gens::message_limits_arb, opcode_costs::gens::opcode_costs_arb, storage_costs::gens::storage_costs_arb, }, WasmConfig, diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs index c462152b80..42b8891c2c 100644 --- a/types/src/contract_messages/messages.rs +++ b/types/src/contract_messages/messages.rs @@ -187,3 +187,28 @@ impl FromBytes for Message { )) } } + +#[cfg(test)] +mod tests { + use crate::{bytesrepr, contract_messages::topics::TOPIC_NAME_HASH_LENGTH, KEY_HASH_LENGTH}; + + use super::*; + + #[test] + fn serialization_roundtrip() { + let message_checksum = MessageChecksum([1; MESSAGE_CHECKSUM_LENGTH]); + bytesrepr::test_serialization_roundtrip(&message_checksum); + + let message_payload = MessagePayload::from_string("message payload".to_string()); + bytesrepr::test_serialization_roundtrip(&message_payload); + + let message = Message::new( + [1; KEY_HASH_LENGTH], + message_payload, + "test_topic".to_string(), + TopicNameHash::new([0x4du8; TOPIC_NAME_HASH_LENGTH]), + 10, + ); + bytesrepr::test_serialization_roundtrip(&message); + } +} diff --git a/types/src/contract_messages/topics.rs b/types/src/contract_messages/topics.rs index 85350b40da..9a41d3e39a 100644 --- a/types/src/contract_messages/topics.rs +++ b/types/src/contract_messages/topics.rs @@ -195,6 +195,7 @@ const TOPIC_OPERATION_ADD_TAG: u8 = 0; const OPERATION_MAX_SERIALIZED_LEN: usize = 1; /// Operations that can be performed on message topics. +#[derive(Debug, PartialEq)] pub enum MessageTopicOperation { /// Add a new message topic. Add, @@ -232,3 +233,22 @@ impl FromBytes for MessageTopicOperation { } } } + +#[cfg(test)] +mod tests { + use crate::bytesrepr; + + use super::*; + + #[test] + fn serialization_roundtrip() { + let topic_name_hash = TopicNameHash::new([0x4du8; TOPIC_NAME_HASH_LENGTH]); + bytesrepr::test_serialization_roundtrip(&topic_name_hash); + + let topic_summary = MessageTopicSummary::new(10, BlockTime::new(100)); + bytesrepr::test_serialization_roundtrip(&topic_summary); + + let topic_operation = MessageTopicOperation::Add; + bytesrepr::test_serialization_roundtrip(&topic_operation); + } +} diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 0e98531127..09bb9994b8 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -165,7 +165,7 @@ impl ExecutionResultV2 { .filter_map(|transform| { if let Key::Message(addr) = transform.key() { let topic_name = Alphanumeric.sample_string(rng, 32); - let topic_name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); + let topic_name_hash = crypto::blake2b(&topic_name); Some(Message::new( addr.entity_addr(), MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), diff --git a/types/src/gens.rs b/types/src/gens.rs index 98ceee9c1d..0615ed8b79 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -351,7 +351,7 @@ pub fn message_topics_arb() -> impl Strategy { topic_names .into_iter() .map(|name| { - let name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&name)).into(); + let name_hash = crypto::blake2b(&name).into(); (name, name_hash) }) .collect::>(), diff --git a/types/src/key.rs b/types/src/key.rs index 0355e93be2..4d3bbc939e 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -703,7 +703,7 @@ impl Key { } /// Creates a new [`Key::Message`] variant that identifies an indexed message based on an - /// `entity_addr` `topic_hash` and message `index`. + /// `entity_addr`, `topic_name_hash` and message `index`. pub fn message(entity_addr: HashAddr, topic_name_hash: TopicNameHash, index: u32) -> Key { Key::Message(MessageAddr::new_message_addr( entity_addr, diff --git a/types/src/lib.rs b/types/src/lib.rs index a670f7d618..d98fdc041b 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -105,7 +105,7 @@ pub use chainspec::{ ControlFlowCosts, CoreConfig, DelegatorConfig, DeployConfig, FeeHandling, GenesisAccount, GenesisValidator, GlobalStateUpdate, GlobalStateUpdateConfig, GlobalStateUpdateError, HandlePaymentCosts, HighwayConfig, HostFunction, HostFunctionCost, HostFunctionCosts, - LegacyRequiredFinality, MessagesLimits, MintCosts, NetworkConfig, OpcodeCosts, ProtocolConfig, + LegacyRequiredFinality, MessageLimits, MintCosts, NetworkConfig, OpcodeCosts, ProtocolConfig, RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, TransactionConfig, TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, }; diff --git a/types/src/stored_value.rs b/types/src/stored_value.rs index 8d2ffea2f2..ddb68eb772 100644 --- a/types/src/stored_value.rs +++ b/types/src/stored_value.rs @@ -673,8 +673,9 @@ mod serde_helpers { AddressableEntity(&'a AddressableEntity), /// Variant that stores [`BidKind`]. BidKind(&'a BidKind), - /// Variant that stores [`MessageSummary`]. + /// Variant that stores [`MessageTopicSummary`]. MessageTopic(&'a MessageTopicSummary), + /// Variant that stores a [`MessageChecksum`]. Message(&'a MessageChecksum), } @@ -706,9 +707,9 @@ mod serde_helpers { AddressableEntity(AddressableEntity), /// Variant that stores [`BidKind`]. BidKind(BidKind), - /// Variant that stores [`MessageSummary`]. + /// Variant that stores [`MessageTopicSummary`]. MessageTopic(MessageTopicSummary), - /// Variant that stores [`MessageDigest`]. + /// Variant that stores [`MessageChecksum`]. Message(MessageChecksum), } From 30acebb6e02aae3310ce2c9c066a2e0059b76e52 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Tue, 10 Oct 2023 15:58:57 +0000 Subject: [PATCH 12/27] ee/contract_messages: add more tests Add test for checking that messages topics are carried over to an upgraded contract. Fix check for not allowing session code to register message topics and add test. Signed-off-by: Alexandru Sardan --- Cargo.lock | 16 +++ execution_engine/src/runtime/externals.rs | 12 +-- .../tests/src/test/contract_messages.rs | 98 ++++++++++++++++++- .../contract-messages-emitter/src/main.rs | 23 +++-- .../contract-messages-from-account/Cargo.toml | 16 +++ .../src/main.rs | 15 +++ .../contract-messages-upgrader/Cargo.toml | 16 +++ .../contract-messages-upgrader/src/main.rs | 87 ++++++++++++++++ 8 files changed, 263 insertions(+), 20 deletions(-) create mode 100644 smart_contracts/contracts/test/contract-messages-from-account/Cargo.toml create mode 100644 smart_contracts/contracts/test/contract-messages-from-account/src/main.rs create mode 100644 smart_contracts/contracts/test/contract-messages-upgrader/Cargo.toml create mode 100644 smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 85101ada56..edef621a8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -944,6 +944,22 @@ dependencies = [ "casper-types", ] +[[package]] +name = "contract-messages-from-account" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + +[[package]] +name = "contract-messages-upgrader" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "convert_case" version = "0.4.0" diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index 878bd24012..d89ba037ee 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -12,9 +12,9 @@ use casper_types::{ crypto, package::{ContractPackageKind, ContractPackageStatus}, system::auction::EraInfo, - ApiError, ContractHash, ContractPackageHash, ContractVersion, EraId, Gas, Group, HostFunction, - HostFunctionCost, Key, StoredValue, URef, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, U512, - UREF_SERIALIZED_LENGTH, + ApiError, ContractHash, ContractPackageHash, ContractVersion, EntryPointType, EraId, Gas, + Group, HostFunction, HostFunctionCost, Key, StoredValue, URef, + DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, U512, UREF_SERIALIZED_LENGTH, }; use super::{args::Args, Error, Runtime}; @@ -1141,10 +1141,10 @@ where .t_from_mem(operation_ptr, operation_size) .map_err(|_e| Trap::from(Error::InvalidMessageTopicOperation))?; - // don't allow managing topics for accounts. - if let Key::Account(_) = self.context.get_entity_address() { + // only allow managing messages from stored contracts + let EntryPointType::Contract = self.context.entry_point_type() else { return Err(Trap::from(Error::InvalidContext)); - } + }; let result = match topic_operation { MessageTopicOperation::Add => { diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 0d5a2a6ade..b07d6de34d 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -13,8 +13,11 @@ use casper_types::{ }; const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; -const MESSAGE_EMITTER_PACKAGE_NAME: &str = "messages_emitter_package_name"; +const MESSAGE_EMITTER_UPGRADER_WASM: &str = "contract_messages_upgrader.wasm"; +const MESSAGE_EMITTER_FROM_CONTRACT: &str = "contract_messages_from_account.wasm"; +const MESSAGE_EMITTER_PACKAGE_HASH_KEY_NAME: &str = "messages_emitter_package_hash"; const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; +const MESSAGE_EMITTER_UPGRADED_TOPIC: &str = "new_topic_after_upgrade"; const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; const ARG_TOPIC_NAME: &str = "topic_name"; const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; @@ -45,7 +48,7 @@ fn install_messages_emitter_contract(builder: &RefCell) -> .query( None, Key::from(*DEFAULT_ACCOUNT_ADDR), - &[MESSAGE_EMITTER_PACKAGE_NAME.into()], + &[MESSAGE_EMITTER_PACKAGE_HASH_KEY_NAME.into()], ) .expect("should query"); @@ -63,6 +66,46 @@ fn install_messages_emitter_contract(builder: &RefCell) -> .expect("Should have contract hash") } +fn upgrade_messages_emitter_contract(builder: &RefCell) -> ContractHash { + let upgrade_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + MESSAGE_EMITTER_UPGRADER_WASM, + RuntimeArgs::default(), + ) + .build(); + + // Execute the request to upgrade the message emitting contract. + // This will also register a new topic for the contract to emit messages on. + builder + .borrow_mut() + .exec(upgrade_request) + .expect_success() + .commit(); + + // Get the contract package for the upgraded messages emitter contract. + let query_result = builder + .borrow_mut() + .query( + None, + Key::from(*DEFAULT_ACCOUNT_ADDR), + &[MESSAGE_EMITTER_PACKAGE_HASH_KEY_NAME.into()], + ) + .expect("should query"); + + let message_emitter_package = if let StoredValue::ContractPackage(package) = query_result { + package + } else { + panic!("Stored value is not a contract package: {:?}", query_result); + }; + + // Get the contract hash of the latest version of the messages emitter contract. + *message_emitter_package + .versions() + .contract_hashes() + .last() + .expect("Should have contract hash") +} + fn emit_message_with_suffix( builder: &RefCell, suffix: &str, @@ -494,3 +537,54 @@ fn should_not_exceed_configured_limits() { .expect_failure() .commit(); } + +#[ignore] +#[test] +fn should_carry_message_topics_on_upgraded_contract() { + let builder = RefCell::new(LmdbWasmTestBuilder::default()); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let _ = install_messages_emitter_contract(&builder); + let contract_hash = upgrade_messages_emitter_contract(&builder); + let query_view = ContractQueryView::new(&builder, contract_hash); + + let entity = query_view.entity(); + assert_eq!(entity.message_topics().len(), 2); + let mut expected_topic_names = 0; + for (topic_name, topic_hash) in entity.message_topics().iter() { + if topic_name == MESSAGE_EMITTER_GENERIC_TOPIC + || topic_name == MESSAGE_EMITTER_UPGRADED_TOPIC + { + expected_topic_names += 1; + } + + assert_eq!(query_view.message_topic(*topic_hash).message_count(), 0); + } + assert_eq!(expected_topic_names, 2); +} + +#[ignore] +#[test] +fn should_not_emit_messages_from_account() { + let builder = RefCell::new(LmdbWasmTestBuilder::default()); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + // Request to run a deploy that tries to register a message topic without a contract. + let install_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + MESSAGE_EMITTER_FROM_CONTRACT, + RuntimeArgs::default(), + ) + .build(); + + // Expect to fail since topics can only be registered by stored contracts. + builder + .borrow_mut() + .exec(install_request) + .expect_failure() + .commit(); +} diff --git a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs index ec1c8c425c..084ed8c3d1 100644 --- a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs +++ b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs @@ -3,10 +3,7 @@ #[macro_use] extern crate alloc; -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; use casper_contract::{ contract_api::{runtime, storage}, @@ -20,12 +17,14 @@ use casper_types::{ CLType, CLTyped, Parameter, RuntimeArgs, }; -pub const ENTRY_POINT_INIT: &str = "init"; -pub const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; -pub const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; -pub const MESSAGE_EMITTER_INITIALIZED: &str = "message_emitter_initialized"; -pub const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; -pub const ARG_TOPIC_NAME: &str = "topic_name"; +const ENTRY_POINT_INIT: &str = "init"; +const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; +const MESSAGE_EMITTER_INITIALIZED: &str = "message_emitter_initialized"; +const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; +const ARG_TOPIC_NAME: &str = "topic_name"; +const PACKAGE_HASH_KEY_NAME: &str = "messages_emitter_package_hash"; +const ACCESS_KEY_NAME: &str = "messages_emitter_access"; pub const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; pub const MESSAGE_PREFIX: &str = "generic message: "; @@ -89,8 +88,8 @@ pub extern "C" fn call() { let (stored_contract_hash, _contract_version) = storage::new_contract( emitter_entry_points, Some(NamedKeys::new()), - Some("messages_emitter_package_name".to_string()), - Some("messages_emitter_access_uref".to_string()), + Some(PACKAGE_HASH_KEY_NAME.into()), + Some(ACCESS_KEY_NAME.into()), ); // Call contract to initialize it diff --git a/smart_contracts/contracts/test/contract-messages-from-account/Cargo.toml b/smart_contracts/contracts/test/contract-messages-from-account/Cargo.toml new file mode 100644 index 0000000000..a15fba294c --- /dev/null +++ b/smart_contracts/contracts/test/contract-messages-from-account/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "contract-messages-from-account" +version = "0.1.0" +authors = ["Alexandru Sardan "] +edition = "2018" + +[[bin]] +name = "contract_messages_from_account" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/contract-messages-from-account/src/main.rs b/smart_contracts/contracts/test/contract-messages-from-account/src/main.rs new file mode 100644 index 0000000000..c582143431 --- /dev/null +++ b/smart_contracts/contracts/test/contract-messages-from-account/src/main.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use casper_contract::{contract_api::runtime, unwrap_or_revert::UnwrapOrRevert}; + +use casper_types::contract_messages::MessageTopicOperation; + +const TOPIC_NAME: &str = "messages_topic"; + +#[no_mangle] +pub extern "C" fn call() { + runtime::manage_message_topic(TOPIC_NAME, MessageTopicOperation::Add).unwrap_or_revert(); +} diff --git a/smart_contracts/contracts/test/contract-messages-upgrader/Cargo.toml b/smart_contracts/contracts/test/contract-messages-upgrader/Cargo.toml new file mode 100644 index 0000000000..7db22af9b7 --- /dev/null +++ b/smart_contracts/contracts/test/contract-messages-upgrader/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "contract-messages-upgrader" +version = "0.1.0" +authors = ["Alexandru Sardan "] +edition = "2018" + +[[bin]] +name = "contract_messages_upgrader" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs b/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs new file mode 100644 index 0000000000..322a8db062 --- /dev/null +++ b/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs @@ -0,0 +1,87 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; +use alloc::{string::String, vec::Vec}; + +use casper_contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; + +use casper_types::{ + addressable_entity::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys}, + api_error::ApiError, + contract_messages::{MessagePayload, MessageTopicOperation}, + CLType, CLTyped, ContractPackageHash, Parameter, RuntimeArgs, +}; + +const ENTRY_POINT_INIT: &str = "init"; +const ENTRY_POINT_EMIT_MESSAGE: &str = "upgraded_emit_message"; +const UPGRADED_MESSAGE_EMITTER_INITIALIZED: &str = "upgraded_message_emitter_initialized"; +const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; +const PACKAGE_HASH_KEY_NAME: &str = "messages_emitter_package_hash"; + +pub const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "new_topic_after_upgrade"; +pub const MESSAGE_PREFIX: &str = "generic message: "; + +#[no_mangle] +pub extern "C" fn upgraded_emit_message() { + let suffix: String = runtime::get_named_arg(ARG_MESSAGE_SUFFIX_NAME); + + runtime::emit_message( + MESSAGE_EMITTER_GENERIC_TOPIC, + &MessagePayload::from_string(format!("{}{}", MESSAGE_PREFIX, suffix)), + ) + .unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn init() { + if runtime::has_key(UPGRADED_MESSAGE_EMITTER_INITIALIZED) { + runtime::revert(ApiError::User(0)); + } + + runtime::manage_message_topic(MESSAGE_EMITTER_GENERIC_TOPIC, MessageTopicOperation::Add) + .unwrap_or_revert(); + + runtime::put_key( + UPGRADED_MESSAGE_EMITTER_INITIALIZED, + storage::new_uref(()).into(), + ); +} + +#[no_mangle] +pub extern "C" fn call() { + let mut emitter_entry_points = EntryPoints::new(); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_INIT, + Vec::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_EMIT_MESSAGE, + vec![Parameter::new(ARG_MESSAGE_SUFFIX_NAME, String::cl_type())], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); + + let message_emitter_package_hash: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY_NAME) + .unwrap_or_revert() + .into_hash() + .unwrap() + .into(); + + let (contract_hash, _contract_version) = storage::add_contract_version( + message_emitter_package_hash, + emitter_entry_points, + NamedKeys::new(), + ); + + // Call contract to initialize it + runtime::call_contract::<()>(contract_hash, ENTRY_POINT_INIT, RuntimeArgs::default()); +} From a956dc3947d09311bc96499617c1f43e19eefb67 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Tue, 10 Oct 2023 16:10:09 +0000 Subject: [PATCH 13/27] types/chainspec: fix `WasmConfig` serialization for message limits Signed-off-by: Alexandru Sardan --- types/src/chainspec/vm_config/wasm_config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/src/chainspec/vm_config/wasm_config.rs b/types/src/chainspec/vm_config/wasm_config.rs index 36b2a391f1..ab73b44b6c 100644 --- a/types/src/chainspec/vm_config/wasm_config.rs +++ b/types/src/chainspec/vm_config/wasm_config.rs @@ -99,6 +99,7 @@ impl ToBytes for WasmConfig { ret.append(&mut self.opcode_costs.to_bytes()?); ret.append(&mut self.storage_costs.to_bytes()?); ret.append(&mut self.host_function_costs.to_bytes()?); + ret.append(&mut self.messages_limits.to_bytes()?); Ok(ret) } @@ -109,6 +110,7 @@ impl ToBytes for WasmConfig { + self.opcode_costs.serialized_length() + self.storage_costs.serialized_length() + self.host_function_costs.serialized_length() + + self.messages_limits.serialized_length() } } From ee87c9d1bf1500c0af261d265a4f8f7d9b3b2f1d Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Tue, 10 Oct 2023 16:22:00 +0000 Subject: [PATCH 14/27] types/key: fix `key_max_serialized_length` test Now the `Message` key has the largest serialized length so the test needs to be updated. Signed-off-by: Alexandru Sardan --- types/src/contract_messages.rs | 4 +++- types/src/key.rs | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index 36af634516..a66688e12c 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -6,7 +6,9 @@ mod topics; pub use error::FromStrError; pub use messages::{Message, MessageChecksum, MessagePayload}; -pub use topics::{MessageTopicOperation, MessageTopicSummary, TopicNameHash}; +pub use topics::{ + MessageTopicOperation, MessageTopicSummary, TopicNameHash, TOPIC_NAME_HASH_LENGTH, +}; use crate::{ alloc::string::ToString, diff --git a/types/src/key.rs b/types/src/key.rs index 4d3bbc939e..1d0e0cadad 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -33,9 +33,12 @@ use crate::{ account::{AccountHash, ACCOUNT_HASH_LENGTH}, addressable_entity, addressable_entity::ContractHash, - bytesrepr::{self, Error, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}, + bytesrepr::{ + self, Error, FromBytes, ToBytes, U32_SERIALIZED_LENGTH, U64_SERIALIZED_LENGTH, + U8_SERIALIZED_LENGTH, + }, checksummed_hex, - contract_messages::{self, MessageAddr, TopicNameHash}, + contract_messages::{self, MessageAddr, TopicNameHash, TOPIC_NAME_HASH_LENGTH}, contract_wasm::ContractWasmHash, package::ContractPackageHash, system::auction::{BidAddr, BidAddrTag}, @@ -72,7 +75,6 @@ pub const KEY_DICTIONARY_LENGTH: usize = 32; pub const DICTIONARY_ITEM_KEY_MAX_LENGTH: usize = 128; const PADDING_BYTES: [u8; 32] = [0u8; 32]; const KEY_ID_SERIALIZED_LENGTH: usize = 1; -const BID_TAG_SERIALIZED_LENGTH: usize = 1; // u8 used to determine the ID const KEY_HASH_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_HASH_LENGTH; const KEY_UREF_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + UREF_SERIALIZED_LENGTH; @@ -81,10 +83,6 @@ const KEY_DEPLOY_INFO_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_ const KEY_ERA_INFO_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + U64_SERIALIZED_LENGTH; const KEY_BALANCE_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + UREF_ADDR_LENGTH; const KEY_BID_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_HASH_LENGTH; -const KEY_VALIDATOR_BID_SERIALIZED_LENGTH: usize = - KEY_ID_SERIALIZED_LENGTH + BID_TAG_SERIALIZED_LENGTH + KEY_HASH_LENGTH; -const KEY_DELEGATOR_BID_SERIALIZED_LENGTH: usize = - KEY_VALIDATOR_BID_SERIALIZED_LENGTH + KEY_HASH_LENGTH; const KEY_WITHDRAW_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_HASH_LENGTH; const KEY_UNBOND_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_HASH_LENGTH; const KEY_DICTIONARY_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_DICTIONARY_LENGTH; @@ -95,8 +93,13 @@ const KEY_CHAINSPEC_REGISTRY_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + PADDING_BYTES.len(); const KEY_CHECKSUM_REGISTRY_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + PADDING_BYTES.len(); +const KEY_MESSAGE_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + + KEY_HASH_LENGTH + + TOPIC_NAME_HASH_LENGTH + + U8_SERIALIZED_LENGTH + + U32_SERIALIZED_LENGTH; -const MAX_SERIALIZED_LENGTH: usize = KEY_DELEGATOR_BID_SERIALIZED_LENGTH; +const MAX_SERIALIZED_LENGTH: usize = KEY_MESSAGE_SERIALIZED_LENGTH; /// An alias for [`Key`]s hash variant. pub type HashAddr = [u8; KEY_HASH_LENGTH]; From f6cd4a43d33a9f7dd166ca84a20c0f177afe360c Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Tue, 10 Oct 2023 16:31:42 +0000 Subject: [PATCH 15/27] types/key: fix display test for `Key::Message` variants Signed-off-by: Alexandru Sardan --- types/src/key.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/types/src/key.rs b/types/src/key.rs index 1d0e0cadad..768be323fb 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -1234,7 +1234,7 @@ mod tests { const MESSAGE_KEY: Key = Key::Message(MessageAddr::new_message_addr( [42; 32], TopicNameHash::new([2; 32]), - 9, + 15, )); const KEYS: &[Key] = &[ ACCOUNT_KEY, @@ -1259,6 +1259,9 @@ mod tests { MESSAGE_KEY, ]; const HEX_STRING: &str = "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"; + const TOPIC_NAME_HEX_STRING: &str = + "0202020202020202020202020202020202020202020202020202020202020202"; + const MESSAGE_INDEX_HEX_STRING: &str = "f"; const UNIFIED_HEX_STRING: &str = "002a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"; const VALIDATOR_HEX_STRING: &str = @@ -1393,7 +1396,15 @@ mod tests { ); assert_eq!( format!("{}", MESSAGE_TOPIC_KEY), - format!("Key::MessageTopic({}{})", HEX_STRING, HEX_STRING) + format!("Key::Message({}-{})", HEX_STRING, HEX_STRING) + ); + + assert_eq!( + format!("{}", MESSAGE_KEY), + format!( + "Key::Message({}-{}-{})", + HEX_STRING, TOPIC_NAME_HEX_STRING, MESSAGE_INDEX_HEX_STRING + ) ) } From 8a706e86d4beddf69b8567a513b3abdb832e81b1 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Wed, 11 Oct 2023 12:54:17 +0000 Subject: [PATCH 16/27] types/contract_messages: fix `MessageAddr` from string parsing Signed-off-by: Alexandru Sardan --- types/src/contract_messages.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index a66688e12c..b3a83655cc 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -106,7 +106,7 @@ impl MessageAddr { { Some(topic_string) => (topic_string, None), None => { - let (remainder, message_index_str) = input + let (remainder, message_index_str) = remainder .rsplit_once('-') .ok_or(FromStrError::MissingMessageIndex)?; (remainder, Some(u32::from_str_radix(message_index_str, 16)?)) From c405dbf19fa2d5d668248680b07039f787032108 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Wed, 11 Oct 2023 14:13:35 +0000 Subject: [PATCH 17/27] types: fix lint errors caused by the import of `Key` for contract messages Signed-off-by: Alexandru Sardan --- types/src/execution/execution_result_v2.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 09bb9994b8..f4d12e4e5b 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -27,17 +27,17 @@ use serde::{Deserialize, Serialize}; use super::Effects; #[cfg(feature = "json-schema")] use super::{Transform, TransformKind}; -#[cfg(any(feature = "testing", test))] -use crate::crypto; +#[cfg(any(feature = "testing", feature = "json-schema", test))] +use crate::Key; +#[cfg(feature = "json-schema")] +use crate::KEY_HASH_LENGTH; use crate::{ bytesrepr::{self, FromBytes, ToBytes, RESULT_ERR_TAG, RESULT_OK_TAG, U8_SERIALIZED_LENGTH}, contract_messages::Message, TransferAddr, U512, }; #[cfg(any(feature = "testing", test))] -use crate::{contract_messages::MessagePayload, testing::TestRng}; -#[cfg(feature = "json-schema")] -use crate::{Key, KEY_HASH_LENGTH}; +use crate::{contract_messages::MessagePayload, crypto, testing::TestRng}; #[cfg(feature = "json-schema")] static EXECUTION_RESULT: Lazy = Lazy::new(|| { @@ -114,7 +114,7 @@ impl Distribution for Standard { .filter_map(|transform| { if let Key::Message(addr) = transform.key() { let topic_name = Alphanumeric.sample_string(rng, 32); - let topic_name_hash = crypto::blake2b(AsRef::<[u8]>::as_ref(&topic_name)); + let topic_name_hash = crypto::blake2b(&topic_name); Some(Message::new( addr.entity_addr(), MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), From f1bf8d35d28158e11ad98d2060b4e8d32fe852e5 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 13 Oct 2023 14:32:43 +0000 Subject: [PATCH 18/27] ee/contract_messages: increase gas cost for each emitted message Implemented a mechanism to increase the gas cost for emitting messages linearly for each execution. Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/externals.rs | 3 + execution_engine/src/runtime/mod.rs | 20 +- execution_engine/src/runtime_context/mod.rs | 13 ++ .../tests/src/test/contract_messages.rs | 208 +++++++++++++++++- .../tests/src/test/private_chain.rs | 7 +- .../tests/src/test/regression/ee_966.rs | 5 +- .../tests/src/test/regression/gh_2280.rs | 1 + .../tests/src/test/storage_costs.rs | 5 +- .../src/test/system_contracts/upgrade.rs | 29 +-- .../tests/src/test/system_costs.rs | 7 +- node/src/utils/chain_specification.rs | 6 +- resources/local/chainspec.toml.in | 4 + resources/production/chainspec.toml | 4 + .../contract-messages-emitter/src/main.rs | 26 ++- .../contract-messages-upgrader/src/main.rs | 56 ++++- types/src/chainspec.rs | 4 +- types/src/chainspec/vm_config.rs | 2 + .../vm_config/host_function_costs.rs | 134 +++++++++++ .../src/chainspec/vm_config/message_costs.rs | 143 ++++++++++++ types/src/chainspec/vm_config/opcode_costs.rs | 152 +++++++++++++ .../src/chainspec/vm_config/storage_costs.rs | 23 ++ types/src/chainspec/vm_config/wasm_config.rs | 25 ++- types/src/contract_messages/messages.rs | 12 +- types/src/execution/execution_result_v2.rs | 6 +- types/src/lib.rs | 6 +- 25 files changed, 845 insertions(+), 56 deletions(-) create mode 100644 types/src/chainspec/vm_config/message_costs.rs diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index d89ba037ee..2427f0e814 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -1184,6 +1184,9 @@ where let message = self.t_from_mem(message_ptr, message_size)?; let result = self.emit_message(topic_name, message)?; + if result.is_ok() { + self.charge_emit_message()?; + } Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index b6ffc41832..429a6ae45b 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -1396,8 +1396,10 @@ where // The `runtime`'s context was initialized with our counter from before the call and any gas // charged by the sub-call was added to its counter - so let's copy the correct value of the - // counter from there to our counter. + // counter from there to our counter. Do the same for the message cost tracking. self.context.set_gas_counter(runtime.context.gas_counter()); + self.context + .set_last_message_cost(runtime.context.last_message_cost()); { let transfers = self.context.transfers_mut(); @@ -2974,6 +2976,22 @@ where Ok(()) } + /// Charge gas cost for emitting a message. + fn charge_emit_message(&mut self) -> Result<(), Trap> { + let costs = self.context.engine_config().wasm_config().message_costs(); + let cost = match self.context.last_message_cost() { + last_cost if last_cost == U512::from(0) => Gas::new(costs.first_message_cost().into()), + last_cost => Gas::new( + last_cost + .checked_add(costs.cost_increase_per_message().into()) + .ok_or(Error::GasLimit)?, + ), + }; + self.gas(cost)?; + self.context.set_last_message_cost(cost.value()); + Ok(()) + } + /// Creates a dictionary fn new_dictionary(&mut self, output_size_ptr: u32) -> Result, Error> { // check we can write to the host buffer diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 312f09a558..76e8c62875 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -72,6 +72,7 @@ pub struct RuntimeContext<'a, R> { entity_address: Key, package_kind: ContractPackageKind, account_hash: AccountHash, + last_message_cost: U512, } impl<'a, R> RuntimeContext<'a, R> @@ -126,6 +127,7 @@ where transfers, remaining_spending_limit, package_kind, + last_message_cost: U512::from(0), } } @@ -180,6 +182,7 @@ where transfers, remaining_spending_limit, package_kind, + last_message_cost: self.last_message_cost, } } @@ -665,6 +668,16 @@ where self.tracking_copy.borrow().messages() } + /// Returns the cost charged for the last emitted message. + pub fn last_message_cost(&self) -> U512 { + self.last_message_cost + } + + /// Sets the cost charged for the last emitted message. + pub fn set_last_message_cost(&mut self, last_cost: U512) { + self.last_message_cost = last_cost + } + /// Returns list of transfers. pub fn transfers(&self) -> &Vec { &self.transfers diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index b07d6de34d..1ac97ac262 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -1,3 +1,4 @@ +use num_traits::Zero; use std::cell::RefCell; use casper_engine_test_support::{ @@ -8,17 +9,21 @@ use casper_execution_engine::engine_state::EngineConfigBuilder; use casper_types::{ bytesrepr::ToBytes, contract_messages::{MessageChecksum, MessagePayload, MessageTopicSummary, TopicNameHash}, - crypto, runtime_args, AddressableEntity, ContractHash, Digest, Key, MessageLimits, RuntimeArgs, - StoredValue, WasmConfig, + crypto, runtime_args, AddressableEntity, BlockTime, ContractHash, Digest, HostFunctionCosts, + Key, MessageCosts, MessageLimits, OpcodeCosts, RuntimeArgs, StorageCosts, StoredValue, + WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; const MESSAGE_EMITTER_UPGRADER_WASM: &str = "contract_messages_upgrader.wasm"; -const MESSAGE_EMITTER_FROM_CONTRACT: &str = "contract_messages_from_account.wasm"; +const MESSAGE_EMITTER_FROM_ACCOUNT: &str = "contract_messages_from_account.wasm"; const MESSAGE_EMITTER_PACKAGE_HASH_KEY_NAME: &str = "messages_emitter_package_hash"; const MESSAGE_EMITTER_GENERIC_TOPIC: &str = "generic_messages"; const MESSAGE_EMITTER_UPGRADED_TOPIC: &str = "new_topic_after_upgrade"; const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +const ENTRY_POINT_EMIT_MULTIPLE_MESSAGES: &str = "emit_multiple_messages"; +const ENTRY_POINT_EMIT_MESSAGE_FROM_EACH_VERSION: &str = "emit_message_from_each_version"; +const ARG_NUM_MESSAGES_TO_EMIT: &str = "num_messages_to_emit"; const ARG_TOPIC_NAME: &str = "topic_name"; const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; @@ -232,8 +237,7 @@ fn should_emit_messages() { // Now call the entry point to emit some messages. emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); - let expected_message = - MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test")); + let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test")); let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); let queried_message_summary = query_view .message_summary(*message_topic_hash, 0, None) @@ -271,7 +275,7 @@ fn should_emit_messages() { DEFAULT_BLOCK_TIME + 1, ); let expected_message = - MessagePayload::from_string(format!("{}{}", EMITTER_MESSAGE_PREFIX, "new block time")); + MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "new block time")); let expected_message_hash = crypto::blake2b(expected_message.to_bytes().unwrap()); let queried_message_summary = query_view .message_summary(*message_topic_hash, 0, None) @@ -450,6 +454,7 @@ fn should_not_exceed_configured_limits() { max_message_size: 100, max_topics_per_contract: 2, }, + MessageCosts::default(), )) .build(); @@ -573,10 +578,10 @@ fn should_not_emit_messages_from_account() { .borrow_mut() .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); - // Request to run a deploy that tries to register a message topic without a contract. + // Request to run a deploy that tries to register a message topic without a stored contract. let install_request = ExecuteRequestBuilder::standard( *DEFAULT_ACCOUNT_ADDR, - MESSAGE_EMITTER_FROM_CONTRACT, + MESSAGE_EMITTER_FROM_ACCOUNT, RuntimeArgs::default(), ) .build(); @@ -588,3 +593,190 @@ fn should_not_emit_messages_from_account() { .expect_failure() .commit(); } + +#[ignore] +#[test] +fn should_charge_expected_gas_for_storage() { + const GAS_PER_BYTE_COST: u32 = 100; + + let wasm_config = WasmConfig::new( + DEFAULT_WASM_MAX_MEMORY, + DEFAULT_MAX_STACK_HEIGHT, + OpcodeCosts::zero(), + StorageCosts::new(GAS_PER_BYTE_COST), + HostFunctionCosts::zero(), + MessageLimits::default(), + MessageCosts::zero(), + ); + let custom_engine_config = EngineConfigBuilder::default() + .with_wasm_config(wasm_config) + .build(); + + let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config( + custom_engine_config, + )); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let contract_hash = install_messages_emitter_contract(&builder); + let query_view = ContractQueryView::new(&builder, contract_hash); + + // check the cost of adding a new topic + let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_ADD_TOPIC, + runtime_args! { + ARG_TOPIC_NAME => "cost_topic", + }, + ) + .build(); + + builder + .borrow_mut() + .exec(add_topic_request) + .expect_success() + .commit(); + + let add_topic_cost = builder.borrow().last_exec_gas_cost().value(); + + // cost depends on the entity size since we store the topic names in the entity record. + let entity = query_view.entity(); + let default_topic_summary = MessageTopicSummary::new(0, BlockTime::new(0)); + let written_size_expected = StoredValue::MessageTopic(default_topic_summary.clone()) + .serialized_length() + + StoredValue::AddressableEntity(entity).serialized_length(); + assert_eq!( + U512::from(written_size_expected * GAS_PER_BYTE_COST as usize), + add_topic_cost + ); + + // check that the storage cost charged is invariable with message size that is emitted. + let written_size_expected = StoredValue::Message(MessageChecksum([0; 32])).serialized_length() + + StoredValue::MessageTopic(default_topic_summary).serialized_length(); + + emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); + let emit_message_gas_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!( + U512::from(written_size_expected * GAS_PER_BYTE_COST as usize), + emit_message_gas_cost + ); + + emit_message_with_suffix(&builder, "test 12345", &contract_hash, DEFAULT_BLOCK_TIME); + let emit_message_gas_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!( + U512::from(written_size_expected * GAS_PER_BYTE_COST as usize), + emit_message_gas_cost + ); + + // emitting messages in a different block will also prune the old entries so check the cost. + emit_message_with_suffix( + &builder, + "message in different block", + &contract_hash, + DEFAULT_BLOCK_TIME + 1, + ); + let emit_message_gas_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!( + U512::from(written_size_expected * GAS_PER_BYTE_COST as usize), + emit_message_gas_cost + ); +} + +#[ignore] +#[test] +fn should_charge_increasing_gas_cost_for_multiple_messages_emitted() { + const FIRST_MESSAGE_EMIT_COST: u32 = 100; + const COST_INCREASE_PER_MESSAGE: u32 = 50; + const MESSAGES_TO_EMIT: u32 = 4; + const EMIT_MULTIPLE_EXPECTED_COST: u32 = FIRST_MESSAGE_EMIT_COST * MESSAGES_TO_EMIT + + (MESSAGES_TO_EMIT - 1) * MESSAGES_TO_EMIT / 2 * COST_INCREASE_PER_MESSAGE; + const EMIT_MESSAGES_FROM_MULTIPLE_CONTRACTS: u32 = + FIRST_MESSAGE_EMIT_COST * 3 + 3 * COST_INCREASE_PER_MESSAGE; // simplification of above + + let wasm_config = WasmConfig::new( + DEFAULT_WASM_MAX_MEMORY, + DEFAULT_MAX_STACK_HEIGHT, + OpcodeCosts::zero(), + StorageCosts::zero(), + HostFunctionCosts::zero(), + MessageLimits::default(), + MessageCosts { + first_message_cost: FIRST_MESSAGE_EMIT_COST, + cost_increase_per_message: COST_INCREASE_PER_MESSAGE, + }, + ); + + let custom_engine_config = EngineConfigBuilder::default() + .with_wasm_config(wasm_config) + .build(); + + let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config( + custom_engine_config, + )); + builder + .borrow_mut() + .run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); + + let contract_hash = install_messages_emitter_contract(&builder); + + // Emit one message in this execution. Cost should be `FIRST_MESSAGE_EMIT_COST`. + emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); + let emit_message_gas_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!(emit_message_gas_cost, FIRST_MESSAGE_EMIT_COST.into()); + + // Emit multiple messages in this execution. Cost should increase for each message emitted. + let emit_messages_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_EMIT_MULTIPLE_MESSAGES, + runtime_args! { + ARG_NUM_MESSAGES_TO_EMIT => MESSAGES_TO_EMIT, + }, + ) + .build(); + builder + .borrow_mut() + .exec(emit_messages_request) + .expect_success() + .commit(); + + let emit_multiple_messages_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!( + emit_multiple_messages_cost, + EMIT_MULTIPLE_EXPECTED_COST.into() + ); + + // Try another execution where we emit a single message. + // Cost should be `FIRST_MESSAGE_EMIT_COST` + emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); + let emit_message_gas_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!(emit_message_gas_cost, FIRST_MESSAGE_EMIT_COST.into()); + + // Check gas cost when multiple messages are emitted from different contracts. + let contract_hash = upgrade_messages_emitter_contract(&builder); + let emit_message_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + contract_hash, + ENTRY_POINT_EMIT_MESSAGE_FROM_EACH_VERSION, + runtime_args! { + ARG_MESSAGE_SUFFIX_NAME => "test message", + }, + ) + .build(); + + builder + .borrow_mut() + .exec(emit_message_request) + .expect_success() + .commit(); + + // 3 messages are emitted by this execution so the cost would be: + // `EMIT_MESSAGES_FROM_MULTIPLE_CONTRACTS` + let emit_message_gas_cost = builder.borrow().last_exec_gas_cost().value(); + assert_eq!( + emit_message_gas_cost, + U512::from(EMIT_MESSAGES_FROM_MULTIPLE_CONTRACTS) + ); +} diff --git a/execution_engine_testing/tests/src/test/private_chain.rs b/execution_engine_testing/tests/src/test/private_chain.rs index e428d24bf4..7a0b1f194f 100644 --- a/execution_engine_testing/tests/src/test/private_chain.rs +++ b/execution_engine_testing/tests/src/test/private_chain.rs @@ -21,9 +21,9 @@ use once_cell::sync::Lazy; use casper_types::{ account::AccountHash, system::auction::DELEGATION_RATE_DENOMINATOR, AdministratorAccount, - FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessageLimits, - Motes, OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, WasmConfig, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessageCosts, + MessageLimits, Motes, OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, + WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; use tempfile::TempDir; @@ -209,6 +209,7 @@ fn make_wasm_config() -> WasmConfig { StorageCosts::default(), host_functions, MessageLimits::default(), + MessageCosts::default(), ) } diff --git a/execution_engine_testing/tests/src/test/regression/ee_966.rs b/execution_engine_testing/tests/src/test/regression/ee_966.rs index 4c92f52d87..2abc5fdfb4 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_966.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_966.rs @@ -13,8 +13,8 @@ use casper_execution_engine::{ }; use casper_types::{ addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, ApiError, EraId, HostFunctionCosts, - MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, + MessageCosts, MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, + WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, }; const CONTRACT_EE_966_REGRESSION: &str = "ee_966_regression.wasm"; @@ -29,6 +29,7 @@ static DOUBLED_WASM_MEMORY_LIMIT: Lazy = Lazy::new(|| { StorageCosts::default(), HostFunctionCosts::default(), MessageLimits::default(), + MessageCosts::default(), ) }); static NEW_PROTOCOL_VERSION: Lazy = Lazy::new(|| { diff --git a/execution_engine_testing/tests/src/test/regression/gh_2280.rs b/execution_engine_testing/tests/src/test/regression/gh_2280.rs index e7a5852d38..c88c81bb24 100644 --- a/execution_engine_testing/tests/src/test/regression/gh_2280.rs +++ b/execution_engine_testing/tests/src/test/regression/gh_2280.rs @@ -759,6 +759,7 @@ fn make_wasm_config( old_wasm_config.storage_costs(), new_host_function_costs, old_wasm_config.messages_limits(), + old_wasm_config.message_costs(), ) } diff --git a/execution_engine_testing/tests/src/test/storage_costs.rs b/execution_engine_testing/tests/src/test/storage_costs.rs index af469f421d..afd98770a3 100644 --- a/execution_engine_testing/tests/src/test/storage_costs.rs +++ b/execution_engine_testing/tests/src/test/storage_costs.rs @@ -13,8 +13,8 @@ use casper_types::DEFAULT_ADD_BID_COST; use casper_types::{ bytesrepr::{Bytes, ToBytes}, BrTableCost, CLValue, ContractHash, ControlFlowCosts, EraId, HostFunction, HostFunctionCosts, - MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, StoredValue, - WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + MessageCosts, MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, + StoredValue, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; #[cfg(not(feature = "use-as-wasm"))] use casper_types::{ @@ -144,6 +144,7 @@ static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { StorageCosts::default(), *NEW_HOST_FUNCTION_COSTS, MessageLimits::default(), + MessageCosts::default(), ) }); diff --git a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs index 7386d35399..35a76ba545 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs @@ -17,19 +17,20 @@ use casper_types::{ }, mint::ROUND_SEIGNIORAGE_RATE_KEY, }, - BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessageLimits, OpcodeCosts, - ProtocolVersion, StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, DEFAULT_BIT_COST, - DEFAULT_CONST_COST, DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, - DEFAULT_CONTROL_FLOW_BR_OPCODE, DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER, - DEFAULT_CONTROL_FLOW_BR_TABLE_OPCODE, DEFAULT_CONTROL_FLOW_CALL_INDIRECT_OPCODE, - DEFAULT_CONTROL_FLOW_CALL_OPCODE, DEFAULT_CONTROL_FLOW_DROP_OPCODE, - DEFAULT_CONTROL_FLOW_ELSE_OPCODE, DEFAULT_CONTROL_FLOW_END_OPCODE, - DEFAULT_CONTROL_FLOW_IF_OPCODE, DEFAULT_CONTROL_FLOW_LOOP_OPCODE, - DEFAULT_CONTROL_FLOW_RETURN_OPCODE, DEFAULT_CONTROL_FLOW_SELECT_OPCODE, - DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, DEFAULT_DIV_COST, DEFAULT_GLOBAL_COST, - DEFAULT_GROW_MEMORY_COST, DEFAULT_INTEGER_COMPARISON_COST, DEFAULT_LOAD_COST, - DEFAULT_LOCAL_COST, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MUL_COST, DEFAULT_NOP_COST, - DEFAULT_STORE_COST, DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, U256, U512, + BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessageCosts, MessageLimits, + OpcodeCosts, ProtocolVersion, StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, + DEFAULT_BIT_COST, DEFAULT_CONST_COST, DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, + DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, DEFAULT_CONTROL_FLOW_BR_OPCODE, + DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER, DEFAULT_CONTROL_FLOW_BR_TABLE_OPCODE, + DEFAULT_CONTROL_FLOW_CALL_INDIRECT_OPCODE, DEFAULT_CONTROL_FLOW_CALL_OPCODE, + DEFAULT_CONTROL_FLOW_DROP_OPCODE, DEFAULT_CONTROL_FLOW_ELSE_OPCODE, + DEFAULT_CONTROL_FLOW_END_OPCODE, DEFAULT_CONTROL_FLOW_IF_OPCODE, + DEFAULT_CONTROL_FLOW_LOOP_OPCODE, DEFAULT_CONTROL_FLOW_RETURN_OPCODE, + DEFAULT_CONTROL_FLOW_SELECT_OPCODE, DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, + DEFAULT_DIV_COST, DEFAULT_GLOBAL_COST, DEFAULT_GROW_MEMORY_COST, + DEFAULT_INTEGER_COMPARISON_COST, DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, + DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MUL_COST, DEFAULT_NOP_COST, DEFAULT_STORE_COST, + DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, U256, U512, }; const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V1_0_0; @@ -75,6 +76,7 @@ fn get_upgraded_wasm_config() -> WasmConfig { let storage_costs = StorageCosts::default(); let host_function_costs = HostFunctionCosts::default(); let messages_limits = MessageLimits::default(); + let message_costs = MessageCosts::default(); WasmConfig::new( DEFAULT_WASM_MAX_MEMORY, DEFAULT_MAX_STACK_HEIGHT * 2, @@ -82,6 +84,7 @@ fn get_upgraded_wasm_config() -> WasmConfig { storage_costs, host_function_costs, messages_limits, + message_costs, ) } diff --git a/execution_engine_testing/tests/src/test/system_costs.rs b/execution_engine_testing/tests/src/test/system_costs.rs index db34b022e3..d1c48eecb4 100644 --- a/execution_engine_testing/tests/src/test/system_costs.rs +++ b/execution_engine_testing/tests/src/test/system_costs.rs @@ -16,9 +16,9 @@ use casper_types::{ handle_payment, mint, AUCTION, }, AuctionCosts, BrTableCost, ControlFlowCosts, EraId, Gas, GenesisAccount, GenesisValidator, - HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, - MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, - StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, + HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageCosts, + MessageLimits, MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, + SecretKey, StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_TRANSFER_COST, DEFAULT_WASMLESS_TRANSFER_COST, DEFAULT_WASM_MAX_MEMORY, U512, }; @@ -980,6 +980,7 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { new_storage_costs, new_host_function_costs, MessageLimits::default(), + MessageCosts::default(), ); let new_wasmless_transfer_cost = 0; diff --git a/node/src/utils/chain_specification.rs b/node/src/utils/chain_specification.rs index a387f8cb6e..de2caa1098 100644 --- a/node/src/utils/chain_specification.rs +++ b/node/src/utils/chain_specification.rs @@ -129,8 +129,9 @@ mod tests { use casper_types::{ bytesrepr::FromBytes, ActivationPoint, BrTableCost, ChainspecRawBytes, ControlFlowCosts, CoreConfig, EraId, GlobalStateUpdate, HighwayConfig, HostFunction, HostFunctionCosts, - MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, - StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, WasmConfig, U512, + MessageCosts, MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, + StorageCosts, StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, + WasmConfig, U512, }; use super::*; @@ -232,6 +233,7 @@ mod tests { EXPECTED_GENESIS_STORAGE_COSTS, *EXPECTED_GENESIS_HOST_FUNCTION_COSTS, MessageLimits::default(), + MessageCosts::default(), ) }); diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 343d714dfc..3cda18e078 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -261,6 +261,10 @@ max_topic_name_size = 256 max_topics_per_contract = 128 max_message_size = 1_024 +[wasm.message_costs] +first_message_cost = 100 +cost_increase_per_message = 50 + [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index f56713248d..333a5d45ba 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -272,6 +272,10 @@ max_topic_name_size = 256 max_topics_per_contract = 128 max_message_size = 1_024 +[wasm.message_costs] +first_message_cost = 100 +cost_increase_per_message = 50 + [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs index 084ed8c3d1..3bf23c3d21 100644 --- a/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs +++ b/smart_contracts/contracts/test/contract-messages-emitter/src/main.rs @@ -13,15 +13,17 @@ use casper_contract::{ use casper_types::{ addressable_entity::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys}, api_error::ApiError, - contract_messages::{MessagePayload, MessageTopicOperation}, + contract_messages::MessageTopicOperation, CLType, CLTyped, Parameter, RuntimeArgs, }; const ENTRY_POINT_INIT: &str = "init"; const ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; +const ENTRY_POINT_EMIT_MULTIPLE_MESSAGES: &str = "emit_multiple_messages"; const ENTRY_POINT_ADD_TOPIC: &str = "add_topic"; const MESSAGE_EMITTER_INITIALIZED: &str = "message_emitter_initialized"; const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; +const ARG_NUM_MESSAGES_TO_EMIT: &str = "num_messages_to_emit"; const ARG_TOPIC_NAME: &str = "topic_name"; const PACKAGE_HASH_KEY_NAME: &str = "messages_emitter_package_hash"; const ACCESS_KEY_NAME: &str = "messages_emitter_access"; @@ -35,11 +37,24 @@ pub extern "C" fn emit_message() { runtime::emit_message( MESSAGE_EMITTER_GENERIC_TOPIC, - &MessagePayload::from_string(format!("{}{}", MESSAGE_PREFIX, suffix)), + &format!("{}{}", MESSAGE_PREFIX, suffix).into(), ) .unwrap_or_revert(); } +#[no_mangle] +pub extern "C" fn emit_multiple_messages() { + let num_messages: u32 = runtime::get_named_arg(ARG_NUM_MESSAGES_TO_EMIT); + + for i in 0..num_messages { + runtime::emit_message( + MESSAGE_EMITTER_GENERIC_TOPIC, + &format!("{}{}", MESSAGE_PREFIX, i).into(), + ) + .unwrap_or_revert(); + } +} + #[no_mangle] pub extern "C" fn add_topic() { let topic_name: String = runtime::get_named_arg(ARG_TOPIC_NAME); @@ -84,6 +99,13 @@ pub extern "C" fn call() { EntryPointAccess::Public, EntryPointType::Contract, )); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_EMIT_MULTIPLE_MESSAGES, + vec![Parameter::new(ARG_NUM_MESSAGES_TO_EMIT, u32::cl_type())], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); let (stored_contract_hash, _contract_version) = storage::new_contract( emitter_entry_points, diff --git a/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs b/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs index 322a8db062..70d678ba08 100644 --- a/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs +++ b/smart_contracts/contracts/test/contract-messages-upgrader/src/main.rs @@ -13,12 +13,14 @@ use casper_contract::{ use casper_types::{ addressable_entity::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys}, api_error::ApiError, - contract_messages::{MessagePayload, MessageTopicOperation}, - CLType, CLTyped, ContractPackageHash, Parameter, RuntimeArgs, + contract_messages::MessageTopicOperation, + runtime_args, CLType, CLTyped, ContractPackageHash, Parameter, RuntimeArgs, }; const ENTRY_POINT_INIT: &str = "init"; +const FIRST_VERSION_ENTRY_POINT_EMIT_MESSAGE: &str = "emit_message"; const ENTRY_POINT_EMIT_MESSAGE: &str = "upgraded_emit_message"; +const ENTRY_POINT_EMIT_MESSAGE_FROM_EACH_VERSION: &str = "emit_message_from_each_version"; const UPGRADED_MESSAGE_EMITTER_INITIALIZED: &str = "upgraded_message_emitter_initialized"; const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; const PACKAGE_HASH_KEY_NAME: &str = "messages_emitter_package_hash"; @@ -32,7 +34,41 @@ pub extern "C" fn upgraded_emit_message() { runtime::emit_message( MESSAGE_EMITTER_GENERIC_TOPIC, - &MessagePayload::from_string(format!("{}{}", MESSAGE_PREFIX, suffix)), + &format!("{}{}", MESSAGE_PREFIX, suffix).into(), + ) + .unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn emit_message_from_each_version() { + let suffix: String = runtime::get_named_arg(ARG_MESSAGE_SUFFIX_NAME); + let contract_package_hash: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY_NAME) + .expect("should have contract package key") + .into_hash() + .unwrap_or_revert() + .into(); + + // Emit a message from this contract. + runtime::emit_message( + MESSAGE_EMITTER_GENERIC_TOPIC, + &"emitting multiple messages".into(), + ) + .unwrap_or_revert(); + + // Call previous contract version which will emit a message. + runtime::call_versioned_contract::<()>( + contract_package_hash, + Some(1), + FIRST_VERSION_ENTRY_POINT_EMIT_MESSAGE, + runtime_args! { + ARG_MESSAGE_SUFFIX_NAME => suffix.clone(), + }, + ); + + // Emit another message from this version. + runtime::emit_message( + MESSAGE_EMITTER_GENERIC_TOPIC, + &format!("{}{}", MESSAGE_PREFIX, suffix).into(), ) .unwrap_or_revert(); } @@ -69,6 +105,13 @@ pub extern "C" fn call() { EntryPointAccess::Public, EntryPointType::Contract, )); + emitter_entry_points.add_entry_point(EntryPoint::new( + ENTRY_POINT_EMIT_MESSAGE_FROM_EACH_VERSION, + vec![Parameter::new(ARG_MESSAGE_SUFFIX_NAME, String::cl_type())], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); let message_emitter_package_hash: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY_NAME) .unwrap_or_revert() @@ -76,10 +119,15 @@ pub extern "C" fn call() { .unwrap() .into(); + let mut named_keys = NamedKeys::new(); + named_keys.insert( + PACKAGE_HASH_KEY_NAME.into(), + message_emitter_package_hash.into(), + ); let (contract_hash, _contract_version) = storage::add_contract_version( message_emitter_package_hash, emitter_entry_points, - NamedKeys::new(), + named_keys, ); // Call contract to initialize it diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index 6e8727c8ff..b61090af7a 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -47,8 +47,8 @@ pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Confi pub use transaction_config::{DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES}; pub use vm_config::{ AuctionCosts, BrTableCost, ChainspecRegistry, ControlFlowCosts, HandlePaymentCosts, - HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, MintCosts, OpcodeCosts, - StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, + HostFunction, HostFunctionCost, HostFunctionCosts, MessageCosts, MessageLimits, MintCosts, + OpcodeCosts, StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, }; #[cfg(any(feature = "testing", test))] diff --git a/types/src/chainspec/vm_config.rs b/types/src/chainspec/vm_config.rs index 34bb856e63..33f39d3d4d 100644 --- a/types/src/chainspec/vm_config.rs +++ b/types/src/chainspec/vm_config.rs @@ -2,6 +2,7 @@ mod auction_costs; mod chainspec_registry; mod handle_payment_costs; mod host_function_costs; +mod message_costs; mod message_limits; mod mint_costs; mod opcode_costs; @@ -18,6 +19,7 @@ pub use host_function_costs::{ Cost as HostFunctionCost, HostFunction, HostFunctionCosts, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_NEW_DICTIONARY_COST, }; +pub use message_costs::MessageCosts; pub use message_limits::MessageLimits; pub use mint_costs::{MintCosts, DEFAULT_TRANSFER_COST}; pub use opcode_costs::{BrTableCost, ControlFlowCosts, OpcodeCosts}; diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index 92e42ed78f..50a5da5d5a 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -1,6 +1,9 @@ //! Support for host function gas cost tables. +use core::ops::Add; + #[cfg(feature = "datasize")] use datasize::DataSize; +use num_traits::Zero; use rand::{distributions::Standard, prelude::Distribution, Rng}; use serde::{Deserialize, Serialize}; @@ -153,6 +156,18 @@ where } } +impl Add for HostFunction<[Cost; COUNT]> { + type Output = HostFunction<[Cost; COUNT]>; + + fn add(self, rhs: Self) -> Self::Output { + let mut result = HostFunction::new(self.cost + rhs.cost, [0; COUNT]); + for i in 0..COUNT { + result.arguments[i] = self.arguments[i] + rhs.arguments[i]; + } + result + } +} + impl Distribution> for Standard where Standard: Distribution, @@ -301,6 +316,125 @@ pub struct HostFunctionCosts { pub emit_message: HostFunction<[Cost; 4]>, } +impl Add for HostFunctionCosts { + type Output = HostFunctionCosts; + + fn add(self, rhs: Self) -> Self::Output { + Self { + read_value: self.read_value + rhs.read_value, + dictionary_get: self.dictionary_get + rhs.dictionary_get, + write: self.write + rhs.write, + dictionary_put: self.dictionary_put + rhs.dictionary_put, + add: self.add + rhs.add, + new_uref: self.new_uref + rhs.new_uref, + load_named_keys: self.load_named_keys + rhs.load_named_keys, + ret: self.ret + rhs.ret, + get_key: self.get_key + rhs.get_key, + has_key: self.has_key + rhs.has_key, + put_key: self.put_key + rhs.put_key, + remove_key: self.remove_key + rhs.remove_key, + revert: self.revert + rhs.revert, + is_valid_uref: self.is_valid_uref + rhs.is_valid_uref, + add_associated_key: self.add_associated_key + rhs.add_associated_key, + remove_associated_key: self.remove_associated_key + rhs.remove_associated_key, + update_associated_key: self.update_associated_key + rhs.update_associated_key, + set_action_threshold: self.set_action_threshold + rhs.set_action_threshold, + get_caller: self.get_caller + rhs.get_caller, + get_blocktime: self.get_blocktime + rhs.get_blocktime, + create_purse: self.create_purse + rhs.create_purse, + transfer_to_account: self.transfer_to_account + rhs.transfer_to_account, + transfer_from_purse_to_account: self.transfer_from_purse_to_account + + rhs.transfer_from_purse_to_account, + transfer_from_purse_to_purse: self.transfer_from_purse_to_purse + + rhs.transfer_from_purse_to_purse, + get_balance: self.get_balance + rhs.get_balance, + get_phase: self.get_phase + rhs.get_phase, + get_system_contract: self.get_system_contract + rhs.get_system_contract, + get_main_purse: self.get_main_purse + rhs.get_main_purse, + read_host_buffer: self.read_host_buffer + rhs.read_host_buffer, + create_contract_package_at_hash: self.create_contract_package_at_hash + + rhs.create_contract_package_at_hash, + create_contract_user_group: self.create_contract_user_group + + rhs.create_contract_user_group, + add_contract_version: self.add_contract_version + rhs.add_contract_version, + disable_contract_version: self.disable_contract_version + rhs.disable_contract_version, + call_contract: self.call_contract + rhs.call_contract, + call_versioned_contract: self.call_versioned_contract + rhs.call_versioned_contract, + get_named_arg_size: self.get_named_arg_size + rhs.get_named_arg_size, + get_named_arg: self.get_named_arg + rhs.get_named_arg, + remove_contract_user_group: self.remove_contract_user_group + + rhs.remove_contract_user_group, + provision_contract_user_group_uref: self.provision_contract_user_group_uref + + rhs.provision_contract_user_group_uref, + remove_contract_user_group_urefs: self.remove_contract_user_group_urefs + + rhs.remove_contract_user_group_urefs, + print: self.print + rhs.print, + blake2b: self.blake2b + rhs.blake2b, + random_bytes: self.random_bytes + rhs.random_bytes, + enable_contract_version: self.enable_contract_version + rhs.enable_contract_version, + manage_message_topic: self.manage_message_topic + rhs.manage_message_topic, + emit_message: self.emit_message + rhs.emit_message, + } + } +} + +impl Zero for HostFunctionCosts { + fn zero() -> Self { + Self { + read_value: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + dictionary_get: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + write: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + dictionary_put: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + add: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + new_uref: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + load_named_keys: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + ret: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + get_key: HostFunction::<[Cost; 5]>::new(0, [0; 5]), + has_key: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + put_key: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + remove_key: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + revert: HostFunction::<[Cost; 1]>::new(0, [0; 1]), + is_valid_uref: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + add_associated_key: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + remove_associated_key: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + update_associated_key: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + set_action_threshold: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + get_caller: HostFunction::<[Cost; 1]>::new(0, [0; 1]), + get_blocktime: HostFunction::<[Cost; 1]>::new(0, [0; 1]), + create_purse: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + transfer_to_account: HostFunction::<[Cost; 7]>::new(0, [0; 7]), + transfer_from_purse_to_account: HostFunction::<[Cost; 9]>::new(0, [0; 9]), + transfer_from_purse_to_purse: HostFunction::<[Cost; 8]>::new(0, [0; 8]), + get_balance: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + get_phase: HostFunction::<[Cost; 1]>::new(0, [0; 1]), + get_system_contract: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + get_main_purse: HostFunction::<[Cost; 1]>::new(0, [0; 1]), + read_host_buffer: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + create_contract_package_at_hash: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + create_contract_user_group: HostFunction::<[Cost; 8]>::new(0, [0; 8]), + add_contract_version: HostFunction::<[Cost; 10]>::new(0, [0; 10]), + disable_contract_version: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + call_contract: HostFunction::<[Cost; 7]>::new(0, [0; 7]), + call_versioned_contract: HostFunction::<[Cost; 9]>::new(0, [0; 9]), + get_named_arg_size: HostFunction::<[Cost; 3]>::new(0, [0; 3]), + get_named_arg: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + remove_contract_user_group: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + provision_contract_user_group_uref: HostFunction::<[Cost; 5]>::new(0, [0; 5]), + remove_contract_user_group_urefs: HostFunction::<[Cost; 6]>::new(0, [0; 6]), + print: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + blake2b: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + random_bytes: HostFunction::<[Cost; 2]>::new(0, [0; 2]), + enable_contract_version: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + manage_message_topic: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + emit_message: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + } + } + + fn is_zero(&self) -> bool { + todo!() + } +} + impl Default for HostFunctionCosts { fn default() -> Self { Self { diff --git a/types/src/chainspec/vm_config/message_costs.rs b/types/src/chainspec/vm_config/message_costs.rs new file mode 100644 index 0000000000..98b1513591 --- /dev/null +++ b/types/src/chainspec/vm_config/message_costs.rs @@ -0,0 +1,143 @@ +#[cfg(feature = "datasize")] +use datasize::DataSize; +use num_traits::Zero; +use rand::{distributions::Standard, prelude::*, Rng}; +use serde::{Deserialize, Serialize}; +use std::ops::Add; + +use crate::bytesrepr::{self, FromBytes, ToBytes}; + +/// Configuration for messages limits. +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[serde(deny_unknown_fields)] +pub struct MessageCosts { + /// Cost for emitting a message. + pub first_message_cost: u32, + /// Cost increase for successive messages emitted for each execution. + pub cost_increase_per_message: u32, +} + +impl MessageCosts { + /// Return cost for emitting a message. + pub fn first_message_cost(&self) -> u32 { + self.first_message_cost + } + + /// Return the ratio of cost increase for successive messages emitted for each execution. + pub fn cost_increase_per_message(&self) -> u32 { + self.cost_increase_per_message + } +} + +impl Default for MessageCosts { + fn default() -> Self { + Self { + first_message_cost: 100, + cost_increase_per_message: 50, + } + } +} + +impl Zero for MessageCosts { + fn zero() -> Self { + MessageCosts { + first_message_cost: 0, + cost_increase_per_message: 0, + } + } + + fn is_zero(&self) -> bool { + self.first_message_cost == 0 && self.cost_increase_per_message == 0 + } +} + +impl Add for MessageCosts { + type Output = MessageCosts; + + fn add(self, other: MessageCosts) -> MessageCosts { + MessageCosts { + first_message_cost: self.first_message_cost + other.first_message_cost, + cost_increase_per_message: self.cost_increase_per_message + + other.cost_increase_per_message, + } + } +} + +impl ToBytes for MessageCosts { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + + ret.append(&mut self.first_message_cost.to_bytes()?); + ret.append(&mut self.cost_increase_per_message.to_bytes()?); + + Ok(ret) + } + + fn serialized_length(&self) -> usize { + self.first_message_cost.serialized_length() + + self.cost_increase_per_message.serialized_length() + } +} + +impl FromBytes for MessageCosts { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (first_message_cost, rem) = FromBytes::from_bytes(bytes)?; + let (cost_increase_per_message, rem) = FromBytes::from_bytes(rem)?; + + Ok(( + MessageCosts { + first_message_cost, + cost_increase_per_message, + }, + rem, + )) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> MessageCosts { + MessageCosts { + first_message_cost: rng.gen(), + cost_increase_per_message: rng.gen(), + } + } +} + +#[doc(hidden)] +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::{num, prop_compose}; + + use super::MessageCosts; + + prop_compose! { + pub fn message_costs_arb()( + first_message_cost in num::u32::ANY, + cost_increase_per_message in num::u32::ANY, + ) -> MessageCosts { + MessageCosts { + first_message_cost, + cost_increase_per_message, + } + } + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use crate::bytesrepr; + + use super::gens; + + proptest! { + #[test] + fn should_serialize_and_deserialize_with_arbitrary_values( + message_limits in gens::message_costs_arb() + ) { + bytesrepr::test_serialization_roundtrip(&message_limits); + } + } +} diff --git a/types/src/chainspec/vm_config/opcode_costs.rs b/types/src/chainspec/vm_config/opcode_costs.rs index e7bfa2cd12..1724e7cd7a 100644 --- a/types/src/chainspec/vm_config/opcode_costs.rs +++ b/types/src/chainspec/vm_config/opcode_costs.rs @@ -1,6 +1,9 @@ //! Support for Wasm opcode costs. +use core::ops::Add; + #[cfg(feature = "datasize")] use datasize::DataSize; +use num_traits::Zero; use rand::{distributions::Standard, prelude::*, Rng}; use serde::{Deserialize, Serialize}; @@ -141,6 +144,30 @@ impl FromBytes for BrTableCost { } } +impl Add for BrTableCost { + type Output = Self; + + fn add(self, other: Self) -> Self { + BrTableCost { + cost: self.cost + other.cost, + size_multiplier: self.size_multiplier + other.size_multiplier, + } + } +} + +impl Zero for BrTableCost { + fn zero() -> Self { + BrTableCost { + cost: 0, + size_multiplier: 0, + } + } + + fn is_zero(&self) -> bool { + self.cost.is_zero() && self.size_multiplier.is_zero() + } +} + /// Definition of a cost table for a Wasm control flow opcodes. #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] @@ -321,6 +348,64 @@ impl Distribution for Standard { } } +impl Zero for ControlFlowCosts { + fn zero() -> Self { + ControlFlowCosts { + block: 0, + op_loop: 0, + op_if: 0, + op_else: 0, + end: 0, + br: 0, + br_if: 0, + op_return: 0, + call: 0, + call_indirect: 0, + drop: 0, + select: 0, + br_table: BrTableCost::zero(), + } + } + + fn is_zero(&self) -> bool { + self.block.is_zero() + && self.op_loop.is_zero() + && self.op_if.is_zero() + && self.op_else.is_zero() + && self.end.is_zero() + && self.br.is_zero() + && self.br_if.is_zero() + && self.op_return.is_zero() + && self.call.is_zero() + && self.call_indirect.is_zero() + && self.drop.is_zero() + && self.select.is_zero() + && self.br_table.is_zero() + } +} + +impl Add for ControlFlowCosts { + type Output = Self; + + fn add(self, other: Self) -> Self { + ControlFlowCosts { + block: self.block + other.block, + op_loop: self.op_loop + other.op_loop, + op_if: self.op_if + other.op_if, + op_else: self.op_else + other.op_else, + end: self.end + other.end, + br: self.br + other.br, + br_if: self.br_if + other.br_if, + op_return: self.op_return + other.op_return, + call: self.call + other.call, + call_indirect: self.call_indirect + other.call_indirect, + drop: self.drop + other.drop, + select: self.select + other.select, + br_table: self.br_table + other.br_table, + } + } +} + /// Definition of a cost table for Wasm opcodes. /// /// This is taken (partially) from parity-ethereum. @@ -531,6 +616,73 @@ impl FromBytes for OpcodeCosts { } } +impl Add for OpcodeCosts { + type Output = OpcodeCosts; + + fn add(self, rhs: Self) -> Self::Output { + Self { + bit: self.bit + rhs.bit, + add: self.add + rhs.add, + mul: self.mul + rhs.mul, + div: self.div + rhs.div, + load: self.load + rhs.load, + store: self.store + rhs.store, + op_const: self.op_const + rhs.op_const, + local: self.local + rhs.local, + global: self.global + rhs.global, + integer_comparison: self.integer_comparison + rhs.integer_comparison, + conversion: self.conversion + rhs.conversion, + unreachable: self.unreachable + rhs.unreachable, + nop: self.nop + rhs.nop, + current_memory: self.current_memory + rhs.current_memory, + grow_memory: self.grow_memory + rhs.grow_memory, + control_flow: self.control_flow + rhs.control_flow, + } + } +} + +impl Zero for OpcodeCosts { + fn zero() -> Self { + Self { + bit: 0, + add: 0, + mul: 0, + div: 0, + load: 0, + store: 0, + op_const: 0, + local: 0, + global: 0, + integer_comparison: 0, + conversion: 0, + unreachable: 0, + nop: 0, + current_memory: 0, + grow_memory: 0, + control_flow: ControlFlowCosts::zero(), + } + } + + fn is_zero(&self) -> bool { + self.bit.is_zero() + && self.add.is_zero() + && self.mul.is_zero() + && self.div.is_zero() + && self.load.is_zero() + && self.store.is_zero() + && self.op_const.is_zero() + && self.local.is_zero() + && self.global.is_zero() + && self.integer_comparison.is_zero() + && self.conversion.is_zero() + && self.unreachable.is_zero() + && self.nop.is_zero() + && self.current_memory.is_zero() + && self.grow_memory.is_zero() + && self.control_flow.is_zero() + } +} + #[doc(hidden)] #[cfg(any(feature = "gens", test))] pub mod gens { diff --git a/types/src/chainspec/vm_config/storage_costs.rs b/types/src/chainspec/vm_config/storage_costs.rs index 144cdba719..5c65b85438 100644 --- a/types/src/chainspec/vm_config/storage_costs.rs +++ b/types/src/chainspec/vm_config/storage_costs.rs @@ -1,6 +1,9 @@ //! Support for storage costs. +use core::ops::Add; + #[cfg(feature = "datasize")] use datasize::DataSize; +use num_traits::Zero; use rand::{distributions::Standard, prelude::*, Rng}; use serde::{Deserialize, Serialize}; @@ -77,6 +80,26 @@ impl FromBytes for StorageCosts { } } +impl Add for StorageCosts { + type Output = Self; + + fn add(self, other: Self) -> Self { + StorageCosts { + gas_per_byte: self.gas_per_byte + other.gas_per_byte, + } + } +} + +impl Zero for StorageCosts { + fn zero() -> Self { + StorageCosts { gas_per_byte: 0 } + } + + fn is_zero(&self) -> bool { + self.gas_per_byte.is_zero() + } +} + #[cfg(test)] pub mod tests { use crate::U512; diff --git a/types/src/chainspec/vm_config/wasm_config.rs b/types/src/chainspec/vm_config/wasm_config.rs index ab73b44b6c..f5c26b9d6d 100644 --- a/types/src/chainspec/vm_config/wasm_config.rs +++ b/types/src/chainspec/vm_config/wasm_config.rs @@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize}; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - chainspec::vm_config::{HostFunctionCosts, MessageLimits, OpcodeCosts, StorageCosts}, + chainspec::vm_config::{ + HostFunctionCosts, MessageCosts, MessageLimits, OpcodeCosts, StorageCosts, + }, }; /// Default maximum number of pages of the Wasm memory. @@ -34,6 +36,8 @@ pub struct WasmConfig { host_function_costs: HostFunctionCosts, /// Messages limits. messages_limits: MessageLimits, + /// Messages costs. + message_costs: MessageCosts, } impl WasmConfig { @@ -45,6 +49,7 @@ impl WasmConfig { storage_costs: StorageCosts, host_function_costs: HostFunctionCosts, messages_limits: MessageLimits, + message_costs: MessageCosts, ) -> Self { Self { max_memory, @@ -53,6 +58,7 @@ impl WasmConfig { storage_costs, host_function_costs, messages_limits, + message_costs, } } @@ -75,6 +81,11 @@ impl WasmConfig { pub fn messages_limits(&self) -> MessageLimits { self.messages_limits } + + /// Returns the costs for emitting messages. + pub fn message_costs(&self) -> MessageCosts { + self.message_costs + } } impl Default for WasmConfig { @@ -86,6 +97,7 @@ impl Default for WasmConfig { storage_costs: StorageCosts::default(), host_function_costs: HostFunctionCosts::default(), messages_limits: MessageLimits::default(), + message_costs: MessageCosts::default(), } } } @@ -100,6 +112,7 @@ impl ToBytes for WasmConfig { ret.append(&mut self.storage_costs.to_bytes()?); ret.append(&mut self.host_function_costs.to_bytes()?); ret.append(&mut self.messages_limits.to_bytes()?); + ret.append(&mut self.message_costs.to_bytes()?); Ok(ret) } @@ -111,6 +124,7 @@ impl ToBytes for WasmConfig { + self.storage_costs.serialized_length() + self.host_function_costs.serialized_length() + self.messages_limits.serialized_length() + + self.message_costs.serialized_length() } } @@ -122,6 +136,7 @@ impl FromBytes for WasmConfig { let (storage_costs, rem) = FromBytes::from_bytes(rem)?; let (host_function_costs, rem) = FromBytes::from_bytes(rem)?; let (messages_limits, rem) = FromBytes::from_bytes(rem)?; + let (message_costs, rem) = FromBytes::from_bytes(rem)?; Ok(( WasmConfig { @@ -131,6 +146,7 @@ impl FromBytes for WasmConfig { storage_costs, host_function_costs, messages_limits, + message_costs, }, rem, )) @@ -146,6 +162,7 @@ impl Distribution for Standard { storage_costs: rng.gen(), host_function_costs: rng.gen(), messages_limits: rng.gen(), + message_costs: rng.gen(), } } } @@ -158,8 +175,8 @@ pub mod gens { use crate::{ chainspec::vm_config::{ host_function_costs::gens::host_function_costs_arb, - message_limits::gens::message_limits_arb, opcode_costs::gens::opcode_costs_arb, - storage_costs::gens::storage_costs_arb, + message_costs::gens::message_costs_arb, message_limits::gens::message_limits_arb, + opcode_costs::gens::opcode_costs_arb, storage_costs::gens::storage_costs_arb, }, WasmConfig, }; @@ -172,6 +189,7 @@ pub mod gens { storage_costs in storage_costs_arb(), host_function_costs in host_function_costs_arb(), messages_limits in message_limits_arb(), + message_costs in message_costs_arb(), ) -> WasmConfig { WasmConfig { max_memory, @@ -180,6 +198,7 @@ pub mod gens { storage_costs, host_function_costs, messages_limits, + message_costs, } } } diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs index 42b8891c2c..cfdc8a961e 100644 --- a/types/src/contract_messages/messages.rs +++ b/types/src/contract_messages/messages.rs @@ -71,10 +71,12 @@ pub enum MessagePayload { String(String), } -impl MessagePayload { - /// Creates a new [`MessagePayload`] from a [`String`]. - pub fn from_string(message: String) -> Self { - Self::String(message) +impl From for MessagePayload +where + T: Into, +{ + fn from(value: T) -> Self { + Self::String(value.into()) } } @@ -199,7 +201,7 @@ mod tests { let message_checksum = MessageChecksum([1; MESSAGE_CHECKSUM_LENGTH]); bytesrepr::test_serialization_roundtrip(&message_checksum); - let message_payload = MessagePayload::from_string("message payload".to_string()); + let message_payload = "message payload".into(); bytesrepr::test_serialization_roundtrip(&message_payload); let message = Message::new( diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index f4d12e4e5b..57250b8213 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -37,7 +37,7 @@ use crate::{ TransferAddr, U512, }; #[cfg(any(feature = "testing", test))] -use crate::{contract_messages::MessagePayload, crypto, testing::TestRng}; +use crate::{crypto, testing::TestRng}; #[cfg(feature = "json-schema")] static EXECUTION_RESULT: Lazy = Lazy::new(|| { @@ -117,7 +117,7 @@ impl Distribution for Standard { let topic_name_hash = crypto::blake2b(&topic_name); Some(Message::new( addr.entity_addr(), - MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), + format!("random_msg: {}", rng.gen::()).into(), topic_name, topic_name_hash.into(), rng.gen::(), @@ -168,7 +168,7 @@ impl ExecutionResultV2 { let topic_name_hash = crypto::blake2b(&topic_name); Some(Message::new( addr.entity_addr(), - MessagePayload::from_string(format!("random_msg: {}", rng.gen::())), + format!("random_msg: {}", rng.gen::()).into(), topic_name, topic_name_hash.into(), rng.gen::(), diff --git a/types/src/lib.rs b/types/src/lib.rs index d98fdc041b..8fda61a3f7 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -105,9 +105,9 @@ pub use chainspec::{ ControlFlowCosts, CoreConfig, DelegatorConfig, DeployConfig, FeeHandling, GenesisAccount, GenesisValidator, GlobalStateUpdate, GlobalStateUpdateConfig, GlobalStateUpdateError, HandlePaymentCosts, HighwayConfig, HostFunction, HostFunctionCost, HostFunctionCosts, - LegacyRequiredFinality, MessageLimits, MintCosts, NetworkConfig, OpcodeCosts, ProtocolConfig, - RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, TransactionConfig, - TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, + LegacyRequiredFinality, MessageCosts, MessageLimits, MintCosts, NetworkConfig, OpcodeCosts, + ProtocolConfig, RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, + TransactionConfig, TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, }; #[cfg(any(all(feature = "std", feature = "testing"), test))] pub use chainspec::{ From 5673e3e67920d00ee37209dc567d3a5f18025ac6 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Mon, 16 Oct 2023 15:30:12 +0000 Subject: [PATCH 19/27] ee/tests: adjust expected costs for faucet host fn metrics Adjust the expected costs for the faucet and the host function metrics contract since the code size has increased due to the addition of the contract messages code. Also the size of the addressable entity stored value has increased because of the stored topic names. Signed-off-by: Alexandru Sardan --- .../tests/src/test/explorer/faucet.rs | 8 ++++---- .../regression/host_function_metrics_size_and_gas_cost.rs | 2 +- smart_contracts/contracts/explorer/faucet/README.md | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/execution_engine_testing/tests/src/test/explorer/faucet.rs b/execution_engine_testing/tests/src/test/explorer/faucet.rs index 1c2c8125fb..b9d8c446eb 100644 --- a/execution_engine_testing/tests/src/test/explorer/faucet.rs +++ b/execution_engine_testing/tests/src/test/explorer/faucet.rs @@ -869,10 +869,10 @@ fn faucet_costs() { // This test will fail if execution costs vary. The expected costs should not be updated // without understanding why the cost has changed. If the costs do change, it should be // reflected in the "Costs by Entry Point" section of the faucet crate's README.md. - const EXPECTED_FAUCET_INSTALL_COST: u64 = 83_594_845_660; - const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 648_705_070; - const EXPECTED_FAUCET_CALL_BY_INSTALLER_COST: u64 = 3_244_975_770; - const EXPECTED_FAUCET_CALL_BY_USER_COST: u64 = 3_364_807_470; + const EXPECTED_FAUCET_INSTALL_COST: u64 = 86_435_387_520; + const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 650_882_080; + const EXPECTED_FAUCET_CALL_BY_INSTALLER_COST: u64 = 3_249_693_500; + const EXPECTED_FAUCET_CALL_BY_USER_COST: u64 = 3_368_742_560; let installer_account = AccountHash::new([1u8; 32]); let user_account: AccountHash = AccountHash::new([2u8; 32]); diff --git a/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs b/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs index 621de0d085..723d09c6f3 100644 --- a/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs +++ b/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs @@ -22,7 +22,7 @@ const CONTRACT_TRANSFER_TO_ACCOUNT_U512: &str = "transfer_to_account_u512.wasm"; // This value is not systemic, as code is added the size of WASM will increase, // you can change this value to reflect the increase in WASM size. const HOST_FUNCTION_METRICS_STANDARD_SIZE: usize = 97_569; -const HOST_FUNCTION_METRICS_STANDARD_GAS_COST: u64 = 347_080_271_020; +const HOST_FUNCTION_METRICS_STANDARD_GAS_COST: u64 = 364_434_284_571; /// Acceptable size regression/improvement in percentage. const SIZE_MARGIN: usize = 5; diff --git a/smart_contracts/contracts/explorer/faucet/README.md b/smart_contracts/contracts/explorer/faucet/README.md index 696b63ee91..921a5391a6 100644 --- a/smart_contracts/contracts/explorer/faucet/README.md +++ b/smart_contracts/contracts/explorer/faucet/README.md @@ -35,7 +35,7 @@ If you try to invoke the contract before these variables are set, then you'll ge | feature | cost | |--------------------------|------------------| -| faucet install | `83_594_845_660` | -| faucet set variables | `648_705_070` | -| faucet call by installer | `3_244_975_770` | -| faucet call by user | `3_364_807_470` | +| faucet install | `86_435_387_520` | +| faucet set variables | `650_882_080` | +| faucet call by installer | `3_249_693_500` | +| faucet call by user | `3_368_742_560` | From 6df9553c118fee551d8fa9e73c930344e0dcaa2c Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Mon, 16 Oct 2023 15:34:47 +0000 Subject: [PATCH 20/27] casper-validation: fix fixture for `AddressableEntity` The `AddressableEntity` record now includes the name of the message topics so the fixture needs to be adjusted. Signed-off-by: Alexandru Sardan --- types/src/execution/execution_result_v2.rs | 10 ++++------ utils/validation/Cargo.toml | 2 +- utils/validation/tests/fixtures/ABI/stored_value.json | 5 +++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 57250b8213..57aec91a46 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -10,8 +10,6 @@ use alloc::{string::String, vec::Vec}; #[cfg(feature = "datasize")] use datasize::DataSize; -#[cfg(any(feature = "testing", test))] -use itertools::Itertools; #[cfg(feature = "json-schema")] use once_cell::sync::Lazy; #[cfg(any(feature = "testing", test))] @@ -108,7 +106,7 @@ impl Distribution for Standard { } let effects = Effects::random(rng); - let messages = effects + let messages: Vec = effects .transforms() .iter() .filter_map(|transform| { @@ -126,7 +124,7 @@ impl Distribution for Standard { None } }) - .collect_vec(); + .collect(); if rng.gen() { ExecutionResultV2::Failure { @@ -159,7 +157,7 @@ impl ExecutionResultV2 { #[cfg(any(feature = "testing", test))] pub fn random(rng: &mut TestRng) -> Self { let effects = Effects::random(rng); - let messages = effects + let messages: Vec = effects .transforms() .iter() .filter_map(|transform| { @@ -177,7 +175,7 @@ impl ExecutionResultV2 { None } }) - .collect_vec(); + .collect(); let transfer_count = rng.gen_range(0..6); let mut transfers = vec![]; diff --git a/utils/validation/Cargo.toml b/utils/validation/Cargo.toml index 2411c87c42..60bbeef48a 100644 --- a/utils/validation/Cargo.toml +++ b/utils/validation/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] anyhow = "1" base16 = "0.2.1" -casper-types = { path = "../../types", features = ["testing"] } +casper-types = { path = "../../types", features = ["testing", "std"] } clap = { version ="3.0.0-rc.0", features = ["derive"] } derive_more = "0.99.13" hex = { version = "0.4.2", features = ["serde"] } diff --git a/utils/validation/tests/fixtures/ABI/stored_value.json b/utils/validation/tests/fixtures/ABI/stored_value.json index b27040d127..98b13142f3 100644 --- a/utils/validation/tests/fixtures/ABI/stored_value.json +++ b/utils/validation/tests/fixtures/ABI/stored_value.json @@ -78,12 +78,13 @@ "name": "uref" } ], - "protocol_version": "1.0.0" + "protocol_version": "1.0.0", + "message_topics": [] } } } ], - "output": "0b64646464646464646464646464646464646464646464646464646464646464646565656565656565656565656565656565656565656565656565656565656565020000000400000068617368012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b04000000757265660211111111111111111111111111111111111111111111111111111111111111110701000000170000007075626c69635f656e7472795f706f696e745f66756e63170000007075626c69635f656e7472795f706f696e745f66756e630200000006000000706172616d310806000000706172616d320a090101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101" + "output": "0b64646464646464646464646464646464646464646464646464646464646464646565656565656565656565656565656565656565656565656565656565656565020000000400000068617368012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b04000000757265660211111111111111111111111111111111111111111111111111111111111111110701000000170000007075626c69635f656e7472795f706f696e745f66756e63170000007075626c69635f656e7472795f706f696e745f66756e630200000006000000706172616d310806000000706172616d320a09010101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100000000" }, "Bid": { "input": [ From c77dfd020cddf4f601ebc2e6e60a4c5978de9b97 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Mon, 16 Oct 2023 17:17:03 +0000 Subject: [PATCH 21/27] ee/contract_messages: return error if topic is full When emitting messages, return an error if there's no space left in the topic. Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 429a6ae45b..2df8960f41 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -3300,10 +3300,10 @@ where prev_topic_summary.message_count() }; - let new_topic_summary = MessageTopicSummary::new( - message_index + 1, //TODO[AS]: need checked add here - current_blocktime, - ); + let Some(message_count) = message_index.checked_add(1) else { + return Ok(Err(ApiError::MessageTopicFull)); + }; + let new_topic_summary = MessageTopicSummary::new(message_count, current_blocktime); let message_key = Key::message(entity_addr, topic_name_hash, message_index); let message_checksum = MessageChecksum(crypto::blake2b( From ffe7e18b9a7139defaa1cebf8c97bde61722a229 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Tue, 17 Oct 2023 14:01:50 +0000 Subject: [PATCH 22/27] ee/contract_messages: fix CR comments Signed-off-by: Alexandru Sardan --- Cargo.lock | 1 + .../tests/src/test/contract_messages.rs | 13 +- types/Cargo.toml | 1 + .../vm_config/host_function_costs.rs | 214 +++++++++--------- .../src/chainspec/vm_config/message_costs.rs | 16 +- types/src/chainspec/vm_config/opcode_costs.rs | 67 +----- .../src/chainspec/vm_config/storage_costs.rs | 15 +- 7 files changed, 124 insertions(+), 203 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edef621a8d..72ee258cdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,6 +689,7 @@ dependencies = [ "blake2", "criterion", "datasize", + "derive_more", "derp", "ed25519-dalek", "getrandom", diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 1ac97ac262..e414a5cbfe 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -30,6 +30,9 @@ const ARG_MESSAGE_SUFFIX_NAME: &str = "message_suffix"; const EMITTER_MESSAGE_PREFIX: &str = "generic message: "; +// Number of messages that will be emitted when calling `ENTRY_POINT_EMIT_MESSAGE_FROM_EACH_VERSION` +const EMIT_MESSAGE_FROM_EACH_VERSION_NUM_MESSAGES: u32 = 3; + fn install_messages_emitter_contract(builder: &RefCell) -> ContractHash { // Request to install the contract that will be emitting messages. let install_request = ExecuteRequestBuilder::standard( @@ -689,11 +692,15 @@ fn should_charge_expected_gas_for_storage() { fn should_charge_increasing_gas_cost_for_multiple_messages_emitted() { const FIRST_MESSAGE_EMIT_COST: u32 = 100; const COST_INCREASE_PER_MESSAGE: u32 = 50; + const fn emit_cost_per_execution(num_messages: u32) -> u32 { + FIRST_MESSAGE_EMIT_COST * num_messages + + (num_messages - 1) * num_messages / 2 * COST_INCREASE_PER_MESSAGE + } + const MESSAGES_TO_EMIT: u32 = 4; - const EMIT_MULTIPLE_EXPECTED_COST: u32 = FIRST_MESSAGE_EMIT_COST * MESSAGES_TO_EMIT - + (MESSAGES_TO_EMIT - 1) * MESSAGES_TO_EMIT / 2 * COST_INCREASE_PER_MESSAGE; + const EMIT_MULTIPLE_EXPECTED_COST: u32 = emit_cost_per_execution(MESSAGES_TO_EMIT); const EMIT_MESSAGES_FROM_MULTIPLE_CONTRACTS: u32 = - FIRST_MESSAGE_EMIT_COST * 3 + 3 * COST_INCREASE_PER_MESSAGE; // simplification of above + emit_cost_per_execution(EMIT_MESSAGE_FROM_EACH_VERSION_NUM_MESSAGES); let wasm_config = WasmConfig::new( DEFAULT_WASM_MAX_MEMORY, diff --git a/types/Cargo.toml b/types/Cargo.toml index 8891c0e5c6..de567fbe5e 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -47,6 +47,7 @@ tracing = { version = "0.1.37", default-features = false } uint = { version = "0.9.0", default-features = false } untrusted = { version = "0.7.1", optional = true } version-sync = { version = "0.9", optional = true } +derive_more = "0.99.17" [dev-dependencies] base16 = { version = "0.2.1", features = ["std"] } diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index 50a5da5d5a..d8740e8482 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -3,6 +3,7 @@ use core::ops::Add; #[cfg(feature = "datasize")] use datasize::DataSize; +use derive_more::Add; use num_traits::Zero; use rand::{distributions::Standard, prelude::Distribution, Rng}; use serde::{Deserialize, Serialize}; @@ -168,6 +169,16 @@ impl Add for HostFunction<[Cost; COUNT]> { } } +impl Zero for HostFunction<[Cost; COUNT]> { + fn zero() -> Self { + HostFunction::new(0, [0; COUNT]) + } + + fn is_zero(&self) -> bool { + !self.arguments.iter().any(|cost| *cost != 0) && self.cost.is_zero() + } +} + impl Distribution> for Standard where Standard: Distribution, @@ -216,7 +227,7 @@ where } /// Definition of a host function cost table. -#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Add, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct HostFunctionCosts { @@ -316,122 +327,105 @@ pub struct HostFunctionCosts { pub emit_message: HostFunction<[Cost; 4]>, } -impl Add for HostFunctionCosts { - type Output = HostFunctionCosts; - - fn add(self, rhs: Self) -> Self::Output { - Self { - read_value: self.read_value + rhs.read_value, - dictionary_get: self.dictionary_get + rhs.dictionary_get, - write: self.write + rhs.write, - dictionary_put: self.dictionary_put + rhs.dictionary_put, - add: self.add + rhs.add, - new_uref: self.new_uref + rhs.new_uref, - load_named_keys: self.load_named_keys + rhs.load_named_keys, - ret: self.ret + rhs.ret, - get_key: self.get_key + rhs.get_key, - has_key: self.has_key + rhs.has_key, - put_key: self.put_key + rhs.put_key, - remove_key: self.remove_key + rhs.remove_key, - revert: self.revert + rhs.revert, - is_valid_uref: self.is_valid_uref + rhs.is_valid_uref, - add_associated_key: self.add_associated_key + rhs.add_associated_key, - remove_associated_key: self.remove_associated_key + rhs.remove_associated_key, - update_associated_key: self.update_associated_key + rhs.update_associated_key, - set_action_threshold: self.set_action_threshold + rhs.set_action_threshold, - get_caller: self.get_caller + rhs.get_caller, - get_blocktime: self.get_blocktime + rhs.get_blocktime, - create_purse: self.create_purse + rhs.create_purse, - transfer_to_account: self.transfer_to_account + rhs.transfer_to_account, - transfer_from_purse_to_account: self.transfer_from_purse_to_account - + rhs.transfer_from_purse_to_account, - transfer_from_purse_to_purse: self.transfer_from_purse_to_purse - + rhs.transfer_from_purse_to_purse, - get_balance: self.get_balance + rhs.get_balance, - get_phase: self.get_phase + rhs.get_phase, - get_system_contract: self.get_system_contract + rhs.get_system_contract, - get_main_purse: self.get_main_purse + rhs.get_main_purse, - read_host_buffer: self.read_host_buffer + rhs.read_host_buffer, - create_contract_package_at_hash: self.create_contract_package_at_hash - + rhs.create_contract_package_at_hash, - create_contract_user_group: self.create_contract_user_group - + rhs.create_contract_user_group, - add_contract_version: self.add_contract_version + rhs.add_contract_version, - disable_contract_version: self.disable_contract_version + rhs.disable_contract_version, - call_contract: self.call_contract + rhs.call_contract, - call_versioned_contract: self.call_versioned_contract + rhs.call_versioned_contract, - get_named_arg_size: self.get_named_arg_size + rhs.get_named_arg_size, - get_named_arg: self.get_named_arg + rhs.get_named_arg, - remove_contract_user_group: self.remove_contract_user_group - + rhs.remove_contract_user_group, - provision_contract_user_group_uref: self.provision_contract_user_group_uref - + rhs.provision_contract_user_group_uref, - remove_contract_user_group_urefs: self.remove_contract_user_group_urefs - + rhs.remove_contract_user_group_urefs, - print: self.print + rhs.print, - blake2b: self.blake2b + rhs.blake2b, - random_bytes: self.random_bytes + rhs.random_bytes, - enable_contract_version: self.enable_contract_version + rhs.enable_contract_version, - manage_message_topic: self.manage_message_topic + rhs.manage_message_topic, - emit_message: self.emit_message + rhs.emit_message, - } - } -} - impl Zero for HostFunctionCosts { fn zero() -> Self { Self { - read_value: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - dictionary_get: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - write: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - dictionary_put: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - add: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - new_uref: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - load_named_keys: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - ret: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - get_key: HostFunction::<[Cost; 5]>::new(0, [0; 5]), - has_key: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - put_key: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - remove_key: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - revert: HostFunction::<[Cost; 1]>::new(0, [0; 1]), - is_valid_uref: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - add_associated_key: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - remove_associated_key: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - update_associated_key: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - set_action_threshold: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - get_caller: HostFunction::<[Cost; 1]>::new(0, [0; 1]), - get_blocktime: HostFunction::<[Cost; 1]>::new(0, [0; 1]), - create_purse: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - transfer_to_account: HostFunction::<[Cost; 7]>::new(0, [0; 7]), - transfer_from_purse_to_account: HostFunction::<[Cost; 9]>::new(0, [0; 9]), - transfer_from_purse_to_purse: HostFunction::<[Cost; 8]>::new(0, [0; 8]), - get_balance: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - get_phase: HostFunction::<[Cost; 1]>::new(0, [0; 1]), - get_system_contract: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - get_main_purse: HostFunction::<[Cost; 1]>::new(0, [0; 1]), - read_host_buffer: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - create_contract_package_at_hash: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - create_contract_user_group: HostFunction::<[Cost; 8]>::new(0, [0; 8]), - add_contract_version: HostFunction::<[Cost; 10]>::new(0, [0; 10]), - disable_contract_version: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - call_contract: HostFunction::<[Cost; 7]>::new(0, [0; 7]), - call_versioned_contract: HostFunction::<[Cost; 9]>::new(0, [0; 9]), - get_named_arg_size: HostFunction::<[Cost; 3]>::new(0, [0; 3]), - get_named_arg: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - remove_contract_user_group: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - provision_contract_user_group_uref: HostFunction::<[Cost; 5]>::new(0, [0; 5]), - remove_contract_user_group_urefs: HostFunction::<[Cost; 6]>::new(0, [0; 6]), - print: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - blake2b: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - random_bytes: HostFunction::<[Cost; 2]>::new(0, [0; 2]), - enable_contract_version: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - manage_message_topic: HostFunction::<[Cost; 4]>::new(0, [0; 4]), - emit_message: HostFunction::<[Cost; 4]>::new(0, [0; 4]), + read_value: HostFunction::zero(), + dictionary_get: HostFunction::zero(), + write: HostFunction::zero(), + dictionary_put: HostFunction::zero(), + add: HostFunction::zero(), + new_uref: HostFunction::zero(), + load_named_keys: HostFunction::zero(), + ret: HostFunction::zero(), + get_key: HostFunction::zero(), + has_key: HostFunction::zero(), + put_key: HostFunction::zero(), + remove_key: HostFunction::zero(), + revert: HostFunction::zero(), + is_valid_uref: HostFunction::zero(), + add_associated_key: HostFunction::zero(), + remove_associated_key: HostFunction::zero(), + update_associated_key: HostFunction::zero(), + set_action_threshold: HostFunction::zero(), + get_caller: HostFunction::zero(), + get_blocktime: HostFunction::zero(), + create_purse: HostFunction::zero(), + transfer_to_account: HostFunction::zero(), + transfer_from_purse_to_account: HostFunction::zero(), + transfer_from_purse_to_purse: HostFunction::zero(), + get_balance: HostFunction::zero(), + get_phase: HostFunction::zero(), + get_system_contract: HostFunction::zero(), + get_main_purse: HostFunction::zero(), + read_host_buffer: HostFunction::zero(), + create_contract_package_at_hash: HostFunction::zero(), + create_contract_user_group: HostFunction::zero(), + add_contract_version: HostFunction::zero(), + disable_contract_version: HostFunction::zero(), + call_contract: HostFunction::zero(), + call_versioned_contract: HostFunction::zero(), + get_named_arg_size: HostFunction::zero(), + get_named_arg: HostFunction::zero(), + remove_contract_user_group: HostFunction::zero(), + provision_contract_user_group_uref: HostFunction::zero(), + remove_contract_user_group_urefs: HostFunction::zero(), + print: HostFunction::zero(), + blake2b: HostFunction::zero(), + random_bytes: HostFunction::zero(), + enable_contract_version: HostFunction::zero(), + manage_message_topic: HostFunction::zero(), + emit_message: HostFunction::zero(), } } fn is_zero(&self) -> bool { - todo!() + self.read_value.is_zero() + && self.dictionary_get.is_zero() + && self.write.is_zero() + && self.dictionary_put.is_zero() + && self.add.is_zero() + && self.new_uref.is_zero() + && self.load_named_keys.is_zero() + && self.ret.is_zero() + && self.get_key.is_zero() + && self.has_key.is_zero() + && self.put_key.is_zero() + && self.remove_key.is_zero() + && self.revert.is_zero() + && self.is_valid_uref.is_zero() + && self.add_associated_key.is_zero() + && self.remove_associated_key.is_zero() + && self.update_associated_key.is_zero() + && self.set_action_threshold.is_zero() + && self.get_caller.is_zero() + && self.get_blocktime.is_zero() + && self.create_purse.is_zero() + && self.transfer_to_account.is_zero() + && self.transfer_from_purse_to_account.is_zero() + && self.transfer_from_purse_to_purse.is_zero() + && self.get_balance.is_zero() + && self.get_phase.is_zero() + && self.get_system_contract.is_zero() + && self.get_main_purse.is_zero() + && self.read_host_buffer.is_zero() + && self.create_contract_package_at_hash.is_zero() + && self.create_contract_user_group.is_zero() + && self.add_contract_version.is_zero() + && self.disable_contract_version.is_zero() + && self.call_contract.is_zero() + && self.call_versioned_contract.is_zero() + && self.get_named_arg_size.is_zero() + && self.get_named_arg.is_zero() + && self.remove_contract_user_group.is_zero() + && self.provision_contract_user_group_uref.is_zero() + && self.remove_contract_user_group_urefs.is_zero() + && self.print.is_zero() + && self.blake2b.is_zero() + && self.random_bytes.is_zero() + && self.enable_contract_version.is_zero() + && self.manage_message_topic.is_zero() + && self.emit_message.is_zero() } } diff --git a/types/src/chainspec/vm_config/message_costs.rs b/types/src/chainspec/vm_config/message_costs.rs index 98b1513591..a74b8bc78b 100644 --- a/types/src/chainspec/vm_config/message_costs.rs +++ b/types/src/chainspec/vm_config/message_costs.rs @@ -1,14 +1,14 @@ #[cfg(feature = "datasize")] use datasize::DataSize; +use derive_more::Add; use num_traits::Zero; use rand::{distributions::Standard, prelude::*, Rng}; use serde::{Deserialize, Serialize}; -use std::ops::Add; use crate::bytesrepr::{self, FromBytes, ToBytes}; /// Configuration for messages limits. -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Add)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct MessageCosts { @@ -52,18 +52,6 @@ impl Zero for MessageCosts { } } -impl Add for MessageCosts { - type Output = MessageCosts; - - fn add(self, other: MessageCosts) -> MessageCosts { - MessageCosts { - first_message_cost: self.first_message_cost + other.first_message_cost, - cost_increase_per_message: self.cost_increase_per_message - + other.cost_increase_per_message, - } - } -} - impl ToBytes for MessageCosts { fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut ret = bytesrepr::unchecked_allocate_buffer(self); diff --git a/types/src/chainspec/vm_config/opcode_costs.rs b/types/src/chainspec/vm_config/opcode_costs.rs index 1724e7cd7a..0bc789a59d 100644 --- a/types/src/chainspec/vm_config/opcode_costs.rs +++ b/types/src/chainspec/vm_config/opcode_costs.rs @@ -1,8 +1,7 @@ //! Support for Wasm opcode costs. -use core::ops::Add; - #[cfg(feature = "datasize")] use datasize::DataSize; +use derive_more::Add; use num_traits::Zero; use rand::{distributions::Standard, prelude::*, Rng}; use serde::{Deserialize, Serialize}; @@ -77,7 +76,7 @@ pub const DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER: u32 = 100; /// cost + (len(br_table.targets) * size_multiplier) /// ``` // This is done to encourage users to avoid writing code with very long `br_table`s. -#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Add, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct BrTableCost { @@ -144,17 +143,6 @@ impl FromBytes for BrTableCost { } } -impl Add for BrTableCost { - type Output = Self; - - fn add(self, other: Self) -> Self { - BrTableCost { - cost: self.cost + other.cost, - size_multiplier: self.size_multiplier + other.size_multiplier, - } - } -} - impl Zero for BrTableCost { fn zero() -> Self { BrTableCost { @@ -169,7 +157,7 @@ impl Zero for BrTableCost { } /// Definition of a cost table for a Wasm control flow opcodes. -#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Add, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct ControlFlowCosts { @@ -384,32 +372,10 @@ impl Zero for ControlFlowCosts { } } -impl Add for ControlFlowCosts { - type Output = Self; - - fn add(self, other: Self) -> Self { - ControlFlowCosts { - block: self.block + other.block, - op_loop: self.op_loop + other.op_loop, - op_if: self.op_if + other.op_if, - op_else: self.op_else + other.op_else, - end: self.end + other.end, - br: self.br + other.br, - br_if: self.br_if + other.br_if, - op_return: self.op_return + other.op_return, - call: self.call + other.call, - call_indirect: self.call_indirect + other.call_indirect, - drop: self.drop + other.drop, - select: self.select + other.select, - br_table: self.br_table + other.br_table, - } - } -} - /// Definition of a cost table for Wasm opcodes. /// /// This is taken (partially) from parity-ethereum. -#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Add, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct OpcodeCosts { @@ -616,31 +582,6 @@ impl FromBytes for OpcodeCosts { } } -impl Add for OpcodeCosts { - type Output = OpcodeCosts; - - fn add(self, rhs: Self) -> Self::Output { - Self { - bit: self.bit + rhs.bit, - add: self.add + rhs.add, - mul: self.mul + rhs.mul, - div: self.div + rhs.div, - load: self.load + rhs.load, - store: self.store + rhs.store, - op_const: self.op_const + rhs.op_const, - local: self.local + rhs.local, - global: self.global + rhs.global, - integer_comparison: self.integer_comparison + rhs.integer_comparison, - conversion: self.conversion + rhs.conversion, - unreachable: self.unreachable + rhs.unreachable, - nop: self.nop + rhs.nop, - current_memory: self.current_memory + rhs.current_memory, - grow_memory: self.grow_memory + rhs.grow_memory, - control_flow: self.control_flow + rhs.control_flow, - } - } -} - impl Zero for OpcodeCosts { fn zero() -> Self { Self { diff --git a/types/src/chainspec/vm_config/storage_costs.rs b/types/src/chainspec/vm_config/storage_costs.rs index 5c65b85438..0ce4e9ce6d 100644 --- a/types/src/chainspec/vm_config/storage_costs.rs +++ b/types/src/chainspec/vm_config/storage_costs.rs @@ -1,8 +1,7 @@ //! Support for storage costs. -use core::ops::Add; - #[cfg(feature = "datasize")] use datasize::DataSize; +use derive_more::Add; use num_traits::Zero; use rand::{distributions::Standard, prelude::*, Rng}; use serde::{Deserialize, Serialize}; @@ -16,7 +15,7 @@ use crate::{ pub const DEFAULT_GAS_PER_BYTE_COST: u32 = 630_000; /// Represents a cost table for storage costs. -#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Add, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct StorageCosts { @@ -80,16 +79,6 @@ impl FromBytes for StorageCosts { } } -impl Add for StorageCosts { - type Output = Self; - - fn add(self, other: Self) -> Self { - StorageCosts { - gas_per_byte: self.gas_per_byte + other.gas_per_byte, - } - } -} - impl Zero for StorageCosts { fn zero() -> Self { StorageCosts { gas_per_byte: 0 } From 68cfc48878613e015f1b9416cd5e1b8bf5665aef Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Wed, 18 Oct 2023 13:41:21 +0000 Subject: [PATCH 23/27] types/contract_messages: add getters for `Message` fields Also add convenience methods to convert contract message related stored values to their inner types. Signed-off-by: Alexandru Sardan --- types/src/contract_messages/messages.rs | 25 +++++++++++++++++++++++++ types/src/stored_value.rs | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs index cfdc8a961e..6956b0ea5a 100644 --- a/types/src/contract_messages/messages.rs +++ b/types/src/contract_messages/messages.rs @@ -148,6 +148,31 @@ impl Message { index, } } + + /// Returns a reference to the identity of the entity that produced the message. + pub fn entity_addr(&self) -> &HashAddr { + &self.entity_addr + } + + /// Returns a reference to the payload of the message. + pub fn payload(&self) -> &MessagePayload { + &self.message + } + + /// Returns a reference to the name of the topic on which the message was emitted on. + pub fn topic_name(&self) -> &String { + &self.topic_name + } + + /// Returns a reference to the hash of the name of the topic. + pub fn topic_name_hash(&self) -> &TopicNameHash { + &self.topic_name_hash + } + + /// Returns the index of the message in the topic. + pub fn index(&self) -> u32 { + self.index + } } impl ToBytes for Message { diff --git a/types/src/stored_value.rs b/types/src/stored_value.rs index ddb68eb772..7b59159788 100644 --- a/types/src/stored_value.rs +++ b/types/src/stored_value.rs @@ -186,6 +186,24 @@ impl StoredValue { } } + /// Returns a reference to the wrapped `MessageTopicSummary` if this is a `MessageTopic` + /// variant. + pub fn as_message_topic_summary(&self) -> Option<&MessageTopicSummary> { + match self { + StoredValue::MessageTopic(summary) => Some(summary), + _ => None, + } + } + + /// Returns a reference to the wrapped `MessageChecksum` if this is a `Message` + /// variant. + pub fn as_message_checksum(&self) -> Option<&MessageChecksum> { + match self { + StoredValue::Message(checksum) => Some(checksum), + _ => None, + } + } + /// Returns a reference to the wrapped `BidKind` if this is a `BidKind` variant. pub fn as_bid_kind(&self) -> Option<&BidKind> { match self { From 8171806b24ac2178a16b6721587d073eeb42a0d5 Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Wed, 18 Oct 2023 17:01:57 +0000 Subject: [PATCH 24/27] ee/contract_messages: always charge for emit_message Don't increase the cost if the message was not emitted. Also simplify config emit message cost config. Signed-off-by: Alexandru Sardan --- execution_engine/src/runtime/externals.rs | 18 ++- execution_engine/src/runtime/mod.rs | 18 +-- execution_engine/src/runtime_context/mod.rs | 20 ++- .../tests/src/test/contract_messages.rs | 14 +- .../tests/src/test/private_chain.rs | 7 +- .../tests/src/test/regression/ee_966.rs | 5 +- .../tests/src/test/regression/gh_2280.rs | 1 - .../tests/src/test/storage_costs.rs | 57 +------- .../src/test/system_contracts/upgrade.rs | 29 ++-- .../tests/src/test/system_costs.rs | 53 +------ node/src/utils/chain_specification.rs | 7 +- resources/local/chainspec.toml.in | 5 +- resources/production/chainspec.toml | 5 +- types/src/chainspec.rs | 4 +- types/src/chainspec/vm_config.rs | 2 - .../vm_config/host_function_costs.rs | 18 ++- .../src/chainspec/vm_config/message_costs.rs | 131 ------------------ types/src/chainspec/vm_config/wasm_config.rs | 25 +--- types/src/lib.rs | 6 +- 19 files changed, 92 insertions(+), 333 deletions(-) delete mode 100644 types/src/chainspec/vm_config/message_costs.rs diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index 2427f0e814..f1a39d9498 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -1161,9 +1161,15 @@ where // args(3) = size of the serialized message payload in wasm memory let (topic_name_ptr, topic_name_size, message_ptr, message_size) = Args::parse(args)?; + + // Charge for the call to emit message. This increases for every message emitted + // within an execution so we're not using the static value from the wasm config. + self.context + .charge_gas(Gas::new(self.context.emit_message_cost()))?; + // Charge for parameter weights. self.charge_host_function_call( - &host_function_costs.emit_message, - [topic_name_ptr, topic_name_size, message_ptr, message_size], + &HostFunction::new(0, host_function_costs.emit_message.arguments()), + &[topic_name_ptr, topic_name_size, message_ptr, message_size], )?; let limits = self.context.engine_config().wasm_config().messages_limits(); @@ -1185,7 +1191,13 @@ where let result = self.emit_message(topic_name, message)?; if result.is_ok() { - self.charge_emit_message()?; + // Increase the cost for the next call to emit a message. + let new_cost = self + .context + .emit_message_cost() + .checked_add(host_function_costs.cost_increase_per_message.into()) + .ok_or(Error::GasLimit)?; + self.context.set_emit_message_cost(new_cost); } Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 2df8960f41..691ecedb65 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -1399,7 +1399,7 @@ where // counter from there to our counter. Do the same for the message cost tracking. self.context.set_gas_counter(runtime.context.gas_counter()); self.context - .set_last_message_cost(runtime.context.last_message_cost()); + .set_emit_message_cost(runtime.context.emit_message_cost()); { let transfers = self.context.transfers_mut(); @@ -2976,22 +2976,6 @@ where Ok(()) } - /// Charge gas cost for emitting a message. - fn charge_emit_message(&mut self) -> Result<(), Trap> { - let costs = self.context.engine_config().wasm_config().message_costs(); - let cost = match self.context.last_message_cost() { - last_cost if last_cost == U512::from(0) => Gas::new(costs.first_message_cost().into()), - last_cost => Gas::new( - last_cost - .checked_add(costs.cost_increase_per_message().into()) - .ok_or(Error::GasLimit)?, - ), - }; - self.gas(cost)?; - self.context.set_last_message_cost(cost.value()); - Ok(()) - } - /// Creates a dictionary fn new_dictionary(&mut self, output_size_ptr: u32) -> Result, Error> { // check we can write to the host buffer diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 76e8c62875..b0d4f5061d 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -72,7 +72,7 @@ pub struct RuntimeContext<'a, R> { entity_address: Key, package_kind: ContractPackageKind, account_hash: AccountHash, - last_message_cost: U512, + emit_message_cost: U512, } impl<'a, R> RuntimeContext<'a, R> @@ -106,6 +106,12 @@ where remaining_spending_limit: U512, entry_point_type: EntryPointType, ) -> Self { + let emit_message_cost = engine_config + .wasm_config() + .take_host_function_costs() + .emit_message + .cost() + .into(); RuntimeContext { tracking_copy, entry_point_type, @@ -127,7 +133,7 @@ where transfers, remaining_spending_limit, package_kind, - last_message_cost: U512::from(0), + emit_message_cost, } } @@ -182,7 +188,7 @@ where transfers, remaining_spending_limit, package_kind, - last_message_cost: self.last_message_cost, + emit_message_cost: self.emit_message_cost, } } @@ -669,13 +675,13 @@ where } /// Returns the cost charged for the last emitted message. - pub fn last_message_cost(&self) -> U512 { - self.last_message_cost + pub fn emit_message_cost(&self) -> U512 { + self.emit_message_cost } /// Sets the cost charged for the last emitted message. - pub fn set_last_message_cost(&mut self, last_cost: U512) { - self.last_message_cost = last_cost + pub fn set_emit_message_cost(&mut self, cost: U512) { + self.emit_message_cost = cost } /// Returns list of transfers. diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index e414a5cbfe..9eaa7267cf 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -9,8 +9,8 @@ use casper_execution_engine::engine_state::EngineConfigBuilder; use casper_types::{ bytesrepr::ToBytes, contract_messages::{MessageChecksum, MessagePayload, MessageTopicSummary, TopicNameHash}, - crypto, runtime_args, AddressableEntity, BlockTime, ContractHash, Digest, HostFunctionCosts, - Key, MessageCosts, MessageLimits, OpcodeCosts, RuntimeArgs, StorageCosts, StoredValue, + crypto, runtime_args, AddressableEntity, BlockTime, ContractHash, Digest, HostFunction, + HostFunctionCosts, Key, MessageLimits, OpcodeCosts, RuntimeArgs, StorageCosts, StoredValue, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; @@ -457,7 +457,6 @@ fn should_not_exceed_configured_limits() { max_message_size: 100, max_topics_per_contract: 2, }, - MessageCosts::default(), )) .build(); @@ -609,7 +608,6 @@ fn should_charge_expected_gas_for_storage() { StorageCosts::new(GAS_PER_BYTE_COST), HostFunctionCosts::zero(), MessageLimits::default(), - MessageCosts::zero(), ); let custom_engine_config = EngineConfigBuilder::default() .with_wasm_config(wasm_config) @@ -707,12 +705,12 @@ fn should_charge_increasing_gas_cost_for_multiple_messages_emitted() { DEFAULT_MAX_STACK_HEIGHT, OpcodeCosts::zero(), StorageCosts::zero(), - HostFunctionCosts::zero(), - MessageLimits::default(), - MessageCosts { - first_message_cost: FIRST_MESSAGE_EMIT_COST, + HostFunctionCosts { + emit_message: HostFunction::fixed(FIRST_MESSAGE_EMIT_COST), cost_increase_per_message: COST_INCREASE_PER_MESSAGE, + ..Zero::zero() }, + MessageLimits::default(), ); let custom_engine_config = EngineConfigBuilder::default() diff --git a/execution_engine_testing/tests/src/test/private_chain.rs b/execution_engine_testing/tests/src/test/private_chain.rs index 7a0b1f194f..e428d24bf4 100644 --- a/execution_engine_testing/tests/src/test/private_chain.rs +++ b/execution_engine_testing/tests/src/test/private_chain.rs @@ -21,9 +21,9 @@ use once_cell::sync::Lazy; use casper_types::{ account::AccountHash, system::auction::DELEGATION_RATE_DENOMINATOR, AdministratorAccount, - FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessageCosts, - MessageLimits, Motes, OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, - WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + FeeHandling, GenesisAccount, GenesisValidator, HostFunction, HostFunctionCosts, MessageLimits, + Motes, OpcodeCosts, PublicKey, RefundHandling, SecretKey, StorageCosts, WasmConfig, + DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; use tempfile::TempDir; @@ -209,7 +209,6 @@ fn make_wasm_config() -> WasmConfig { StorageCosts::default(), host_functions, MessageLimits::default(), - MessageCosts::default(), ) } diff --git a/execution_engine_testing/tests/src/test/regression/ee_966.rs b/execution_engine_testing/tests/src/test/regression/ee_966.rs index 2abc5fdfb4..4c92f52d87 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_966.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_966.rs @@ -13,8 +13,8 @@ use casper_execution_engine::{ }; use casper_types::{ addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, ApiError, EraId, HostFunctionCosts, - MessageCosts, MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, - WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, + MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, + DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, }; const CONTRACT_EE_966_REGRESSION: &str = "ee_966_regression.wasm"; @@ -29,7 +29,6 @@ static DOUBLED_WASM_MEMORY_LIMIT: Lazy = Lazy::new(|| { StorageCosts::default(), HostFunctionCosts::default(), MessageLimits::default(), - MessageCosts::default(), ) }); static NEW_PROTOCOL_VERSION: Lazy = Lazy::new(|| { diff --git a/execution_engine_testing/tests/src/test/regression/gh_2280.rs b/execution_engine_testing/tests/src/test/regression/gh_2280.rs index c88c81bb24..e7a5852d38 100644 --- a/execution_engine_testing/tests/src/test/regression/gh_2280.rs +++ b/execution_engine_testing/tests/src/test/regression/gh_2280.rs @@ -759,7 +759,6 @@ fn make_wasm_config( old_wasm_config.storage_costs(), new_host_function_costs, old_wasm_config.messages_limits(), - old_wasm_config.message_costs(), ) } diff --git a/execution_engine_testing/tests/src/test/storage_costs.rs b/execution_engine_testing/tests/src/test/storage_costs.rs index afd98770a3..00b24678c7 100644 --- a/execution_engine_testing/tests/src/test/storage_costs.rs +++ b/execution_engine_testing/tests/src/test/storage_costs.rs @@ -1,4 +1,5 @@ use num_rational::Ratio; +use num_traits::Zero; use once_cell::sync::Lazy; #[cfg(not(feature = "use-as-wasm"))] @@ -12,9 +13,9 @@ use casper_execution_engine::engine_state::EngineConfigBuilder; use casper_types::DEFAULT_ADD_BID_COST; use casper_types::{ bytesrepr::{Bytes, ToBytes}, - BrTableCost, CLValue, ContractHash, ControlFlowCosts, EraId, HostFunction, HostFunctionCosts, - MessageCosts, MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, - StoredValue, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + BrTableCost, CLValue, ContractHash, ControlFlowCosts, EraId, HostFunctionCosts, MessageLimits, + OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, StoredValue, WasmConfig, + DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; #[cfg(not(feature = "use-as-wasm"))] use casper_types::{ @@ -88,54 +89,7 @@ const NEW_OPCODE_COSTS: OpcodeCosts = OpcodeCosts { grow_memory: 0, }; -static NEW_HOST_FUNCTION_COSTS: Lazy = Lazy::new(|| HostFunctionCosts { - read_value: HostFunction::fixed(0), - dictionary_get: HostFunction::fixed(0), - write: HostFunction::fixed(0), - dictionary_put: HostFunction::fixed(0), - add: HostFunction::fixed(0), - new_uref: HostFunction::fixed(0), - load_named_keys: HostFunction::fixed(0), - ret: HostFunction::fixed(0), - get_key: HostFunction::fixed(0), - has_key: HostFunction::fixed(0), - put_key: HostFunction::fixed(0), - remove_key: HostFunction::fixed(0), - revert: HostFunction::fixed(0), - is_valid_uref: HostFunction::fixed(0), - add_associated_key: HostFunction::fixed(0), - remove_associated_key: HostFunction::fixed(0), - update_associated_key: HostFunction::fixed(0), - set_action_threshold: HostFunction::fixed(0), - get_caller: HostFunction::fixed(0), - get_blocktime: HostFunction::fixed(0), - create_purse: HostFunction::fixed(0), - transfer_to_account: HostFunction::fixed(0), - transfer_from_purse_to_account: HostFunction::fixed(0), - transfer_from_purse_to_purse: HostFunction::fixed(0), - get_balance: HostFunction::fixed(0), - get_phase: HostFunction::fixed(0), - get_system_contract: HostFunction::fixed(0), - get_main_purse: HostFunction::fixed(0), - read_host_buffer: HostFunction::fixed(0), - create_contract_package_at_hash: HostFunction::fixed(0), - create_contract_user_group: HostFunction::fixed(0), - add_contract_version: HostFunction::fixed(0), - disable_contract_version: HostFunction::fixed(0), - call_contract: HostFunction::fixed(0), - call_versioned_contract: HostFunction::fixed(0), - get_named_arg_size: HostFunction::fixed(0), - get_named_arg: HostFunction::fixed(0), - remove_contract_user_group: HostFunction::fixed(0), - provision_contract_user_group_uref: HostFunction::fixed(0), - remove_contract_user_group_urefs: HostFunction::fixed(0), - print: HostFunction::fixed(0), - blake2b: HostFunction::fixed(0), - random_bytes: HostFunction::fixed(0), - enable_contract_version: HostFunction::fixed(0), - manage_message_topic: HostFunction::fixed(0), - emit_message: HostFunction::fixed(0), -}); +static NEW_HOST_FUNCTION_COSTS: Lazy = Lazy::new(HostFunctionCosts::zero); static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { WasmConfig::new( DEFAULT_WASM_MAX_MEMORY, @@ -144,7 +98,6 @@ static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { StorageCosts::default(), *NEW_HOST_FUNCTION_COSTS, MessageLimits::default(), - MessageCosts::default(), ) }); diff --git a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs index 35a76ba545..7386d35399 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs @@ -17,20 +17,19 @@ use casper_types::{ }, mint::ROUND_SEIGNIORAGE_RATE_KEY, }, - BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessageCosts, MessageLimits, - OpcodeCosts, ProtocolVersion, StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, - DEFAULT_BIT_COST, DEFAULT_CONST_COST, DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, - DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, DEFAULT_CONTROL_FLOW_BR_OPCODE, - DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER, DEFAULT_CONTROL_FLOW_BR_TABLE_OPCODE, - DEFAULT_CONTROL_FLOW_CALL_INDIRECT_OPCODE, DEFAULT_CONTROL_FLOW_CALL_OPCODE, - DEFAULT_CONTROL_FLOW_DROP_OPCODE, DEFAULT_CONTROL_FLOW_ELSE_OPCODE, - DEFAULT_CONTROL_FLOW_END_OPCODE, DEFAULT_CONTROL_FLOW_IF_OPCODE, - DEFAULT_CONTROL_FLOW_LOOP_OPCODE, DEFAULT_CONTROL_FLOW_RETURN_OPCODE, - DEFAULT_CONTROL_FLOW_SELECT_OPCODE, DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, - DEFAULT_DIV_COST, DEFAULT_GLOBAL_COST, DEFAULT_GROW_MEMORY_COST, - DEFAULT_INTEGER_COMPARISON_COST, DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MUL_COST, DEFAULT_NOP_COST, DEFAULT_STORE_COST, - DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, U256, U512, + BrTableCost, CLValue, ControlFlowCosts, EraId, HostFunctionCosts, MessageLimits, OpcodeCosts, + ProtocolVersion, StorageCosts, StoredValue, WasmConfig, DEFAULT_ADD_COST, DEFAULT_BIT_COST, + DEFAULT_CONST_COST, DEFAULT_CONTROL_FLOW_BLOCK_OPCODE, DEFAULT_CONTROL_FLOW_BR_IF_OPCODE, + DEFAULT_CONTROL_FLOW_BR_OPCODE, DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER, + DEFAULT_CONTROL_FLOW_BR_TABLE_OPCODE, DEFAULT_CONTROL_FLOW_CALL_INDIRECT_OPCODE, + DEFAULT_CONTROL_FLOW_CALL_OPCODE, DEFAULT_CONTROL_FLOW_DROP_OPCODE, + DEFAULT_CONTROL_FLOW_ELSE_OPCODE, DEFAULT_CONTROL_FLOW_END_OPCODE, + DEFAULT_CONTROL_FLOW_IF_OPCODE, DEFAULT_CONTROL_FLOW_LOOP_OPCODE, + DEFAULT_CONTROL_FLOW_RETURN_OPCODE, DEFAULT_CONTROL_FLOW_SELECT_OPCODE, + DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, DEFAULT_DIV_COST, DEFAULT_GLOBAL_COST, + DEFAULT_GROW_MEMORY_COST, DEFAULT_INTEGER_COMPARISON_COST, DEFAULT_LOAD_COST, + DEFAULT_LOCAL_COST, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MUL_COST, DEFAULT_NOP_COST, + DEFAULT_STORE_COST, DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, U256, U512, }; const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V1_0_0; @@ -76,7 +75,6 @@ fn get_upgraded_wasm_config() -> WasmConfig { let storage_costs = StorageCosts::default(); let host_function_costs = HostFunctionCosts::default(); let messages_limits = MessageLimits::default(); - let message_costs = MessageCosts::default(); WasmConfig::new( DEFAULT_WASM_MAX_MEMORY, DEFAULT_MAX_STACK_HEIGHT * 2, @@ -84,7 +82,6 @@ fn get_upgraded_wasm_config() -> WasmConfig { storage_costs, host_function_costs, messages_limits, - message_costs, ) } diff --git a/execution_engine_testing/tests/src/test/system_costs.rs b/execution_engine_testing/tests/src/test/system_costs.rs index d1c48eecb4..204fa5952d 100644 --- a/execution_engine_testing/tests/src/test/system_costs.rs +++ b/execution_engine_testing/tests/src/test/system_costs.rs @@ -16,9 +16,9 @@ use casper_types::{ handle_payment, mint, AUCTION, }, AuctionCosts, BrTableCost, ControlFlowCosts, EraId, Gas, GenesisAccount, GenesisValidator, - HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageCosts, - MessageLimits, MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, - SecretKey, StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, + HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, + MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, + StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_TRANSFER_COST, DEFAULT_WASMLESS_TRANSFER_COST, DEFAULT_WASM_MAX_MEMORY, U512, }; @@ -925,52 +925,8 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { // This will verify that user pays for the transfer host function _only_ while host does not // additionally charge for calling mint's "transfer" entrypoint under the hood. let new_host_function_costs = HostFunctionCosts { - read_value: HostFunction::fixed(0), - dictionary_get: HostFunction::fixed(0), - write: HostFunction::fixed(0), - dictionary_put: HostFunction::fixed(0), - add: HostFunction::fixed(0), - new_uref: HostFunction::fixed(0), - load_named_keys: HostFunction::fixed(0), - ret: HostFunction::fixed(0), - get_key: HostFunction::fixed(0), - has_key: HostFunction::fixed(0), - put_key: HostFunction::fixed(0), - remove_key: HostFunction::fixed(0), - revert: HostFunction::fixed(0), - is_valid_uref: HostFunction::fixed(0), - add_associated_key: HostFunction::fixed(0), - remove_associated_key: HostFunction::fixed(0), - update_associated_key: HostFunction::fixed(0), - set_action_threshold: HostFunction::fixed(0), - get_caller: HostFunction::fixed(0), - get_blocktime: HostFunction::fixed(0), - create_purse: HostFunction::fixed(0), - transfer_to_account: HostFunction::fixed(0), - transfer_from_purse_to_account: HostFunction::fixed(0), - transfer_from_purse_to_purse: HostFunction::fixed(0), - get_balance: HostFunction::fixed(0), - get_phase: HostFunction::fixed(0), - get_system_contract: HostFunction::fixed(0), - get_main_purse: HostFunction::fixed(0), - read_host_buffer: HostFunction::fixed(0), - create_contract_package_at_hash: HostFunction::fixed(0), - create_contract_user_group: HostFunction::fixed(0), - add_contract_version: HostFunction::fixed(0), - disable_contract_version: HostFunction::fixed(0), call_contract: HostFunction::fixed(UPDATED_CALL_CONTRACT_COST), - call_versioned_contract: HostFunction::fixed(0), - get_named_arg_size: HostFunction::fixed(0), - get_named_arg: HostFunction::fixed(0), - remove_contract_user_group: HostFunction::fixed(0), - provision_contract_user_group_uref: HostFunction::fixed(0), - remove_contract_user_group_urefs: HostFunction::fixed(0), - print: HostFunction::fixed(0), - blake2b: HostFunction::fixed(0), - random_bytes: HostFunction::fixed(0), - enable_contract_version: HostFunction::fixed(0), - manage_message_topic: HostFunction::fixed(0), - emit_message: HostFunction::fixed(0), + ..Zero::zero() }; let new_wasm_config = WasmConfig::new( @@ -980,7 +936,6 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { new_storage_costs, new_host_function_costs, MessageLimits::default(), - MessageCosts::default(), ); let new_wasmless_transfer_cost = 0; diff --git a/node/src/utils/chain_specification.rs b/node/src/utils/chain_specification.rs index de2caa1098..1fea49971b 100644 --- a/node/src/utils/chain_specification.rs +++ b/node/src/utils/chain_specification.rs @@ -129,9 +129,8 @@ mod tests { use casper_types::{ bytesrepr::FromBytes, ActivationPoint, BrTableCost, ChainspecRawBytes, ControlFlowCosts, CoreConfig, EraId, GlobalStateUpdate, HighwayConfig, HostFunction, HostFunctionCosts, - MessageCosts, MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, - StorageCosts, StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, - WasmConfig, U512, + MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, + StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, WasmConfig, U512, }; use super::*; @@ -224,6 +223,7 @@ mod tests { enable_contract_version: HostFunction::new(142, [0, 1, 2, 3]), manage_message_topic: HostFunction::new(100, [0, 1, 2, 4]), emit_message: HostFunction::new(100, [0, 1, 2, 3]), + cost_increase_per_message: 50, }); static EXPECTED_GENESIS_WASM_COSTS: Lazy = Lazy::new(|| { WasmConfig::new( @@ -233,7 +233,6 @@ mod tests { EXPECTED_GENESIS_STORAGE_COSTS, *EXPECTED_GENESIS_HOST_FUNCTION_COSTS, MessageLimits::default(), - MessageCosts::default(), ) }); diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 3cda18e078..7af9f49f22 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -255,16 +255,13 @@ write = { cost = 14_000, arguments = [0, 0, 0, 980] } write_local = { cost = 9_500, arguments = [0, 1_800, 0, 520] } manage_message_topic = { cost = 200, arguments = [0, 0, 0, 0] } emit_message = { cost = 200, arguments = [0, 0, 0, 0] } +cost_increase_per_message = 50 [wasm.messages_limits] max_topic_name_size = 256 max_topics_per_contract = 128 max_message_size = 1_024 -[wasm.message_costs] -first_message_cost = 100 -cost_increase_per_message = 50 - [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 333a5d45ba..651e863973 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -266,16 +266,13 @@ write_local = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 0, 0, 0] } emit_message = { cost = 200, arguments = [0, 0, 0, 0] } +cost_increase_per_message = 50 [wasm.messages_limits] max_topic_name_size = 256 max_topics_per_contract = 128 max_message_size = 1_024 -[wasm.message_costs] -first_message_cost = 100 -cost_increase_per_message = 50 - [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index b61090af7a..6e8727c8ff 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -47,8 +47,8 @@ pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Confi pub use transaction_config::{DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES}; pub use vm_config::{ AuctionCosts, BrTableCost, ChainspecRegistry, ControlFlowCosts, HandlePaymentCosts, - HostFunction, HostFunctionCost, HostFunctionCosts, MessageCosts, MessageLimits, MintCosts, - OpcodeCosts, StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, + HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, MintCosts, OpcodeCosts, + StandardPaymentCosts, StorageCosts, SystemConfig, UpgradeConfig, WasmConfig, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, }; #[cfg(any(feature = "testing", test))] diff --git a/types/src/chainspec/vm_config.rs b/types/src/chainspec/vm_config.rs index 33f39d3d4d..34bb856e63 100644 --- a/types/src/chainspec/vm_config.rs +++ b/types/src/chainspec/vm_config.rs @@ -2,7 +2,6 @@ mod auction_costs; mod chainspec_registry; mod handle_payment_costs; mod host_function_costs; -mod message_costs; mod message_limits; mod mint_costs; mod opcode_costs; @@ -19,7 +18,6 @@ pub use host_function_costs::{ Cost as HostFunctionCost, HostFunction, HostFunctionCosts, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_NEW_DICTIONARY_COST, }; -pub use message_costs::MessageCosts; pub use message_limits::MessageLimits; pub use mint_costs::{MintCosts, DEFAULT_TRANSFER_COST}; pub use opcode_costs::{BrTableCost, ControlFlowCosts, OpcodeCosts}; diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index d8740e8482..332c3ad010 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -88,6 +88,10 @@ pub const DEFAULT_NEW_DICTIONARY_COST: u32 = DEFAULT_NEW_UREF_COST; pub const DEFAULT_HOST_FUNCTION_NEW_DICTIONARY: HostFunction<[Cost; 1]> = HostFunction::new(DEFAULT_NEW_DICTIONARY_COST, [NOT_USED]); +/// Default value that the cost of calling `casper_emit_message` increases by for every new message +/// emitted within an execution. +pub const DEFAULT_COST_INCREASE_PER_MESSAGE_EMITTED: u32 = 50; + /// Representation of a host function cost. /// /// The total gas cost is equal to `cost` + sum of each argument weight multiplied by the byte size @@ -231,6 +235,8 @@ where #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct HostFunctionCosts { + /// Cost increase for successive calls to `casper_emit_message` within an execution. + pub cost_increase_per_message: u32, /// Cost of calling the `read_value` host function. pub read_value: HostFunction<[Cost; 3]>, /// Cost of calling the `dictionary_get` host function. @@ -376,6 +382,7 @@ impl Zero for HostFunctionCosts { enable_contract_version: HostFunction::zero(), manage_message_topic: HostFunction::zero(), emit_message: HostFunction::zero(), + cost_increase_per_message: Zero::zero(), } } @@ -426,6 +433,7 @@ impl Zero for HostFunctionCosts { && self.enable_contract_version.is_zero() && self.manage_message_topic.is_zero() && self.emit_message.is_zero() + && self.cost_increase_per_message.is_zero() } } @@ -561,6 +569,7 @@ impl Default for HostFunctionCosts { enable_contract_version: HostFunction::default(), manage_message_topic: HostFunction::default(), emit_message: HostFunction::default(), + cost_increase_per_message: DEFAULT_COST_INCREASE_PER_MESSAGE_EMITTED, } } } @@ -614,6 +623,7 @@ impl ToBytes for HostFunctionCosts { ret.append(&mut self.enable_contract_version.to_bytes()?); ret.append(&mut self.manage_message_topic.to_bytes()?); ret.append(&mut self.emit_message.to_bytes()?); + ret.append(&mut self.cost_increase_per_message.to_bytes()?); Ok(ret) } @@ -664,6 +674,7 @@ impl ToBytes for HostFunctionCosts { + self.enable_contract_version.serialized_length() + self.manage_message_topic.serialized_length() + self.emit_message.serialized_length() + + self.cost_increase_per_message.serialized_length() } } @@ -715,6 +726,7 @@ impl FromBytes for HostFunctionCosts { let (enable_contract_version, rem) = FromBytes::from_bytes(rem)?; let (manage_message_topic, rem) = FromBytes::from_bytes(rem)?; let (emit_message, rem) = FromBytes::from_bytes(rem)?; + let (cost_increase_per_message, rem) = FromBytes::from_bytes(rem)?; Ok(( HostFunctionCosts { read_value, @@ -763,6 +775,7 @@ impl FromBytes for HostFunctionCosts { enable_contract_version, manage_message_topic, emit_message, + cost_increase_per_message, }, rem, )) @@ -818,6 +831,7 @@ impl Distribution for Standard { enable_contract_version: rng.gen(), manage_message_topic: rng.gen(), emit_message: rng.gen(), + cost_increase_per_message: rng.gen(), } } } @@ -825,7 +839,7 @@ impl Distribution for Standard { #[doc(hidden)] #[cfg(any(feature = "gens", test))] pub mod gens { - use proptest::prelude::*; + use proptest::{num, prelude::*}; use crate::{HostFunction, HostFunctionCost, HostFunctionCosts}; @@ -883,6 +897,7 @@ pub mod gens { enable_contract_version in host_function_cost_arb(), manage_message_topic in host_function_cost_arb(), emit_message in host_function_cost_arb(), + cost_increase_per_message in num::u32::ANY, ) -> HostFunctionCosts { HostFunctionCosts { read_value, @@ -931,6 +946,7 @@ pub mod gens { enable_contract_version, manage_message_topic, emit_message, + cost_increase_per_message, } } } diff --git a/types/src/chainspec/vm_config/message_costs.rs b/types/src/chainspec/vm_config/message_costs.rs deleted file mode 100644 index a74b8bc78b..0000000000 --- a/types/src/chainspec/vm_config/message_costs.rs +++ /dev/null @@ -1,131 +0,0 @@ -#[cfg(feature = "datasize")] -use datasize::DataSize; -use derive_more::Add; -use num_traits::Zero; -use rand::{distributions::Standard, prelude::*, Rng}; -use serde::{Deserialize, Serialize}; - -use crate::bytesrepr::{self, FromBytes, ToBytes}; - -/// Configuration for messages limits. -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Add)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[serde(deny_unknown_fields)] -pub struct MessageCosts { - /// Cost for emitting a message. - pub first_message_cost: u32, - /// Cost increase for successive messages emitted for each execution. - pub cost_increase_per_message: u32, -} - -impl MessageCosts { - /// Return cost for emitting a message. - pub fn first_message_cost(&self) -> u32 { - self.first_message_cost - } - - /// Return the ratio of cost increase for successive messages emitted for each execution. - pub fn cost_increase_per_message(&self) -> u32 { - self.cost_increase_per_message - } -} - -impl Default for MessageCosts { - fn default() -> Self { - Self { - first_message_cost: 100, - cost_increase_per_message: 50, - } - } -} - -impl Zero for MessageCosts { - fn zero() -> Self { - MessageCosts { - first_message_cost: 0, - cost_increase_per_message: 0, - } - } - - fn is_zero(&self) -> bool { - self.first_message_cost == 0 && self.cost_increase_per_message == 0 - } -} - -impl ToBytes for MessageCosts { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut ret = bytesrepr::unchecked_allocate_buffer(self); - - ret.append(&mut self.first_message_cost.to_bytes()?); - ret.append(&mut self.cost_increase_per_message.to_bytes()?); - - Ok(ret) - } - - fn serialized_length(&self) -> usize { - self.first_message_cost.serialized_length() - + self.cost_increase_per_message.serialized_length() - } -} - -impl FromBytes for MessageCosts { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (first_message_cost, rem) = FromBytes::from_bytes(bytes)?; - let (cost_increase_per_message, rem) = FromBytes::from_bytes(rem)?; - - Ok(( - MessageCosts { - first_message_cost, - cost_increase_per_message, - }, - rem, - )) - } -} - -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> MessageCosts { - MessageCosts { - first_message_cost: rng.gen(), - cost_increase_per_message: rng.gen(), - } - } -} - -#[doc(hidden)] -#[cfg(any(feature = "gens", test))] -pub mod gens { - use proptest::{num, prop_compose}; - - use super::MessageCosts; - - prop_compose! { - pub fn message_costs_arb()( - first_message_cost in num::u32::ANY, - cost_increase_per_message in num::u32::ANY, - ) -> MessageCosts { - MessageCosts { - first_message_cost, - cost_increase_per_message, - } - } - } -} - -#[cfg(test)] -mod tests { - use proptest::proptest; - - use crate::bytesrepr; - - use super::gens; - - proptest! { - #[test] - fn should_serialize_and_deserialize_with_arbitrary_values( - message_limits in gens::message_costs_arb() - ) { - bytesrepr::test_serialization_roundtrip(&message_limits); - } - } -} diff --git a/types/src/chainspec/vm_config/wasm_config.rs b/types/src/chainspec/vm_config/wasm_config.rs index f5c26b9d6d..ab73b44b6c 100644 --- a/types/src/chainspec/vm_config/wasm_config.rs +++ b/types/src/chainspec/vm_config/wasm_config.rs @@ -6,9 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - chainspec::vm_config::{ - HostFunctionCosts, MessageCosts, MessageLimits, OpcodeCosts, StorageCosts, - }, + chainspec::vm_config::{HostFunctionCosts, MessageLimits, OpcodeCosts, StorageCosts}, }; /// Default maximum number of pages of the Wasm memory. @@ -36,8 +34,6 @@ pub struct WasmConfig { host_function_costs: HostFunctionCosts, /// Messages limits. messages_limits: MessageLimits, - /// Messages costs. - message_costs: MessageCosts, } impl WasmConfig { @@ -49,7 +45,6 @@ impl WasmConfig { storage_costs: StorageCosts, host_function_costs: HostFunctionCosts, messages_limits: MessageLimits, - message_costs: MessageCosts, ) -> Self { Self { max_memory, @@ -58,7 +53,6 @@ impl WasmConfig { storage_costs, host_function_costs, messages_limits, - message_costs, } } @@ -81,11 +75,6 @@ impl WasmConfig { pub fn messages_limits(&self) -> MessageLimits { self.messages_limits } - - /// Returns the costs for emitting messages. - pub fn message_costs(&self) -> MessageCosts { - self.message_costs - } } impl Default for WasmConfig { @@ -97,7 +86,6 @@ impl Default for WasmConfig { storage_costs: StorageCosts::default(), host_function_costs: HostFunctionCosts::default(), messages_limits: MessageLimits::default(), - message_costs: MessageCosts::default(), } } } @@ -112,7 +100,6 @@ impl ToBytes for WasmConfig { ret.append(&mut self.storage_costs.to_bytes()?); ret.append(&mut self.host_function_costs.to_bytes()?); ret.append(&mut self.messages_limits.to_bytes()?); - ret.append(&mut self.message_costs.to_bytes()?); Ok(ret) } @@ -124,7 +111,6 @@ impl ToBytes for WasmConfig { + self.storage_costs.serialized_length() + self.host_function_costs.serialized_length() + self.messages_limits.serialized_length() - + self.message_costs.serialized_length() } } @@ -136,7 +122,6 @@ impl FromBytes for WasmConfig { let (storage_costs, rem) = FromBytes::from_bytes(rem)?; let (host_function_costs, rem) = FromBytes::from_bytes(rem)?; let (messages_limits, rem) = FromBytes::from_bytes(rem)?; - let (message_costs, rem) = FromBytes::from_bytes(rem)?; Ok(( WasmConfig { @@ -146,7 +131,6 @@ impl FromBytes for WasmConfig { storage_costs, host_function_costs, messages_limits, - message_costs, }, rem, )) @@ -162,7 +146,6 @@ impl Distribution for Standard { storage_costs: rng.gen(), host_function_costs: rng.gen(), messages_limits: rng.gen(), - message_costs: rng.gen(), } } } @@ -175,8 +158,8 @@ pub mod gens { use crate::{ chainspec::vm_config::{ host_function_costs::gens::host_function_costs_arb, - message_costs::gens::message_costs_arb, message_limits::gens::message_limits_arb, - opcode_costs::gens::opcode_costs_arb, storage_costs::gens::storage_costs_arb, + message_limits::gens::message_limits_arb, opcode_costs::gens::opcode_costs_arb, + storage_costs::gens::storage_costs_arb, }, WasmConfig, }; @@ -189,7 +172,6 @@ pub mod gens { storage_costs in storage_costs_arb(), host_function_costs in host_function_costs_arb(), messages_limits in message_limits_arb(), - message_costs in message_costs_arb(), ) -> WasmConfig { WasmConfig { max_memory, @@ -198,7 +180,6 @@ pub mod gens { storage_costs, host_function_costs, messages_limits, - message_costs, } } } diff --git a/types/src/lib.rs b/types/src/lib.rs index 8fda61a3f7..d98fdc041b 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -105,9 +105,9 @@ pub use chainspec::{ ControlFlowCosts, CoreConfig, DelegatorConfig, DeployConfig, FeeHandling, GenesisAccount, GenesisValidator, GlobalStateUpdate, GlobalStateUpdateConfig, GlobalStateUpdateError, HandlePaymentCosts, HighwayConfig, HostFunction, HostFunctionCost, HostFunctionCosts, - LegacyRequiredFinality, MessageCosts, MessageLimits, MintCosts, NetworkConfig, OpcodeCosts, - ProtocolConfig, RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, - TransactionConfig, TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, + LegacyRequiredFinality, MessageLimits, MintCosts, NetworkConfig, OpcodeCosts, ProtocolConfig, + RefundHandling, StandardPaymentCosts, StorageCosts, SystemConfig, TransactionConfig, + TransactionV1Config, UpgradeConfig, ValidatorConfig, WasmConfig, }; #[cfg(any(all(feature = "std", feature = "testing"), test))] pub use chainspec::{ From ca7e41e996289f45df708272f59ebbbf77ca95da Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 20 Oct 2023 18:51:17 +0000 Subject: [PATCH 25/27] contract_messages: don't store contract messages with exec results Separate contract messages from the execution results; we generally don't want to store messages since they are supposed to be ephemeral. Making them part of the exec results will make them get stored automatically in the node DB with the exec results. Signed-off-by: Alexandru Sardan --- .../src/engine_state/execution_result.rs | 38 +++++--- execution_engine/src/runtime_context/mod.rs | 4 +- execution_engine/src/tracking_copy/mod.rs | 6 +- node/src/components/contract_runtime.rs | 2 +- .../components/contract_runtime/operations.rs | 42 ++++++--- node/src/components/contract_runtime/types.rs | 3 +- node/src/components/event_stream_server.rs | 2 + .../components/event_stream_server/event.rs | 2 + .../event_stream_server/sse_server.rs | 14 ++- .../rpc_server/rpcs/speculative_exec.rs | 11 ++- node/src/effect.rs | 3 +- node/src/effect/requests.rs | 3 +- node/src/reactor/main_reactor.rs | 54 +++++++++--- node/src/types/block/meta_block.rs | 20 ++--- resources/test/rpc_schema.json | 88 ++----------------- resources/test/sse_data_schema.json | 23 ++--- types/src/contract_messages.rs | 2 +- types/src/contract_messages/messages.rs | 25 ++++++ types/src/execution/execution_result_v2.rs | 74 ++-------------- 19 files changed, 181 insertions(+), 235 deletions(-) diff --git a/execution_engine/src/engine_state/execution_result.rs b/execution_engine/src/engine_state/execution_result.rs index 6b379ef071..58b742886b 100644 --- a/execution_engine/src/engine_state/execution_result.rs +++ b/execution_engine/src/engine_state/execution_result.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use casper_types::{ bytesrepr::FromBytes, - contract_messages::Message, + contract_messages::Messages, execution::{Effects, ExecutionResultV2 as TypesExecutionResult, Transform, TransformKind}, CLTyped, CLValue, Gas, Key, Motes, StoredValue, TransferAddr, }; @@ -27,7 +27,7 @@ pub enum ExecutionResult { /// Execution effects. effects: Effects, /// Messages emitted during execution. - messages: Vec, + messages: Messages, }, /// Execution was finished successfully Success { @@ -38,7 +38,7 @@ pub enum ExecutionResult { /// Execution effects. effects: Effects, /// Messages emitted during execution. - messages: Vec, + messages: Messages, }, } @@ -330,7 +330,15 @@ impl ExecutionResult { } } -impl From for TypesExecutionResult { +/// A versioned execution result and the messages produced by that execution. +pub struct ExecutionResultAndMessages { + /// Execution result + pub execution_result: TypesExecutionResult, + /// Messages emitted during execution + pub messages: Messages, +} + +impl From for ExecutionResultAndMessages { fn from(execution_result: ExecutionResult) -> Self { match execution_result { ExecutionResult::Success { @@ -338,10 +346,12 @@ impl From for TypesExecutionResult { cost, effects, messages, - } => TypesExecutionResult::Success { - effects, - transfers, - cost: cost.value(), + } => ExecutionResultAndMessages { + execution_result: TypesExecutionResult::Success { + effects, + transfers, + cost: cost.value(), + }, messages, }, ExecutionResult::Failure { @@ -350,11 +360,13 @@ impl From for TypesExecutionResult { cost, effects, messages, - } => TypesExecutionResult::Failure { - effects, - transfers, - cost: cost.value(), - error_message: error.to_string(), + } => ExecutionResultAndMessages { + execution_result: TypesExecutionResult::Failure { + effects, + transfers, + cost: cost.value(), + error_message: error.to_string(), + }, messages, }, } diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 1234d42fb8..96cf39a64f 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -23,7 +23,7 @@ use casper_types::{ }, bytesrepr::ToBytes, contract_messages::{ - Message, MessageAddr, MessageChecksum, MessageTopicSummary, TopicNameHash, + Message, MessageAddr, MessageChecksum, MessageTopicSummary, Messages, TopicNameHash, }, execution::Effects, package::{PackageKind, PackageKindTag}, @@ -574,7 +574,7 @@ where } /// Returns a copy of the current messages of a tracking copy. - pub fn messages(&self) -> Vec { + pub fn messages(&self) -> Messages { self.tracking_copy.borrow().messages() } diff --git a/execution_engine/src/tracking_copy/mod.rs b/execution_engine/src/tracking_copy/mod.rs index b26ef7f01b..7b27926cca 100644 --- a/execution_engine/src/tracking_copy/mod.rs +++ b/execution_engine/src/tracking_copy/mod.rs @@ -19,7 +19,7 @@ use casper_storage::global_state::{state::StateReader, trie::merkle_proof::TrieM use casper_types::{ addressable_entity::NamedKeys, bytesrepr::{self}, - contract_messages::Message, + contract_messages::{Message, Messages}, execution::{Effects, Transform, TransformError, TransformInstruction, TransformKind}, CLType, CLValue, CLValueError, Digest, Key, KeyTag, StoredValue, StoredValueTypeMismatch, Tagged, U512, @@ -224,7 +224,7 @@ pub struct TrackingCopy { reader: R, cache: TrackingCopyCache, effects: Effects, - messages: Vec, + messages: Messages, } /// Result of executing an "add" operation on a value in the state. @@ -457,7 +457,7 @@ impl> TrackingCopy { } /// Returns a copy of the messages cached by this instance. - pub fn messages(&self) -> Vec { + pub fn messages(&self) -> Messages { self.messages.clone() } diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index c6658bb942..775c14a310 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -977,7 +977,7 @@ impl ContractRuntime { let execution_results_map: HashMap<_, _> = execution_results .iter() .cloned() - .map(|(deploy_hash, _, execution_result)| (deploy_hash, execution_result)) + .map(|(deploy_hash, _, execution_result, _)| (deploy_hash, execution_result)) .collect(); if meta_block_state.register_as_stored().was_updated() { effect_builder diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index b8f976b52c..ce94171ea4 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -5,10 +5,12 @@ use tracing::{debug, error, info, trace, warn}; use casper_execution_engine::{ engine_state::{ - self, execution_result::ExecutionResults, step::EvictItem, ChecksumRegistry, DeployItem, - EngineState, ExecuteRequest, ExecutionResult as EngineExecutionResult, - GetEraValidatorsRequest, PruneConfig, PruneResult, QueryRequest, QueryResult, StepError, - StepRequest, StepSuccess, + self, + execution_result::{ExecutionResultAndMessages, ExecutionResults}, + step::EvictItem, + ChecksumRegistry, DeployItem, EngineState, ExecuteRequest, + ExecutionResult as EngineExecutionResult, GetEraValidatorsRequest, PruneConfig, + PruneResult, QueryRequest, QueryResult, StepError, StepRequest, StepSuccess, }, execution, }; @@ -18,6 +20,7 @@ use casper_storage::{ }; use casper_types::{ bytesrepr::{self, ToBytes, U32_SERIALIZED_LENGTH}, + contract_messages::Messages, execution::{Effects, ExecutionResult, ExecutionResultV2, Transform, TransformKind}, AddressableEntity, BlockV2, CLValue, DeployHash, Digest, EraEndV2, EraId, HashAddr, Key, ProtocolVersion, PublicKey, StoredValue, U512, @@ -157,20 +160,21 @@ pub fn execute_finalized_block( trace!(?deploy_hash, ?result, "deploy execution result"); // As for now a given state is expected to exist. - let (state_hash, execution_result) = commit_execution_results( + let (state_hash, execution_result, messages) = commit_execution_results( &scratch_state, metrics.clone(), state_root_hash, deploy_hash, result, )?; - execution_results.push((deploy_hash, deploy_header, execution_result)); + execution_results.push((deploy_hash, deploy_header, execution_result, messages)); state_root_hash = state_hash; } // Write the deploy approvals' and execution results' checksums to global state. - let execution_results_checksum = - compute_execution_results_checksum(execution_results.iter().map(|(_, _, result)| result))?; + let execution_results_checksum = compute_execution_results_checksum( + execution_results.iter().map(|(_, _, result, _)| result), + )?; let mut checksum_registry = ChecksumRegistry::new(); checksum_registry.insert(APPROVALS_CHECKSUM_NAME, approvals_checksum); @@ -377,7 +381,7 @@ fn commit_execution_results( state_root_hash: Digest, deploy_hash: DeployHash, execution_results: ExecutionResults, -) -> Result<(Digest, ExecutionResult), BlockExecutionError> +) -> Result<(Digest, ExecutionResult, Messages), BlockExecutionError> where S: StateProvider + CommitProvider, S::Error: Into, @@ -408,9 +412,12 @@ where } }; let new_state_root = commit_transforms(engine_state, metrics, state_root_hash, effects)?; - let versioned_execution_result = - ExecutionResult::from(ExecutionResultV2::from(ee_execution_result)); - Ok((new_state_root, versioned_execution_result)) + let ExecutionResultAndMessages { + execution_result, + messages, + } = ExecutionResultAndMessages::from(ee_execution_result); + let versioned_execution_result = ExecutionResult::from(execution_result); + Ok((new_state_root, versioned_execution_result, messages)) } fn commit_transforms( @@ -441,7 +448,7 @@ pub fn execute_only( engine_state: &EngineState, execution_state: SpeculativeExecutionState, deploy: DeployItem, -) -> Result, engine_state::Error> +) -> Result, engine_state::Error> where S: StateProvider + CommitProvider, S::Error: Into, @@ -473,7 +480,14 @@ where // with `Some(_)` but `pop_front` already returns an `Option`. // We need to transform the `engine_state::ExecutionResult` into // `casper_types::ExecutionResult` as well. - execution_results.pop_front().map(Into::into) + execution_results.pop_front().map(|result| { + let ExecutionResultAndMessages { + execution_result, + messages, + } = result.into(); + + (execution_result, messages) + }) } }) } diff --git a/node/src/components/contract_runtime/types.rs b/node/src/components/contract_runtime/types.rs index ed326aef58..761e32aff6 100644 --- a/node/src/components/contract_runtime/types.rs +++ b/node/src/components/contract_runtime/types.rs @@ -4,6 +4,7 @@ use datasize::DataSize; use casper_execution_engine::engine_state::GetEraValidatorsRequest; use casper_types::{ + contract_messages::Messages, execution::{Effects, ExecutionResult}, BlockV2, DeployHash, DeployHeader, Digest, EraId, ProtocolVersion, PublicKey, U512, }; @@ -124,7 +125,7 @@ pub struct BlockAndExecutionResults { /// The [`ApprovalsHashes`] for the deploys in this block. pub(crate) approvals_hashes: Box, /// The results from executing the deploys in the block. - pub(crate) execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult)>, + pub(crate) execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult, Messages)>, /// The [`Effects`] and the upcoming validator sets determined by the `step` pub(crate) maybe_step_effects_and_upcoming_era_validators: Option, diff --git a/node/src/components/event_stream_server.rs b/node/src/components/event_stream_server.rs index 3b50af2e66..6a4e042cd5 100644 --- a/node/src/components/event_stream_server.rs +++ b/node/src/components/event_stream_server.rs @@ -294,6 +294,7 @@ where deploy_header, block_hash, execution_result, + messages, } => self.broadcast(SseData::DeployProcessed { deploy_hash: Box::new(deploy_hash), account: Box::new(deploy_header.account().clone()), @@ -302,6 +303,7 @@ where dependencies: deploy_header.dependencies().clone(), block_hash: Box::new(block_hash), execution_result, + messages, }), Event::DeploysExpired(deploy_hashes) => deploy_hashes .into_iter() diff --git a/node/src/components/event_stream_server/event.rs b/node/src/components/event_stream_server/event.rs index 1bd55f51cc..2c73c10ba1 100644 --- a/node/src/components/event_stream_server/event.rs +++ b/node/src/components/event_stream_server/event.rs @@ -6,6 +6,7 @@ use std::{ use itertools::Itertools; use casper_types::{ + contract_messages::Messages, execution::{Effects, ExecutionResult}, Block, BlockHash, Deploy, DeployHash, DeployHeader, EraId, FinalitySignature, PublicKey, Timestamp, @@ -21,6 +22,7 @@ pub enum Event { deploy_header: Box, block_hash: BlockHash, execution_result: Box, + messages: Messages, }, DeploysExpired(Vec), Fault { diff --git a/node/src/components/event_stream_server/sse_server.rs b/node/src/components/event_stream_server/sse_server.rs index 33596ddec0..6039a5c77f 100644 --- a/node/src/components/event_stream_server/sse_server.rs +++ b/node/src/components/event_stream_server/sse_server.rs @@ -32,13 +32,14 @@ use warp::{ Filter, Reply, }; -#[cfg(test)] -use casper_types::{execution::ExecutionResultV2, testing::TestRng, TestBlockBuilder}; use casper_types::{ + contract_messages::Messages, execution::{Effects, ExecutionResult}, Block, BlockHash, Deploy, DeployHash, EraId, FinalitySignature, ProtocolVersion, PublicKey, TimeDiff, Timestamp, }; +#[cfg(test)] +use casper_types::{execution::ExecutionResultV2, testing::TestRng, TestBlockBuilder}; /// The URL root path. pub const SSE_API_PATH: &str = "events"; @@ -73,8 +74,9 @@ pub enum SseData { ttl: TimeDiff, dependencies: Vec, block_hash: Box, - #[data_size(skip)] + //#[data_size(skip)] execution_result: Box, + messages: Messages, }, /// The given deploy has expired. DeployExpired { deploy_hash: DeployHash }, @@ -118,6 +120,11 @@ impl SseData { /// Returns a random `SseData::DeployProcessed`. pub(super) fn random_deploy_processed(rng: &mut TestRng) -> Self { let deploy = Deploy::random(rng); + let message_count = rng.gen_range(0..6); + let messages = std::iter::repeat_with(|| rng.gen()) + .take(message_count) + .collect(); + SseData::DeployProcessed { deploy_hash: Box::new(*deploy.hash()), account: Box::new(deploy.header().account().clone()), @@ -126,6 +133,7 @@ impl SseData { dependencies: deploy.header().dependencies().clone(), block_hash: Box::new(BlockHash::random(rng)), execution_result: Box::new(ExecutionResult::from(ExecutionResultV2::random(rng))), + messages, } } diff --git a/node/src/components/rpc_server/rpcs/speculative_exec.rs b/node/src/components/rpc_server/rpcs/speculative_exec.rs index af8f9ff8fe..966ee72d5d 100644 --- a/node/src/components/rpc_server/rpcs/speculative_exec.rs +++ b/node/src/components/rpc_server/rpcs/speculative_exec.rs @@ -9,7 +9,10 @@ use serde::{Deserialize, Serialize}; use casper_execution_engine::engine_state::Error as EngineStateError; use casper_json_rpc::ReservedErrorCode; -use casper_types::{execution::ExecutionResultV2, BlockHash, Deploy, ProtocolVersion, Transaction}; +use casper_types::{ + contract_messages::Messages, execution::ExecutionResultV2, BlockHash, Deploy, ProtocolVersion, + Transaction, +}; use super::{ chain::BlockIdentifier, @@ -27,6 +30,7 @@ static SPECULATIVE_EXEC_RESULT: Lazy = Lazy::new(|| Specu api_version: DOCS_EXAMPLE_PROTOCOL_VERSION, block_hash: *BlockHash::example(), execution_result: ExecutionResultV2::example().clone(), + messages: Vec::new(), }); /// Params for "speculative_exec" RPC request. @@ -56,6 +60,8 @@ pub struct SpeculativeExecResult { pub block_hash: BlockHash, /// Result of the execution. pub execution_result: ExecutionResultV2, + /// Contract messages emitted during execution. + pub messages: Messages, } impl DocExample for SpeculativeExecResult { @@ -113,11 +119,12 @@ impl RpcWithParams for SpeculativeExec { .await; match result { - Ok(Some(execution_result)) => { + Ok(Some((execution_result, messages))) => { let result = Self::ResponseResult { api_version, block_hash, execution_result, + messages, }; Ok(result) } diff --git a/node/src/effect.rs b/node/src/effect.rs index 7797f59e78..8e8d11fc26 100644 --- a/node/src/effect.rs +++ b/node/src/effect.rs @@ -122,6 +122,7 @@ use casper_execution_engine::engine_state::{ use casper_storage::global_state::trie::TrieRaw; use casper_types::{ bytesrepr::Bytes, + contract_messages::Messages, execution::{Effects as ExecutionEffects, ExecutionResult, ExecutionResultV2}, package::Package, system::auction::EraValidators, @@ -2248,7 +2249,7 @@ impl EffectBuilder { self, execution_prestate: SpeculativeExecutionState, deploy: Arc, - ) -> Result, engine_state::Error> + ) -> Result, engine_state::Error> where REv: From, { diff --git a/node/src/effect/requests.rs b/node/src/effect/requests.rs index 277d5993ca..bae7abaa83 100644 --- a/node/src/effect/requests.rs +++ b/node/src/effect/requests.rs @@ -27,6 +27,7 @@ use casper_storage::global_state::trie::TrieRaw; use casper_types::{ addressable_entity::AddressableEntity, bytesrepr::Bytes, + contract_messages::Messages, execution::{ExecutionResult, ExecutionResultV2}, system::auction::EraValidators, Block, BlockHash, BlockHeader, BlockSignatures, BlockV2, ChainspecRawBytes, Deploy, DeployHash, @@ -958,7 +959,7 @@ pub(crate) enum ContractRuntimeRequest { /// Deploy to execute. deploy: Arc, /// Results - responder: Responder, engine_state::Error>>, + responder: Responder, engine_state::Error>>, }, } diff --git a/node/src/reactor/main_reactor.rs b/node/src/reactor/main_reactor.rs index 4c5361fae5..83c65f4841 100644 --- a/node/src/reactor/main_reactor.rs +++ b/node/src/reactor/main_reactor.rs @@ -1457,6 +1457,7 @@ impl MainReactor { if let MetaBlock::Forward(forward_meta_block) = &meta_block { let block = forward_meta_block.block.clone(); + let execution_results = forward_meta_block.execution_results.clone(); if meta_block .mut_state() @@ -1470,7 +1471,7 @@ impl MainReactor { ); let meta_block = ForwardMetaBlock { block, - execution_results: meta_block.execution_results().clone(), + execution_results, state: *meta_block.state(), }; effects.extend(reactor::wrap_effects( @@ -1584,19 +1585,44 @@ impl MainReactor { ), )); - for (deploy_hash, deploy_header, execution_result) in meta_block.execution_results().clone() - { - let event = event_stream_server::Event::DeployProcessed { - deploy_hash, - deploy_header: Box::new(deploy_header), - block_hash: meta_block.hash(), - execution_result: Box::new(execution_result), - }; - effects.extend(reactor::wrap_effects( - MainEvent::EventStreamServer, - self.event_stream_server - .handle_event(effect_builder, rng, event), - )); + match &meta_block { + MetaBlock::Forward(fwd_meta_block) => { + for (deploy_hash, deploy_header, execution_result, messages) in + fwd_meta_block.execution_results.iter() + { + let event = event_stream_server::Event::DeployProcessed { + deploy_hash: *deploy_hash, + deploy_header: Box::new(deploy_header.clone()), + block_hash: *fwd_meta_block.block.hash(), + execution_result: Box::new(execution_result.clone()), + messages: messages.clone(), + }; + + effects.extend(reactor::wrap_effects( + MainEvent::EventStreamServer, + self.event_stream_server + .handle_event(effect_builder, rng, event), + )); + } + } + MetaBlock::Historical(historical_meta_block) => { + for (deploy_hash, deploy_header, execution_result) in + historical_meta_block.execution_results.iter() + { + let event = event_stream_server::Event::DeployProcessed { + deploy_hash: *deploy_hash, + deploy_header: Box::new(deploy_header.clone()), + block_hash: *historical_meta_block.block.hash(), + execution_result: Box::new(execution_result.clone()), + messages: Vec::new(), + }; + effects.extend(reactor::wrap_effects( + MainEvent::EventStreamServer, + self.event_stream_server + .handle_event(effect_builder, rng, event), + )); + } + } } debug!( diff --git a/node/src/types/block/meta_block.rs b/node/src/types/block/meta_block.rs index a22da43e05..491de032d3 100644 --- a/node/src/types/block/meta_block.rs +++ b/node/src/types/block/meta_block.rs @@ -7,8 +7,8 @@ use datasize::DataSize; use serde::Serialize; use casper_types::{ - execution::ExecutionResult, ActivationPoint, Block, BlockHash, BlockV2, DeployHash, - DeployHeader, EraId, + contract_messages::Messages, execution::ExecutionResult, ActivationPoint, Block, BlockHash, + BlockV2, DeployHash, DeployHeader, EraId, }; pub(crate) use merge_mismatch_error::MergeMismatchError; @@ -30,7 +30,7 @@ pub(crate) enum MetaBlock { impl MetaBlock { pub(crate) fn new_forward( block: Arc, - execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult)>, + execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult, Messages)>, state: State, ) -> Self { Self::Forward(ForwardMetaBlock { @@ -93,19 +93,12 @@ impl MetaBlock { MetaBlock::Historical(meta_block) => &meta_block.state, } } - - pub(crate) fn execution_results(&self) -> &Vec<(DeployHash, DeployHeader, ExecutionResult)> { - match &self { - MetaBlock::Forward(meta_block) => &meta_block.execution_results, - MetaBlock::Historical(meta_block) => &meta_block.execution_results, - } - } } #[derive(Clone, Eq, PartialEq, Serialize, Debug, DataSize)] pub(crate) struct ForwardMetaBlock { pub(crate) block: Arc, - pub(crate) execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult)>, + pub(crate) execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult, Messages)>, pub(crate) state: State, } @@ -190,6 +183,7 @@ mod tests { *deploy.hash(), deploy.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), + Vec::new(), )]; let state = State::new_already_stored(); @@ -244,6 +238,7 @@ mod tests { *deploy.hash(), deploy.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), + Vec::new(), )]; let state = State::new_not_to_be_gossiped(); @@ -281,6 +276,7 @@ mod tests { *deploy.hash(), deploy.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), + Vec::new(), )]; let state = State::new(); @@ -313,12 +309,14 @@ mod tests { *deploy1.hash(), deploy1.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), + Vec::new(), )]; let deploy2 = Deploy::random(rng); let execution_results2 = vec![( *deploy2.hash(), deploy2.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), + Vec::new(), )]; let state = State::new(); diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index 02bd55f0a4..3cdbd2667f 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -277,8 +277,7 @@ "transfer-5959595959595959595959595959595959595959595959595959595959595959", "transfer-8282828282828282828282828282828282828282828282828282828282828282" ], - "cost": "123456", - "messages": [] + "cost": "123456" } } } @@ -3571,7 +3570,6 @@ "cost", "effects", "error_message", - "messages", "transfers" ], "properties": { @@ -3601,13 +3599,6 @@ "error_message": { "description": "The error message associated with executing the deploy.", "type": "string" - }, - "messages": { - "description": "Messages that were emitted during execution.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - } } }, "additionalProperties": false @@ -3627,7 +3618,6 @@ "required": [ "cost", "effects", - "messages", "transfers" ], "properties": { @@ -3653,13 +3643,6 @@ "$ref": "#/components/schemas/U512" } ] - }, - "messages": { - "description": "Messages that were emitted during execution.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - } } }, "additionalProperties": false @@ -3676,71 +3659,6 @@ "$ref": "#/components/schemas/Transform" } }, - "Message": { - "description": "Message that was emitted by an addressable entity during execution.", - "type": "object", - "required": [ - "entity_addr", - "index", - "message", - "topic_name", - "topic_name_hash" - ], - "properties": { - "entity_addr": { - "description": "The identity of the entity that produced the message.", - "type": "string" - }, - "message": { - "description": "The payload of the message.", - "allOf": [ - { - "$ref": "#/components/schemas/MessagePayload" - } - ] - }, - "topic_name": { - "description": "The name of the topic on which the message was emitted on.", - "type": "string" - }, - "topic_name_hash": { - "description": "The hash of the name of the topic.", - "allOf": [ - { - "$ref": "#/components/schemas/TopicNameHash" - } - ] - }, - "index": { - "description": "Message index in the topic.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - } - }, - "MessagePayload": { - "description": "The payload of the message emitted by an addressable entity during execution.", - "oneOf": [ - { - "description": "Human readable string message.", - "type": "object", - "required": [ - "String" - ], - "properties": { - "String": { - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "TopicNameHash": { - "description": "The hash of the name of the message topic.", - "type": "string" - }, "AccountIdentifier": { "description": "Identifier of an account.", "anyOf": [ @@ -4643,6 +4561,10 @@ } } }, + "TopicNameHash": { + "description": "The hash of the name of the message topic.", + "type": "string" + }, "Package": { "description": "Entity definition, metadata, and security container.", "type": "object", diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 2bb854c069..401d762e57 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -82,6 +82,7 @@ "dependencies", "deploy_hash", "execution_result", + "messages", "timestamp", "ttl" ], @@ -109,6 +110,12 @@ }, "execution_result": { "$ref": "#/definitions/ExecutionResult" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/Message" + } } } } @@ -2610,7 +2617,6 @@ "cost", "effects", "error_message", - "messages", "transfers" ], "properties": { @@ -2640,13 +2646,6 @@ "error_message": { "description": "The error message associated with executing the deploy.", "type": "string" - }, - "messages": { - "description": "Messages that were emitted during execution.", - "type": "array", - "items": { - "$ref": "#/definitions/Message" - } } }, "additionalProperties": false @@ -2666,7 +2665,6 @@ "required": [ "cost", "effects", - "messages", "transfers" ], "properties": { @@ -2692,13 +2690,6 @@ "$ref": "#/definitions/U512" } ] - }, - "messages": { - "description": "Messages that were emitted during execution.", - "type": "array", - "items": { - "$ref": "#/definitions/Message" - } } }, "additionalProperties": false diff --git a/types/src/contract_messages.rs b/types/src/contract_messages.rs index 4752de9dbf..7bf3ccc9ab 100644 --- a/types/src/contract_messages.rs +++ b/types/src/contract_messages.rs @@ -5,7 +5,7 @@ mod messages; mod topics; pub use error::FromStrError; -pub use messages::{Message, MessageChecksum, MessagePayload}; +pub use messages::{Message, MessageChecksum, MessagePayload, Messages}; pub use topics::{ MessageTopicOperation, MessageTopicSummary, TopicNameHash, TOPIC_NAME_HASH_LENGTH, }; diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs index d51f3de185..2b02550c37 100644 --- a/types/src/contract_messages/messages.rs +++ b/types/src/contract_messages/messages.rs @@ -8,12 +8,20 @@ use core::fmt::Debug; #[cfg(feature = "datasize")] use datasize::DataSize; +#[cfg(any(feature = "testing", test))] +use rand::{ + distributions::{Alphanumeric, DistString, Distribution, Standard}, + Rng, +}; #[cfg(feature = "json-schema")] use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use super::TopicNameHash; +/// Collection of multiple messages. +pub type Messages = Vec; + /// The length of a message digest pub const MESSAGE_CHECKSUM_LENGTH: usize = 32; @@ -215,6 +223,23 @@ impl FromBytes for Message { } } +#[cfg(any(feature = "testing", test))] +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Message { + let topic_name = Alphanumeric.sample_string(rng, 32); + let topic_name_hash = crate::crypto::blake2b(&topic_name).into(); + let message = Alphanumeric.sample_string(rng, 64).into(); + + Message { + entity_addr: rng.gen(), + message, + topic_name, + topic_name_hash, + index: rng.gen(), + } + } +} + #[cfg(test)] mod tests { use crate::{bytesrepr, contract_messages::topics::TOPIC_NAME_HASH_LENGTH, KEY_HASH_LENGTH}; diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 57aec91a46..2b3be0c6a4 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -13,11 +13,7 @@ use datasize::DataSize; #[cfg(feature = "json-schema")] use once_cell::sync::Lazy; #[cfg(any(feature = "testing", test))] -use rand::{ - distributions::{Alphanumeric, DistString, Standard}, - prelude::Distribution, - Rng, -}; +use rand::{distributions::Standard, prelude::Distribution, Rng}; #[cfg(feature = "json-schema")] use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -25,17 +21,16 @@ use serde::{Deserialize, Serialize}; use super::Effects; #[cfg(feature = "json-schema")] use super::{Transform, TransformKind}; +#[cfg(any(feature = "testing", test))] +use crate::testing::TestRng; #[cfg(any(feature = "testing", feature = "json-schema", test))] use crate::Key; #[cfg(feature = "json-schema")] use crate::KEY_HASH_LENGTH; use crate::{ bytesrepr::{self, FromBytes, ToBytes, RESULT_ERR_TAG, RESULT_OK_TAG, U8_SERIALIZED_LENGTH}, - contract_messages::Message, TransferAddr, U512, }; -#[cfg(any(feature = "testing", test))] -use crate::{crypto, testing::TestRng}; #[cfg(feature = "json-schema")] static EXECUTION_RESULT: Lazy = Lazy::new(|| { @@ -60,7 +55,6 @@ static EXECUTION_RESULT: Lazy = Lazy::new(|| { effects, transfers, cost: U512::from(123_456), - messages: Vec::default(), } }); @@ -80,8 +74,6 @@ pub enum ExecutionResultV2 { cost: U512, /// The error message associated with executing the deploy. error_message: String, - /// Messages that were emitted during execution. - messages: Vec, }, /// The result of a successful execution. Success { @@ -91,8 +83,6 @@ pub enum ExecutionResultV2 { transfers: Vec, /// The cost in Motes of executing the deploy. cost: U512, - /// Messages that were emitted during execution. - messages: Vec, }, } @@ -106,25 +96,6 @@ impl Distribution for Standard { } let effects = Effects::random(rng); - let messages: Vec = effects - .transforms() - .iter() - .filter_map(|transform| { - if let Key::Message(addr) = transform.key() { - let topic_name = Alphanumeric.sample_string(rng, 32); - let topic_name_hash = crypto::blake2b(&topic_name); - Some(Message::new( - addr.entity_addr(), - format!("random_msg: {}", rng.gen::()).into(), - topic_name, - topic_name_hash.into(), - rng.gen::(), - )) - } else { - None - } - }) - .collect(); if rng.gen() { ExecutionResultV2::Failure { @@ -132,14 +103,12 @@ impl Distribution for Standard { transfers, cost: rng.gen::().into(), error_message: format!("Error message {}", rng.gen::()), - messages, } } else { ExecutionResultV2::Success { effects, transfers, cost: rng.gen::().into(), - messages, } } } @@ -157,25 +126,6 @@ impl ExecutionResultV2 { #[cfg(any(feature = "testing", test))] pub fn random(rng: &mut TestRng) -> Self { let effects = Effects::random(rng); - let messages: Vec = effects - .transforms() - .iter() - .filter_map(|transform| { - if let Key::Message(addr) = transform.key() { - let topic_name = Alphanumeric.sample_string(rng, 32); - let topic_name_hash = crypto::blake2b(&topic_name); - Some(Message::new( - addr.entity_addr(), - format!("random_msg: {}", rng.gen::()).into(), - topic_name, - topic_name_hash.into(), - rng.gen::(), - )) - } else { - None - } - }) - .collect(); let transfer_count = rng.gen_range(0..6); let mut transfers = vec![]; @@ -191,14 +141,12 @@ impl ExecutionResultV2 { transfers, cost, error_message: format!("Error message {}", rng.gen::()), - messages, } } else { ExecutionResultV2::Success { effects, transfers, cost, - messages, } } } @@ -212,26 +160,22 @@ impl ToBytes for ExecutionResultV2 { transfers, cost, error_message, - messages, } => { RESULT_ERR_TAG.write_bytes(writer)?; effects.write_bytes(writer)?; transfers.write_bytes(writer)?; cost.write_bytes(writer)?; - error_message.write_bytes(writer)?; - messages.write_bytes(writer) + error_message.write_bytes(writer) } ExecutionResultV2::Success { effects, transfers, cost, - messages, } => { RESULT_OK_TAG.write_bytes(writer)?; effects.write_bytes(writer)?; transfers.write_bytes(writer)?; - cost.write_bytes(writer)?; - messages.write_bytes(writer) + cost.write_bytes(writer) } } } @@ -250,24 +194,20 @@ impl ToBytes for ExecutionResultV2 { transfers, cost, error_message, - messages, } => { effects.serialized_length() + transfers.serialized_length() + cost.serialized_length() + error_message.serialized_length() - + messages.serialized_length() } ExecutionResultV2::Success { effects, transfers, cost, - messages, } => { effects.serialized_length() + transfers.serialized_length() + cost.serialized_length() - + messages.serialized_length() } } } @@ -282,13 +222,11 @@ impl FromBytes for ExecutionResultV2 { let (transfers, remainder) = Vec::::from_bytes(remainder)?; let (cost, remainder) = U512::from_bytes(remainder)?; let (error_message, remainder) = String::from_bytes(remainder)?; - let (messages, remainder) = Vec::::from_bytes(remainder)?; let execution_result = ExecutionResultV2::Failure { effects, transfers, cost, error_message, - messages, }; Ok((execution_result, remainder)) } @@ -296,12 +234,10 @@ impl FromBytes for ExecutionResultV2 { let (effects, remainder) = Effects::from_bytes(remainder)?; let (transfers, remainder) = Vec::::from_bytes(remainder)?; let (cost, remainder) = U512::from_bytes(remainder)?; - let (messages, remainder) = Vec::::from_bytes(remainder)?; let execution_result = ExecutionResultV2::Success { effects, transfers, cost, - messages, }; Ok((execution_result, remainder)) } From 8da9ebcb2db7b723fc574773e8f500157a4bccfc Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Mon, 30 Oct 2023 18:25:44 +0000 Subject: [PATCH 26/27] types/contract_messages: serialize message checksum to string Signed-off-by: Alexandru Sardan --- types/src/contract_messages/messages.rs | 68 +++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/types/src/contract_messages/messages.rs b/types/src/contract_messages/messages.rs index 2b02550c37..50f4e51560 100644 --- a/types/src/contract_messages/messages.rs +++ b/types/src/contract_messages/messages.rs @@ -1,10 +1,10 @@ use crate::{ bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - AddressableEntityHash, + checksummed_hex, AddressableEntityHash, Key, }; use alloc::{string::String, vec::Vec}; -use core::fmt::Debug; +use core::{convert::TryFrom, fmt::Debug}; #[cfg(feature = "datasize")] use datasize::DataSize; @@ -15,9 +15,9 @@ use rand::{ }; #[cfg(feature = "json-schema")] use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer}; -use super::TopicNameHash; +use super::{FromStrError, TopicNameHash}; /// Collection of multiple messages. pub type Messages = Vec; @@ -25,9 +25,11 @@ pub type Messages = Vec; /// The length of a message digest pub const MESSAGE_CHECKSUM_LENGTH: usize = 32; +const MESSAGE_CHECKSUM_STRING_PREFIX: &str = "message-checksum-"; + /// A newtype wrapping an array which contains the raw bytes of /// the hash of the message emitted. -#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[cfg_attr( feature = "json-schema", @@ -44,6 +46,27 @@ impl MessageChecksum { pub fn value(&self) -> [u8; MESSAGE_CHECKSUM_LENGTH] { self.0 } + + /// Formats the `MessageChecksum` as a human readable string. + pub fn to_formatted_string(self) -> String { + format!( + "{}{}", + MESSAGE_CHECKSUM_STRING_PREFIX, + base16::encode_lower(&self.0), + ) + } + + /// Parses a string formatted as per `Self::to_formatted_string()` into a + /// `MessageChecksum`. + pub fn from_formatted_str(input: &str) -> Result { + let hex_addr = input + .strip_prefix(MESSAGE_CHECKSUM_STRING_PREFIX) + .ok_or(FromStrError::InvalidPrefix)?; + + let bytes = + <[u8; MESSAGE_CHECKSUM_LENGTH]>::try_from(checksummed_hex::decode(hex_addr)?.as_ref())?; + Ok(MessageChecksum(bytes)) + } } impl ToBytes for MessageChecksum { @@ -65,6 +88,28 @@ impl FromBytes for MessageChecksum { } } +impl Serialize for MessageChecksum { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.to_formatted_string().serialize(serializer) + } else { + self.0.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for MessageChecksum { + fn deserialize>(deserializer: D) -> Result { + if deserializer.is_human_readable() { + let formatted_string = String::deserialize(deserializer)?; + MessageChecksum::from_formatted_str(&formatted_string).map_err(SerdeError::custom) + } else { + let bytes = <[u8; MESSAGE_CHECKSUM_LENGTH]>::deserialize(deserializer)?; + Ok(MessageChecksum(bytes)) + } + } +} + const MESSAGE_PAYLOAD_TAG_LENGTH: usize = U8_SERIALIZED_LENGTH; /// Tag for a message payload that contains a human readable string. @@ -181,6 +226,19 @@ impl Message { pub fn index(&self) -> u32 { self.index } + + /// Returns a new [`Key::Message`] based on the information in the message. + /// This key can be used to query the checksum record for the message in global state. + pub fn message_key(&self) -> Key { + Key::message(self.entity_addr, self.topic_name_hash, self.index) + } + + /// Returns a new [`Key::Message`] based on the information in the message. + /// This key can be used to query the control record for the topic of this message in global + /// state. + pub fn topic_key(&self) -> Key { + Key::message_topic(self.entity_addr, self.topic_name_hash) + } } impl ToBytes for Message { From 53483e16590b67de9be9be4d9ba4e6b64d1f22ee Mon Sep 17 00:00:00 2001 From: Alexandru Sardan Date: Fri, 3 Nov 2023 15:20:35 +0000 Subject: [PATCH 27/27] contract_messages: address CR comments Signed-off-by: Alexandru Sardan --- execution_engine/src/engine_state/mod.rs | 3 +- execution_engine/src/execution/error.rs | 5 + execution_engine/src/runtime/externals.rs | 11 +- execution_engine/src/runtime/mod.rs | 10 +- execution_engine/src/runtime_context/mod.rs | 2 +- .../tests/src/test/contract_messages.rs | 8 +- node/src/components/contract_runtime.rs | 4 +- .../components/contract_runtime/operations.rs | 16 +- node/src/components/contract_runtime/types.rs | 27 +++- node/src/reactor/main_reactor.rs | 12 +- node/src/types/block/meta_block.rs | 20 +-- .../contract/src/contract_api/runtime.rs | 13 +- smart_contracts/contract/src/ext_ffi.rs | 4 +- types/src/addressable_entity.rs | 6 +- .../vm_config/host_function_costs.rs | 146 ++++++++++++------ types/src/chainspec/vm_config/opcode_costs.rs | 97 ++++++++---- 16 files changed, 261 insertions(+), 123 deletions(-) diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index 15fe7812f0..bcccf294c6 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -2725,7 +2725,8 @@ fn should_charge_for_errors_in_wasm(execution_result: &ExecutionResult) -> bool | ExecError::InvalidPackageKind(_) | ExecError::Transform(_) | ExecError::InvalidEntryPointType - | ExecError::InvalidMessageTopicOperation => false, + | ExecError::InvalidMessageTopicOperation + | ExecError::InvalidUtf8Encoding(_) => false, ExecError::DisabledUnrestrictedTransfers => false, }, Error::WasmPreprocessing(_) => true, diff --git a/execution_engine/src/execution/error.rs b/execution_engine/src/execution/error.rs index 2d60e952ad..38a540de20 100644 --- a/execution_engine/src/execution/error.rs +++ b/execution_engine/src/execution/error.rs @@ -1,4 +1,6 @@ //! Execution error and supporting code. +use std::str::Utf8Error; + use casper_storage::global_state; use parity_wasm::elements; use thiserror::Error; @@ -200,6 +202,9 @@ pub enum Error { /// Invalid message topic operation. #[error("The requested operation is invalid for a message topic")] InvalidMessageTopicOperation, + /// Invalid string encoding. + #[error("Invalid UTF-8 string encoding: {0}")] + InvalidUtf8Encoding(Utf8Error), } impl From for Error { diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index ad3fc37eb8..40474be14c 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -1150,7 +1150,10 @@ where ))))); } - let topic_name = self.string_from_mem(topic_name_ptr, topic_name_size)?; + let topic_name_bytes = + self.bytes_from_mem(topic_name_ptr, topic_name_size as usize)?; + let topic_name = std::str::from_utf8(&topic_name_bytes) + .map_err(|e| Trap::from(Error::InvalidUtf8Encoding(e)))?; if operation_size as usize > MessageTopicOperation::max_serialized_len() { return Err(Trap::from(Error::InvalidMessageTopicOperation)); @@ -1204,7 +1207,11 @@ where ))))); } - let topic_name = self.string_from_mem(topic_name_ptr, topic_name_size)?; + let topic_name_bytes = + self.bytes_from_mem(topic_name_ptr, topic_name_size as usize)?; + let topic_name = std::str::from_utf8(&topic_name_bytes) + .map_err(|e| Trap::from(Error::InvalidUtf8Encoding(e)))?; + let message = self.t_from_mem(message_ptr, message_size)?; let result = self.emit_message(topic_name, message)?; diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index f00681880f..cd0ae2fc0a 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -3346,8 +3346,8 @@ where } } - fn add_message_topic(&mut self, topic_name: String) -> Result, Error> { - let topic_hash = crypto::blake2b(&topic_name).into(); + fn add_message_topic(&mut self, topic_name: &str) -> Result, Error> { + let topic_hash = crypto::blake2b(topic_name).into(); self.context .add_message_topic(topic_name, topic_hash) @@ -3356,7 +3356,7 @@ where fn emit_message( &mut self, - topic_name: String, + topic_name: &str, message: MessagePayload, ) -> Result, Trap> { let entity_addr = self @@ -3365,7 +3365,7 @@ where .into_entity_hash() .ok_or(Error::InvalidContext)?; - let topic_name_hash = crypto::blake2b(&topic_name).into(); + let topic_name_hash = crypto::blake2b(topic_name).into(); let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)); // Check if the topic exists and get the summary. @@ -3403,7 +3403,7 @@ where Message::new( entity_addr, message, - topic_name, + topic_name.to_string(), topic_name_hash, message_index, ), diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 31a9878bd6..a020e468df 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -1358,7 +1358,7 @@ where /// Adds new message topic. pub(crate) fn add_message_topic( &mut self, - topic_name: String, + topic_name: &str, topic_name_hash: TopicNameHash, ) -> Result, Error> { let entity_key: Key = self.get_entity_key(); diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index 92d8efaadc..c545362608 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -477,8 +477,8 @@ fn should_not_exceed_configured_limits() { let contract_hash = install_messages_emitter_contract(&builder); // if the topic larger than the limit, registering should fail. - // string is 29 bytes + 4 bytes for size = 33 bytes > limit established above - let too_large_topic_name = std::str::from_utf8(&[0x4du8; 29]).unwrap(); + // string is 33 bytes > limit established above + let too_large_topic_name = std::str::from_utf8(&[0x4du8; 33]).unwrap(); let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, contract_hash, @@ -496,8 +496,8 @@ fn should_not_exceed_configured_limits() { .commit(); // if the topic name is equal to the limit, registering should work. - // string is 28 bytes + 4 bytes for size = 32 bytes == limit established above - let topic_name_at_limit = std::str::from_utf8(&[0x4du8; 28]).unwrap(); + // string is 32 bytes == limit established above + let topic_name_at_limit = std::str::from_utf8(&[0x4du8; 32]).unwrap(); let add_topic_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, contract_hash, diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index 3368b16471..71c3b0dc7f 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -75,7 +75,7 @@ pub(crate) use operations::compute_execution_results_checksum; pub use operations::execute_finalized_block; use operations::execute_only; pub(crate) use types::{ - BlockAndExecutionResults, EraValidatorsRequest, RoundSeigniorageRateRequest, + BlockAndExecutionResults, EraValidatorsRequest, ExecutionArtifact, RoundSeigniorageRateRequest, StepEffectsAndUpcomingEraValidators, TotalSupplyRequest, }; @@ -983,7 +983,7 @@ impl ContractRuntime { let execution_results_map: HashMap<_, _> = execution_results .iter() .cloned() - .map(|(deploy_hash, _, execution_result, _)| (deploy_hash, execution_result)) + .map(|artifact| (artifact.deploy_hash, artifact.execution_result)) .collect(); if meta_block_state.register_as_stored().was_updated() { effect_builder diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index 90d2f2b220..77fa101d19 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -37,6 +37,8 @@ use crate::{ utils::fetch_id, }; +use super::ExecutionArtifact; + fn generate_range_by_index( highest_era: u64, batch_size: u64, @@ -112,7 +114,8 @@ pub fn execute_finalized_block( next_block_height, } = execution_pre_state; let mut state_root_hash = pre_state_root_hash; - let mut execution_results = Vec::with_capacity(executable_block.deploys.len()); + let mut execution_results: Vec = + Vec::with_capacity(executable_block.deploys.len()); // Run any deploys that must be executed let block_time = executable_block.timestamp.millis(); let start = Instant::now(); @@ -162,13 +165,20 @@ pub fn execute_finalized_block( deploy_hash, result, )?; - execution_results.push((deploy_hash, deploy_header, execution_result, messages)); + execution_results.push(ExecutionArtifact::new( + deploy_hash, + deploy_header, + execution_result, + messages, + )); state_root_hash = state_hash; } // Write the deploy approvals' and execution results' checksums to global state. let execution_results_checksum = compute_execution_results_checksum( - execution_results.iter().map(|(_, _, result, _)| result), + execution_results + .iter() + .map(|artifact| &artifact.execution_result), )?; let mut checksum_registry = ChecksumRegistry::new(); diff --git a/node/src/components/contract_runtime/types.rs b/node/src/components/contract_runtime/types.rs index 761e32aff6..da6b4c284b 100644 --- a/node/src/components/contract_runtime/types.rs +++ b/node/src/components/contract_runtime/types.rs @@ -8,6 +8,7 @@ use casper_types::{ execution::{Effects, ExecutionResult}, BlockV2, DeployHash, DeployHeader, Digest, EraId, ProtocolVersion, PublicKey, U512, }; +use serde::Serialize; use crate::types::ApprovalsHashes; @@ -115,6 +116,30 @@ pub(crate) struct StepEffectsAndUpcomingEraValidators { pub(crate) step_effects: Effects, } +#[derive(Clone, Debug, DataSize, PartialEq, Eq, Serialize)] +pub(crate) struct ExecutionArtifact { + pub(crate) deploy_hash: DeployHash, + pub(crate) deploy_header: DeployHeader, + pub(crate) execution_result: ExecutionResult, + pub(crate) messages: Messages, +} + +impl ExecutionArtifact { + pub(crate) fn new( + deploy_hash: DeployHash, + deploy_header: DeployHeader, + execution_result: ExecutionResult, + messages: Messages, + ) -> Self { + Self { + deploy_hash, + deploy_header, + execution_result, + messages, + } + } +} + #[doc(hidden)] /// A [`Block`] that was the result of execution in the `ContractRuntime` along with any execution /// effects it may have. @@ -125,7 +150,7 @@ pub struct BlockAndExecutionResults { /// The [`ApprovalsHashes`] for the deploys in this block. pub(crate) approvals_hashes: Box, /// The results from executing the deploys in the block. - pub(crate) execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult, Messages)>, + pub(crate) execution_results: Vec, /// The [`Effects`] and the upcoming validator sets determined by the `step` pub(crate) maybe_step_effects_and_upcoming_era_validators: Option, diff --git a/node/src/reactor/main_reactor.rs b/node/src/reactor/main_reactor.rs index c82b12871e..4441d114bd 100644 --- a/node/src/reactor/main_reactor.rs +++ b/node/src/reactor/main_reactor.rs @@ -1582,17 +1582,15 @@ impl MainReactor { match &meta_block { MetaBlock::Forward(fwd_meta_block) => { - for (deploy_hash, deploy_header, execution_result, messages) in - fwd_meta_block.execution_results.iter() - { + for exec_artifact in fwd_meta_block.execution_results.iter() { let event = event_stream_server::Event::TransactionProcessed { - transaction_hash: TransactionHash::Deploy(*deploy_hash), + transaction_hash: TransactionHash::Deploy(exec_artifact.deploy_hash), transaction_header: Box::new(TransactionHeader::Deploy( - deploy_header.clone(), + exec_artifact.deploy_header.clone(), )), block_hash: *fwd_meta_block.block.hash(), - execution_result: Box::new(execution_result.clone()), - messages: messages.clone(), + execution_result: Box::new(exec_artifact.execution_result.clone()), + messages: exec_artifact.messages.clone(), }; effects.extend(reactor::wrap_effects( diff --git a/node/src/types/block/meta_block.rs b/node/src/types/block/meta_block.rs index 491de032d3..b6e0ac52c8 100644 --- a/node/src/types/block/meta_block.rs +++ b/node/src/types/block/meta_block.rs @@ -7,13 +7,15 @@ use datasize::DataSize; use serde::Serialize; use casper_types::{ - contract_messages::Messages, execution::ExecutionResult, ActivationPoint, Block, BlockHash, - BlockV2, DeployHash, DeployHeader, EraId, + execution::ExecutionResult, ActivationPoint, Block, BlockHash, BlockV2, DeployHash, + DeployHeader, EraId, }; pub(crate) use merge_mismatch_error::MergeMismatchError; pub(crate) use state::State; +use crate::contract_runtime::ExecutionArtifact; + /// A block along with its execution results and state recording which actions have been taken /// related to the block. /// @@ -30,7 +32,7 @@ pub(crate) enum MetaBlock { impl MetaBlock { pub(crate) fn new_forward( block: Arc, - execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult, Messages)>, + execution_results: Vec, state: State, ) -> Self { Self::Forward(ForwardMetaBlock { @@ -98,7 +100,7 @@ impl MetaBlock { #[derive(Clone, Eq, PartialEq, Serialize, Debug, DataSize)] pub(crate) struct ForwardMetaBlock { pub(crate) block: Arc, - pub(crate) execution_results: Vec<(DeployHash, DeployHeader, ExecutionResult, Messages)>, + pub(crate) execution_results: Vec, pub(crate) state: State, } @@ -179,7 +181,7 @@ mod tests { let block = Arc::new(TestBlockBuilder::new().build(rng)); let deploy = Deploy::random(rng); - let execution_results = vec![( + let execution_results = vec![ExecutionArtifact::new( *deploy.hash(), deploy.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), @@ -234,7 +236,7 @@ mod tests { let block = Arc::new(TestBlockBuilder::new().build(rng)); let deploy = Deploy::random(rng); - let execution_results = vec![( + let execution_results = vec![ExecutionArtifact::new( *deploy.hash(), deploy.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), @@ -272,7 +274,7 @@ mod tests { .build(rng), ); let deploy = Deploy::random(rng); - let execution_results = vec![( + let execution_results = vec![ExecutionArtifact::new( *deploy.hash(), deploy.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), @@ -305,14 +307,14 @@ mod tests { let block = Arc::new(TestBlockBuilder::new().build(rng)); let deploy1 = Deploy::random(rng); - let execution_results1 = vec![( + let execution_results1 = vec![ExecutionArtifact::new( *deploy1.hash(), deploy1.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; let deploy2 = Deploy::random(rng); - let execution_results2 = vec![( + let execution_results2 = vec![ExecutionArtifact::new( *deploy2.hash(), deploy2.take_header(), ExecutionResult::from(ExecutionResultV2::random(rng)), diff --git a/smart_contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs index ce549c53ca..d8decfe847 100644 --- a/smart_contracts/contract/src/contract_api/runtime.rs +++ b/smart_contracts/contract/src/contract_api/runtime.rs @@ -414,12 +414,11 @@ pub fn manage_message_topic( return Err(ApiError::InvalidArgument); } - let (topic_name_ptr, topic_name_size, _bytes) = contract_api::to_ptr(topic_name); let (operation_ptr, operation_size, _bytes) = contract_api::to_ptr(operation); let result = unsafe { ext_ffi::casper_manage_message_topic( - topic_name_ptr, - topic_name_size, + topic_name.as_ptr(), + topic_name.len(), operation_ptr, operation_size, ) @@ -433,11 +432,15 @@ pub fn emit_message(topic_name: &str, message: &MessagePayload) -> Result<(), Ap return Err(ApiError::InvalidArgument); } - let (topic_name_ptr, topic_name_size, _bytes) = contract_api::to_ptr(topic_name); let (message_ptr, message_size, _bytes) = contract_api::to_ptr(message); let result = unsafe { - ext_ffi::casper_emit_message(topic_name_ptr, topic_name_size, message_ptr, message_size) + ext_ffi::casper_emit_message( + topic_name.as_ptr(), + topic_name.len(), + message_ptr, + message_size, + ) }; api_error::result_from(result) diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index 06d561a17c..4997d99f51 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -812,7 +812,7 @@ extern "C" { /// /// # Arguments /// - /// * `topic_name_ptr` - pointer to the serialized topic name string. + /// * `topic_name_ptr` - pointer to the topic name UTF-8 string. /// * `topic_name_size` - size of the serialized name string. /// * `operation_ptr` - pointer to the management operation to be performed for the specified /// topic. @@ -827,7 +827,7 @@ extern "C" { /// /// # Arguments /// - /// * `topic_name_ptr` - pointer to the serialized topic name string where the message will be + /// * `topic_name_ptr` - pointer to the topic name UTF-8 string where the message will be /// emitted. /// * `topic_name_size` - size of the serialized name string. /// * `message_ptr` - pointer to the serialized message payload to be emitted. diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index 559c589cf6..9ea6d671fc 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -693,14 +693,14 @@ impl MessageTopics { /// Adds new message topic by topic name. pub fn add_topic( &mut self, - topic_name: String, + topic_name: &str, topic_name_hash: TopicNameHash, ) -> Result<(), MessageTopicError> { if self.0.len() >= u32::MAX as usize { return Err(MessageTopicError::MaxTopicsExceeded); } - match self.0.entry(topic_name) { + match self.0.entry(topic_name.to_string()) { Entry::Vacant(entry) => { entry.insert(topic_name_hash); Ok(()) @@ -1054,7 +1054,7 @@ impl AddressableEntity { /// Adds a new message topic to the entity pub fn add_message_topic( &mut self, - topic_name: String, + topic_name: &str, topic_name_hash: TopicNameHash, ) -> Result<(), MessageTopicError> { self.message_topics.add_topic(topic_name, topic_name_hash) diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index 47419beb0b..c536fa762e 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -390,54 +390,104 @@ impl Zero for HostFunctionCosts { } fn is_zero(&self) -> bool { - self.read_value.is_zero() - && self.dictionary_get.is_zero() - && self.write.is_zero() - && self.dictionary_put.is_zero() - && self.add.is_zero() - && self.new_uref.is_zero() - && self.load_named_keys.is_zero() - && self.ret.is_zero() - && self.get_key.is_zero() - && self.has_key.is_zero() - && self.put_key.is_zero() - && self.remove_key.is_zero() - && self.revert.is_zero() - && self.is_valid_uref.is_zero() - && self.add_associated_key.is_zero() - && self.remove_associated_key.is_zero() - && self.update_associated_key.is_zero() - && self.set_action_threshold.is_zero() - && self.get_caller.is_zero() - && self.get_blocktime.is_zero() - && self.create_purse.is_zero() - && self.transfer_to_account.is_zero() - && self.transfer_from_purse_to_account.is_zero() - && self.transfer_from_purse_to_purse.is_zero() - && self.get_balance.is_zero() - && self.get_phase.is_zero() - && self.get_system_contract.is_zero() - && self.get_main_purse.is_zero() - && self.read_host_buffer.is_zero() - && self.create_contract_package_at_hash.is_zero() - && self.create_contract_user_group.is_zero() - && self.add_contract_version.is_zero() - && self.disable_contract_version.is_zero() - && self.call_contract.is_zero() - && self.call_versioned_contract.is_zero() - && self.get_named_arg_size.is_zero() - && self.get_named_arg.is_zero() - && self.remove_contract_user_group.is_zero() - && self.provision_contract_user_group_uref.is_zero() - && self.remove_contract_user_group_urefs.is_zero() - && self.print.is_zero() - && self.blake2b.is_zero() - && self.random_bytes.is_zero() - && self.enable_contract_version.is_zero() - && self.add_session_version.is_zero() - && self.manage_message_topic.is_zero() - && self.emit_message.is_zero() - && self.cost_increase_per_message.is_zero() + let HostFunctionCosts { + cost_increase_per_message, + read_value, + dictionary_get, + write, + dictionary_put, + add, + new_uref, + load_named_keys, + ret, + get_key, + has_key, + put_key, + remove_key, + revert, + is_valid_uref, + add_associated_key, + remove_associated_key, + update_associated_key, + set_action_threshold, + get_caller, + get_blocktime, + create_purse, + transfer_to_account, + transfer_from_purse_to_account, + transfer_from_purse_to_purse, + get_balance, + get_phase, + get_system_contract, + get_main_purse, + read_host_buffer, + create_contract_package_at_hash, + create_contract_user_group, + add_contract_version, + disable_contract_version, + call_contract, + call_versioned_contract, + get_named_arg_size, + get_named_arg, + remove_contract_user_group, + provision_contract_user_group_uref, + remove_contract_user_group_urefs, + print, + blake2b, + random_bytes, + enable_contract_version, + add_session_version, + manage_message_topic, + emit_message, + } = self; + read_value.is_zero() + && dictionary_get.is_zero() + && write.is_zero() + && dictionary_put.is_zero() + && add.is_zero() + && new_uref.is_zero() + && load_named_keys.is_zero() + && ret.is_zero() + && get_key.is_zero() + && has_key.is_zero() + && put_key.is_zero() + && remove_key.is_zero() + && revert.is_zero() + && is_valid_uref.is_zero() + && add_associated_key.is_zero() + && remove_associated_key.is_zero() + && update_associated_key.is_zero() + && set_action_threshold.is_zero() + && get_caller.is_zero() + && get_blocktime.is_zero() + && create_purse.is_zero() + && transfer_to_account.is_zero() + && transfer_from_purse_to_account.is_zero() + && transfer_from_purse_to_purse.is_zero() + && get_balance.is_zero() + && get_phase.is_zero() + && get_system_contract.is_zero() + && get_main_purse.is_zero() + && read_host_buffer.is_zero() + && create_contract_package_at_hash.is_zero() + && create_contract_user_group.is_zero() + && add_contract_version.is_zero() + && disable_contract_version.is_zero() + && call_contract.is_zero() + && call_versioned_contract.is_zero() + && get_named_arg_size.is_zero() + && get_named_arg.is_zero() + && remove_contract_user_group.is_zero() + && provision_contract_user_group_uref.is_zero() + && remove_contract_user_group_urefs.is_zero() + && print.is_zero() + && blake2b.is_zero() + && random_bytes.is_zero() + && enable_contract_version.is_zero() + && add_session_version.is_zero() + && manage_message_topic.is_zero() + && emit_message.is_zero() + && cost_increase_per_message.is_zero() } } diff --git a/types/src/chainspec/vm_config/opcode_costs.rs b/types/src/chainspec/vm_config/opcode_costs.rs index 0bc789a59d..19c00a07cf 100644 --- a/types/src/chainspec/vm_config/opcode_costs.rs +++ b/types/src/chainspec/vm_config/opcode_costs.rs @@ -152,7 +152,11 @@ impl Zero for BrTableCost { } fn is_zero(&self) -> bool { - self.cost.is_zero() && self.size_multiplier.is_zero() + let BrTableCost { + cost, + size_multiplier, + } = self; + cost.is_zero() && size_multiplier.is_zero() } } @@ -356,19 +360,34 @@ impl Zero for ControlFlowCosts { } fn is_zero(&self) -> bool { - self.block.is_zero() - && self.op_loop.is_zero() - && self.op_if.is_zero() - && self.op_else.is_zero() - && self.end.is_zero() - && self.br.is_zero() - && self.br_if.is_zero() - && self.op_return.is_zero() - && self.call.is_zero() - && self.call_indirect.is_zero() - && self.drop.is_zero() - && self.select.is_zero() - && self.br_table.is_zero() + let ControlFlowCosts { + block, + op_loop, + op_if, + op_else, + end, + br, + br_if, + op_return, + call, + call_indirect, + drop, + select, + br_table, + } = self; + block.is_zero() + && op_loop.is_zero() + && op_if.is_zero() + && op_else.is_zero() + && end.is_zero() + && br.is_zero() + && br_if.is_zero() + && op_return.is_zero() + && call.is_zero() + && call_indirect.is_zero() + && drop.is_zero() + && select.is_zero() + && br_table.is_zero() } } @@ -605,22 +624,40 @@ impl Zero for OpcodeCosts { } fn is_zero(&self) -> bool { - self.bit.is_zero() - && self.add.is_zero() - && self.mul.is_zero() - && self.div.is_zero() - && self.load.is_zero() - && self.store.is_zero() - && self.op_const.is_zero() - && self.local.is_zero() - && self.global.is_zero() - && self.integer_comparison.is_zero() - && self.conversion.is_zero() - && self.unreachable.is_zero() - && self.nop.is_zero() - && self.current_memory.is_zero() - && self.grow_memory.is_zero() - && self.control_flow.is_zero() + let OpcodeCosts { + bit, + add, + mul, + div, + load, + store, + op_const, + local, + global, + integer_comparison, + conversion, + unreachable, + nop, + current_memory, + grow_memory, + control_flow, + } = self; + bit.is_zero() + && add.is_zero() + && mul.is_zero() + && div.is_zero() + && load.is_zero() + && store.is_zero() + && op_const.is_zero() + && local.is_zero() + && global.is_zero() + && integer_comparison.is_zero() + && conversion.is_zero() + && unreachable.is_zero() + && nop.is_zero() + && current_memory.is_zero() + && grow_memory.is_zero() + && control_flow.is_zero() } }