diff --git a/examples/counter/src/contract.rs b/examples/counter/src/contract.rs index 5f906c63044d..130772e01dad 100644 --- a/examples/counter/src/contract.rs +++ b/examples/counter/src/contract.rs @@ -45,11 +45,11 @@ impl Contract for CounterContract { self.state.value.set(value); } - async fn execute_operation(&mut self, operation: CounterOperation) -> u64 { - let CounterOperation::Increment(operation) = operation; - let new_value = self.state.value.get() + operation; + async fn execute_operation(&mut self, operation: CounterOperation) -> CounterOperation { + let CounterOperation::Increment(increment) = operation; + let new_value = self.state.value.get() + increment; self.state.value.set(new_value); - new_value + operation } async fn execute_message(&mut self, _message: ()) { @@ -84,8 +84,8 @@ mod tests { let expected_value = initial_value + increment; - assert_eq!(response, expected_value); - assert_eq!(*counter.state.value.get(), initial_value + increment); + assert_eq!(response, CounterOperation::Increment(increment)); + assert_eq!(*counter.state.value.get(), expected_value); } #[test] @@ -115,7 +115,7 @@ mod tests { let expected_value = initial_value + increment; - assert_eq!(response, expected_value); + assert_eq!(response, CounterOperation::Increment(increment)); assert_eq!(*counter.state.value.get(), expected_value); } diff --git a/examples/counter/src/lib.rs b/examples/counter/src/lib.rs index 4357cc98b2dd..7b08cf409b05 100644 --- a/examples/counter/src/lib.rs +++ b/examples/counter/src/lib.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; pub struct CounterAbi; -#[derive(Debug, Deserialize, Serialize, GraphQLMutationRoot)] +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, GraphQLMutationRoot)] pub enum CounterOperation { /// Increment the counter by the given value Increment(u64), @@ -20,7 +20,7 @@ pub enum CounterOperation { impl ContractAbi for CounterAbi { type Operation = CounterOperation; - type Response = u64; + type Response = CounterOperation; } impl ServiceAbi for CounterAbi { diff --git a/linera-chain/src/block_tracker.rs b/linera-chain/src/block_tracker.rs index a96a434996a2..ea9293757ca2 100644 --- a/linera-chain/src/block_tracker.rs +++ b/linera-chain/src/block_tracker.rs @@ -170,6 +170,13 @@ impl<'resources, 'blobs> BlockExecutionTracker<'resources, 'blobs> { /// Returns a new TransactionTracker for the current transaction. fn new_transaction_tracker(&mut self) -> Result { + // Convert operation results to Vec> + let previous_operation_results = self + .operation_results + .iter() + .map(|result| result.0.clone()) + .collect(); + Ok(TransactionTracker::new( self.local_time, self.transaction_index, @@ -177,6 +184,7 @@ impl<'resources, 'blobs> BlockExecutionTracker<'resources, 'blobs> { self.next_chain_index, self.oracle_responses()?, &self.blobs, + previous_operation_results, )) } diff --git a/linera-chain/src/data_types.rs b/linera-chain/src/data_types.rs index 00891d5e0dce..12105b11a639 100644 --- a/linera-chain/src/data_types.rs +++ b/linera-chain/src/data_types.rs @@ -181,11 +181,14 @@ impl From<&Operation> for OperationMetadata { }, Operation::User { application_id, - bytes, + input, } => OperationMetadata { operation_type: "User".to_string(), application_id: Some(*application_id), - user_bytes_hex: Some(hex::encode(bytes)), + user_bytes_hex: Some(match input { + linera_execution::OperationInput::Direct(bytes) => hex::encode(bytes), + linera_execution::OperationInput::Composed => "composed".to_string(), + }), system_bytes_hex: None, }, } diff --git a/linera-chain/src/unit_tests/chain_tests.rs b/linera-chain/src/unit_tests/chain_tests.rs index 18b159dffc47..ea75824083ad 100644 --- a/linera-chain/src/unit_tests/chain_tests.rs +++ b/linera-chain/src/unit_tests/chain_tests.rs @@ -26,7 +26,8 @@ use linera_execution::{ committee::{Committee, ValidatorState}, test_utils::{ExpectedCall, MockApplication}, BaseRuntime, ContractRuntime, ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, - Operation, ResourceControlPolicy, ServiceRuntime, SystemOperation, TestExecutionRuntimeContext, + Operation, OperationInput, ResourceControlPolicy, ServiceRuntime, SystemOperation, + TestExecutionRuntimeContext, }; use linera_views::{ context::{Context as _, MemoryContext, ViewContext}, @@ -313,11 +314,11 @@ async fn test_application_permissions() -> anyhow::Result<()> { application.expect_call(ExpectedCall::default_finalize()); let app_operation = Operation::User { application_id, - bytes: b"foo".to_vec(), + input: OperationInput::Direct(b"foo".to_vec()), }; let another_app_operation = Operation::User { application_id: another_app_id, - bytes: b"bar".to_vec(), + input: OperationInput::Direct(b"bar".to_vec()), }; let valid_block = make_first_block(chain_id) @@ -740,7 +741,7 @@ async fn prepare_test_with_dummy_mock_application( let block = make_first_block(chain_id).with_operation(Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }); Ok((application, application_id, chain, block, time)) diff --git a/linera-client/src/benchmark.rs b/linera-client/src/benchmark.rs index aef478432f71..2b822a35ed2d 100644 --- a/linera-client/src/benchmark.rs +++ b/linera-client/src/benchmark.rs @@ -19,7 +19,7 @@ use linera_core::{ client::{ChainClient, ChainClientError}, Environment, }; -use linera_execution::{system::SystemOperation, Operation}; +use linera_execution::{system::SystemOperation, Operation, OperationInput}; use linera_sdk::abis::fungible::FungibleOperation; use num_format::{Locale, ToFormattedString}; use prometheus_parse::{HistogramCount, Scrape, Value}; @@ -730,7 +730,7 @@ impl Benchmark { .expect("should serialize fungible token operation"); Operation::User { application_id, - bytes, + input: OperationInput::Direct(bytes), } } } diff --git a/linera-core/src/unit_tests/wasm_client_tests.rs b/linera-core/src/unit_tests/wasm_client_tests.rs index b1c568e05cda..a886de458154 100644 --- a/linera-core/src/unit_tests/wasm_client_tests.rs +++ b/linera-core/src/unit_tests/wasm_client_tests.rs @@ -166,8 +166,10 @@ where let increment = 5_u64; let counter_operation = counter::CounterOperation::Increment(increment); + let operation1 = Operation::user(application_id, &counter_operation)?; + let operation2 = Operation::user_composed(application_id.forget_abi()); creator - .execute_operation(Operation::user(application_id, &counter_operation)?) + .execute_operations(vec![operation1, operation2], vec![]) .await .unwrap(); @@ -177,9 +179,10 @@ where .await .unwrap(); + // We do an increment by 5 and then immediately reiterate it. let expected = QueryOutcome { response: async_graphql::Response::new( - async_graphql::Value::from_json(json!({"value": 15})).unwrap(), + async_graphql::Value::from_json(json!({"value": 20})).unwrap(), ), operations: vec![], }; diff --git a/linera-core/src/unit_tests/wasm_worker_tests.rs b/linera-core/src/unit_tests/wasm_worker_tests.rs index b4df5d712ae9..17473ef09df2 100644 --- a/linera-core/src/unit_tests/wasm_worker_tests.rs +++ b/linera-core/src/unit_tests/wasm_worker_tests.rs @@ -28,7 +28,8 @@ use linera_chain::{ }; use linera_execution::{ system::SystemOperation, test_utils::SystemExecutionState, ExecutionRuntimeContext, Operation, - OperationContext, ResourceController, TransactionTracker, WasmContractModule, WasmRuntime, + OperationContext, OperationInput, ResourceController, TransactionTracker, WasmContractModule, + WasmRuntime, }; use linera_storage::{DbStorage, Storage}; #[cfg(feature = "dynamodb")] @@ -258,7 +259,7 @@ where .with_timestamp(3) .with_operation(Operation::User { application_id, - bytes: user_operation.clone(), + input: OperationInput::Direct(user_operation.clone()), }); let operation_context = OperationContext { chain_id: creator_chain.id(), @@ -273,7 +274,7 @@ where operation_context, Operation::User { application_id, - bytes: user_operation, + input: OperationInput::Direct(user_operation), }, &mut TransactionTracker::new( Timestamp::from(3), @@ -282,6 +283,7 @@ where 0, Some(vec![OracleResponse::Blob(application_description_blob_id)]), &[], + vec![], ), &mut controller, ) @@ -291,6 +293,7 @@ where .system .used_blobs .insert(&application_description_blob_id)?; + let result = counter::CounterOperation::Increment(5); let run_block_proposal = ConfirmedBlock::new( BlockExecutionOutcome { messages: vec![Vec::new()], @@ -300,7 +303,7 @@ where blobs: vec![Vec::new()], state_hash: creator_state.crypto_hash().await?, oracle_responses: vec![vec![]], - operation_results: vec![OperationResult(bcs::to_bytes(&15u64)?)], + operation_results: vec![OperationResult(bcs::to_bytes(&result)?)], } .with(run_block), ); diff --git a/linera-execution/src/evm/revm.rs b/linera-execution/src/evm/revm.rs index c8f283ae22d0..7629dca12ce9 100644 --- a/linera-execution/src/evm/revm.rs +++ b/linera-execution/src/evm/revm.rs @@ -38,8 +38,9 @@ use crate::{ database::{DatabaseRuntime, StorageStats, EVM_SERVICE_GAS_LIMIT}, }, BaseRuntime, ContractRuntime, ContractSyncRuntimeHandle, DataBlobHash, EvmExecutionError, - EvmRuntime, ExecutionError, ServiceRuntime, ServiceSyncRuntimeHandle, UserContract, - UserContractInstance, UserContractModule, UserService, UserServiceInstance, UserServiceModule, + EvmRuntime, ExecutionError, OperationInput, ServiceRuntime, ServiceSyncRuntimeHandle, + UserContract, UserContractInstance, UserContractModule, UserService, UserServiceInstance, + UserServiceModule, }; /// This is the selector of the `execute_message` that should be called @@ -1449,7 +1450,8 @@ where EvmQuery::Query(vec) => vec, EvmQuery::Mutation(operation) => { let mut runtime = self.db.runtime.lock().expect("The lock should be possible"); - runtime.schedule_operation(operation)?; + let input = OperationInput::Direct(operation); + runtime.schedule_operation(input)?; return Ok(Vec::new()); } }; diff --git a/linera-execution/src/execution.rs b/linera-execution/src/execution.rs index f835bfb8b580..340085f2f1cc 100644 --- a/linera-execution/src/execution.rs +++ b/linera-execution/src/execution.rs @@ -132,6 +132,7 @@ where next_chain_index, None, &[], + vec![], // No previous operation results for testing ); txn_tracker.add_created_blob(blob); self.run_user_action( @@ -310,8 +311,22 @@ where } Operation::User { application_id, - bytes, + input, } => { + // Get the bytes for the operation based on the input type + let bytes = match input { + crate::OperationInput::Direct(bytes) => bytes.clone(), + crate::OperationInput::Composed => { + // For composed operations, use the result from the last operation + let previous_results = txn_tracker.previous_operation_results(); + if previous_results.is_empty() { + return Err(ExecutionError::ComposedOperationCannotBeFirst); + } + // Get the last operation result as input + previous_results.last().unwrap().clone() + } + }; + self.run_user_action( application_id, UserAction::Operation(context, bytes), diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index 6c2bd04a4793..6227ac5aa5c4 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -49,6 +49,7 @@ use linera_base::{ vm::VmRuntime, }; use linera_views::{batch::Batch, ViewError}; +use linera_witty::{WitLoad, WitStore, WitType}; use serde::{Deserialize, Serialize}; use system::AdminOperation; use thiserror::Error; @@ -210,7 +211,8 @@ pub enum ExecutionError { DecompressionError(#[from] DecompressionError), #[error("The given promise is invalid or was polled once already")] InvalidPromise, - + #[error("Composed operation cannot be first")] + ComposedOperationCannotBeFirst, #[error("Attempted to perform a reentrant call to application {0}")] ReentrantCall(ApplicationId), #[error( @@ -679,7 +681,7 @@ pub trait ServiceRuntime: BaseRuntime { ) -> Result, ExecutionError>; /// Schedules an operation to be included in the block proposed after execution. - fn schedule_operation(&mut self, operation: Vec) -> Result<(), ExecutionError>; + fn schedule_operation(&mut self, input: OperationInput) -> Result<(), ExecutionError>; /// Checks if the service has exceeded its execution time limit. fn check_execution_time(&mut self) -> Result<(), ExecutionError>; @@ -817,17 +819,29 @@ pub trait ContractRuntime: BaseRuntime { fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError>; } +/// Input for an operation, which can be either direct bytes or a composed operation. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, WitType, WitLoad, WitStore)] +pub enum OperationInput { + /// Direct input as bytes. + Direct( + #[serde(with = "serde_bytes")] + #[debug(with = "hex_debug")] + Vec, + ), + /// Composed input that uses the result from the previous operation in the block. + /// If operation_results is empty, an error should be raised during execution. + Composed, +} + /// An operation to be executed in a block. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub enum Operation { /// A system operation. System(Box), - /// A user operation (in serialized form). + /// A user operation. User { application_id: ApplicationId, - #[serde(with = "serde_bytes")] - #[debug(with = "hex_debug")] - bytes: Vec, + input: OperationInput, }, } @@ -1176,10 +1190,20 @@ impl Operation { ) -> Result { Ok(Operation::User { application_id, - bytes: bcs::to_bytes(&operation)?, + input: OperationInput::Direct(bcs::to_bytes(&operation)?), }) } + /// Creates a new composed user application operation that uses the result from the + /// previous operation as input. + #[cfg(with_testing)] + pub fn user_composed(application_id: ApplicationId) -> Self { + Operation::User { + application_id, + input: OperationInput::Composed, + } + } + /// Returns a reference to the [`SystemOperation`] in this [`Operation`], if this [`Operation`] /// is for the system application. pub fn as_system_operation(&self) -> Option<&SystemOperation> { diff --git a/linera-execution/src/resources.rs b/linera-execution/src/resources.rs index f41952b93ac2..b763615c130f 100644 --- a/linera-execution/src/resources.rs +++ b/linera-execution/src/resources.rs @@ -238,8 +238,16 @@ where self.update_balance(self.policy.operation)?; match operation { Operation::System(_) => Ok(()), - Operation::User { bytes, .. } => { - let size = bytes.len(); + Operation::User { input, .. } => { + let size = match input { + crate::OperationInput::Direct(bytes) => bytes.len(), + crate::OperationInput::Composed => { + // For composed operations, we don't know the size until execution time + // Since the input comes from the previous operation result. + // We'll use 0 for now but this might need to be revisited. + 0 + } + }; self.tracker.as_mut().operation_bytes = self .tracker .as_mut() diff --git a/linera-execution/src/runtime.rs b/linera-execution/src/runtime.rs index 59cbb9e22f66..3fac2e09fee9 100644 --- a/linera-execution/src/runtime.rs +++ b/linera-execution/src/runtime.rs @@ -33,9 +33,9 @@ use crate::{ util::{ReceiverExt, UnboundedSenderExt}, ApplicationDescription, ApplicationId, BaseRuntime, ContractRuntime, DataBlobHash, ExecutionError, FinalizeContext, Message, MessageContext, MessageKind, ModuleId, Operation, - OutgoingMessage, QueryContext, QueryOutcome, ServiceRuntime, TransactionTracker, - UserContractCode, UserContractInstance, UserServiceCode, UserServiceInstance, - MAX_STREAM_NAME_LEN, + OperationInput, OutgoingMessage, QueryContext, QueryOutcome, ServiceRuntime, + TransactionTracker, UserContractCode, UserContractInstance, UserServiceCode, + UserServiceInstance, MAX_STREAM_NAME_LEN, }; #[cfg(test)] @@ -1809,13 +1809,13 @@ impl ServiceRuntime for ServiceSyncRuntimeHandle { Ok(response) } - fn schedule_operation(&mut self, operation: Vec) -> Result<(), ExecutionError> { + fn schedule_operation(&mut self, input: OperationInput) -> Result<(), ExecutionError> { let mut this = self.inner(); let application_id = this.current_application().id; this.scheduled_operations.push(Operation::User { application_id, - bytes: operation, + input, }); Ok(()) diff --git a/linera-execution/src/transaction_tracker.rs b/linera-execution/src/transaction_tracker.rs index e3e67ddde704..e02528a05976 100644 --- a/linera-execution/src/transaction_tracker.rs +++ b/linera-execution/src/transaction_tracker.rs @@ -49,6 +49,8 @@ pub struct TransactionTracker { previously_created_blobs: BTreeMap, /// Operation result. operation_result: Option>, + /// Results from previous operations in the current block. + previous_operation_results: Vec>, /// Streams that have been updated but not yet processed during this transaction. streams_to_process: BTreeMap, /// Published blobs this transaction refers to by [`BlobId`]. @@ -82,6 +84,7 @@ impl TransactionTracker { next_chain_index: u32, oracle_responses: Option>, blobs: &[Vec], + previous_operation_results: Vec>, ) -> Self { let mut previously_created_blobs = BTreeMap::new(); for tx_blobs in blobs { @@ -96,6 +99,7 @@ impl TransactionTracker { next_chain_index, replaying_oracle_responses: oracle_responses.map(Vec::into_iter), previously_created_blobs, + previous_operation_results, ..Self::default() } } @@ -174,6 +178,11 @@ impl TransactionTracker { self.operation_result = result } + /// Returns the previous operation results in the current block. + pub fn previous_operation_results(&self) -> &[Vec] { + &self.previous_operation_results + } + pub fn add_stream_to_process( &mut self, application_id: ApplicationId, @@ -281,6 +290,7 @@ impl TransactionTracker { blobs, previously_created_blobs: _, operation_result, + previous_operation_results: _, streams_to_process, blobs_published, } = self; @@ -316,7 +326,15 @@ impl TransactionTracker { /// Creates a new [`TransactionTracker`] for testing, with default values and the given /// oracle responses. pub fn new_replaying(oracle_responses: Vec) -> Self { - TransactionTracker::new(Timestamp::from(0), 0, 0, 0, Some(oracle_responses), &[]) + TransactionTracker::new( + Timestamp::from(0), + 0, + 0, + 0, + Some(oracle_responses), + &[], + vec![], + ) } /// Creates a new [`TransactionTracker`] for testing, with default values and oracle responses diff --git a/linera-execution/src/wasm/runtime_api.rs b/linera-execution/src/wasm/runtime_api.rs index 65be95f9be3a..b4be49252239 100644 --- a/linera-execution/src/wasm/runtime_api.rs +++ b/linera-execution/src/wasm/runtime_api.rs @@ -17,7 +17,10 @@ use linera_witty::{wit_export, Instance, RuntimeError}; use tracing::log; use super::WasmExecutionError; -use crate::{BaseRuntime, ContractRuntime, DataBlobHash, ExecutionError, ModuleId, ServiceRuntime}; +use crate::{ + BaseRuntime, ContractRuntime, DataBlobHash, ExecutionError, ModuleId, OperationInput, + ServiceRuntime, +}; /// Common host data used as the `UserData` of the system API implementations. pub struct RuntimeApiData { @@ -687,7 +690,16 @@ where caller .user_data_mut() .runtime - .schedule_operation(operation) + .schedule_operation(OperationInput::Direct(operation)) + .map_err(|error| RuntimeError::Custom(error.into())) + } + + /// Schedules a composed operation to be included in the block being built by this query. + fn schedule_composed_operation(caller: &mut Caller) -> Result<(), RuntimeError> { + caller + .user_data_mut() + .runtime + .schedule_operation(OperationInput::Composed) .map_err(|error| RuntimeError::Custom(error.into())) } diff --git a/linera-execution/tests/contract_runtime_apis.rs b/linera-execution/tests/contract_runtime_apis.rs index b4fd833c92d5..986fe08a9fd6 100644 --- a/linera-execution/tests/contract_runtime_apis.rs +++ b/linera-execution/tests/contract_runtime_apis.rs @@ -27,8 +27,8 @@ use linera_execution::{ RegisterMockApplication, SystemExecutionState, }, BaseRuntime, ContractRuntime, ExecutionError, Message, MessageContext, Operation, - OperationContext, ResourceController, SystemExecutionStateView, TestExecutionRuntimeContext, - TransactionOutcome, TransactionTracker, + OperationContext, OperationInput, ResourceController, SystemExecutionStateView, + TestExecutionRuntimeContext, TransactionOutcome, TransactionTracker, }; use linera_views::context::MemoryContext; use test_case::{test_case, test_matrix}; @@ -85,7 +85,7 @@ async fn test_transfer_system_api( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let mut tracker = TransactionTracker::new_replaying_blobs([ app_desc_blob_id, @@ -169,7 +169,7 @@ async fn test_unauthorized_transfer_system_api( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let result = view .execute_operation( @@ -258,7 +258,7 @@ async fn test_claim_system_api( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let mut tracker = TransactionTracker::new_replaying_blobs([ app_desc_blob_id, @@ -394,7 +394,7 @@ async fn test_unauthorized_claims( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let mut tracker = TransactionTracker::new_replaying_blobs([ app_desc_blob_id, @@ -448,7 +448,7 @@ async fn test_read_chain_balance_system_api(chain_balance: Amount) { let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -495,7 +495,7 @@ async fn test_read_owner_balance_system_api( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -532,7 +532,7 @@ async fn test_read_owner_balance_returns_zero_for_missing_accounts(missing_accou let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -576,7 +576,7 @@ async fn test_read_owner_balances_system_api( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -620,7 +620,7 @@ async fn test_read_balance_owners_system_api( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -854,7 +854,7 @@ async fn test_query_service(authorized_apps: Option>) -> Result<(), Exec let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -926,7 +926,7 @@ async fn test_perform_http_request(authorized_apps: Option>) -> Result<( let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( @@ -978,7 +978,7 @@ async fn test_create_multiple_data_blobs() -> anyhow::Result<()> { let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let mut tracker = TransactionTracker::new_replaying_blobs(blobs); @@ -1040,7 +1040,7 @@ async fn test_publish_module_different_bytecode() -> anyhow::Result<()> { let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let mut tracker = TransactionTracker::new_replaying_blobs(blobs); @@ -1114,7 +1114,7 @@ async fn test_callee_api_calls() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: dummy_operation.clone(), + input: OperationInput::Direct(dummy_operation.clone()), }, &mut txn_tracker, &mut controller, diff --git a/linera-execution/tests/revm.rs b/linera-execution/tests/revm.rs index 4139c3a6af88..d79459969d61 100644 --- a/linera-execution/tests/revm.rs +++ b/linera-execution/tests/revm.rs @@ -17,8 +17,8 @@ use linera_execution::{ solidity::{load_solidity_example, read_evm_u64_entry}, SystemExecutionState, }, - ExecutionRuntimeConfig, ExecutionRuntimeContext, Operation, OperationContext, Query, - QueryContext, QueryResponse, ResourceControlPolicy, ResourceController, ResourceTracker, + ExecutionRuntimeConfig, ExecutionRuntimeContext, Operation, OperationContext, OperationInput, + Query, QueryContext, QueryResponse, ResourceControlPolicy, ResourceController, ResourceTracker, TransactionTracker, }; use linera_views::{context::Context as _, views::View}; @@ -115,7 +115,7 @@ async fn test_fuel_for_counter_revm_application() -> anyhow::Result<()> { let bytes = operation.abi_encode(); let operation = Operation::User { application_id: app_id, - bytes, + input: OperationInput::Direct(bytes), }; view.execute_operation( operation_context, @@ -233,7 +233,7 @@ async fn test_terminate_execute_operation_by_lack_of_fuel() -> anyhow::Result<() let bytes = operation.abi_encode(); let operation = Operation::User { application_id: app_id, - bytes, + input: OperationInput::Direct(bytes), }; let result = view .execute_operation( @@ -411,7 +411,7 @@ async fn test_basic_evm_features() -> anyhow::Result<()> { let bytes = operation.abi_encode(); let operation = Operation::User { application_id: app_id, - bytes, + input: OperationInput::Direct(bytes), }; let result = view .execute_operation( diff --git a/linera-execution/tests/test_execution.rs b/linera-execution/tests/test_execution.rs index b2d6cffe34c9..de6c0ccc0495 100644 --- a/linera-execution/tests/test_execution.rs +++ b/linera-execution/tests/test_execution.rs @@ -24,8 +24,8 @@ use linera_execution::{ SystemExecutionState, }, BaseRuntime, ContractRuntime, ExecutionError, ExecutionRuntimeContext, Message, Operation, - OperationContext, OutgoingMessage, Query, QueryContext, QueryOutcome, QueryResponse, - ResourceController, SystemOperation, TransactionTracker, + OperationContext, OperationInput, OutgoingMessage, Query, QueryContext, QueryOutcome, + QueryResponse, ResourceController, SystemOperation, TransactionTracker, }; use linera_views::{batch::Batch, context::Context, views::View}; use test_case::test_case; @@ -53,7 +53,7 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { context, Operation::User { application_id: *app_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs([ app_desc_blob_id, @@ -145,7 +145,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: dummy_operation.clone(), + input: OperationInput::Direct(dummy_operation.clone()), }, &mut txn_tracker, &mut controller, @@ -179,7 +179,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { context, Query::User { application_id: caller_id, - bytes: vec![] + bytes: vec![], }, Some(&mut service_runtime_endpoint), ) @@ -196,7 +196,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { context, Query::User { application_id: caller_id, - bytes: vec![] + bytes: vec![], }, Some(&mut service_runtime_endpoint), ) @@ -288,7 +288,7 @@ async fn test_simulated_session() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut txn_tracker, &mut controller, @@ -355,7 +355,7 @@ async fn test_simulated_session_leak() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs(caller_blobs.iter().chain(&target_blobs)), &mut controller, @@ -391,7 +391,7 @@ async fn test_rejecting_block_from_finalize() -> anyhow::Result<()> { context, Operation::User { application_id: id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs(blobs), &mut controller, @@ -453,7 +453,7 @@ async fn test_rejecting_block_from_called_applications_finalize() -> anyhow::Res context, Operation::User { application_id: first_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs( first_app_blobs @@ -591,7 +591,7 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { context, Operation::User { application_id: first_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut txn_tracker, &mut controller, @@ -640,7 +640,7 @@ async fn test_cross_application_call_from_finalize() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs(caller_blobs.iter().chain(&target_blobs)), &mut controller, @@ -693,7 +693,7 @@ async fn test_cross_application_call_from_finalize_of_called_application() -> an context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs(caller_blobs.iter().chain(&target_blobs)), &mut controller, @@ -745,7 +745,7 @@ async fn test_calling_application_again_from_finalize() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs(caller_blobs.iter().chain(&target_blobs)), &mut controller, @@ -795,7 +795,7 @@ async fn test_cross_application_error() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut TransactionTracker::new_replaying_blobs( caller_blobs.iter().chain(&target_blobs) @@ -850,7 +850,7 @@ async fn test_simple_message() -> anyhow::Result<()> { context, Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut txn_tracker, &mut controller, @@ -918,7 +918,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut txn_tracker, &mut controller, @@ -1000,7 +1000,7 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut txn_tracker, &mut controller, @@ -1111,7 +1111,7 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< context, Operation::User { application_id: caller_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }, &mut txn_tracker, &mut controller, @@ -1219,7 +1219,7 @@ async fn test_open_chain() -> anyhow::Result<()> { let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; let mut txn_tracker = TransactionTracker::new( Timestamp::from(0), @@ -1228,6 +1228,7 @@ async fn test_open_chain() -> anyhow::Result<()> { 0, Some(blob_oracle_responses(blobs.iter())), &[], + vec![], ); view.execute_operation(context, operation, &mut txn_tracker, &mut controller) .await?; @@ -1305,7 +1306,7 @@ async fn test_close_chain() -> anyhow::Result<()> { let mut controller = ResourceController::default(); let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( context, @@ -1337,7 +1338,7 @@ async fn test_close_chain() -> anyhow::Result<()> { let operation = Operation::User { application_id, - bytes: vec![], + input: OperationInput::Direct(vec![]), }; view.execute_operation( context, diff --git a/linera-rpc/tests/format.rs b/linera-rpc/tests/format.rs index fec8f96e72da..d384e48d4539 100644 --- a/linera-rpc/tests/format.rs +++ b/linera-rpc/tests/format.rs @@ -17,7 +17,7 @@ use linera_chain::{ use linera_core::{data_types::CrossChainRequest, node::NodeError}; use linera_execution::{ system::{AdminOperation, SystemMessage, SystemOperation}, - Message, MessageKind, Operation, + Message, MessageKind, Operation, OperationInput, }; use linera_rpc::RpcMessage; use serde_reflection::{Registry, Result, Samples, Tracer, TracerConfig}; @@ -62,6 +62,7 @@ fn get_registry() -> Result { tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; + tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 84c1a8370367..5865c0f956ff 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -826,7 +826,15 @@ Operation: STRUCT: - application_id: TYPENAME: ApplicationId - - bytes: BYTES + - input: + TYPENAME: OperationInput +OperationInput: + ENUM: + 0: + Direct: + NEWTYPE: BYTES + 1: + Composed: UNIT OperationResult: NEWTYPESTRUCT: BYTES OracleResponse: diff --git a/linera-sdk/src/test/block.rs b/linera-sdk/src/test/block.rs index 967ab76e7a80..912872c0663f 100644 --- a/linera-sdk/src/test/block.rs +++ b/linera-sdk/src/test/block.rs @@ -19,7 +19,7 @@ use linera_chain::{ types::{ConfirmedBlock, ConfirmedBlockCertificate}, }; use linera_core::worker::WorkerError; -use linera_execution::{system::SystemOperation, Operation}; +use linera_execution::{system::SystemOperation, Operation, OperationInput}; use super::TestValidator; @@ -142,20 +142,38 @@ impl BlockBuilder { { let operation = Abi::serialize_operation(&operation) .expect("Failed to serialize `Operation` in BlockBuilder"); + let operation = OperationInput::Direct(operation); self.with_raw_operation(application_id.forget_abi(), operation) } + /// Adds a user composed `operation` to this block. + /// + /// The composed operation is being inserted and added to the block, marked to be executed + /// by `application`. + pub fn with_composed_operation(&mut self, application_id: ApplicationId) -> &mut Self + where + Abi: ContractAbi, + { + self.block + .transactions + .push(Transaction::ExecuteOperation(Operation::User { + application_id: application_id.forget_abi(), + input: OperationInput::Composed, + })); + self + } + /// Adds an already serialized user `operation` to this block. pub fn with_raw_operation( &mut self, application_id: ApplicationId, - operation: impl Into>, + input: OperationInput, ) -> &mut Self { self.block .transactions .push(Transaction::ExecuteOperation(Operation::User { application_id, - bytes: operation.into(), + input, })); self } diff --git a/linera-sdk/src/test/chain.rs b/linera-sdk/src/test/chain.rs index 3e70f3433a0c..c52f5c741398 100644 --- a/linera-sdk/src/test/chain.rs +++ b/linera-sdk/src/test/chain.rs @@ -728,9 +728,9 @@ impl ActiveChain { match operation { Operation::User { application_id, - bytes, + input, } => { - block.with_raw_operation(application_id, bytes); + block.with_raw_operation(application_id, input); } Operation::System(system_operation) => { block.with_system_operation(*system_operation); diff --git a/linera-sdk/wit/service-runtime-api.wit b/linera-sdk/wit/service-runtime-api.wit index 3b9258b027fe..a8d51d61fc3c 100644 --- a/linera-sdk/wit/service-runtime-api.wit +++ b/linera-sdk/wit/service-runtime-api.wit @@ -2,6 +2,7 @@ package linera:app; interface service-runtime-api { schedule-operation: func(operation: list); + schedule-composed-operation: func(); try-query-application: func(application: application-id, argument: list) -> list; check-execution-time: func(fuel-consumed: u64); diff --git a/linera-service-graphql-client/src/service.rs b/linera-service-graphql-client/src/service.rs index d1bb07c14d6f..5a5f4699c0ac 100644 --- a/linera-service-graphql-client/src/service.rs +++ b/linera-service-graphql-client/src/service.rs @@ -66,7 +66,7 @@ mod types { manager::ChainManager, }; pub use linera_core::worker::{Notification, Reason}; - pub use linera_execution::{Message, MessageKind, Operation, SystemOperation}; + pub use linera_execution::{Message, MessageKind, Operation, OperationInput, SystemOperation}; } pub use types::*; @@ -237,20 +237,23 @@ mod from { ) })?; - // Convert hex string to bytes - let bytes = hex::decode(bytes_hex).map_err(|_| { - ConversionError::UnexpectedCertificateType( - "Invalid hex in user_bytes_hex".to_string(), - ) - })?; - Operation::User { application_id: application_id.parse().map_err(|_| { ConversionError::UnexpectedCertificateType( "Invalid application_id format".to_string(), ) })?, - bytes, + input: if bytes_hex == "composed" { + OperationInput::Composed + } else { + // Convert hex string to bytes + let bytes = hex::decode(&bytes_hex).map_err(|_| { + ConversionError::UnexpectedCertificateType( + "Invalid hex in user_bytes_hex".to_string(), + ) + })?; + OperationInput::Direct(bytes) + }, } } _ => {