diff --git a/Cargo.lock b/Cargo.lock index 37ee7ad599..cdd0884c31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2333,6 +2333,14 @@ dependencies = [ "casper-types", ] +[[package]] +name = "get-blockinfo" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "get-blocktime" version = "0.1.0" diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index a45e2a93c4..53789d57c8 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -124,7 +124,6 @@ impl ExecutionEngineV1 { Err(ese) => return WasmV1Result::precondition_failure(gas_limit, ese), }; let access_rights = entity.extract_access_rights(entity_hash, &named_keys); - let block_time = block_info.block_time; Executor::new(self.config().clone()).exec( execution_kind, args, @@ -134,7 +133,7 @@ impl ExecutionEngineV1 { access_rights, authorization_keys, account_hash, - block_time, + block_info, transaction_hash, gas_limit, protocol_version, diff --git a/execution_engine/src/engine_state/wasm_v1.rs b/execution_engine/src/engine_state/wasm_v1.rs index 9d0d00b449..428d534b99 100644 --- a/execution_engine/src/engine_state/wasm_v1.rs +++ b/execution_engine/src/engine_state/wasm_v1.rs @@ -99,10 +99,25 @@ impl BlockInfo { self.state_hash = state_hash; } + /// State hash. + pub fn state_hash(&self) -> Digest { + self.state_hash + } + /// Block time. pub fn block_time(&self) -> BlockTime { self.block_time } + + /// Parent block hash. + pub fn parent_block_hash(&self) -> BlockHash { + self.parent_block_hash + } + + /// Block height. + pub fn block_height(&self) -> u64 { + self.block_height + } } /// A request to execute the given Wasm on the V1 runtime. diff --git a/execution_engine/src/execution/error.rs b/execution_engine/src/execution/error.rs index 624a399469..007f8d8d3a 100644 --- a/execution_engine/src/execution/error.rs +++ b/execution_engine/src/execution/error.rs @@ -183,9 +183,9 @@ pub enum Error { /// The EntryPoints contains an invalid entry. #[error("The EntryPoints contains an invalid entry")] InvalidEntryPointType, - /// Invalid message topic operation. - #[error("The requested operation is invalid for a message topic")] - InvalidMessageTopicOperation, + /// Invalid operation. + #[error("The imputed operation is invalid")] + InvalidImputedOperation, /// Invalid string encoding. #[error("Invalid UTF-8 string encoding: {0}")] InvalidUtf8Encoding(Utf8Error), diff --git a/execution_engine/src/execution/executor.rs b/execution_engine/src/execution/executor.rs index 6df0f3ac6b..43125c3a11 100644 --- a/execution_engine/src/execution/executor.rs +++ b/execution_engine/src/execution/executor.rs @@ -7,13 +7,13 @@ use casper_storage::{ }; use casper_types::{ account::AccountHash, addressable_entity::NamedKeys, contract_messages::Messages, - execution::Effects, AddressableEntity, AddressableEntityHash, BlockTime, ContextAccessRights, + execution::Effects, AddressableEntity, AddressableEntityHash, ContextAccessRights, EntryPointType, Gas, Key, Phase, ProtocolVersion, RuntimeArgs, StoredValue, Tagged, TransactionHash, U512, }; use crate::{ - engine_state::{execution_kind::ExecutionKind, EngineConfig, WasmV1Result}, + engine_state::{execution_kind::ExecutionKind, BlockInfo, EngineConfig, WasmV1Result}, execution::ExecError, runtime::{Runtime, RuntimeStack}, runtime_context::{CallingAddContractVersion, RuntimeContext}, @@ -53,7 +53,7 @@ impl Executor { access_rights: ContextAccessRights, authorization_keys: BTreeSet, account_hash: AccountHash, - blocktime: BlockTime, + block_info: BlockInfo, txn_hash: TransactionHash, gas_limit: Gas, protocol_version: ProtocolVersion, @@ -101,7 +101,7 @@ impl Executor { account_hash, address_generator, tracking_copy, - blocktime, + block_info, protocol_version, txn_hash, phase, @@ -163,7 +163,7 @@ impl Executor { account_hash: AccountHash, address_generator: Rc>, tracking_copy: Rc>>, - blocktime: BlockTime, + block_info: BlockInfo, protocol_version: ProtocolVersion, txn_hash: TransactionHash, phase: Phase, @@ -189,7 +189,7 @@ impl Executor { address_generator, tracking_copy, self.config.clone(), - blocktime, + block_info, protocol_version, txn_hash, phase, diff --git a/execution_engine/src/resolvers/v1_function_index.rs b/execution_engine/src/resolvers/v1_function_index.rs index 3ec135cd00..fb6d1049b8 100644 --- a/execution_engine/src/resolvers/v1_function_index.rs +++ b/execution_engine/src/resolvers/v1_function_index.rs @@ -60,6 +60,7 @@ pub(crate) enum FunctionIndex { ManageMessageTopic, EmitMessage, LoadCallerInformation, + GetBlockInfoIndex, } impl From for usize { diff --git a/execution_engine/src/resolvers/v1_resolver.rs b/execution_engine/src/resolvers/v1_resolver.rs index fa35f8f3d7..5aa241d0df 100644 --- a/execution_engine/src/resolvers/v1_resolver.rs +++ b/execution_engine/src/resolvers/v1_resolver.rs @@ -253,6 +253,10 @@ impl ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), FunctionIndex::EmitMessage.into(), ), + "casper_get_block_info" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], None), + FunctionIndex::GetBlockInfoIndex.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 d8f76bc123..8cf5cf04e4 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -606,6 +606,7 @@ where )?; Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) } + FunctionIndex::AddContractVersion => { // args(0) = pointer to package key in wasm memory // args(1) = size of package key in wasm memory @@ -660,6 +661,7 @@ where )?; Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) } + FunctionIndex::AddPackageVersion => { // args(0) = pointer to package hash in wasm memory // args(1) = size of package hash in wasm memory @@ -1152,6 +1154,7 @@ where Ok(Some(RuntimeValue::I32(0))) } + FunctionIndex::EnableContractVersion => { // args(0) = pointer to package hash in wasm memory // args(1) = size of package hash in wasm memory @@ -1175,6 +1178,7 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + FunctionIndex::ManageMessageTopic => { // args(0) = pointer to the serialized topic name string in wasm memory // args(1) = size of the serialized topic name string in wasm memory @@ -1206,11 +1210,11 @@ where .map_err(|e| Trap::from(ExecError::InvalidUtf8Encoding(e)))?; if operation_size as usize > MessageTopicOperation::max_serialized_len() { - return Err(Trap::from(ExecError::InvalidMessageTopicOperation)); + return Err(Trap::from(ExecError::InvalidImputedOperation)); } let topic_operation = self .t_from_mem(operation_ptr, operation_size) - .map_err(|_e| Trap::from(ExecError::InvalidMessageTopicOperation))?; + .map_err(|_e| Trap::from(ExecError::InvalidImputedOperation))?; // only allow managing messages from stored contracts if !self.context.get_entity_key().is_smart_contract_key() { @@ -1225,6 +1229,7 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + FunctionIndex::EmitMessage => { // args(0) = pointer to the serialized topic name string in wasm memory // args(1) = size of the serialized name string in wasm memory @@ -1276,6 +1281,16 @@ where } Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + + FunctionIndex::GetBlockInfoIndex => { + // args(0) = field selector + // args(1) = pointer to output pointer where host will write argument bytes + let (field_idx, dest_ptr): (u8, u32) = Args::parse(args)?; + + self.charge_host_function_call(&host_function_costs.get_block_info, [0u32, 0u32])?; + self.get_block_info(field_idx, dest_ptr)?; + Ok(None) + } } } } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 8debe05174..0e5a2364c7 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -434,11 +434,48 @@ where .map_err(|e| ExecError::Interpreter(e.into()).into()) } + /// Writes requested field from runtime context's block info to dest_ptr in the Wasm memory. + fn get_block_info(&self, field_idx: u8, dest_ptr: u32) -> Result<(), Trap> { + if field_idx == 0 { + // original functionality + return self.get_blocktime(dest_ptr); + } + let block_info = self.context.get_block_info(); + + let mut data: Vec = vec![]; + if field_idx == 1 { + data = block_info + .block_height() + .into_bytes() + .map_err(ExecError::BytesRepr)?; + } + if field_idx == 2 { + data = block_info + .parent_block_hash() + .into_bytes() + .map_err(ExecError::BytesRepr)?; + } + if field_idx == 3 { + data = block_info + .state_hash() + .into_bytes() + .map_err(ExecError::BytesRepr)?; + } + if data.is_empty() { + Err(ExecError::InvalidImputedOperation.into()) + } else { + Ok(self + .try_get_memory()? + .set(dest_ptr, &data) + .map_err(|e| ExecError::Interpreter(e.into()))?) + } + } + /// Writes current blocktime to dest_ptr in Wasm memory. fn get_blocktime(&self, dest_ptr: u32) -> Result<(), Trap> { - let blocktime = self - .context - .get_blocktime() + let block_info = self.context.get_block_info(); + let blocktime = block_info + .block_time() .into_bytes() .map_err(ExecError::BytesRepr)?; self.try_get_memory()? @@ -2017,10 +2054,8 @@ where for (_, topic_hash) in previous_message_topics.iter() { let topic_key = Key::message_topic(entity_addr, *topic_hash); - let summary = StoredValue::MessageTopic(MessageTopicSummary::new( - 0, - self.context.get_blocktime(), - )); + let block_time = self.context.get_block_info().block_time(); + let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, block_time)); self.context.metered_write_gs_unsafe(topic_key, summary)?; } @@ -3557,7 +3592,7 @@ where return Ok(Err(ApiError::MessageTopicNotRegistered)); }; - let current_blocktime = self.context.get_blocktime(); + let current_blocktime = self.context.get_block_info().block_time(); let topic_message_index = if prev_topic_summary.blocktime() != current_blocktime { for index in 1..prev_topic_summary.message_count() { self.context diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 96d4638256..0348a81000 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -41,7 +41,10 @@ use casper_types::{ DICTIONARY_ITEM_KEY_MAX_LENGTH, KEY_HASH_LENGTH, U512, }; -use crate::{engine_state::EngineConfig, execution::ExecError}; +use crate::{ + engine_state::{BlockInfo, EngineConfig}, + execution::ExecError, +}; /// Number of bytes returned from the `random_bytes` function. pub const RANDOM_BYTES_COUNT: usize = 32; @@ -64,7 +67,7 @@ pub struct RuntimeContext<'a, R> { access_rights: ContextAccessRights, args: RuntimeArgs, authorization_keys: BTreeSet, - blocktime: BlockTime, + block_info: BlockInfo, transaction_hash: TransactionHash, gas_limit: Gas, gas_counter: Gas, @@ -104,7 +107,7 @@ where address_generator: Rc>, tracking_copy: Rc>>, engine_config: EngineConfig, - blocktime: BlockTime, + block_info: BlockInfo, protocol_version: ProtocolVersion, transaction_hash: TransactionHash, phase: Phase, @@ -132,7 +135,7 @@ where entity_key, authorization_keys, account_hash, - blocktime, + block_info, transaction_hash, gas_limit, gas_counter, @@ -165,7 +168,7 @@ where let tracking_copy = self.state(); let engine_config = self.engine_config.clone(); - let blocktime = self.blocktime; + let block_info = self.block_info; let protocol_version = self.protocol_version; let transaction_hash = self.transaction_hash; let phase = self.phase; @@ -186,7 +189,7 @@ where entity_key, authorization_keys, account_hash, - blocktime, + block_info, transaction_hash, gas_limit, gas_counter, @@ -265,9 +268,9 @@ where self.remove_key_from_entity(name) } - /// Returns the block time. - pub fn get_blocktime(&self) -> BlockTime { - self.blocktime + /// Returns block info. + pub fn get_block_info(&self) -> BlockInfo { + self.block_info } /// Returns the transaction hash. @@ -1425,7 +1428,8 @@ where }; 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 block_time = self.block_info.block_time(); + let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, block_time)); let entity_value = self.addressable_entity_to_validated_value(entity)?; diff --git a/execution_engine_testing/test_support/src/execute_request_builder.rs b/execution_engine_testing/test_support/src/execute_request_builder.rs index b01aeddd3b..7960298077 100644 --- a/execution_engine_testing/test_support/src/execute_request_builder.rs +++ b/execution_engine_testing/test_support/src/execute_request_builder.rs @@ -26,6 +26,8 @@ pub struct ExecuteRequest { pub struct ExecuteRequestBuilder { state_hash: Digest, block_time: BlockTime, + block_height: u64, + parent_block_hash: BlockHash, transaction_hash: TransactionHash, initiator_addr: InitiatorAddr, payment: Option, @@ -95,6 +97,8 @@ impl ExecuteRequestBuilder { ExecuteRequestBuilder { state_hash: session.block_info.state_hash, block_time: session.block_info.block_time, + block_height: session.block_info.block_height, + parent_block_hash: session.block_info.parent_block_hash, transaction_hash: session.transaction_hash, initiator_addr: session.initiator_addr, payment, @@ -156,6 +160,8 @@ impl ExecuteRequestBuilder { ExecuteRequestBuilder { state_hash: session.block_info.state_hash, block_time: session.block_info.block_time, + block_height: session.block_info.block_height, + parent_block_hash: session.block_info.parent_block_hash, transaction_hash: session.transaction_hash, initiator_addr: session.initiator_addr, payment, @@ -285,6 +291,24 @@ impl ExecuteRequestBuilder { self } + /// Sets the block height of the [`WasmV1Request`]s. + pub fn with_block_height(mut self, block_height: u64) -> Self { + self.block_height = block_height; + self + } + + /// Sets the parent block hash of the [`WasmV1Request`]s. + pub fn with_parent_block_hash(mut self, parent_block_hash: BlockHash) -> Self { + self.parent_block_hash = parent_block_hash; + self + } + + /// Sets the parent block hash of the [`WasmV1Request`]s. + pub fn with_state_hash(mut self, state_hash: Digest) -> Self { + self.state_hash = state_hash; + self + } + /// Sets the authorization keys used by the [`WasmV1Request`]s. pub fn with_authorization_keys(mut self, authorization_keys: BTreeSet) -> Self { self.authorization_keys = authorization_keys; @@ -296,6 +320,8 @@ impl ExecuteRequestBuilder { let ExecuteRequestBuilder { state_hash, block_time, + block_height, + parent_block_hash, transaction_hash, initiator_addr, payment, @@ -309,7 +335,7 @@ impl ExecuteRequestBuilder { authorization_keys, } = self; - let block_info = BlockInfo::new(state_hash, block_time, BlockHash::default(), 0); + let block_info = BlockInfo::new(state_hash, block_time, parent_block_hash, block_height); let maybe_custom_payment = payment.map(|executable_item| WasmV1Request { block_info, transaction_hash, @@ -322,7 +348,6 @@ impl ExecuteRequestBuilder { phase: Phase::Payment, }); - let block_info = BlockInfo::new(state_hash, block_time, BlockHash::default(), 0); let session = WasmV1Request { block_info, transaction_hash, diff --git a/execution_engine_testing/tests/src/test/contract_api/get_block_info.rs b/execution_engine_testing/tests/src/test/contract_api/get_block_info.rs new file mode 100644 index 0000000000..314fed3e94 --- /dev/null +++ b/execution_engine_testing/tests/src/test/contract_api/get_block_info.rs @@ -0,0 +1,113 @@ +use casper_engine_test_support::{ + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, LOCAL_GENESIS_REQUEST, +}; +use casper_types::bytesrepr::ToBytes; +use casper_types::{runtime_args, BlockHash}; + +const CONTRACT_GET_BLOCKINFO: &str = "get_blockinfo.wasm"; +const ARG_FIELD_IDX: &str = "field_idx"; + +const FIELD_IDX_BLOCK_TIME: u8 = 0; +const ARG_KNOWN_BLOCK_TIME: &str = "known_block_time"; + +#[ignore] +#[test] +fn should_run_get_block_time() { + let block_time: u64 = 42; + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_BLOCK_TIME, + ARG_KNOWN_BLOCK_TIME => block_time + }, + ) + .with_block_time(block_time) + .build(); + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec(exec_request) + .commit() + .expect_success(); +} + +const FIELD_IDX_BLOCK_HEIGHT: u8 = 1; +const ARG_KNOWN_BLOCK_HEIGHT: &str = "known_block_height"; + +#[ignore] +#[test] +fn should_run_get_block_height() { + let block_height: u64 = 1; + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_BLOCK_HEIGHT, + ARG_KNOWN_BLOCK_HEIGHT => block_height + }, + ) + .with_block_height(block_height) + .build(); + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec(exec_request) + .expect_success() + .commit(); +} + +const FIELD_IDX_PARENT_BLOCK_HASH: u8 = 2; +const ARG_KNOWN_BLOCK_PARENT_HASH: &str = "known_block_parent_hash"; + +#[ignore] +#[test] +fn should_run_get_block_parent_hash() { + let block_hash = BlockHash::default(); + let digest = block_hash.inner(); + let digest_bytes = digest.to_bytes().expect("should serialize"); + let bytes = casper_types::bytesrepr::Bytes::from(digest_bytes); + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_PARENT_BLOCK_HASH, + ARG_KNOWN_BLOCK_PARENT_HASH => bytes + }, + ) + .with_parent_block_hash(block_hash) + .build(); + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec(exec_request) + .expect_success() + .commit(); +} + +const FIELD_IDX_STATE_HASH: u8 = 3; +const ARG_KNOWN_STATE_HASH: &str = "known_state_hash"; + +#[ignore] +#[test] +fn should_run_get_state_hash() { + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + + let state_hash = builder.get_post_state_hash(); + let digest_bytes = state_hash.to_bytes().expect("should serialize"); + let bytes = casper_types::bytesrepr::Bytes::from(digest_bytes); + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_STATE_HASH, + ARG_KNOWN_STATE_HASH => bytes + }, + ) + .with_state_hash(state_hash) + .build(); + + builder.exec(exec_request).expect_success().commit(); +} diff --git a/execution_engine_testing/tests/src/test/contract_api/mod.rs b/execution_engine_testing/tests/src/test/contract_api/mod.rs index 4b9b90f553..935a4d9cf1 100644 --- a/execution_engine_testing/tests/src/test/contract_api/mod.rs +++ b/execution_engine_testing/tests/src/test/contract_api/mod.rs @@ -3,6 +3,7 @@ mod add_contract_version; mod create_purse; mod dictionary; mod get_arg; +mod get_block_info; mod get_blocktime; mod get_call_stack; mod get_caller; diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 69c5b92b2a..a91f336a95 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -318,6 +318,7 @@ enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } cost_increase_per_message = 50 +get_block_info = { cost = 330, arguments = [0, 0] } [wasm.messages_limits] max_topic_name_size = 256 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index ad9bdcabf2..3d9dc4a013 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -328,6 +328,7 @@ enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } cost_increase_per_message = 50 +get_block_info = { cost = 330, arguments = [0, 0] } [wasm.messages_limits] max_topic_name_size = 256 diff --git a/smart_contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs index ad56d7b14a..62ea1afb7f 100644 --- a/smart_contracts/contract/src/contract_api/runtime.rs +++ b/smart_contracts/contract/src/contract_api/runtime.rs @@ -7,11 +7,11 @@ use casper_types::{ account::AccountHash, addressable_entity::NamedKeys, api_error, - bytesrepr::{self, FromBytes}, + bytesrepr::{self, FromBytes, U64_SERIALIZED_LENGTH}, contract_messages::{MessagePayload, MessageTopicOperation}, system::Caller, - AddressableEntityHash, ApiError, BlockTime, CLTyped, CLValue, EntityVersion, Key, PackageHash, - Phase, RuntimeArgs, URef, BLAKE2B_DIGEST_LENGTH, BLOCKTIME_SERIALIZED_LENGTH, + AddressableEntityHash, ApiError, BlockTime, CLTyped, CLValue, Digest, EntityVersion, Key, + PackageHash, Phase, RuntimeArgs, URef, BLAKE2B_DIGEST_LENGTH, BLOCKTIME_SERIALIZED_LENGTH, PHASE_SERIALIZED_LENGTH, }; @@ -252,6 +252,59 @@ pub fn get_blocktime() -> BlockTime { bytesrepr::deserialize(bytes).unwrap_or_revert() } +/// The default length of hashes such as account hash, state hash, hash addresses, etc. +pub const DEFAULT_HASH_LENGTH: u8 = 32; +/// Index for the block time field of block info. +pub const BLOCK_TIME_FIELD_IDX: u8 = 0; +/// Index for the block height field of block info. +pub const BLOCK_HEIGHT_FIELD_IDX: u8 = 1; +/// Index for the parent block hash field of block info. +pub const PARENT_BLOCK_HASH_FIELD_IDX: u8 = 2; +/// Index for the state hash field of block info. +pub const STATE_HASH_FIELD_IDX: u8 = 3; + +/// Returns the block height. +pub fn get_block_height() -> u64 { + let dest_non_null_ptr = contract_api::alloc_bytes(U64_SERIALIZED_LENGTH); + let bytes = unsafe { + ext_ffi::casper_get_block_info(BLOCK_HEIGHT_FIELD_IDX, dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + U64_SERIALIZED_LENGTH, + U64_SERIALIZED_LENGTH, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Returns the parent block hash. +pub fn get_parent_block_hash() -> Digest { + let dest_non_null_ptr = contract_api::alloc_bytes(DEFAULT_HASH_LENGTH as usize); + let bytes = unsafe { + ext_ffi::casper_get_block_info(PARENT_BLOCK_HASH_FIELD_IDX, dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + DEFAULT_HASH_LENGTH as usize, + DEFAULT_HASH_LENGTH as usize, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Returns the state root hash. +pub fn get_state_hash() -> Digest { + let dest_non_null_ptr = contract_api::alloc_bytes(DEFAULT_HASH_LENGTH as usize); + let bytes = unsafe { + ext_ffi::casper_get_block_info(STATE_HASH_FIELD_IDX, dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + DEFAULT_HASH_LENGTH as usize, + DEFAULT_HASH_LENGTH as usize, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + /// Returns the current [`Phase`]. pub fn get_phase() -> Phase { let dest_non_null_ptr = contract_api::alloc_bytes(PHASE_SERIALIZED_LENGTH); diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index a8d5ec6df1..151a1ddcd5 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -843,4 +843,18 @@ extern "C" { call_stack_len_ptr: *mut usize, result_size_ptr: *mut usize, ) -> i32; + + /// This function gets the requested field at `field_ptr`. It is up to + /// the caller to ensure that the correct number of bytes for the field data + /// are allocated at `dest_ptr`, otherwise data corruption in the wasm memory may occur. + /// + /// # Arguments + /// + /// * `field_ptr` - what info field is requested? + /// 0 => block time (functionally equivalent to earlier get_blocktime ffi) + /// 1 => block height + /// 2 => parent block hash + /// 3 => state hash + /// * `dest_ptr` => pointer in wasm memory where to write the result + pub fn casper_get_block_info(field_idx: u8, dest_ptr: *const u8); } diff --git a/smart_contracts/contracts/test/get-blockinfo/Cargo.toml b/smart_contracts/contracts/test/get-blockinfo/Cargo.toml new file mode 100644 index 0000000000..1882282d98 --- /dev/null +++ b/smart_contracts/contracts/test/get-blockinfo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "get-blockinfo" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2021" + +[[bin]] +name = "get_blockinfo" +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/get-blockinfo/src/main.rs b/smart_contracts/contracts/test/get-blockinfo/src/main.rs new file mode 100644 index 0000000000..f9bcb07d2e --- /dev/null +++ b/smart_contracts/contracts/test/get-blockinfo/src/main.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] + +use casper_contract::contract_api::runtime; +use casper_contract::contract_api::runtime::revert; +use casper_contract::unwrap_or_revert::UnwrapOrRevert; +use casper_types::bytesrepr::{Bytes, FromBytes}; +use casper_types::{ApiError, BlockTime, Digest}; + +const ARG_FIELD_IDX: &str = "field_idx"; +const FIELD_IDX_BLOCK_TIME: u8 = 0; +const FIELD_IDX_BLOCK_HEIGHT: u8 = 1; +const FIELD_IDX_PARENT_BLOCK_HASH: u8 = 2; +const FIELD_IDX_STATE_HASH: u8 = 3; + +const CURRENT_UBOUND: u8 = FIELD_IDX_STATE_HASH; +const ARG_KNOWN_BLOCK_TIME: &str = "known_block_time"; +const ARG_KNOWN_BLOCK_HEIGHT: &str = "known_block_height"; +const ARG_KNOWN_BLOCK_PARENT_HASH: &str = "known_block_parent_hash"; +const ARG_KNOWN_STATE_HASH: &str = "known_state_hash"; + +#[no_mangle] +pub extern "C" fn call() { + let field_idx: u8 = runtime::get_named_arg(ARG_FIELD_IDX); + if field_idx > CURRENT_UBOUND { + revert(ApiError::Unhandled); + } + if field_idx == FIELD_IDX_BLOCK_TIME { + let expected = BlockTime::new(runtime::get_named_arg(ARG_KNOWN_BLOCK_TIME)); + let actual: BlockTime = runtime::get_blocktime(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } + if field_idx == FIELD_IDX_BLOCK_HEIGHT { + let expected: u64 = runtime::get_named_arg(ARG_KNOWN_BLOCK_HEIGHT); + let actual = runtime::get_block_height(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } + if field_idx == FIELD_IDX_PARENT_BLOCK_HASH { + let bytes: Bytes = runtime::get_named_arg(ARG_KNOWN_BLOCK_PARENT_HASH); + let (expected, _rem) = Digest::from_bytes(bytes.inner_bytes()) + .unwrap_or_revert_with(ApiError::User(CURRENT_UBOUND as u16 + 1)); + let actual = runtime::get_parent_block_hash(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } + if field_idx == FIELD_IDX_STATE_HASH { + let bytes: Bytes = runtime::get_named_arg(ARG_KNOWN_STATE_HASH); + let (expected, _rem) = Digest::from_bytes(bytes.inner_bytes()) + .unwrap_or_revert_with(ApiError::User(CURRENT_UBOUND as u16 + 2)); + let actual = runtime::get_state_hash(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } +} diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index cf02f007c3..24846d2d8d 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -337,6 +337,8 @@ pub struct HostFunctionCosts { pub manage_message_topic: HostFunction<[Cost; 4]>, /// Cost of calling the `casper_emit_message` host function. pub emit_message: HostFunction<[Cost; 4]>, + /// Cost of calling the `get_block_info` host function. + pub get_block_info: HostFunction<[Cost; 2]>, } impl Zero for HostFunctionCosts { @@ -390,6 +392,7 @@ impl Zero for HostFunctionCosts { manage_message_topic: HostFunction::zero(), emit_message: HostFunction::zero(), cost_increase_per_message: Zero::zero(), + get_block_info: HostFunction::zero(), } } @@ -443,6 +446,7 @@ impl Zero for HostFunctionCosts { enable_contract_version, manage_message_topic, emit_message, + get_block_info, } = self; read_value.is_zero() && dictionary_get.is_zero() @@ -492,6 +496,7 @@ impl Zero for HostFunctionCosts { && emit_message.is_zero() && cost_increase_per_message.is_zero() && add_package_version.is_zero() + && get_block_info.is_zero() } } @@ -679,6 +684,7 @@ impl Default for HostFunctionCosts { ], ), cost_increase_per_message: DEFAULT_COST_INCREASE_PER_MESSAGE_EMITTED, + get_block_info: HostFunction::new(DEFAULT_FIXED_COST, [NOT_USED, NOT_USED]), } } } @@ -734,6 +740,7 @@ impl ToBytes for HostFunctionCosts { 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()?); + ret.append(&mut self.get_block_info.to_bytes()?); Ok(ret) } @@ -786,6 +793,7 @@ impl ToBytes for HostFunctionCosts { + self.manage_message_topic.serialized_length() + self.emit_message.serialized_length() + self.cost_increase_per_message.serialized_length() + + self.get_block_info.serialized_length() } } @@ -839,6 +847,7 @@ impl FromBytes for HostFunctionCosts { 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)?; + let (get_block_info, rem) = FromBytes::from_bytes(rem)?; Ok(( HostFunctionCosts { read_value, @@ -889,6 +898,7 @@ impl FromBytes for HostFunctionCosts { manage_message_topic, emit_message, cost_increase_per_message, + get_block_info, }, rem, )) @@ -947,6 +957,7 @@ impl Distribution for Standard { manage_message_topic: rng.gen(), emit_message: rng.gen(), cost_increase_per_message: rng.gen(), + get_block_info: rng.gen(), } } } @@ -1014,6 +1025,7 @@ pub mod gens { manage_message_topic in host_function_cost_arb(), emit_message in host_function_cost_arb(), cost_increase_per_message in num::u32::ANY, + get_block_info in host_function_cost_arb(), ) -> HostFunctionCosts { HostFunctionCosts { read_value, @@ -1064,6 +1076,7 @@ pub mod gens { manage_message_topic, emit_message, cost_increase_per_message, + get_block_info } } }