From 4b14440500fd24ed6a61a188d1834627ad229c05 Mon Sep 17 00:00:00 2001 From: Andre da Silva Date: Mon, 7 Oct 2024 12:34:47 -0300 Subject: [PATCH] Replace ApplicationDescription with Blobs --- CLI.md | 19 -- examples/amm/README.md | 30 +- examples/amm/src/lib.rs | 30 +- examples/crowd-funding/README.md | 25 -- examples/crowd-funding/src/lib.rs | 25 -- .../crowd-funding/tests/campaign_lifecycle.rs | 22 +- examples/fungible/src/lib.rs | 6 +- examples/fungible/tests/cross_chain.rs | 4 +- examples/matching-engine/README.md | 7 - examples/matching-engine/src/lib.rs | 7 - examples/matching-engine/tests/transaction.rs | 19 +- examples/social/README.md | 31 +- examples/social/src/lib.rs | 31 +- linera-base/src/data_types.rs | 147 +++++--- linera-base/src/identifiers.rs | 85 +++-- linera-base/src/unit_tests.rs | 15 +- linera-chain/src/chain.rs | 35 +- linera-chain/src/data_types.rs | 46 ++- linera-chain/src/lib.rs | 3 - linera-chain/src/unit_tests/chain_tests.rs | 73 ++-- linera-client/src/chain_clients.rs | 10 +- linera-client/src/client_context.rs | 54 +-- linera-client/src/client_options.rs | 15 - linera-core/src/chain_worker/actor.rs | 23 +- .../chain_worker/state/attempted_changes.rs | 5 + linera-core/src/chain_worker/state/mod.rs | 57 +++- .../chain_worker/state/temporary_changes.rs | 34 +- linera-core/src/client/mod.rs | 218 ++++++------ linera-core/src/data_types.rs | 2 +- linera-core/src/local_node.rs | 70 ++-- linera-core/src/node.rs | 6 +- linera-core/src/unit_tests/client_tests.rs | 18 +- .../src/unit_tests/value_cache_tests.rs | 2 +- .../src/unit_tests/wasm_client_tests.rs | 193 ++++++++--- .../src/unit_tests/wasm_worker_tests.rs | 90 +++-- linera-core/src/unit_tests/worker_tests.rs | 4 +- linera-core/src/worker.rs | 19 +- linera-execution/src/applications.rs | 177 ---------- linera-execution/src/execution.rs | 172 ++++------ linera-execution/src/execution_state_actor.rs | 44 ++- linera-execution/src/lib.rs | 31 +- linera-execution/src/runtime.rs | 23 +- linera-execution/src/system.rs | 235 ++++++------- linera-execution/src/test_utils/mod.rs | 123 +++++-- .../src/test_utils/system_execution_state.rs | 7 - linera-execution/src/transaction_tracker.rs | 24 +- .../src/unit_tests/applications_tests.rs | 120 ------- .../src/unit_tests/runtime_tests.rs | 21 +- linera-execution/tests/fee_consumption.rs | 21 +- linera-execution/tests/test_execution.rs | 317 +++++++++--------- .../tests/test_system_execution.rs | 9 +- linera-execution/tests/wasm.rs | 44 ++- .../tests/snapshots/format__format.yaml.snap | 49 ++- .../src/contract/conversions_from_wit.rs | 8 +- linera-sdk/src/contract/conversions_to_wit.rs | 2 +- .../src/service/conversions_from_wit.rs | 20 +- linera-sdk/src/service/conversions_to_wit.rs | 14 +- linera-sdk/src/test/block.rs | 11 - linera-sdk/src/test/chain.rs | 108 ++---- linera-sdk/wit/contract-system-api.wit | 2 +- linera-sdk/wit/service-system-api.wit | 8 +- .../gql/service_requests.graphql | 8 + .../gql/service_schema.graphql | 8 +- linera-service-graphql-client/src/service.rs | 8 + linera-service-graphql-client/tests/test.rs | 15 +- linera-service/src/cli_wrappers/wallet.rs | 61 ++-- linera-service/src/linera/main.rs | 28 +- linera-service/src/node_service.rs | 108 +++--- linera-service/tests/linera_net_tests.rs | 151 +++------ linera-storage/src/db_storage.rs | 16 +- linera-storage/src/lib.rs | 34 +- linera-views/src/views/mod.rs | 6 +- 72 files changed, 1532 insertions(+), 1981 deletions(-) delete mode 100644 linera-execution/src/applications.rs delete mode 100644 linera-execution/src/unit_tests/applications_tests.rs diff --git a/CLI.md b/CLI.md index 851b1238b17..c131171bdfe 100644 --- a/CLI.md +++ b/CLI.md @@ -31,7 +31,6 @@ This document contains the help content for the `linera` command-line program. * [`linera read-data-blob`↴](#linera-read-data-blob) * [`linera create-application`↴](#linera-create-application) * [`linera publish-and-create`↴](#linera-publish-and-create) -* [`linera request-application`↴](#linera-request-application) * [`linera keygen`↴](#linera-keygen) * [`linera assign`↴](#linera-assign) * [`linera retry-pending-block`↴](#linera-retry-pending-block) @@ -83,7 +82,6 @@ A Byzantine-fault tolerant sidechain with low-latency finality and high throughp * `read-data-blob` — Verify that a data blob is readable * `create-application` — Create an application * `publish-and-create` — Create an application, and publish the required bytecode -* `request-application` — Request an application from another chain, so it can be used on this one * `keygen` — Create an unassigned key-pair * `assign` — Link a key owned by the wallet to a chain that was just created for that key * `retry-pending-block` — Retry a block we unsuccessfully tried to propose earlier @@ -627,23 +625,6 @@ Create an application, and publish the required bytecode -## `linera request-application` - -Request an application from another chain, so it can be used on this one - -**Usage:** `linera request-application [OPTIONS] ` - -###### **Arguments:** - -* `` — The ID of the application to request - -###### **Options:** - -* `--target-chain-id ` — The target chain on which the application is already registered. If not specified, the chain on which the application was created is used -* `--requester-chain-id ` — The owned chain on which the application is missing - - - ## `linera keygen` Create an unassigned key-pair diff --git a/examples/amm/README.md b/examples/amm/README.md index 56dfbad62c4..0d89daa435c 100644 --- a/examples/amm/README.md +++ b/examples/amm/README.md @@ -167,25 +167,11 @@ To properly setup the tokens in the proper chains, we need to do some transfer o ``` All operations can only be from a remote chain i.e. other than the chain on which `AMM` is deployed to. -We can do it from GraphiQL by performing the `requestApplication` mutation so that we can perform the -operation from the chain. - -```gql,uri=http://localhost:8080 -mutation { - requestApplication ( - chainId:"$CHAIN_1", - applicationId: "$AMM_APPLICATION_ID", - targetChainId: "$CHAIN_AMM" - ) -} -``` - -Note: The above mutation has to be performed from `http://localhost:8080`. Before performing any operation we need to provide liquidity to it, so we will use the `AddLiquidity` operation, navigate to the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID"`. -To perform `AddLiquidity` operation: +To perform the `AddLiquidity` operation: ```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID mutation { @@ -197,19 +183,7 @@ mutation { } ``` -```gql,uri=http://localhost:8080 -mutation { - requestApplication ( - chainId:"$CHAIN_2", - applicationId: "$AMM_APPLICATION_ID", - targetChainId: "$CHAIN_AMM" - ) -} -``` - -Note: The above mutation has to be performed from `http://localhost:8080`. - -To perform `Swap` operation, navigate to the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID"` and +To perform the `Swap` operation, navigate to the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID"` and perform the following mutation: ```gql,uri=http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID diff --git a/examples/amm/src/lib.rs b/examples/amm/src/lib.rs index e9b22abe0eb..fe6b1a33338 100644 --- a/examples/amm/src/lib.rs +++ b/examples/amm/src/lib.rs @@ -171,25 +171,11 @@ To properly setup the tokens in the proper chains, we need to do some transfer o ``` All operations can only be from a remote chain i.e. other than the chain on which `AMM` is deployed to. -We can do it from GraphiQL by performing the `requestApplication` mutation so that we can perform the -operation from the chain. - -```gql,uri=http://localhost:8080 -mutation { - requestApplication ( - chainId:"$CHAIN_1", - applicationId: "$AMM_APPLICATION_ID", - targetChainId: "$CHAIN_AMM" - ) -} -``` - -Note: The above mutation has to be performed from `http://localhost:8080`. Before performing any operation we need to provide liquidity to it, so we will use the `AddLiquidity` operation, navigate to the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID"`. -To perform `AddLiquidity` operation: +To perform the `AddLiquidity` operation: ```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID mutation { @@ -201,19 +187,7 @@ mutation { } ``` -```gql,uri=http://localhost:8080 -mutation { - requestApplication ( - chainId:"$CHAIN_2", - applicationId: "$AMM_APPLICATION_ID", - targetChainId: "$CHAIN_AMM" - ) -} -``` - -Note: The above mutation has to be performed from `http://localhost:8080`. - -To perform `Swap` operation, navigate to the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID"` and +To perform the `Swap` operation, navigate to the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID"` and perform the following mutation: ```gql,uri=http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID diff --git a/examples/crowd-funding/README.md b/examples/crowd-funding/README.md index 62e4e4ced6d..581f16a7859 100644 --- a/examples/crowd-funding/README.md +++ b/examples/crowd-funding/README.md @@ -157,31 +157,6 @@ sleep 2 Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above. -Point your browser to http://localhost:8080, and enter the query: - -```gql,uri=http://localhost:8080 -query { applications( - chainId: "$CHAIN_0" -) { id link } } -``` - -The response will have two entries, one for each application. - -If you do the same with the other chain ID in http://localhost:8081, the node service for the -other wallet, it will have no entries at all, because the applications haven't been registered -there yet. Request `crowd-funding` from the other chain. As an application ID, use `$APP_ID_1`: - -```gql,uri=http://localhost:8081 -mutation { requestApplication( - chainId: "$CHAIN_1" - applicationId: "$APP_ID_1" -) } -``` - -If you enter the `applications` query again, both entries will appear in the second wallet as -well now. `$APP_ID_0` has been registered, too, because it is a dependency of the other -application. - On both http://localhost:8080 and http://localhost:8081, you recognize the crowd-funding application by its ID. The entry also has a field `link`. If you open that in a new tab, you see the GraphQL API for that application on that chain. diff --git a/examples/crowd-funding/src/lib.rs b/examples/crowd-funding/src/lib.rs index 6a973b4fae4..1cddf7d3e1a 100644 --- a/examples/crowd-funding/src/lib.rs +++ b/examples/crowd-funding/src/lib.rs @@ -165,31 +165,6 @@ sleep 2 Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above. -Point your browser to http://localhost:8080, and enter the query: - -```gql,uri=http://localhost:8080 -query { applications( - chainId: "$CHAIN_0" -) { id link } } -``` - -The response will have two entries, one for each application. - -If you do the same with the other chain ID in http://localhost:8081, the node service for the -other wallet, it will have no entries at all, because the applications haven't been registered -there yet. Request `crowd-funding` from the other chain. As an application ID, use `$APP_ID_1`: - -```gql,uri=http://localhost:8081 -mutation { requestApplication( - chainId: "$CHAIN_1" - applicationId: "$APP_ID_1" -) } -``` - -If you enter the `applications` query again, both entries will appear in the second wallet as -well now. `$APP_ID_0` has been registered, too, because it is a dependency of the other -application. - On both http://localhost:8080 and http://localhost:8081, you recognize the crowd-funding application by its ID. The entry also has a field `link`. If you open that in a new tab, you see the GraphQL API for that application on that chain. diff --git a/examples/crowd-funding/tests/campaign_lifecycle.rs b/examples/crowd-funding/tests/campaign_lifecycle.rs index bad603b7cda..2a931673402 100644 --- a/examples/crowd-funding/tests/campaign_lifecycle.rs +++ b/examples/crowd-funding/tests/campaign_lifecycle.rs @@ -53,19 +53,12 @@ async fn collect_pledges() { target: target_amount, }; let campaign_id = campaign_chain - .create_application( - bytecode_id, - token_id, - campaign_state, - vec![token_id.forget_abi()], - ) + .create_application(bytecode_id, token_id, campaign_state, vec![]) .await; let mut pledges_and_transfers = Vec::new(); for (backer_chain, backer_account, _balance) in &backers { - backer_chain.register_application(campaign_id).await; - let pledge_certificate = backer_chain .add_block(|block| { block.with_operation( @@ -78,7 +71,7 @@ async fn collect_pledges() { }) .await; - assert_eq!(pledge_certificate.outgoing_message_count(), 3); + assert_eq!(pledge_certificate.outgoing_message_count(), 2); pledges_and_transfers.push(pledge_certificate); } @@ -157,19 +150,12 @@ async fn cancel_successful_campaign() { target: target_amount, }; let campaign_id = campaign_chain - .create_application( - bytecode_id, - token_id, - campaign_state, - vec![token_id.forget_abi()], - ) + .create_application(bytecode_id, token_id, campaign_state, vec![]) .await; let mut pledges_and_transfers = Vec::new(); for (backer_chain, backer_account, _balance) in &backers { - backer_chain.register_application(campaign_id).await; - let pledge_certificate = backer_chain .add_block(|block| { block.with_operation( @@ -182,7 +168,7 @@ async fn cancel_successful_campaign() { }) .await; - assert_eq!(pledge_certificate.outgoing_message_count(), 3); + assert_eq!(pledge_certificate.outgoing_message_count(), 2); pledges_and_transfers.push(pledge_certificate); } diff --git a/examples/fungible/src/lib.rs b/examples/fungible/src/lib.rs index e7cf656c7aa..fa1cab1290f 100644 --- a/examples/fungible/src/lib.rs +++ b/examples/fungible/src/lib.rs @@ -309,8 +309,6 @@ pub async fn create_with_accounts( .await; for (chain, account, initial_amount) in &accounts { - chain.register_application(application_id).await; - let claim_certificate = chain .add_block(|block| { block.with_operation( @@ -330,7 +328,7 @@ pub async fn create_with_accounts( }) .await; - assert_eq!(claim_certificate.outgoing_message_count(), 2); + assert_eq!(claim_certificate.outgoing_message_count(), 1); let transfer_certificate = token_chain .add_block(|block| { @@ -338,7 +336,7 @@ pub async fn create_with_accounts( }) .await; - assert_eq!(transfer_certificate.outgoing_message_count(), 2); + assert_eq!(transfer_certificate.outgoing_message_count(), 1); chain .add_block(|block| { diff --git a/examples/fungible/tests/cross_chain.rs b/examples/fungible/tests/cross_chain.rs index 9d1e61ebca6..a6c36ae2b3d 100644 --- a/examples/fungible/tests/cross_chain.rs +++ b/examples/fungible/tests/cross_chain.rs @@ -95,8 +95,6 @@ async fn test_bouncing_tokens() { let receiver_chain = validator.new_chain().await; let receiver_account = AccountOwner::from(receiver_chain.public_key()); - receiver_chain.register_application(application_id).await; - let certificate = sender_chain .add_block(|block| { block.with_operation( @@ -118,7 +116,7 @@ async fn test_bouncing_tokens() { Some(initial_amount.saturating_sub(transfer_amount)), ); - assert_eq!(certificate.outgoing_message_count(), 2); + assert_eq!(certificate.outgoing_message_count(), 1); receiver_chain .add_block(move |block| { diff --git a/examples/matching-engine/README.md b/examples/matching-engine/README.md index 3e04faff503..130e344014f 100644 --- a/examples/matching-engine/README.md +++ b/examples/matching-engine/README.md @@ -104,13 +104,6 @@ MATCHING_ENGINE=$(linera --wait-for-outgoing-messages \ --required-application-ids $FUN1_APP_ID $FUN2_APP_ID) ``` -And make sure chain 2 also has it: - -```bash -linera --wait-for-outgoing-messages request-application \ - --requester-chain-id $CHAIN_2 $MATCHING_ENGINE -``` - ## Using the Matching Engine Application First, a node service for the current wallet has to be started: diff --git a/examples/matching-engine/src/lib.rs b/examples/matching-engine/src/lib.rs index d1a4da0d1c8..fc3ce28d753 100644 --- a/examples/matching-engine/src/lib.rs +++ b/examples/matching-engine/src/lib.rs @@ -108,13 +108,6 @@ MATCHING_ENGINE=$(linera --wait-for-outgoing-messages \ --required-application-ids $FUN1_APP_ID $FUN2_APP_ID) ``` -And make sure chain 2 also has it: - -```bash -linera --wait-for-outgoing-messages request-application \ - --requester-chain-id $CHAIN_2 $MATCHING_ENGINE -``` - ## Using the Matching Engine Application First, a node service for the current wallet has to be started: diff --git a/examples/matching-engine/tests/transaction.rs b/examples/matching-engine/tests/transaction.rs index d427904716a..d012a5c8d90 100644 --- a/examples/matching-engine/tests/transaction.rs +++ b/examples/matching-engine/tests/transaction.rs @@ -106,9 +106,6 @@ async fn single_transaction() { ) .await; - user_chain_a.register_application(token_id_b).await; - user_chain_b.register_application(token_id_a).await; - // Check the initial starting amounts for chain a and chain b for (owner, amount) in [ (admin_account, None), @@ -131,16 +128,8 @@ async fn single_transaction() { let tokens = [token_id_a, token_id_b]; let matching_parameter = Parameters { tokens }; let matching_id = matching_chain - .create_application( - bytecode_id, - matching_parameter, - (), - vec![token_id_a.forget_abi(), token_id_b.forget_abi()], - ) + .create_application(bytecode_id, matching_parameter, (), vec![]) .await; - // Doing the registrations - user_chain_a.register_application(matching_id).await; - user_chain_b.register_application(matching_id).await; // Creating the bid orders let mut bid_certificates = Vec::new(); @@ -159,7 +148,7 @@ async fn single_transaction() { }) .await; - assert_eq!(bid_certificate.outgoing_message_count(), 3); + assert_eq!(bid_certificate.outgoing_message_count(), 2); bid_certificates.push(bid_certificate); } @@ -211,7 +200,7 @@ async fn single_transaction() { }) .await; - assert_eq!(ask_certificate.outgoing_message_count(), 3); + assert_eq!(ask_certificate.outgoing_message_count(), 2); ask_certificates.push(ask_certificate); } @@ -263,7 +252,7 @@ async fn single_transaction() { block.with_operation(matching_id, operation); }) .await; - assert_eq!(order_certificate.outgoing_message_count(), 2); + assert_eq!(order_certificate.outgoing_message_count(), 1); matching_chain .add_block(|block| { block.with_messages_from(&order_certificate); diff --git a/examples/social/README.md b/examples/social/README.md index 345d08a2b05..e5fbf4cdb56 100644 --- a/examples/social/README.md +++ b/examples/social/README.md @@ -84,36 +84,7 @@ sleep 2 Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above. -Point your browser to http://localhost:8081. This is the wallet that didn't create the -application, so we have to request it from the creator chain. As the chain ID specify the -one of the chain where it isn't registered yet: - -```gql,uri=http://localhost:8081 -mutation { - requestApplication( - chainId: "$CHAIN_1", - applicationId: "$APP_ID" - ) -} -``` - -Now in both http://localhost:8080 and http://localhost:8081, this should list the -application and provide a link to its GraphQL API. Remember to use each wallet's chain ID: - -```gql,uri=http://localhost:8081 -query { - applications( - chainId: "$CHAIN_1" - ) { - id - link - } -} -``` - -Open both URLs under the entry `link`. Now you can use the application on each chain. -For the 8081 tab, you can run `echo "http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID"` -to print the URL to navigate to, then subscribe to the other chain using the following query: +Run `echo "http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID"` to print the URL to navigate to, then subscribe to the other chain using the following query: ```gql,uri=http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID mutation { diff --git a/examples/social/src/lib.rs b/examples/social/src/lib.rs index defc866c130..f4c9312a721 100644 --- a/examples/social/src/lib.rs +++ b/examples/social/src/lib.rs @@ -93,36 +93,7 @@ sleep 2 Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above. -Point your browser to http://localhost:8081. This is the wallet that didn't create the -application, so we have to request it from the creator chain. As the chain ID specify the -one of the chain where it isn't registered yet: - -```gql,uri=http://localhost:8081 -mutation { - requestApplication( - chainId: "$CHAIN_1", - applicationId: "$APP_ID" - ) -} -``` - -Now in both http://localhost:8080 and http://localhost:8081, this should list the -application and provide a link to its GraphQL API. Remember to use each wallet's chain ID: - -```gql,uri=http://localhost:8081 -query { - applications( - chainId: "$CHAIN_1" - ) { - id - link - } -} -``` - -Open both URLs under the entry `link`. Now you can use the application on each chain. -For the 8081 tab, you can run `echo "http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID"` -to print the URL to navigate to, then subscribe to the other chain using the following query: +Run `echo "http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID"` to print the URL to navigate to, then subscribe to the other chain using the following query: ```gql,uri=http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID mutation { diff --git a/linera-base/src/data_types.rs b/linera-base/src/data_types.rs index 1cad22fecf3..776574b8974 100644 --- a/linera-base/src/data_types.rs +++ b/linera-base/src/data_types.rs @@ -31,10 +31,10 @@ use thiserror::Error; #[cfg(with_metrics)] use crate::prometheus_util::{self, MeasureLatency}; use crate::{ - crypto::BcsHashable, + crypto::{BcsHashable, CryptoHash}, doc_scalar, hex_debug, identifiers::{ - ApplicationId, BlobId, BlobType, BytecodeId, Destination, GenericApplicationId, MessageId, + ApplicationId, BlobId, BlobType, BytecodeId, ChainId, Destination, GenericApplicationId, UserApplicationId, }, time::{Duration, SystemTime}, @@ -790,12 +790,16 @@ impl FromStr for OracleResponse { } /// Description of the necessary information to run a user application. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize, WitType, WitStore)] pub struct UserApplicationDescription { /// The unique ID of the bytecode to use for the application. pub bytecode_id: BytecodeId, - /// The unique ID of the application's creation. - pub creation: MessageId, + /// The chain ID that created the application. + pub creator_chain_id: ChainId, + /// Height of the block that created this application. + pub block_height: BlockHeight, + /// At what operation index of the block this application was created. + pub operation_index: u32, /// The parameters of the application. #[serde(with = "serde_bytes")] #[debug(with = "hex_debug")] @@ -804,12 +808,23 @@ pub struct UserApplicationDescription { pub required_application_ids: Vec, } -impl From<&UserApplicationDescription> for UserApplicationId { - fn from(description: &UserApplicationDescription) -> Self { - UserApplicationId { - bytecode_id: description.bytecode_id, - creation: description.creation, - } +impl TryFrom<&UserApplicationDescription> for UserApplicationId { + type Error = bcs::Error; + + fn try_from(description: &UserApplicationDescription) -> Result { + Ok(UserApplicationId::new( + CryptoHash::new(&description.blob_bytes()?), + description.bytecode_id, + )) + } +} + +impl BcsHashable for UserApplicationDescription {} + +impl UserApplicationDescription { + /// Gets the `BlobBytes` for this `UserApplicationDescription`. + pub fn blob_bytes(&self) -> Result { + Ok(BlobBytes(bcs::to_bytes(self)?)) } } @@ -946,6 +961,8 @@ pub enum BlobContent { ContractBytecode(CompressedBytecode), /// A blob containing service bytecode. ServiceBytecode(CompressedBytecode), + /// A blob containing an application description. + ApplicationDescription(UserApplicationDescription), } impl fmt::Debug for BlobContent { @@ -954,14 +971,21 @@ impl fmt::Debug for BlobContent { BlobContent::Data(_) => write!(f, "BlobContent::Data"), BlobContent::ContractBytecode(_) => write!(f, "BlobContent::ContractBytecode"), BlobContent::ServiceBytecode(_) => write!(f, "BlobContent::ServiceBytecode"), + BlobContent::ApplicationDescription(description) => f + .debug_struct("BlobContent::ApplicationDescription") + .field("bytecode_id", &description.bytecode_id) + .field("creator_chain_id", &description.creator_chain_id) + .field("block_height", &description.block_height) + .field("operation_index", &description.operation_index) + .finish_non_exhaustive(), } } } impl BlobContent { /// Creates a new [`BlobContent`] from the provided bytes and [`BlobId`]. Does not check if the bytes match the ID! - pub fn new_with_id_unchecked(id: BlobId, bytes: Vec) -> Self { - match id.blob_type { + pub fn new_with_id_unchecked(id: BlobId, bytes: Vec) -> Result { + Ok(match id.blob_type { BlobType::Data => BlobContent::Data(bytes), BlobType::ContractBytecode => BlobContent::ContractBytecode(CompressedBytecode { compressed_bytes: bytes, @@ -969,7 +993,10 @@ impl BlobContent { BlobType::ServiceBytecode => BlobContent::ServiceBytecode(CompressedBytecode { compressed_bytes: bytes, }), - } + BlobType::ApplicationDescription => { + BlobContent::ApplicationDescription(bcs::from_bytes(&bytes)?) + } + }) } /// Creates a new data [`BlobContent`] from the provided bytes. @@ -987,6 +1014,13 @@ impl BlobContent { BlobContent::ServiceBytecode(compressed_bytecode) } + /// Creates a new application description [`BlobContent`] from a [`UserApplicationDescription`]. + pub fn new_application_description( + application_description: UserApplicationDescription, + ) -> Self { + BlobContent::ApplicationDescription(application_description) + } + /// Creates a `Blob` without checking that this is the correct `BlobId`. pub fn with_blob_id_unchecked(self, blob_id: BlobId) -> Blob { Blob { @@ -1005,10 +1039,15 @@ impl BlobContent { BlobType::ServiceBytecode if matches!(&self, BlobContent::ServiceBytecode(_)) => { Some(()) } + BlobType::ApplicationDescription + if matches!(&self, BlobContent::ApplicationDescription(_)) => + { + Some(()) + } _ => None, }?; - let expected_blob_id = BlobId::from_content(&self); + let expected_blob_id = BlobId::from_content(&self).ok()?; if blob_id == expected_blob_id { Some(self.with_blob_id_unchecked(expected_blob_id)) @@ -1018,18 +1057,32 @@ impl BlobContent { } /// Gets the inner blob's bytes. - pub fn inner_bytes(&self) -> Vec { - match self { - BlobContent::Data(bytes) => bytes, + pub fn inner_bytes(&self) -> Result, bcs::Error> { + Ok(match self { + BlobContent::Data(bytes) => bytes.clone(), BlobContent::ContractBytecode(compressed_bytecode) => { - &compressed_bytecode.compressed_bytes + compressed_bytecode.compressed_bytes.clone() } BlobContent::ServiceBytecode(compressed_bytecode) => { - &compressed_bytecode.compressed_bytes + compressed_bytecode.compressed_bytes.clone() } + BlobContent::ApplicationDescription(description) => bcs::to_bytes(description)?, + }) + } + + /// Moves ownership of the blob's application description. If the `BlobContent` is of the wrong type, returns `None`. + pub fn into_inner_application_description(self) -> Option { + match self { + BlobContent::ApplicationDescription(description) => Some(description), + _ => None, } .clone() } + + /// Gets the `BlobBytes` for this `BlobContent`. + pub fn blob_bytes(&self) -> Result { + Ok(BlobBytes(self.inner_bytes()?)) + } } impl From for BlobContent { @@ -1038,12 +1091,14 @@ impl From for BlobContent { } } -impl From for Blob { - fn from(content: BlobContent) -> Blob { - Self { - id: BlobId::from_content(&content), +impl TryFrom for Blob { + type Error = bcs::Error; + + fn try_from(content: BlobContent) -> Result { + Ok(Self { + id: BlobId::from_content(&content)?, content, - } + }) } } @@ -1059,23 +1114,34 @@ pub struct Blob { impl Blob { /// Creates a new [`Blob`] from the provided bytes and [`BlobId`]. Does not check if the bytes match the ID! - pub fn new_with_id_unchecked(id: BlobId, bytes: Vec) -> Self { - BlobContent::new_with_id_unchecked(id, bytes).into() + pub fn new_with_id_unchecked(id: BlobId, bytes: Vec) -> Result { + BlobContent::new_with_id_unchecked(id, bytes)?.try_into() } /// Creates a new data [`Blob`] from the provided bytes. - pub fn new_data(bytes: Vec) -> Self { - BlobContent::new_data(bytes).into() + pub fn new_data(bytes: Vec) -> Result { + BlobContent::new_data(bytes).try_into() } /// Creates a new contract bytecode [`BlobContent`] from the provided bytes. - pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self { - BlobContent::new_contract_bytecode(compressed_bytecode).into() + pub fn new_contract_bytecode( + compressed_bytecode: CompressedBytecode, + ) -> Result { + BlobContent::new_contract_bytecode(compressed_bytecode).try_into() } /// Creates a new service bytecode [`BlobContent`] from the provided bytes. - pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self { - BlobContent::new_service_bytecode(compressed_bytecode).into() + pub fn new_service_bytecode( + compressed_bytecode: CompressedBytecode, + ) -> Result { + BlobContent::new_service_bytecode(compressed_bytecode).try_into() + } + + /// Creates a new application description [`Blob`] from a [`UserApplicationDescription`]. + pub fn new_application_description( + application_description: UserApplicationDescription, + ) -> Result { + BlobContent::new_application_description(application_description).try_into() } /// A content-addressed blob ID i.e. the hash of the `Blob`. @@ -1094,13 +1160,18 @@ impl Blob { } /// Gets the inner blob's bytes. - pub fn inner_bytes(&self) -> Vec { + pub fn inner_bytes(&self) -> Result, bcs::Error> { self.content.inner_bytes() } + /// Moves ownership of the blob's application description. If the `Blob` is of the wrong type, returns `None`. + pub fn into_inner_application_description(self) -> Option { + self.content.into_inner_application_description() + } + /// Loads data blob content from a file. - pub async fn load_data_blob_from_file(path: impl AsRef) -> io::Result { - Ok(Self::new_data(fs::read(path)?)) + pub async fn load_data_blob_from_file(path: impl AsRef) -> Result { + Self::new_data(fs::read(path)?).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } } @@ -1130,13 +1201,13 @@ impl<'a> Deserialize<'a> for Blob { bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?; Ok(Blob { - id: BlobId::from_content(&content), + id: BlobId::from_content(&content).map_err(serde::de::Error::custom)?, content, }) } else { let content = BlobContent::deserialize(deserializer)?; Ok(Blob { - id: BlobId::from_content(&content), + id: BlobId::from_content(&content).map_err(serde::de::Error::custom)?, content, }) } diff --git a/linera-base/src/identifiers.rs b/linera-base/src/identifiers.rs index e01a2312f36..e5f49da636b 100644 --- a/linera-base/src/identifiers.rs +++ b/linera-base/src/identifiers.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; use crate::{ bcs_scalar, crypto::{BcsHashable, CryptoError, CryptoHash, PublicKey}, - data_types::{BlobBytes, BlobContent, BlockHeight}, + data_types::{BlobContent, BlockHeight}, doc_scalar, }; @@ -171,6 +171,8 @@ pub enum BlobType { ContractBytecode, /// A blob containing service bytecode. ServiceBytecode, + /// A blob containing an application description. + ApplicationDescription, } impl Display for BlobType { @@ -196,6 +198,7 @@ impl From<&BlobContent> for BlobType { BlobContent::Data(_) => BlobType::Data, BlobContent::ContractBytecode(_) => BlobType::ContractBytecode, BlobContent::ServiceBytecode(_) => BlobType::ServiceBytecode, + BlobContent::ApplicationDescription(_) => BlobType::ApplicationDescription, } } } @@ -226,11 +229,11 @@ pub struct BlobId { impl BlobId { /// Creates a new `BlobId` from a `BlobContent` - pub fn from_content(content: &BlobContent) -> Self { - Self { - hash: CryptoHash::new(&BlobBytes(content.inner_bytes())), + pub fn from_content(content: &BlobContent) -> Result { + Ok(Self { + hash: CryptoHash::new(&content.blob_bytes()?), blob_type: content.into(), - } + }) } /// Creates a new `BlobId` from a `CryptoHash`. This must be a hash of the blob's bytes! @@ -293,10 +296,10 @@ pub struct MessageId { #[derive(WitLoad, WitStore, WitType)] #[cfg_attr(with_testing, derive(Default))] pub struct ApplicationId { + /// The hash of the `UserApplicationDescription` this refers to. + pub application_description_hash: CryptoHash, /// The bytecode to use for the application. pub bytecode_id: BytecodeId, - /// The unique ID of the application's creation. - pub creation: MessageId, } /// Alias for `ApplicationId`. Use this alias in the core @@ -673,11 +676,8 @@ impl Copy for ApplicationId {} impl PartialEq for ApplicationId { fn eq(&self, other: &Self) -> bool { - let ApplicationId { - bytecode_id, - creation, - } = other; - self.bytecode_id == *bytecode_id && self.creation == *creation + self.application_description_hash == other.application_description_hash + && self.bytecode_id == other.bytecode_id } } @@ -685,50 +685,33 @@ impl Eq for ApplicationId {} impl PartialOrd for ApplicationId { fn partial_cmp(&self, other: &Self) -> Option { - let ApplicationId { - bytecode_id, - creation, - } = other; - match self.bytecode_id.partial_cmp(bytecode_id) { - Some(std::cmp::Ordering::Equal) => self.creation.partial_cmp(creation), - result => result, - } + (self.application_description_hash, self.bytecode_id) + .partial_cmp(&(other.application_description_hash, other.bytecode_id)) } } impl Ord for ApplicationId { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let ApplicationId { - bytecode_id, - creation, - } = other; - match self.bytecode_id.cmp(bytecode_id) { - std::cmp::Ordering::Equal => self.creation.cmp(creation), - result => result, - } + (self.application_description_hash, self.bytecode_id) + .cmp(&(other.application_description_hash, other.bytecode_id)) } } impl Hash for ApplicationId { fn hash(&self, state: &mut H) { - let ApplicationId { - bytecode_id, - creation, - } = self; - bytecode_id.hash(state); - creation.hash(state); + self.application_description_hash.hash(state); + self.bytecode_id.hash(state); } } impl Debug for ApplicationId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let ApplicationId { - bytecode_id, - creation, - } = self; f.debug_struct("ApplicationId") - .field("bytecode_id", bytecode_id) - .field("creation", creation) + .field( + "application_description_hash", + &self.application_description_hash, + ) + .field("bytecode_id", &self.bytecode_id) .finish() } } @@ -736,8 +719,8 @@ impl Debug for ApplicationId { #[derive(Serialize, Deserialize)] #[serde(rename = "ApplicationId")] struct SerializableApplicationId { + pub application_description_hash: CryptoHash, pub bytecode_id: BytecodeId, - pub creation: MessageId, } impl Serialize for ApplicationId { @@ -747,16 +730,16 @@ impl Serialize for ApplicationId { { if serializer.is_human_readable() { let bytes = bcs::to_bytes(&SerializableApplicationId { + application_description_hash: self.application_description_hash, bytecode_id: self.bytecode_id.forget_abi(), - creation: self.creation, }) .map_err(serde::ser::Error::custom)?; serializer.serialize_str(&hex::encode(bytes)) } else { SerializableApplicationId::serialize( &SerializableApplicationId { + application_description_hash: self.application_description_hash, bytecode_id: self.bytecode_id.forget_abi(), - creation: self.creation, }, serializer, ) @@ -775,25 +758,33 @@ impl<'de, A> Deserialize<'de> for ApplicationId { let application_id: SerializableApplicationId = bcs::from_bytes(&application_id_bytes).map_err(serde::de::Error::custom)?; Ok(ApplicationId { + application_description_hash: application_id.application_description_hash, bytecode_id: application_id.bytecode_id.with_abi(), - creation: application_id.creation, }) } else { let value = SerializableApplicationId::deserialize(deserializer)?; Ok(ApplicationId { + application_description_hash: value.application_description_hash, bytecode_id: value.bytecode_id.with_abi(), - creation: value.creation, }) } } } impl ApplicationId { + /// Creates an application ID from the application description hash. + pub fn new(application_description_hash: CryptoHash, bytecode_id: BytecodeId) -> Self { + ApplicationId { + application_description_hash, + bytecode_id, + } + } + /// Specializes an application ID for a given ABI. pub fn with_abi(self) -> ApplicationId { ApplicationId { + application_description_hash: self.application_description_hash, bytecode_id: self.bytecode_id.with_abi(), - creation: self.creation, } } } @@ -802,8 +793,8 @@ impl ApplicationId { /// Forgets the ABI of a bytecode ID (if any). pub fn forget_abi(self) -> ApplicationId { ApplicationId { + application_description_hash: self.application_description_hash, bytecode_id: self.bytecode_id.forget_abi(), - creation: self.creation, } } } diff --git a/linera-base/src/unit_tests.rs b/linera-base/src/unit_tests.rs index e7e69ece075..8eae13fd37d 100644 --- a/linera-base/src/unit_tests.rs +++ b/linera-base/src/unit_tests.rs @@ -98,17 +98,10 @@ fn message_id_test_case() -> MessageId { /// Creates a dummy [`ApplicationId`] instance to use for the WIT roundtrip test. fn application_id_test_case() -> ApplicationId { - ApplicationId { - bytecode_id: BytecodeId::new( - CryptoHash::test_hash("contract bytecode"), - CryptoHash::test_hash("service bytecode"), - ), - creation: MessageId { - chain_id: ChainId::root(0), - height: BlockHeight(0), - index: 0, - }, - } + ApplicationId::new( + CryptoHash::test_hash("application description"), + bytecode_id_test_case(), + ) } /// Creates a dummy [`BytecodeId`] instance to use for the WIT roundtrip test. diff --git a/linera-chain/src/chain.rs b/linera-chain/src/chain.rs index 9bc4cde820d..ff79155ffb2 100644 --- a/linera-chain/src/chain.rs +++ b/linera-chain/src/chain.rs @@ -17,8 +17,8 @@ use linera_base::{ }, ensure, identifiers::{ - ChainId, ChannelName, Destination, GenericApplicationId, MessageId, Owner, StreamId, - UserApplicationId, + ApplicationId, ChainId, ChannelName, Destination, GenericApplicationId, MessageId, Owner, + StreamId, UserApplicationId, }, }; use linera_execution::{ @@ -393,20 +393,6 @@ where Ok(response) } - pub async fn describe_application( - &mut self, - application_id: UserApplicationId, - ) -> Result { - self.execution_state - .system - .registry - .describe_application(application_id) - .await - .map_err(|err| { - ChainError::ExecutionError(err.into(), ChainExecutionContext::DescribeApplication) - }) - } - pub async fn mark_messages_as_received( &mut self, target: &Target, @@ -692,6 +678,7 @@ where block: &Block, local_time: Timestamp, replaying_oracle_responses: Option>>, + pending_applications: BTreeMap, ) -> Result { #[cfg(with_metrics)] let _execution_latency = BLOCK_EXECUTION_LATENCY.measure_latency(); @@ -788,6 +775,8 @@ where let mut oracle_responses = Vec::new(); let mut events = Vec::new(); let mut messages = Vec::new(); + let pending_applications = Arc::new(pending_applications); + let mut operation_index = 0; for (txn_index, transaction) in block.transactions() { let chain_execution_context = match transaction { Transaction::ReceiveMessages(_) => ChainExecutionContext::IncomingBundle(txn_index), @@ -800,7 +789,11 @@ where Some(None) => return Err(ChainError::MissingOracleResponseList), None => None, }; - let mut txn_tracker = TransactionTracker::new(next_message_index, maybe_responses); + let mut txn_tracker = TransactionTracker::new( + next_message_index, + maybe_responses, + pending_applications.clone(), + ); match transaction { Transaction::ReceiveMessages(incoming_bundle) => { resource_controller @@ -829,10 +822,12 @@ where let context = OperationContext { chain_id, height: block.height, - index: Some(txn_index), + txn_index: Some(txn_index), + operation_index: Some(operation_index), authenticated_signer: block.authenticated_signer, authenticated_caller_id: None, }; + operation_index += 1; self.execution_state .execute_operation( context, @@ -851,10 +846,6 @@ where } } - self.execution_state - .update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await - .map_err(with_context)?; let (txn_outcomes, txn_oracle_responses, new_next_message_index) = txn_tracker.destructure().map_err(with_context)?; next_message_index = new_next_message_index; diff --git a/linera-chain/src/data_types.rs b/linera-chain/src/data_types.rs index 8b1b3dde188..2420f7d46be 100644 --- a/linera-chain/src/data_types.rs +++ b/linera-chain/src/data_types.rs @@ -10,8 +10,8 @@ use linera_base::{ data_types::{Amount, Blob, BlockHeight, OracleResponse, Round, Timestamp}, doc_scalar, ensure, identifiers::{ - Account, BlobId, BlobType, ChainId, ChannelName, Destination, GenericApplicationId, - MessageId, Owner, StreamId, + Account, ApplicationId, BlobId, BlobType, ChainId, ChannelName, Destination, + GenericApplicationId, MessageId, Owner, StreamId, }, }; use linera_execution::{ @@ -74,11 +74,53 @@ impl Block { BlobId::new(bytecode_id.service_blob_hash, BlobType::ServiceBytecode), ]); } + if let Operation::System(SystemOperation::CreateApplication { + application_id, .. + }) = operation + { + blob_ids.insert(BlobId::new( + application_id.application_description_hash, + BlobType::ApplicationDescription, + )); + } } blob_ids } + /// Returns all the published application blob IDs in this block's operations. + pub fn published_application_blob_ids(&self) -> HashSet { + let mut blob_ids = HashSet::new(); + for operation in &self.operations { + if let Operation::System(SystemOperation::CreateApplication { + application_id, .. + }) = operation + { + blob_ids.insert(BlobId::new( + application_id.application_description_hash, + BlobType::ApplicationDescription, + )); + } + } + + blob_ids + } + + /// Returns all the published application IDs in this block's operations. + pub fn published_application_ids(&self) -> HashSet { + let mut application_ids = HashSet::new(); + for operation in &self.operations { + if let Operation::System(SystemOperation::CreateApplication { + application_id, .. + }) = operation + { + application_ids.insert(*application_id); + } + } + + application_ids + } + /// Returns whether the block contains only rejected incoming messages, which /// makes it admissible even on closed chains. pub fn has_only_rejected_messages(&self) -> bool { diff --git a/linera-chain/src/lib.rs b/linera-chain/src/lib.rs index 258a4ea334f..269a0a67882 100644 --- a/linera-chain/src/lib.rs +++ b/linera-chain/src/lib.rs @@ -155,10 +155,7 @@ pub enum ChainError { #[derive(Copy, Clone, Debug)] pub enum ChainExecutionContext { Query, - DescribeApplication, IncomingBundle(u32), Operation(u32), Block, - #[cfg(with_testing)] - ReadBytecodeLocation, } diff --git a/linera-chain/src/unit_tests/chain_tests.rs b/linera-chain/src/unit_tests/chain_tests.rs index 0f6449f888a..987d30dea9b 100644 --- a/linera-chain/src/unit_tests/chain_tests.rs +++ b/linera-chain/src/unit_tests/chain_tests.rs @@ -59,19 +59,25 @@ where } } -fn make_app_description() -> UserApplicationDescription { +fn make_app_description() -> (UserApplicationDescription, Blob, Blob) { let contract = Bytecode::new(b"contract".into()); let service = Bytecode::new(b"service".into()); - let contract_blob = Blob::new_contract_bytecode(contract.compress()); - let service_blob = Blob::new_service_bytecode(service.compress()); + let contract_blob = Blob::new_contract_bytecode(contract.compress()).unwrap(); + let service_blob = Blob::new_service_bytecode(service.compress()).unwrap(); let bytecode_id = BytecodeId::new(contract_blob.id().hash, service_blob.id().hash); - UserApplicationDescription { - bytecode_id, - creation: make_admin_message_id(BlockHeight(2)), - required_application_ids: vec![], - parameters: vec![], - } + ( + UserApplicationDescription { + bytecode_id, + creator_chain_id: admin_id(), + block_height: BlockHeight(2), + operation_index: 0, + required_application_ids: vec![], + parameters: vec![], + }, + contract_blob, + service_blob, + ) } fn admin_id() -> ChainId { @@ -154,7 +160,9 @@ async fn test_block_size_limit() { recipient: Recipient::root(0), amount: Amount::ONE, }); - let result = chain.execute_block(&invalid_block, time, None).await; + let result = chain + .execute_block(&invalid_block, time, None, BTreeMap::new()) + .await; assert_matches!( result, Err(ChainError::ExecutionError( @@ -164,7 +172,10 @@ async fn test_block_size_limit() { ); // The valid block is accepted... - let outcome = chain.execute_block(&valid_block, time, None).await.unwrap(); + let outcome = chain + .execute_block(&valid_block, time, None, BTreeMap::new()) + .await + .unwrap(); let executed_block = outcome.with(valid_block); // ...because its size is exactly at the allowed limit. @@ -182,17 +193,18 @@ async fn test_application_permissions() { let mut chain = ChainStateView::new(chain_id).await; // Create a mock application. - let app_description = make_app_description(); - let application_id = ApplicationId::from(&app_description); + let (app_description, contract_blob, service_blob) = make_app_description(); + let application_id = ApplicationId::try_from(&app_description).unwrap(); let application = MockApplication::default(); let extra = &chain.context().extra(); + + let app_blob = Blob::new_application_description(app_description).unwrap(); extra .user_contracts() .insert(application_id, application.clone().into()); - let contract_blob = Blob::new_contract_bytecode(Bytecode::new(b"contract".into()).compress()); extra.add_blob(contract_blob); - let service_blob = Blob::new_service_bytecode(Bytecode::new(b"service".into()).compress()); extra.add_blob(service_blob); + extra.add_blob(app_blob); // Initialize the chain, with a chain application. let config = OpenChainConfig { @@ -205,10 +217,6 @@ async fn test_application_permissions() { .unwrap(); let open_chain_message = Message::System(SystemMessage::OpenChain(config)); - let register_app_message = SystemMessage::RegisterApplications { - applications: vec![app_description], - }; - // The OpenChain message must be included in the first block. Also register the app. let bundle = IncomingBundle { origin: Origin::chain(admin_id()), @@ -217,10 +225,7 @@ async fn test_application_permissions() { height: BlockHeight(1), transaction_index: 0, timestamp: Timestamp::from(0), - messages: vec![ - open_chain_message.to_posted(0, MessageKind::Protected), - register_app_message.to_posted(1, MessageKind::Simple), - ], + messages: vec![open_chain_message.to_posted(0, MessageKind::Protected)], }, action: MessageAction::Accept, }; @@ -229,7 +234,9 @@ async fn test_application_permissions() { let invalid_block = make_first_block(chain_id) .with_incoming_bundle(bundle.clone()) .with_simple_transfer(chain_id, Amount::ONE); - let result = chain.execute_block(&invalid_block, time, None).await; + let result = chain + .execute_block(&invalid_block, time, None, BTreeMap::new()) + .await; assert_matches!(result, Err(ChainError::AuthorizedApplications(app_ids)) if app_ids == vec![application_id] ); @@ -244,21 +251,28 @@ async fn test_application_permissions() { let valid_block = make_first_block(chain_id) .with_incoming_bundle(bundle) .with_operation(app_operation.clone()); - let outcome = chain.execute_block(&valid_block, time, None).await.unwrap(); + let outcome = chain + .execute_block(&valid_block, time, None, BTreeMap::new()) + .await + .unwrap(); let value = HashedCertificateValue::new_confirmed(outcome.with(valid_block)); // In the second block, other operations are still not allowed. let invalid_block = make_child_block(&value) .with_simple_transfer(chain_id, Amount::ONE) .with_operation(app_operation.clone()); - let result = chain.execute_block(&invalid_block, time, None).await; + let result = chain + .execute_block(&invalid_block, time, None, BTreeMap::new()) + .await; assert_matches!(result, Err(ChainError::AuthorizedApplications(app_ids)) if app_ids == vec![application_id] ); // Also, blocks without an application operation or incoming message are forbidden. let invalid_block = make_child_block(&value); - let result = chain.execute_block(&invalid_block, time, None).await; + let result = chain + .execute_block(&invalid_block, time, None, BTreeMap::new()) + .await; assert_matches!(result, Err(ChainError::MissingMandatoryApplications(app_ids)) if app_ids == vec![application_id] ); @@ -267,5 +281,8 @@ async fn test_application_permissions() { application.expect_call(ExpectedCall::execute_operation(|_, _, _| Ok(vec![]))); application.expect_call(ExpectedCall::default_finalize()); let valid_block = make_child_block(&value).with_operation(app_operation); - chain.execute_block(&valid_block, time, None).await.unwrap(); + chain + .execute_block(&valid_block, time, None, BTreeMap::new()) + .await + .unwrap(); } diff --git a/linera-client/src/chain_clients.rs b/linera-client/src/chain_clients.rs index 3605a57815a..d9c1773f459 100644 --- a/linera-client/src/chain_clients.rs +++ b/linera-client/src/chain_clients.rs @@ -32,12 +32,8 @@ where S: Storage, P: 'static, { - async fn client(&self, chain_id: &ChainId) -> Option> { - Some(self.0.lock().await.get(chain_id)?.clone()) - } - pub async fn client_lock(&self, chain_id: &ChainId) -> Option> { - self.client(chain_id).await + Some(self.map_lock().await.get(chain_id)?.clone()) } pub async fn try_client_lock(&self, chain_id: &ChainId) -> Result, Error> { @@ -51,7 +47,7 @@ where } pub async fn add_client(&self, client: ChainClient) { - self.0.lock().await.insert(client.chain_id(), client); + self.map_lock().await.insert(client.chain_id(), client); } pub async fn request_client( @@ -59,7 +55,7 @@ where chain_id: ChainId, context: Arc>>, ) -> ChainClient { - let mut guard = self.0.lock().await; + let mut guard = self.map_lock().await; match guard.get(&chain_id) { Some(client) => client.clone(), None => { diff --git a/linera-client/src/client_context.rs b/linera-client/src/client_context.rs index 04c00106ba7..a46d3c2d663 100644 --- a/linera-client/src/client_context.rs +++ b/linera-client/src/client_context.rs @@ -23,9 +23,17 @@ use linera_rpc::node_provider::{NodeOptions, NodeProvider}; use linera_storage::Storage; use thiserror_context::Context; use tracing::{debug, info}; +#[cfg(feature = "fs")] +use { + linera_base::{ + crypto::CryptoHash, + data_types::{BlobBytes, Bytecode}, + identifiers::BytecodeId, + }, + std::{fs, path::PathBuf}, +}; #[cfg(feature = "benchmark")] use { - futures::{stream, StreamExt as _, TryStreamExt as _}, linera_base::{ crypto::PublicKey, data_types::Amount, @@ -49,15 +57,6 @@ use { }, tracing::{error, trace}, }; -#[cfg(feature = "fs")] -use { - linera_base::{ - crypto::CryptoHash, - data_types::{BlobBytes, Bytecode}, - identifiers::BytecodeId, - }, - std::{fs, path::PathBuf}, -}; #[cfg(web)] use crate::persistent::{LocalPersist as Persist, LocalPersistExt as _}; @@ -599,7 +598,6 @@ where &mut self, key_pairs: &HashMap, application_id: ApplicationId, - max_in_flight: usize, ) -> Result<(), Error> { let default_chain_id = self .wallet @@ -635,40 +633,6 @@ where .expect("should execute block with OpenChain operations"); } self.update_wallet_from_client(&chain_client).await?; - // Make sure all chains have registered the application now. - let futures = key_pairs - .keys() - .map(|&chain_id| { - let chain_client = self.make_chain_client(chain_id); - async move { - for i in 0..5 { - linera_base::time::timer::sleep(Duration::from_secs(i)).await; - chain_client.process_inbox().await?; - let chain_state = chain_client.chain_state_view().await?; - if chain_state - .execution_state - .system - .registry - .known_applications - .contains_key(&application_id) - .await? - { - return Ok::<_, Error>(chain_client); - } - } - panic!("Could not instantiate application on chain {chain_id:?}"); - } - }) - .collect::>(); - // We have to collect the futures to avoid a higher-ranked lifetime error: - // https://github.com/rust-lang/rust/issues/102211#issuecomment-1673201352 - let clients = stream::iter(futures) - .buffer_unordered(max_in_flight) - .try_collect::>() - .await?; - for client in clients { - self.update_wallet_from_client(&client).await?; - } Ok(()) } diff --git a/linera-client/src/client_options.rs b/linera-client/src/client_options.rs index b4cee395945..8b758c3f275 100644 --- a/linera-client/src/client_options.rs +++ b/linera-client/src/client_options.rs @@ -792,21 +792,6 @@ pub enum ClientCommand { required_application_ids: Option>, }, - /// Request an application from another chain, so it can be used on this one. - RequestApplication { - /// The ID of the application to request. - application_id: UserApplicationId, - - /// The target chain on which the application is already registered. - /// If not specified, the chain on which the application was created is used. - #[arg(long)] - target_chain_id: Option, - - /// The owned chain on which the application is missing. - #[arg(long)] - requester_chain_id: Option, - }, - /// Create an unassigned key-pair. Keygen, diff --git a/linera-core/src/chain_worker/actor.rs b/linera-core/src/chain_worker/actor.rs index f9ddd6085b9..c62a20084e4 100644 --- a/linera-core/src/chain_worker/actor.rs +++ b/linera-core/src/chain_worker/actor.rs @@ -11,8 +11,8 @@ use std::{ use linera_base::{ crypto::CryptoHash, - data_types::{Blob, BlockHeight, Timestamp, UserApplicationDescription}, - identifiers::{BlobId, ChainId, UserApplicationId}, + data_types::{Blob, BlockHeight, Timestamp}, + identifiers::{BlobId, ChainId}, }; use linera_chain::{ data_types::{ @@ -69,12 +69,6 @@ where callback: oneshot::Sender>, }, - /// Describe an application. - DescribeApplication { - application_id: UserApplicationId, - callback: oneshot::Sender>, - }, - /// Execute a block but discard any changes to the chain state. StageBlockExecution { block: Block, @@ -247,12 +241,6 @@ where ChainWorkerRequest::QueryApplication { query, callback } => callback .send(self.worker.query_application(query).await) .is_ok(), - ChainWorkerRequest::DescribeApplication { - application_id, - callback, - } => callback - .send(self.worker.describe_application(application_id).await) - .is_ok(), ChainWorkerRequest::StageBlockExecution { block, callback } => callback .send(self.worker.stage_block_execution(block).await) .is_ok(), @@ -365,13 +353,6 @@ where .debug_struct("ChainWorkerRequest::QueryApplication") .field("query", &query) .finish_non_exhaustive(), - ChainWorkerRequest::DescribeApplication { - application_id, - callback: _callback, - } => formatter - .debug_struct("ChainWorkerRequest::DescribeApplication") - .field("application_id", &application_id) - .finish_non_exhaustive(), ChainWorkerRequest::StageBlockExecution { block, callback: _callback, diff --git a/linera-core/src/chain_worker/state/attempted_changes.rs b/linera-core/src/chain_worker/state/attempted_changes.rs index 0d98e39502d..824003346e1 100644 --- a/linera-core/src/chain_worker/state/attempted_changes.rs +++ b/linera-core/src/chain_worker/state/attempted_changes.rs @@ -321,10 +321,15 @@ where // Execute the block and update inboxes. self.state.chain.remove_bundles_from_inboxes(block).await?; let local_time = self.state.storage.clock().current_time(); + let pending_applications = self + .state + .get_pending_application_descriptions(block.published_application_ids()) + .await?; let verified_outcome = Box::pin(self.state.chain.execute_block( block, local_time, Some(executed_block.outcome.oracle_responses.clone()), + pending_applications, )) .await?; // We should always agree on the messages and state hash. diff --git a/linera-core/src/chain_worker/state/mod.rs b/linera-core/src/chain_worker/state/mod.rs index c74fe5c6b48..a1c9739af3d 100644 --- a/linera-core/src/chain_worker/state/mod.rs +++ b/linera-core/src/chain_worker/state/mod.rs @@ -16,7 +16,7 @@ use linera_base::{ crypto::CryptoHash, data_types::{Blob, BlockHeight, UserApplicationDescription}, ensure, - identifiers::{BlobId, ChainId, UserApplicationId}, + identifiers::{ApplicationId, BlobId, BlobType, ChainId}, }; use linera_chain::{ data_types::{ @@ -162,17 +162,6 @@ where .await } - /// Returns an application's description. - pub(super) async fn describe_application( - &mut self, - application_id: UserApplicationId, - ) -> Result { - ChainWorkerStateWithTemporaryChanges::new(self) - .await - .describe_application(application_id) - .await - } - /// Executes a block without persisting any changes to the state. pub(super) async fn stage_block_execution( &mut self, @@ -345,20 +334,60 @@ where } /// Returns the blobs requested by their `blob_ids` that are either in pending in the - /// chain or in the `recent_blobs` cache. + /// chain, in the `recent_blobs` cache or in storage. async fn get_blobs(&self, blob_ids: HashSet) -> Result, WorkerError> { let pending_blobs = &self.chain.manager.get().pending_blobs; let (found_blobs, not_found_blobs): (HashMap, HashSet) = self.recent_blobs.try_get_many(blob_ids).await; let mut blobs = found_blobs.into_values().collect::>(); + let mut missing_blobs = Vec::new(); for blob_id in not_found_blobs { if let Some(blob) = pending_blobs.get(&blob_id) { blobs.push(blob.clone()); + } else if let Ok(blob) = self.storage.read_blob(blob_id).await { + blobs.push(blob); + } else { + missing_blobs.push(blob_id); } } - Ok(blobs) + if missing_blobs.is_empty() { + Ok(blobs) + } else { + Err(WorkerError::BlobsNotFound(missing_blobs)) + } + } + + async fn get_pending_application_descriptions( + &self, + application_ids: HashSet, + ) -> Result, WorkerError> { + let blob_id_to_application_id = application_ids + .into_iter() + .map(|application_id| { + ( + BlobId::new( + application_id.application_description_hash, + BlobType::ApplicationDescription, + ), + application_id, + ) + }) + .collect::>(); + + Ok(self + .get_blobs(blob_id_to_application_id.keys().copied().collect()) + .await? + .into_iter() + .map(|blob| { + ( + blob_id_to_application_id[&blob.id()], + blob.into_inner_application_description() + .expect("Should be an application description blob!"), + ) + }) + .collect()) } /// Inserts a [`Blob`] into the worker's cache. diff --git a/linera-core/src/chain_worker/state/temporary_changes.rs b/linera-core/src/chain_worker/state/temporary_changes.rs index 0c93425ff13..f9442088783 100644 --- a/linera-core/src/chain_worker/state/temporary_changes.rs +++ b/linera-core/src/chain_worker/state/temporary_changes.rs @@ -6,9 +6,9 @@ use std::borrow::Cow; use linera_base::{ - data_types::{ArithmeticError, Timestamp, UserApplicationDescription}, + data_types::{ArithmeticError, Timestamp}, ensure, - identifiers::{GenericApplicationId, UserApplicationId}, + identifiers::GenericApplicationId, }; use linera_chain::{ data_types::{ @@ -107,16 +107,6 @@ where Ok(response) } - /// Returns an application's description. - pub(super) async fn describe_application( - &mut self, - application_id: UserApplicationId, - ) -> Result { - self.0.ensure_is_active()?; - let response = self.0.chain.describe_application(application_id).await?; - Ok(response) - } - /// Executes a block without persisting any changes to the state. pub(super) async fn stage_block_execution( &mut self, @@ -125,9 +115,18 @@ where let local_time = self.0.storage.clock().current_time(); let signer = block.authenticated_signer; - let executed_block = Box::pin(self.0.chain.execute_block(&block, local_time, None)) - .await? - .with(block); + let pending_applications = self + .0 + .get_pending_application_descriptions(block.published_application_ids()) + .await?; + let executed_block = Box::pin(self.0.chain.execute_block( + &block, + local_time, + None, + pending_applications, + )) + .await? + .with(block); let mut response = ChainInfoResponse::new(&self.0.chain, None); if let Some(signer) = signer { @@ -221,10 +220,15 @@ where ); self.0.storage.clock().sleep_until(block.timestamp).await; let local_time = self.0.storage.clock().current_time(); + let pending_applications = self + .0 + .get_pending_application_descriptions(block.published_application_ids()) + .await?; let outcome = Box::pin(self.0.chain.execute_block( block, local_time, forced_oracle_responses.clone(), + pending_applications, )) .await?; if let Some(lite_certificate) = &validated_block_certificate { diff --git a/linera-core/src/client/mod.rs b/linera-core/src/client/mod.rs index 3fe0f7cc43f..0b28891cf49 100644 --- a/linera-core/src/client/mod.rs +++ b/linera-core/src/client/mod.rs @@ -17,7 +17,7 @@ use dashmap::{ DashMap, }; use futures::{ - future::{self, FusedFuture, Future}, + future::{self, try_join_all, FusedFuture, Future}, stream::{self, AbortHandle, FusedStream, FuturesUnordered, StreamExt}, }; #[cfg(not(target_arch = "wasm32"))] @@ -29,11 +29,11 @@ use linera_base::{ crypto::{CryptoHash, KeyPair, PublicKey}, data_types::{ Amount, ApplicationPermissions, ArithmeticError, Blob, BlockHeight, Round, Timestamp, + UserApplicationDescription, }, ensure, identifiers::{ - Account, ApplicationId, BlobId, BlobType, BytecodeId, ChainId, MessageId, Owner, - UserApplicationId, + Account, BlobId, BlobType, BytecodeId, ChainId, MessageId, Owner, UserApplicationId, }, ownership::{ChainOwnership, TimeoutConfig}, }; @@ -49,7 +49,7 @@ use linera_execution::{ committee::{Committee, Epoch, ValidatorName}, system::{ AdminOperation, OpenChainConfig, Recipient, SystemChannel, SystemOperation, - CREATE_APPLICATION_MESSAGE_INDEX, OPEN_CHAIN_MESSAGE_INDEX, + OPEN_CHAIN_MESSAGE_INDEX, }, ExecutionError, Message, Operation, Query, Response, SystemExecutionError, SystemMessage, SystemQuery, SystemResponse, @@ -481,6 +481,9 @@ pub enum ChainClientError { #[error(transparent)] CommunicationError(#[from] CommunicationError), + #[error(transparent)] + BcsError(#[from] bcs::Error), + #[error("Internal error within chain client: {0}")] InternalError(&'static str), @@ -514,8 +517,8 @@ pub enum ChainClientError { #[error(transparent)] ViewError(#[from] ViewError), - #[error("Blob not found: {0}")] - BlobNotFound(BlobId), + #[error("Blobs not found: {0:?}")] + BlobsNotFound(Vec), } impl From for ChainClientError { @@ -1507,21 +1510,12 @@ where .handle_block_proposal(*proposal.clone()) .await { - if let LocalNodeError::WorkerError(WorkerError::ChainError(chain_error)) = - &original_err - { - if let ChainError::ExecutionError( - ExecutionError::SystemError(SystemExecutionError::BlobNotFoundOnRead( - blob_id, - )), - _, - ) = &**chain_error - { - self.update_local_node_with_blob_from(*blob_id, remote_node) - .await?; - continue; // We found the missing blob: retry. - } + if let Some(blob_ids) = original_err.get_blobs_not_found() { + self.update_local_node_with_blobs_from(blob_ids, remote_node) + .await?; + continue; // We found the missing blobs: retry. } + warn!( "Skipping proposal from {} and validator {}: {}", owner, remote_node.name, original_err @@ -1557,40 +1551,77 @@ where /// Downloads and processes from the specified validator a confirmed block certificate that /// uses the given blob. If this succeeds, the blob will be in our storage. - async fn update_local_node_with_blob_from( + async fn update_local_node_with_blobs_from( &self, - blob_id: BlobId, + blob_ids: Vec, remote_node: &RemoteNode, ) -> Result<(), ChainClientError> { - let certificate = remote_node.download_certificate_for_blob(blob_id).await?; - // This will download all ancestors of the certificate and process all of them locally. - self.receive_certificate(certificate).await?; + try_join_all(blob_ids.into_iter().map(|blob_id| async move { + let certificate = remote_node.download_certificate_for_blob(blob_id).await?; + // This will download all ancestors of the certificate and process all of them locally. + self.receive_certificate(certificate).await + })) + .await?; + Ok(()) } /// Downloads and processes a confirmed block certificate that uses the given blob. /// If this succeeds, the blob will be in our storage. - async fn receive_certificate_for_blob(&self, blob_id: BlobId) -> Result<(), ChainClientError> { + pub async fn receive_certificate_for_blob( + &self, + blob_id: BlobId, + ) -> Result<(), ChainClientError> { + self.receive_certificates_for_blobs(vec![blob_id]).await + } + + /// Downloads and processes confirmed block certificates that use the given blobs. + /// If this succeeds, the blobs will be in our storage. + pub async fn receive_certificates_for_blobs( + &self, + blob_ids: Vec, + ) -> Result<(), ChainClientError> { let validators = self.validator_nodes().await?; - let mut tasks = FuturesUnordered::new(); - for remote_node in validators { - let remote_node = remote_node.clone(); - tasks.push(async move { - let cert = remote_node - .download_certificate_for_blob(blob_id) - .await - .ok()?; - self.receive_certificate(cert).await.ok() - }); + let mut tasks = BTreeMap::new(); + + for blob_id in blob_ids { + if tasks.contains_key(&blob_id) { + continue; + } + + tasks.insert(blob_id, FuturesUnordered::new()); + for remote_node in validators.clone() { + let remote_node = remote_node.clone(); + tasks[&blob_id].push(async move { + let cert = remote_node + .download_certificate_for_blob(blob_id) + .await + .ok()?; + self.receive_certificate(cert).await.ok() + }); + } } - while let Some(result) = tasks.next().await { - if result.is_some() { - return Ok(()); + let mut missing_blobs = Vec::new(); + for (blob_id, mut blob_id_tasks) in tasks { + let mut found_blob = false; + while let Some(result) = blob_id_tasks.next().await { + if result.is_some() { + found_blob = true; + break; + } + } + + if !found_blob { + missing_blobs.push(blob_id); } } - Err(ChainClientError::BlobNotFound(blob_id)) + if missing_blobs.is_empty() { + Ok(()) + } else { + Err(ChainClientError::BlobsNotFound(missing_blobs)) + } } /// Attempts to execute the block locally. If any incoming message execution fails, that @@ -1648,14 +1679,9 @@ where .local_node .stage_block_execution(block.clone()) .await; - if let Err(LocalNodeError::WorkerError(WorkerError::ChainError(chain_error))) = &result - { - if let ChainError::ExecutionError( - ExecutionError::SystemError(SystemExecutionError::BlobNotFoundOnRead(blob_id)), - _, - ) = &**chain_error - { - self.receive_certificate_for_blob(*blob_id).await?; + if let Err(err) = &result { + if let Some(blob_ids) = err.get_blobs_not_found() { + self.receive_certificates_for_blobs(blob_ids).await?; continue; // We found the missing blob: retry. } } @@ -1961,12 +1987,20 @@ where /// Queries an application. #[tracing::instrument(level = "trace", skip(query))] pub async fn query_application(&self, query: Query) -> Result { - let response = self - .client - .local_node - .query_application(self.chain_id, query) - .await?; - Ok(response) + loop { + let result = self + .client + .local_node + .query_application(self.chain_id, query.clone()) + .await; + if let Err(err) = &result { + if let Some(blob_ids) = err.get_blobs_not_found() { + self.receive_certificates_for_blobs(blob_ids).await?; + continue; // We found the missing blob: retry. + } + } + return Ok(result?); + } } /// Queries a system application. @@ -1975,11 +2009,7 @@ where &self, query: SystemQuery, ) -> Result { - let response = self - .client - .local_node - .query_application(self.chain_id, Query::System(query)) - .await?; + let response = self.query_application(Query::System(query)).await?; match response { Response::System(response) => Ok(response), _ => Err(ChainClientError::InternalError( @@ -1996,11 +2026,7 @@ where query: &A::Query, ) -> Result { let query = Query::user(application_id, query)?; - let response = self - .client - .local_node - .query_application(self.chain_id, query) - .await?; + let response = self.query_application(query).await?; match response { Response::User(response) => Ok(serde_json::from_slice(&response)?), _ => Err(ChainClientError::InternalError( @@ -2148,22 +2174,6 @@ where Ok(()) } - /// Requests a `RegisterApplications` message from another chain so the application can be used - /// on this one. - #[tracing::instrument(level = "trace")] - pub async fn request_application( - &self, - application_id: UserApplicationId, - chain_id: Option, - ) -> Result, ChainClientError> { - let chain_id = chain_id.unwrap_or(application_id.creation.chain_id); - self.execute_operation(Operation::System(SystemOperation::RequestApplication { - application_id, - chain_id, - })) - .await - } - /// Sends tokens to a chain. #[tracing::instrument(level = "trace")] pub async fn transfer_to_account( @@ -2501,11 +2511,12 @@ where tokio::task::spawn_blocking(move || (contract.compress(), service.compress())) .await .expect("Compression should not panic"); - let contract_blob = Blob::new_contract_bytecode(compressed_contract); - let service_blob = Blob::new_service_bytecode(compressed_service); + let contract_blob = Blob::new_contract_bytecode(compressed_contract)?; + let service_blob = Blob::new_service_bytecode(compressed_service)?; let bytecode_id = BytecodeId::new(contract_blob.id().hash, service_blob.id().hash); - self.add_pending_blobs([contract_blob, service_blob]).await; + self.add_pending_blobs(vec![contract_blob, service_blob]) + .await; self.execute_operation(Operation::System(SystemOperation::PublishBytecode { bytecode_id, })) @@ -2519,9 +2530,12 @@ where &self, bytes: Vec>, ) -> Result, ChainClientError> { - let blobs = bytes.into_iter().map(Blob::new_data); + let mut blobs = Vec::new(); + for byte_vec in bytes { + blobs.push(Blob::new_data(byte_vec)?); + } let publish_blob_operations = blobs - .clone() + .iter() .map(|blob| { Operation::System(SystemOperation::PublishDataBlob { blob_hash: blob.id().hash, @@ -2542,7 +2556,7 @@ where } /// Adds pending blobs - pub async fn add_pending_blobs(&self, pending_blobs: impl IntoIterator) { + pub async fn add_pending_blobs(&self, pending_blobs: Vec) { for blob in pending_blobs { self.client.local_node.cache_recent_blob(&blob).await; self.state_mut().insert_pending_blob(blob); @@ -2596,28 +2610,28 @@ where instantiation_argument: Vec, required_application_ids: Vec, ) -> Result, ChainClientError> { - self.execute_operation(Operation::System(SystemOperation::CreateApplication { + let application_description = UserApplicationDescription { bytecode_id, + creator_chain_id: self.chain_id, + block_height: self.next_block_height(), + operation_index: 0, parameters, + required_application_ids: required_application_ids.clone(), + }; + let application_id = UserApplicationId::try_from(&application_description)?; + let application_blob = Blob::new_application_description(application_description.clone())?; + + self.add_pending_blobs(vec![application_blob]).await; + self.execute_operation(Operation::System(SystemOperation::CreateApplication { + application_id, + creator_chain_id: application_description.creator_chain_id, + block_height: application_description.block_height, + operation_index: application_description.operation_index, instantiation_argument, required_application_ids, })) .await? - .try_map(|certificate| { - // The first message of the only operation created the application. - let creation = certificate - .value() - .executed_block() - .and_then(|executed_block| { - executed_block.message_id_for_operation(0, CREATE_APPLICATION_MESSAGE_INDEX) - }) - .ok_or_else(|| ChainClientError::InternalError("Failed to create application"))?; - let id = ApplicationId { - creation, - bytecode_id, - }; - Ok((id, certificate)) - }) + .try_map(|certificate| Ok((application_id, certificate))) } /// Creates a new committee and starts using it (admin chains only). diff --git a/linera-core/src/data_types.rs b/linera-core/src/data_types.rs index 19ba8364e37..5c108df4ed3 100644 --- a/linera-core/src/data_types.rs +++ b/linera-core/src/data_types.rs @@ -313,7 +313,7 @@ impl ClientOutcome { } } - #[expect(clippy::result_large_err)] + #[allow(clippy::result_large_err)] pub fn try_map(self, f: F) -> Result, ChainClientError> where F: FnOnce(T) -> Result, diff --git a/linera-core/src/local_node.rs b/linera-core/src/local_node.rs index ad8fd80cfcd..8c7aa19de22 100644 --- a/linera-core/src/local_node.rs +++ b/linera-core/src/local_node.rs @@ -11,17 +11,19 @@ use std::{ use futures::future; use linera_base::{ - data_types::{ArithmeticError, Blob, BlockHeight, UserApplicationDescription}, + data_types::{ArithmeticError, Blob, BlockHeight}, ensure, - identifiers::{BlobId, ChainId, MessageId, UserApplicationId}, + identifiers::{BlobId, ChainId, MessageId}, }; use linera_chain::{ data_types::{ Block, BlockProposal, Certificate, CertificateValue, ExecutedBlock, LiteCertificate, }, - ChainStateView, + ChainError, ChainStateView, +}; +use linera_execution::{ + committee::ValidatorName, ExecutionError, Query, Response, SystemExecutionError, }; -use linera_execution::{committee::ValidatorName, Query, Response}; use linera_storage::Storage; use linera_views::views::ViewError; use rand::prelude::SliceRandom; @@ -87,6 +89,36 @@ pub enum LocalNodeError { NodeError(#[from] NodeError), } +impl LocalNodeError { + pub fn get_blobs_not_found(&self) -> Option> { + match self { + LocalNodeError::WorkerError(WorkerError::ChainError(chain_error)) => { + match **chain_error { + ChainError::ExecutionError( + ExecutionError::SystemError(SystemExecutionError::BlobNotFoundOnRead( + blob_id, + )), + _, + ) + | ChainError::ExecutionError( + ExecutionError::ViewError(ViewError::BlobNotFoundOnRead(blob_id)), + _, + ) => Some(vec![blob_id]), + _ => None, + } + } + LocalNodeError::WorkerError(WorkerError::BlobsNotFound(blob_ids)) => { + Some(blob_ids.clone()) + } + LocalNodeError::NodeError(NodeError::BlobNotFoundOnRead(blob_id)) => { + Some(vec![*blob_id]) + } + LocalNodeError::NodeError(NodeError::BlobsNotFound(blob_ids)) => Some(blob_ids.clone()), + _ => None, + } + } +} + impl LocalNodeClient where S: Storage + Clone + Send + Sync + 'static, @@ -274,13 +306,17 @@ where .await; result = match &result { - Err(LocalNodeError::WorkerError(WorkerError::BlobsNotFound(blob_ids))) => { - let blobs = remote_node.try_download_blobs(blob_ids).await; - if blobs.len() != blob_ids.len() { - result + Err(err) => { + if let Some(blob_ids) = err.get_blobs_not_found() { + let blobs = remote_node.try_download_blobs(blob_ids.as_slice()).await; + if blobs.len() != blob_ids.len() { + result + } else { + self.handle_certificate(certificate, blobs, notifications) + .await + } } else { - self.handle_certificate(certificate, blobs, notifications) - .await + result } } _ => result, @@ -331,20 +367,6 @@ where Ok(response) } - #[tracing::instrument(level = "trace", skip(self))] - pub async fn describe_application( - &self, - chain_id: ChainId, - application_id: UserApplicationId, - ) -> Result { - let response = self - .node - .state - .describe_application(chain_id, application_id) - .await?; - Ok(response) - } - #[tracing::instrument(level = "trace", skip(self))] pub async fn recent_blob(&self, blob_id: &BlobId) -> Option { self.node.state.recent_blob(blob_id).await diff --git a/linera-core/src/node.rs b/linera-core/src/node.rs index 461078dfa16..abb0c28ac3c 100644 --- a/linera-core/src/node.rs +++ b/linera-core/src/node.rs @@ -98,7 +98,7 @@ pub trait ValidatorNode { /// Turn an address into a validator node. #[cfg_attr(not(web), trait_variant::make(Send))] -#[expect(clippy::result_large_err)] +#[allow(clippy::result_large_err)] pub trait ValidatorNodeProvider { #[cfg(not(web))] type Node: ValidatorNode + Send + Sync + Clone + 'static; @@ -279,6 +279,10 @@ impl From for NodeError { ChainError::ExecutionError( ExecutionError::SystemError(SystemExecutionError::BlobNotFoundOnRead(blob_id)), _, + ) + | ChainError::ExecutionError( + ExecutionError::ViewError(ViewError::BlobNotFoundOnRead(blob_id)), + _, ) => Self::BlobNotFoundOnRead(blob_id), error => Self::ChainError { error: error.to_string(), diff --git a/linera-core/src/unit_tests/client_tests.rs b/linera-core/src/unit_tests/client_tests.rs index 28a1c88fa3a..afef3ce509f 100644 --- a/linera-core/src/unit_tests/client_tests.rs +++ b/linera-core/src/unit_tests/client_tests.rs @@ -1420,7 +1420,7 @@ where .await; assert_matches!( result, - Err(ChainClientError::BlobNotFound(not_found_blob_id)) if not_found_blob_id == blob0_id + Err(ChainClientError::BlobsNotFound(not_found_blob_ids)) if not_found_blob_ids.contains(&blob0_id) ); // Take one validator down @@ -1462,10 +1462,10 @@ where .await; client2_a.synchronize_from_validators().await.unwrap(); - let blob1 = Blob::new_data(b"blob1".to_vec()); + let blob1 = Blob::new_data(b"blob1".to_vec())?; let blob1_hash = blob1.id().hash; - client2_a.add_pending_blobs([blob1]).await; + client2_a.add_pending_blobs(vec![blob1]).await; let blob_0_1_operations = vec![ Operation::System(SystemOperation::ReadBlob { blob_id: blob0_id }), Operation::System(SystemOperation::PublishDataBlob { @@ -1606,10 +1606,10 @@ where .await; client2_a.synchronize_from_validators().await.unwrap(); - let blob1 = Blob::new_data(b"blob1".to_vec()); + let blob1 = Blob::new_data(b"blob1".to_vec())?; let blob1_hash = blob1.id().hash; - client2_a.add_pending_blobs([blob1]).await; + client2_a.add_pending_blobs(vec![blob1]).await; let blob_0_1_operations = vec![ Operation::System(SystemOperation::ReadBlob { blob_id: blob0_id }), Operation::System(SystemOperation::PublishDataBlob { @@ -1783,10 +1783,10 @@ where .await; client3_a.synchronize_from_validators().await.unwrap(); - let blob1 = Blob::new_data(b"blob1".to_vec()); + let blob1 = Blob::new_data(b"blob1".to_vec())?; let blob1_hash = blob1.id().hash; - client3_a.add_pending_blobs([blob1]).await; + client3_a.add_pending_blobs(vec![blob1]).await; let blob_0_1_operations = vec![ Operation::System(SystemOperation::ReadBlob { blob_id: blob0_id }), Operation::System(SystemOperation::PublishDataBlob { @@ -1858,10 +1858,10 @@ where .await; client3_b.synchronize_from_validators().await.unwrap(); - let blob3 = Blob::new_data(b"blob3".to_vec()); + let blob3 = Blob::new_data(b"blob3".to_vec())?; let blob3_hash = blob3.id().hash; - client3_b.add_pending_blobs([blob3]).await; + client3_b.add_pending_blobs(vec![blob3]).await; let blob_2_3_operations = vec![ Operation::System(SystemOperation::ReadBlob { blob_id: blob2_id }), Operation::System(SystemOperation::PublishDataBlob { diff --git a/linera-core/src/unit_tests/value_cache_tests.rs b/linera-core/src/unit_tests/value_cache_tests.rs index 52b018c4b20..a36cf855f3a 100644 --- a/linera-core/src/unit_tests/value_cache_tests.rs +++ b/linera-core/src/unit_tests/value_cache_tests.rs @@ -528,7 +528,7 @@ fn create_dummy_certificate_value(height: impl Into) -> HashedCerti /// Creates a new dummy data [`Blob`] to use in the tests. fn create_dummy_blob(id: usize) -> Blob { - Blob::new_data(format!("test{}", id).as_bytes().to_vec()) + Blob::new_data(format!("test{}", id).as_bytes().to_vec()).unwrap() } /// Creates a dummy [`HashedCertificateValue::ValidatedBlock`] to use in the tests. diff --git a/linera-core/src/unit_tests/wasm_client_tests.rs b/linera-core/src/unit_tests/wasm_client_tests.rs index e69b8d083c9..d4aed02f185 100644 --- a/linera-core/src/unit_tests/wasm_client_tests.rs +++ b/linera-core/src/unit_tests/wasm_client_tests.rs @@ -19,14 +19,14 @@ use assert_matches::assert_matches; use async_graphql::Request; use counter::CounterAbi; use linera_base::{ - data_types::{Amount, Bytecode, OracleResponse, UserApplicationDescription}, + data_types::{Amount, Bytecode, OracleResponse}, identifiers::{ - AccountOwner, ApplicationId, ChainDescription, ChainId, Destination, Owner, StreamId, + AccountOwner, ApplicationId, BlobId, BlobType, ChainDescription, ChainId, Owner, StreamId, StreamName, }, ownership::{ChainOwnership, TimeoutConfig}, }; -use linera_chain::data_types::{CertificateValue, EventRecord, MessageAction, OutgoingMessage}; +use linera_chain::data_types::{CertificateValue, EventRecord, MessageAction}; use linera_execution::{ Message, MessageKind, Operation, ResourceControlPolicy, SystemMessage, WasmRuntime, }; @@ -296,28 +296,20 @@ where .unwrap() .unwrap(); let (application_id2, certificate) = creator - .create_application( - bytecode_id2, - &application_id1, - &(), - vec![application_id1.forget_abi()], - ) + .create_application(bytecode_id2, &application_id1, &(), vec![]) .await .unwrap() .unwrap(); assert_eq!( certificate.value().executed_block().unwrap().outcome.events, - vec![ - Vec::new(), - vec![EventRecord { - stream_id: StreamId { - application_id: application_id2.forget_abi().into(), - stream_name: StreamName(b"announcements".to_vec()), - }, - key: b"updates".to_vec(), - value: b"instantiated".to_vec(), - }] - ] + vec![vec![EventRecord { + stream_id: StreamId { + application_id: application_id2.forget_abi().into(), + stream_name: StreamName(b"announcements".to_vec()), + }, + key: b"updates".to_vec(), + value: b"instantiated".to_vec(), + }]] ); let query_service = cfg!(feature = "unstable-oracles"); @@ -334,8 +326,25 @@ where panic!("Unexpected oracle responses: {:?}", responses); }; if cfg!(feature = "unstable-oracles") { - let [OracleResponse::Service(json)] = &responses[..] else { - assert_eq!(&responses[..], &[]); + assert_eq!(responses.len(), 4); + assert_eq!( + responses[0..3], + [ + OracleResponse::Blob(BlobId::new( + application_id2.bytecode_id.contract_blob_hash, + BlobType::ContractBytecode + )), + OracleResponse::Blob(BlobId::new( + application_id2.bytecode_id.service_blob_hash, + BlobType::ServiceBytecode + )), + OracleResponse::Blob(BlobId::new( + application_id2.application_description_hash, + BlobType::ApplicationDescription + )), + ] + ); + let OracleResponse::Service(json) = &responses[3] else { panic!("Unexpected oracle responses: {:?}", responses); }; let response_json = serde_json::from_slice::(json).unwrap(); @@ -409,10 +418,6 @@ where assert_eq!(incoming_bundles[0].action, MessageAction::Reject); assert_eq!( incoming_bundles[0].bundle.messages[0].kind, - MessageKind::Simple - ); - assert_eq!( - incoming_bundles[0].bundle.messages[1].kind, MessageKind::Tracked ); let messages = cert.value().messages().unwrap(); @@ -441,17 +446,127 @@ where // Second message is the bounced message. assert_eq!(incoming_bundles[1].action, MessageAction::Accept); assert_eq!( - incoming_bundles[1].bundle.messages[1].kind, + incoming_bundles[1].bundle.messages[0].kind, MessageKind::Bouncing ); assert_matches!( - incoming_bundles[1].bundle.messages[1].message, + incoming_bundles[1].bundle.messages[0].message, Message::User { .. } ); Ok(()) } +#[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] +#[cfg_attr(feature = "wasmtime", test_case(WasmRuntime::Wasmtime ; "wasmtime"))] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn test_memory_meta_counter_with_blobs(wasm_runtime: WasmRuntime) -> anyhow::Result<()> { + run_test_meta_counter_with_blobs(MemoryStorageBuilder::with_wasm_runtime(wasm_runtime)).await +} + +#[ignore] +#[cfg(feature = "storage-service")] +#[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] +#[cfg_attr(feature = "wasmtime", test_case(WasmRuntime::Wasmtime ; "wasmtime"))] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn test_service_meta_counter_with_blobs(wasm_runtime: WasmRuntime) -> anyhow::Result<()> { + run_test_meta_counter_with_blobs(ServiceStorageBuilder::with_wasm_runtime(wasm_runtime).await) + .await +} + +#[ignore] +#[cfg(feature = "rocksdb")] +#[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] +#[cfg_attr(feature = "wasmtime", test_case(WasmRuntime::Wasmtime ; "wasmtime"))] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn test_rocks_db_meta_counter_with_blobs(wasm_runtime: WasmRuntime) -> anyhow::Result<()> { + run_test_meta_counter_with_blobs(RocksDbStorageBuilder::with_wasm_runtime(wasm_runtime).await) + .await +} + +#[ignore] +#[cfg(feature = "dynamodb")] +#[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] +#[cfg_attr(feature = "wasmtime", test_case(WasmRuntime::Wasmtime ; "wasmtime"))] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn test_dynamo_db_meta_counter_with_blobs(wasm_runtime: WasmRuntime) -> anyhow::Result<()> { + run_test_meta_counter_with_blobs(DynamoDbStorageBuilder::with_wasm_runtime(wasm_runtime)).await +} + +#[ignore] +#[cfg(feature = "scylladb")] +#[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] +#[cfg_attr(feature = "wasmtime", test_case(WasmRuntime::Wasmtime ; "wasmtime"))] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn test_scylla_db_meta_counter_with_blobs(wasm_runtime: WasmRuntime) -> anyhow::Result<()> { + run_test_meta_counter_with_blobs(ScyllaDbStorageBuilder::with_wasm_runtime(wasm_runtime)).await +} + +async fn run_test_meta_counter_with_blobs(storage_builder: B) -> anyhow::Result<()> +where + B: StorageBuilder, +{ + let mut builder = TestBuilder::new(storage_builder, 4, 1) + .await? + .with_policy(ResourceControlPolicy::all_categories()); + let client0 = builder + .add_initial_chain(ChainDescription::Root(0), Amount::from_tokens(3)) + .await?; + let client1 = builder + .add_initial_chain(ChainDescription::Root(1), Amount::ONE) + .await?; + + let (counter_bytecode_id, _counter_bytecode_cert) = { + let (contract_path, service_path) = + linera_execution::wasm_test::get_example_bytecode_paths("counter")?; + client0 + .publish_bytecode( + Bytecode::load_from_file(contract_path).await?, + Bytecode::load_from_file(service_path).await?, + ) + .await + .unwrap() + .unwrap() + }; + let counter_bytecode_id = counter_bytecode_id.with_abi::(); + + let (counter_application_id, _) = client0 + .create_application(counter_bytecode_id, &(), &0, vec![]) + .await + .unwrap() + .unwrap(); + + let (meta_counter_bytecode_id, _meta_counter_cert) = { + let (contract_path, service_path) = + linera_execution::wasm_test::get_example_bytecode_paths("meta_counter")?; + client1 + .publish_bytecode( + Bytecode::load_from_file(contract_path).await?, + Bytecode::load_from_file(service_path).await?, + ) + .await + .unwrap() + .unwrap() + }; + let meta_counter_bytecode_id = meta_counter_bytecode_id + .with_abi::, ()>(); + + client1 + .create_application( + meta_counter_bytecode_id, + &counter_application_id, + &(), + vec![], + ) + .await + .unwrap() + .unwrap(); + + assert_eq!(client1.process_inbox().await?.0.len(), 1); + + Ok(()) +} + #[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] #[cfg_attr(feature = "wasmtime", test_case(WasmRuntime::Wasmtime ; "wasmtime"))] #[test_log::test(tokio::test)] @@ -559,23 +674,6 @@ where .unwrap() .unwrap(); - let messages = cert.value().messages().unwrap(); - { - let OutgoingMessage { - destination, - message, - .. - } = &messages[1][0]; - assert_matches!( - message, Message::System(SystemMessage::RegisterApplications { applications }) - if applications.len() == 1 && matches!( - applications[0], UserApplicationDescription{ bytecode_id: b_id, .. } - if b_id == bytecode_id.forget_abi() - ), - "Unexpected message" - ); - assert_eq!(*destination, Destination::Recipient(receiver.chain_id())); - } receiver.synchronize_from_validators().await.unwrap(); receiver .receive_certificate_and_update_validators(cert) @@ -589,11 +687,6 @@ where } _ => panic!("Unexpected value"), }; - assert!(messages.iter().any(|msg| matches!( - &msg.bundle.messages[0].message, - Message::System(SystemMessage::RegisterApplications { applications }) - if applications.iter().any(|app| app.bytecode_id == bytecode_id.forget_abi()) - ))); assert!(messages .iter() .flat_map(|msg| &msg.bundle.messages) diff --git a/linera-core/src/unit_tests/wasm_worker_tests.rs b/linera-core/src/unit_tests/wasm_worker_tests.rs index 79d1dd8bc10..e4a9a8837a4 100644 --- a/linera-core/src/unit_tests/wasm_worker_tests.rs +++ b/linera-core/src/unit_tests/wasm_worker_tests.rs @@ -10,28 +10,23 @@ #![allow(clippy::large_futures)] #![cfg(any(feature = "wasmer", feature = "wasmtime"))] -use std::borrow::Cow; +use std::{borrow::Cow, collections::BTreeMap, sync::Arc}; use linera_base::{ crypto::KeyPair, data_types::{ Amount, Blob, BlockHeight, Bytecode, OracleResponse, Timestamp, UserApplicationDescription, }, - identifiers::{ - BytecodeId, ChainDescription, ChainId, Destination, MessageId, UserApplicationId, - }, + identifiers::{BytecodeId, ChainDescription, ChainId, UserApplicationId}, ownership::ChainOwnership, }; use linera_chain::{ - data_types::{BlockExecutionOutcome, HashedCertificateValue, OutgoingMessage}, + data_types::{BlockExecutionOutcome, HashedCertificateValue}, test::{make_child_block, make_first_block, BlockTestExt}, }; use linera_execution::{ - committee::Epoch, - system::{SystemMessage, SystemOperation}, - test_utils::SystemExecutionState, - Message, MessageKind, Operation, OperationContext, ResourceController, TransactionTracker, - WasmContractModule, WasmRuntime, + committee::Epoch, system::SystemOperation, test_utils::SystemExecutionState, Operation, + OperationContext, ResourceController, TransactionTracker, WasmContractModule, WasmRuntime, }; use linera_storage::{DbStorage, Storage}; #[cfg(feature = "dynamodb")] @@ -115,8 +110,8 @@ where let contract_bytecode = Bytecode::load_from_file(contract_path).await?; let service_bytecode = Bytecode::load_from_file(service_path).await?; - let contract_blob = Blob::new_contract_bytecode(contract_bytecode.clone().compress()); - let service_blob = Blob::new_service_bytecode(service_bytecode.compress()); + let contract_blob = Blob::new_contract_bytecode(contract_bytecode.clone().compress())?; + let service_blob = Blob::new_service_bytecode(service_bytecode.compress())?; let contract_blob_id = contract_blob.id(); let service_blob_id = service_blob.id(); @@ -158,7 +153,7 @@ where .await; let info = worker - .fully_handle_certificate(publish_certificate.clone(), vec![service_blob]) + .fully_handle_certificate(publish_certificate.clone(), vec![service_blob.clone()]) .await .unwrap() .info; @@ -180,33 +175,30 @@ where let initial_value = 10_u64; let initial_value_bytes = serde_json::to_vec(&initial_value)?; let parameters_bytes = serde_json::to_vec(&())?; - let create_operation = SystemOperation::CreateApplication { + + let application_description = UserApplicationDescription { bytecode_id, - parameters: parameters_bytes.clone(), - instantiation_argument: initial_value_bytes.clone(), + creator_chain_id: creator_chain.into(), + block_height: BlockHeight(0), + operation_index: 0, required_application_ids: vec![], + parameters: parameters_bytes.clone(), }; - let application_id = UserApplicationId { - bytecode_id, - creation: MessageId { - chain_id: creator_chain.into(), - height: BlockHeight::from(0), - index: 0, - }, - }; - let application_description = UserApplicationDescription { - bytecode_id, - creation: application_id.creation, + let application_id = UserApplicationId::try_from(&application_description)?; + + let app_blob = Blob::new_application_description(application_description.clone())?; + + let create_operation = SystemOperation::CreateApplication { + application_id, + creator_chain_id: application_description.creator_chain_id, + block_height: application_description.block_height, + operation_index: application_description.operation_index, + instantiation_argument: initial_value_bytes.clone(), required_application_ids: vec![], - parameters: parameters_bytes, }; let create_block = make_first_block(creator_chain.into()) .with_timestamp(2) .with_operation(create_operation); - creator_system_state - .registry - .known_applications - .insert(application_id, application_description.clone()); creator_system_state.timestamp = Timestamp::from(2); let mut creator_state = creator_system_state.into_view().await; creator_state @@ -215,31 +207,28 @@ where Timestamp::from(2), application_description, initial_value_bytes.clone(), + contract_blob, + service_blob, ) .await?; + let oracle_responses = vec![ + OracleResponse::Blob(contract_blob_id), + OracleResponse::Blob(service_blob_id), + OracleResponse::Blob(app_blob.id()), + ]; let create_block_proposal = HashedCertificateValue::new_confirmed( BlockExecutionOutcome { - messages: vec![vec![OutgoingMessage { - destination: Destination::Recipient(creator_chain.into()), - authenticated_signer: None, - grant: Amount::ZERO, - refund_grant_to: None, - kind: MessageKind::Protected, - message: Message::System(SystemMessage::ApplicationCreated), - }]], + messages: vec![Vec::new()], events: vec![Vec::new()], state_hash: creator_state.crypto_hash().await?, - oracle_responses: vec![vec![ - OracleResponse::Blob(contract_blob_id), - OracleResponse::Blob(service_blob_id), - ]], + oracle_responses: vec![oracle_responses.clone()], } .with(create_block), ); let create_certificate = make_certificate(&committee, &worker, create_block_proposal); let info = worker - .fully_handle_certificate(create_certificate.clone(), vec![]) + .fully_handle_certificate(create_certificate.clone(), vec![app_blob]) .await .unwrap() .info; @@ -264,7 +253,8 @@ where authenticated_signer: None, authenticated_caller_id: None, height: run_block.height, - index: Some(0), + txn_index: Some(0), + operation_index: Some(0), }; let mut controller = ResourceController::default(); creator_state @@ -275,7 +265,11 @@ where application_id, bytes: user_operation, }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(oracle_responses.clone()), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await?; @@ -285,7 +279,7 @@ where messages: vec![Vec::new()], events: vec![Vec::new()], state_hash: creator_state.crypto_hash().await?, - oracle_responses: vec![Vec::new()], + oracle_responses: vec![oracle_responses], } .with(run_block), ); diff --git a/linera-core/src/unit_tests/worker_tests.rs b/linera-core/src/unit_tests/worker_tests.rs index b71a2ad388e..60f6dedbe39 100644 --- a/linera-core/src/unit_tests/worker_tests.rs +++ b/linera-core/src/unit_tests/worker_tests.rs @@ -3601,7 +3601,7 @@ where chain.save().await?; } - let (application_id, application) = applications + let (application_id, application, _, _) = applications .next() .expect("Mock application should be registered"); @@ -3691,7 +3691,7 @@ where chain.save().await?; } - let (application_id, application) = applications + let (application_id, application, _, _) = applications .next() .expect("Mock application should be registered"); diff --git a/linera-core/src/worker.rs b/linera-core/src/worker.rs index f91c47fad0c..9ccd7717777 100644 --- a/linera-core/src/worker.rs +++ b/linera-core/src/worker.rs @@ -14,9 +14,9 @@ use std::{ use linera_base::crypto::PublicKey; use linera_base::{ crypto::{CryptoHash, KeyPair}, - data_types::{ArithmeticError, Blob, BlockHeight, Round, UserApplicationDescription}, + data_types::{ArithmeticError, Blob, BlockHeight, Round}, doc_scalar, - identifiers::{BlobId, ChainId, Owner, UserApplicationId}, + identifiers::{BlobId, ChainId, Owner}, time::timer::{sleep, timeout}, }; use linera_chain::{ @@ -496,21 +496,6 @@ where .await } - #[tracing::instrument(level = "trace", skip(self, chain_id, application_id))] - pub async fn describe_application( - &self, - chain_id: ChainId, - application_id: UserApplicationId, - ) -> Result { - self.query_chain_worker(chain_id, move |callback| { - ChainWorkerRequest::DescribeApplication { - application_id, - callback, - } - }) - .await - } - /// Processes a confirmed block (aka a commit). #[tracing::instrument( level = "trace", diff --git a/linera-execution/src/applications.rs b/linera-execution/src/applications.rs deleted file mode 100644 index 2d12a0f2a3c..00000000000 --- a/linera-execution/src/applications.rs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Zefchain Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::{HashMap, HashSet}; - -use linera_base::{data_types::UserApplicationDescription, identifiers::UserApplicationId}; -use linera_views::{ - context::Context, - map_view::HashedMapView, - views::{ClonableView, HashableView}, -}; -#[cfg(with_testing)] -use { - linera_views::context::{create_test_memory_context, MemoryContext}, - linera_views::views::View, - std::collections::BTreeMap, -}; - -use crate::SystemExecutionError; - -#[cfg(test)] -#[path = "unit_tests/applications_tests.rs"] -mod applications_tests; - -#[derive(Debug, ClonableView, HashableView)] -pub struct ApplicationRegistryView { - /// The applications that are known by the chain. - pub known_applications: HashedMapView, -} - -#[cfg(with_testing)] -#[derive(Default, Eq, PartialEq, Debug, Clone)] -pub struct ApplicationRegistry { - pub known_applications: BTreeMap, -} - -impl ApplicationRegistryView -where - C: Context + Clone + Send + Sync + 'static, -{ - #[cfg(with_testing)] - pub fn import(&mut self, registry: ApplicationRegistry) -> Result<(), SystemExecutionError> { - for (id, description) in registry.known_applications { - self.known_applications.insert(&id, description)?; - } - Ok(()) - } - - /// Registers an existing application. - /// - /// Keeps track of an existing application that the current chain is seeing for the first time. - pub async fn register_application( - &mut self, - application: UserApplicationDescription, - ) -> Result { - // Make sure that referenced applications ids have been registered. - for required_id in &application.required_application_ids { - self.describe_application(*required_id).await?; - } - let id = UserApplicationId::from(&application); - self.known_applications.insert(&id, application)?; - Ok(id) - } - - /// Registers a newly created application. - pub async fn register_new_application( - &mut self, - application_id: UserApplicationId, - parameters: Vec, - required_application_ids: Vec, - ) -> Result<(), SystemExecutionError> { - // Make sure that referenced applications ids have been registered. - for required_id in &required_application_ids { - self.describe_application(*required_id).await?; - } - // Create description and register it. - let UserApplicationId { - bytecode_id, - creation, - } = application_id; - let description = UserApplicationDescription { - bytecode_id, - parameters, - creation, - required_application_ids, - }; - self.known_applications - .insert(&application_id, description)?; - Ok(()) - } - - /// Retrieves an application's description. - pub async fn describe_application( - &self, - id: UserApplicationId, - ) -> Result { - self.known_applications - .get(&id) - .await? - .ok_or_else(|| SystemExecutionError::UnknownApplicationId(Box::new(id))) - } - - /// Retrieves the recursive dependencies of applications and apply a topological sort. - pub async fn find_dependencies( - &self, - mut stack: Vec, - registered_apps: &HashMap, - ) -> Result, SystemExecutionError> { - // What we return at the end. - let mut result = Vec::new(); - // The entries already inserted in `result`. - let mut sorted = HashSet::new(); - // The entries for which dependencies have already been pushed once to the stack. - let mut seen = HashSet::new(); - - while let Some(id) = stack.pop() { - if sorted.contains(&id) { - continue; - } - if seen.contains(&id) { - // Second time we see this entry. It was last pushed just before its - // dependencies -- which are now fully sorted. - sorted.insert(id); - result.push(id); - continue; - } - // First time we see this entry: - // 1. Mark it so that its dependencies are no longer pushed to the stack. - seen.insert(id); - // 2. Schedule all the (yet unseen) dependencies, then this entry for a second visit. - stack.push(id); - let app = if let Some(app) = registered_apps.get(&id) { - app.clone() - } else { - self.describe_application(id).await? - }; - for child in app.required_application_ids.iter().rev() { - if !seen.contains(child) { - stack.push(*child); - } - } - } - Ok(result) - } - - /// Retrieves applications' descriptions preceded by their recursive dependencies. - pub async fn describe_applications_with_dependencies( - &self, - ids: Vec, - extra_registered_apps: &HashMap, - ) -> Result, SystemExecutionError> { - let ids_with_deps = self.find_dependencies(ids, extra_registered_apps).await?; - let mut result = Vec::new(); - for id in ids_with_deps { - let description = if let Some(description) = extra_registered_apps.get(&id) { - description.clone() - } else { - self.describe_application(id).await? - }; - result.push(description); - } - Ok(result) - } -} - -#[cfg(with_testing)] -impl ApplicationRegistryView> -where - MemoryContext<()>: Context + Clone + Send + Sync + 'static, -{ - pub async fn new() -> Self { - let context = create_test_memory_context(); - Self::load(context) - .await - .expect("Loading from memory should work") - } -} diff --git a/linera-execution/src/execution.rs b/linera-execution/src/execution.rs index b6074f716e4..13f1a019792 100644 --- a/linera-execution/src/execution.rs +++ b/linera-execution/src/execution.rs @@ -1,15 +1,12 @@ // Copyright (c) Zefchain Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, - mem, vec, -}; +use std::mem; -use futures::{stream::FuturesUnordered, FutureExt, StreamExt, TryStreamExt}; +use futures::{FutureExt, StreamExt}; use linera_base::{ - data_types::{Amount, BlockHeight, Timestamp}, - identifiers::{Account, ChainId, Destination, Owner}, + data_types::{Amount, BlockHeight, OracleResponse, Timestamp}, + identifiers::{Account, BlobId, BlobType, ChainId, Destination, Owner}, }; use linera_views::{ context::Context, @@ -21,19 +18,21 @@ use linera_views_derive::CryptoHashView; #[cfg(with_testing)] use { crate::{ - ResourceControlPolicy, ResourceTracker, TestExecutionRuntimeContext, UserContractCode, + ResourceControlPolicy, ResourceTracker, TestExecutionRuntimeContext, + UserApplicationDescription, UserContractCode, }, + linera_base::data_types::Blob, linera_views::context::MemoryContext, - std::sync::Arc, + std::{collections::BTreeMap, sync::Arc}, }; use super::{runtime::ServiceRuntimeRequest, ExecutionRequest}; use crate::{ resources::ResourceController, system::SystemExecutionStateView, ContractSyncRuntime, - ExecutionError, ExecutionOutcome, ExecutionRuntimeConfig, ExecutionRuntimeContext, Message, - MessageContext, MessageKind, Operation, OperationContext, Query, QueryContext, - RawExecutionOutcome, RawOutgoingMessage, Response, ServiceSyncRuntime, SystemMessage, - TransactionTracker, UserApplicationDescription, UserApplicationId, + ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, Message, MessageContext, + MessageKind, Operation, OperationContext, Query, QueryContext, RawExecutionOutcome, + RawOutgoingMessage, Response, ServiceSyncRuntime, SystemMessage, TransactionTracker, + UserApplicationId, }; /// A view accessing the execution state of a chain. @@ -65,30 +64,43 @@ where local_time: Timestamp, application_description: UserApplicationDescription, instantiation_argument: Vec, + contract_blob: Blob, + service_blob: Blob, ) -> Result<(), ExecutionError> { - let chain_id = application_description.creation.chain_id; + let chain_id = application_description.creator_chain_id; let context = OperationContext { chain_id, authenticated_signer: None, authenticated_caller_id: None, - height: application_description.creation.height, - index: Some(0), + height: application_description.block_height, + txn_index: Some(0), + operation_index: Some(0), }; let action = UserAction::Instantiate(context, instantiation_argument); - let next_message_index = application_description.creation.index + 1; - - let application_id = self - .system - .registry - .register_application(application_description) - .await?; + let application_id = UserApplicationId::try_from(&application_description)?; + let application_blob = Blob::new_application_description(application_description.clone())?; self.context() .extra() .user_contracts() .insert(application_id, contract); + self.context() + .extra() + .blobs() + .insert(contract_blob.id(), contract_blob); + + self.context() + .extra() + .blobs() + .insert(service_blob.id(), service_blob); + + self.context() + .extra() + .blobs() + .insert(application_blob.id(), application_blob.clone()); + let tracker = ResourceTracker::default(); let policy = ResourceControlPolicy::default(); let mut resource_controller = ResourceController { @@ -96,7 +108,11 @@ where tracker, account: None, }; - let mut txn_tracker = TransactionTracker::new(next_message_index, None); + + let mut pending_applications = BTreeMap::new(); + pending_applications.insert(application_id, application_description); + + let mut txn_tracker = TransactionTracker::new(0, None, Arc::new(pending_applications)); self.run_user_action( application_id, chain_id, @@ -108,8 +124,6 @@ where &mut resource_controller, ) .await?; - self.update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await?; Ok(()) } } @@ -156,6 +170,23 @@ where txn_tracker: &mut TransactionTracker, resource_controller: &mut ResourceController>, ) -> Result<(), ExecutionError> { + match action { + UserAction::Instantiate(_, _) => { + self.system + .check_and_record_bytecode_blobs(&application_id.bytecode_id, txn_tracker) + .await?; + txn_tracker.replay_oracle_response(OracleResponse::Blob(BlobId::new( + application_id.application_description_hash, + BlobType::ApplicationDescription, + )))?; + } + UserAction::Operation(_, _) | UserAction::Message(_, _) => { + self.system + .check_and_record_application_blob(&application_id, txn_tracker) + .await?; + } + } + let ExecutionRuntimeConfig {} = self.context().extra().execution_runtime_config(); self.run_user_action_with_runtime( application_id, @@ -222,75 +253,6 @@ where Ok(()) } - /// Schedules application registration messages when needed. - /// - /// Ensures that the outgoing messages in `results` are preceded by a system message that - /// registers the application that will handle the messages. - pub async fn update_execution_outcomes_with_app_registrations( - &self, - txn_tracker: &mut TransactionTracker, - ) -> Result<(), ExecutionError> { - let results = txn_tracker.outcomes_mut(); - let user_application_outcomes = results.iter().filter_map(|outcome| match outcome { - ExecutionOutcome::User(application_id, result) => Some((application_id, result)), - _ => None, - }); - - let mut applications_to_register_per_destination = BTreeMap::<_, BTreeSet<_>>::new(); - - for (application_id, result) in user_application_outcomes { - for message in &result.messages { - applications_to_register_per_destination - .entry(&message.destination) - .or_default() - .insert(*application_id); - } - } - - if applications_to_register_per_destination.is_empty() { - return Ok(()); - } - - let messages = applications_to_register_per_destination - .into_iter() - .map(|(destination, applications_to_describe)| async { - let applications = self - .system - .registry - .describe_applications_with_dependencies( - applications_to_describe.into_iter().collect(), - &HashMap::new(), - ) - .await?; - - Ok::<_, ExecutionError>(RawOutgoingMessage { - destination: destination.clone(), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { applications }, - }) - }) - .collect::>() - .try_collect::>() - .await?; - - let system_outcome = RawExecutionOutcome { - messages, - ..RawExecutionOutcome::default() - }; - - // Insert the message before the first user outcome. - let index = results - .iter() - .position(|outcome| matches!(outcome, ExecutionOutcome::User(_, _))) - .unwrap_or(results.len()); - // TODO(#2362): This inserts messages in front of existing ones, invalidating their IDs. - results.insert(index, ExecutionOutcome::System(system_outcome)); - - Ok(()) - } - pub async fn execute_operation( &mut self, context: OperationContext, @@ -353,10 +315,7 @@ where assert_eq!(context.chain_id, self.context().extra().chain_id()); match message { Message::System(message) => { - let outcome = self - .system - .execute_message(context, message, txn_tracker) - .await?; + let outcome = self.system.execute_message(context, message).await?; txn_tracker.add_system_outcome(outcome)?; } Message::User { @@ -543,19 +502,4 @@ where } } } - - pub async fn list_applications( - &self, - ) -> Result, ExecutionError> { - let mut applications = vec![]; - for index in self.system.registry.known_applications.indices().await? { - let application_description = - self.system.registry.known_applications.get(&index).await?; - - if let Some(application_description) = application_description { - applications.push((index, application_description)); - } - } - Ok(applications) - } } diff --git a/linera-execution/src/execution_state_actor.rs b/linera-execution/src/execution_state_actor.rs index d11aba653fe..87ef9ae13f6 100644 --- a/linera-execution/src/execution_state_actor.rs +++ b/linera-execution/src/execution_state_actor.rs @@ -12,7 +12,7 @@ use futures::channel::mpsc; use linera_base::prometheus_util::{self, MeasureLatency as _}; use linera_base::{ data_types::{Amount, ApplicationPermissions, BlobContent, Timestamp}, - identifiers::{Account, BlobId, MessageId, Owner}, + identifiers::{Account, BlobId, BlobType, MessageId, Owner}, ownership::ChainOwnership, }; use linera_views::{batch::Batch, context::Context, views::View}; @@ -73,10 +73,25 @@ where ) -> Result<(), ExecutionError> { use ExecutionRequest::*; match request { - LoadContract { id, callback } => { + LoadContract { + id, + pending_application_description, + callback, + } => { #[cfg(with_metrics)] let _latency = LOAD_CONTRACT_LATENCY.measure_latency(); - let description = self.system.registry.describe_application(id).await?; + let description = if let Some(description) = pending_application_description { + description + } else { + let blob_id = BlobId::new( + id.application_description_hash, + BlobType::ApplicationDescription, + ); + let blob = self.context().extra().get_blob(blob_id).await?; + blob.into_inner_application_description() + .expect("Should be an application description Blob!") + }; + let code = self .context() .extra() @@ -88,7 +103,17 @@ where LoadService { id, callback } => { #[cfg(with_metrics)] let _latency = LOAD_SERVICE_LATENCY.measure_latency(); - let description = self.system.registry.describe_application(id).await?; + let blob_id = BlobId::new( + id.application_description_hash, + BlobType::ApplicationDescription, + ); + let description = self + .context() + .extra() + .get_blob(blob_id) + .await? + .into_inner_application_description() + .expect("Should be an application description Blob!"); let code = self .context() .extra() @@ -328,6 +353,7 @@ where pub enum ExecutionRequest { LoadContract { id: UserApplicationId, + pending_application_description: Option, callback: Sender<(UserContractCode, UserApplicationDescription)>, }, @@ -458,9 +484,17 @@ pub enum ExecutionRequest { impl Debug for ExecutionRequest { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { match self { - ExecutionRequest::LoadContract { id, .. } => formatter + ExecutionRequest::LoadContract { + id, + pending_application_description, + .. + } => formatter .debug_struct("ExecutionRequest::LoadContract") .field("id", id) + .field( + "pending_application_description", + pending_application_description, + ) .finish_non_exhaustive(), ExecutionRequest::LoadService { id, .. } => formatter diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index bb6b0aa4e10..861551a0f6f 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -6,7 +6,6 @@ #![deny(clippy::large_futures)] -mod applications; pub mod committee; mod execution; mod execution_state_actor; @@ -48,8 +47,6 @@ use serde::{Deserialize, Serialize}; use system::OpenChainConfig; use thiserror::Error; -#[cfg(with_testing)] -pub use crate::applications::ApplicationRegistry; use crate::runtime::ContractSyncRuntime; #[cfg(all(with_testing, with_wasm_runtime))] pub use crate::wasm::test as wasm_test; @@ -59,7 +56,6 @@ pub use crate::wasm::{ ViewSystemApi, WasmContractModule, WasmExecutionError, WasmServiceModule, }; pub use crate::{ - applications::ApplicationRegistryView, execution::{ExecutionStateView, ServiceRuntimeEndpoint}, execution_state_actor::ExecutionRequest, policy::ResourceControlPolicy, @@ -142,6 +138,8 @@ pub enum ExecutionError { JoinError(#[from] linera_base::task::Error), #[error(transparent)] DecompressionError(#[from] DecompressionError), + #[error(transparent)] + BcsError(#[from] bcs::Error), #[error("The given promise is invalid or was polled once already")] InvalidPromise, @@ -184,8 +182,6 @@ pub enum ExecutionError { UnexpectedOracleResponse, #[error("Invalid JSON: {}", .0)] Json(#[from] serde_json::Error), - #[error(transparent)] - Bcs(#[from] bcs::Error), #[error("Recorded response for oracle query has the wrong type")] OracleResponseMismatch, #[error("Assertion failed: local time {local_time} is not earlier than {timestamp}")] @@ -194,9 +190,6 @@ pub enum ExecutionError { local_time: Timestamp, }, - #[error("Blob not found on storage read: {0}")] - BlobNotFoundOnRead(BlobId), - #[error("Event keys can be at most {MAX_EVENT_KEY_LEN} bytes.")] EventKeyTooLong, #[error("Stream names can be at most {MAX_STREAM_NAME_LEN} bytes.")] @@ -277,6 +270,8 @@ pub trait ExecutionRuntimeContext { fn user_services(&self) -> &Arc>; + fn blobs(&self) -> &Arc>; + async fn get_user_contract( &self, description: &UserApplicationDescription, @@ -303,8 +298,10 @@ pub struct OperationContext { pub authenticated_caller_id: Option, /// The current block height. pub height: BlockHeight, + /// The current index of the transaction. + pub txn_index: Option, /// The current index of the operation. - pub index: Option, + pub operation_index: Option, } #[derive(Clone, Copy, Debug)] @@ -762,7 +759,7 @@ pub struct ChannelSubscription { /// Externally visible results of an execution, tagged by their application. #[derive(Debug)] #[cfg_attr(with_testing, derive(Eq, PartialEq))] -#[expect(clippy::large_enum_variant)] +#[allow(clippy::large_enum_variant)] pub enum ExecutionOutcome { System(RawExecutionOutcome), User(UserApplicationId, RawExecutionOutcome, Amount>), @@ -928,11 +925,15 @@ impl ExecutionRuntimeContext for TestExecutionRuntimeContext { &self.user_services } + fn blobs(&self) -> &Arc> { + &self.blobs + } + async fn get_user_contract( &self, description: &UserApplicationDescription, ) -> Result { - let application_id = description.into(); + let application_id = UserApplicationId::try_from(description)?; Ok(self .user_contracts() .get(&application_id) @@ -946,7 +947,7 @@ impl ExecutionRuntimeContext for TestExecutionRuntimeContext { &self, description: &UserApplicationDescription, ) -> Result { - let application_id = description.into(); + let application_id = UserApplicationId::try_from(description)?; Ok(self .user_services() .get(&application_id) @@ -958,14 +959,14 @@ impl ExecutionRuntimeContext for TestExecutionRuntimeContext { async fn get_blob(&self, blob_id: BlobId) -> Result { Ok(self - .blobs + .blobs() .get(&blob_id) .ok_or_else(|| SystemExecutionError::BlobNotFoundOnRead(blob_id))? .clone()) } async fn contains_blob(&self, blob_id: BlobId) -> Result { - Ok(self.blobs.contains_key(&blob_id)) + Ok(self.blobs().contains_key(&blob_id)) } } diff --git a/linera-execution/src/runtime.rs b/linera-execution/src/runtime.rs index ace33f9ce1c..5acaf0c3c06 100644 --- a/linera-execution/src/runtime.rs +++ b/linera-execution/src/runtime.rs @@ -112,6 +112,8 @@ struct ApplicationStatus { signer: Option, /// The current execution outcome of the application. outcome: RawExecutionOutcome>, + /// The chain ID that created the application. + creator_chain_id: ChainId, } /// A loaded application instance. @@ -119,6 +121,7 @@ struct ApplicationStatus { struct LoadedApplication { instance: Arc>, parameters: Vec, + creator_chain_id: ChainId, } impl LoadedApplication { @@ -127,6 +130,7 @@ impl LoadedApplication { LoadedApplication { instance: Arc::new(Mutex::new(instance)), parameters: description.parameters, + creator_chain_id: description.creator_chain_id, } } } @@ -138,6 +142,7 @@ impl Clone for LoadedApplication { LoadedApplication { instance: self.instance.clone(), parameters: self.parameters.clone(), + creator_chain_id: self.creator_chain_id, } } } @@ -382,7 +387,13 @@ impl SyncRuntimeInternal { hash_map::Entry::Vacant(entry) => { let (code, description) = self .execution_state_sender - .send_request(|callback| ExecutionRequest::LoadContract { id, callback })? + .send_request(|callback| ExecutionRequest::LoadContract { + id, + pending_application_description: self + .transaction_tracker + .get_pending_application_description(id), + callback, + })? .recv_response()?; let instance = code.instantiate(SyncRuntimeHandle(this))?; @@ -430,7 +441,8 @@ impl SyncRuntimeInternal { authenticated_signer, authenticated_caller_id, height: self.height, - index: None, + txn_index: None, + operation_index: None, }; self.push_application(ApplicationStatus { caller_id: authenticated_caller_id, @@ -439,6 +451,7 @@ impl SyncRuntimeInternal { // Allow further nested calls to be authenticated if this one is. signer: authenticated_signer, outcome: RawExecutionOutcome::default(), + creator_chain_id: application.creator_chain_id, }); Ok((application.instance, callee_context)) } @@ -718,7 +731,7 @@ impl BaseRuntime for SyncRuntimeInternal { } fn application_creator_chain_id(&mut self) -> Result { - Ok(self.current_application().id.creation.chain_id) + Ok(self.current_application().creator_chain_id) } fn application_parameters(&mut self) -> Result, ExecutionError> { @@ -1009,7 +1022,7 @@ impl BaseRuntime for SyncRuntimeInternal { .execution_state_sender .send_request(|callback| ExecutionRequest::ReadBlobContent { blob_id, callback })? .recv_response()?; - Ok(blob_content.inner_bytes()) + Ok(blob_content.inner_bytes()?) } fn assert_data_blob_exists(&mut self, hash: &CryptoHash) -> Result<(), ExecutionError> { @@ -1118,6 +1131,7 @@ impl ContractSyncRuntime { parameters: application.parameters.clone(), signer, outcome: RawExecutionOutcome::default(), + creator_chain_id: application.creator_chain_id, }; runtime.push_application(status); @@ -1459,6 +1473,7 @@ impl ServiceRuntime for ServiceSyncRuntimeHandle { parameters: application.parameters, signer: None, outcome: RawExecutionOutcome::default(), + creator_chain_id: application.creator_chain_id, }); (query_context, application.instance) }; diff --git a/linera-execution/src/system.rs b/linera-execution/src/system.rs index 748f47b438f..e8c49db743e 100644 --- a/linera-execution/src/system.rs +++ b/linera-execution/src/system.rs @@ -7,7 +7,6 @@ use std::sync::LazyLock; use std::{ collections::BTreeMap, fmt::{self, Display, Formatter}, - iter, }; use async_graphql::Enum; @@ -15,11 +14,13 @@ use custom_debug_derive::Debug; use linera_base::{ crypto::{CryptoHash, PublicKey}, data_types::{ - Amount, ApplicationPermissions, ArithmeticError, BlobContent, OracleResponse, Timestamp, + Amount, ApplicationPermissions, ArithmeticError, BlobContent, BlockHeight, OracleResponse, + Timestamp, }, - ensure, hex_debug, + ensure, identifiers::{ - Account, BlobId, BlobType, BytecodeId, ChainDescription, ChainId, MessageId, Owner, + Account, ApplicationId, BlobId, BlobType, BytecodeId, ChainDescription, ChainId, MessageId, + Owner, }, ownership::{ChainOwnership, TimeoutConfig}, }; @@ -39,20 +40,13 @@ use {linera_base::prometheus_util, prometheus::IntCounterVec}; use crate::test_utils::SystemExecutionState; use crate::{ committee::{Committee, Epoch}, - ApplicationRegistryView, ChannelName, ChannelSubscription, Destination, - ExecutionRuntimeContext, MessageContext, MessageKind, OperationContext, QueryContext, - RawExecutionOutcome, RawOutgoingMessage, TransactionTracker, UserApplicationDescription, - UserApplicationId, + ChannelName, ChannelSubscription, Destination, ExecutionRuntimeContext, MessageContext, + MessageKind, OperationContext, QueryContext, RawExecutionOutcome, RawOutgoingMessage, + TransactionTracker, UserApplicationId, }; /// The relative index of the `OpenChain` message created by the `OpenChain` operation. pub static OPEN_CHAIN_MESSAGE_INDEX: u32 = 0; -/// The relative index of the `ApplicationCreated` message created by the `CreateApplication` -/// operation. -pub static CREATE_APPLICATION_MESSAGE_INDEX: u32 = 0; -/// The relative index of the `BytecodePublished` message created by the `PublishBytecode` -/// operation. -pub static PUBLISH_BYTECODE_MESSAGE_INDEX: u32 = 0; /// The number of times the [`SystemOperation::OpenChain`] was executed. #[cfg(with_metrics)] @@ -89,8 +83,6 @@ pub struct SystemExecutionStateView { pub balances: HashedMapView, /// The timestamp of the most recent block. pub timestamp: HashedRegisterView, - /// Track the locations of known bytecodes as well as the descriptions of known applications. - pub registry: ApplicationRegistryView, /// Whether this chain has been closed. pub closed: HashedRegisterView, /// Permissions for applications on this chain. @@ -164,20 +156,13 @@ pub enum SystemOperation { ReadBlob { blob_id: BlobId }, /// Creates a new application. CreateApplication { - bytecode_id: BytecodeId, - #[serde(with = "serde_bytes")] - #[debug(with = "hex_debug")] - parameters: Vec, - #[serde(with = "serde_bytes")] - #[debug(with = "hex_debug")] + application_id: UserApplicationId, + creator_chain_id: ChainId, + block_height: BlockHeight, + operation_index: u32, instantiation_argument: Vec, required_application_ids: Vec, }, - /// Requests a message from another chain to register a user application on this chain. - RequestApplication { - chain_id: ChainId, - application_id: UserApplicationId, - }, /// Operations that are only allowed on the admin chain. Admin(AdminOperation), } @@ -229,16 +214,6 @@ pub enum SystemMessage { id: ChainId, subscription: ChannelSubscription, }, - /// Notifies that a new application was created. - ApplicationCreated, - /// Shares information about some applications to help the recipient use them. - /// Applications must be registered after their dependencies. - RegisterApplications { - applications: Vec, - }, - /// Requests a `RegisterApplication` message from the target chain to register the specified - /// application on the sender chain. - RequestApplication(UserApplicationId), } /// A query to the system state. @@ -341,8 +316,12 @@ pub enum SystemExecutionError { #[error(transparent)] ViewError(#[from] ViewError), - #[error("Incorrect chain ID: {0}")] - IncorrectChainId(ChainId), + #[error("Trying to create an application in the wrong chain! Expected chain: {0}, Actual chain: {1}")] + WrongChainForApplicationCreation(ChainId, ChainId), + #[error("Trying to create an application in the wrong block height! Expected height: {0}, Actual height: {1}")] + WrongBlockHeightForApplicationCreation(BlockHeight, BlockHeight), + #[error("Trying to create an application in the wrong block effect index! Expected index: {0}, Actual index: {1}")] + WrongBlockEffectIndexForApplicationCreation(u32, u32), #[error("Invalid admin ID in new chain: {0}")] InvalidNewChainAdminId(ChainId), #[error("Invalid committees")] @@ -389,10 +368,6 @@ pub enum SystemExecutionError { CannotRewindEpoch, #[error("Cannot decrease the chain's timestamp")] TicksOutOfOrder, - #[error("Attempt to create an application using unregistered bytecode identifier {0:?}")] - UnknownBytecodeId(BytecodeId), - #[error("Application {0:?} is not registered by the chain")] - UnknownApplicationId(Box), #[error("Chain is not active yet.")] InactiveChain, @@ -608,49 +583,47 @@ where )))?; } CreateApplication { - bytecode_id, - parameters, + application_id, + creator_chain_id, + block_height, + operation_index, instantiation_argument, required_application_ids, } => { - let id = UserApplicationId { - bytecode_id, - creation: context.next_message_id(txn_tracker.next_message_index()), - }; - for application in required_application_ids.iter().chain(iter::once(&id)) { - self.check_and_record_bytecode_blobs(&application.bytecode_id, txn_tracker) - .await?; - } - self.registry - .register_new_application( - id, - parameters.clone(), - required_application_ids.clone(), + ensure!( + context.chain_id == creator_chain_id, + SystemExecutionError::WrongChainForApplicationCreation( + creator_chain_id, + context.chain_id ) - .await?; - // Send a message to ourself to increment the message ID. - let message = RawOutgoingMessage { - destination: Destination::Recipient(context.chain_id), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Protected, - message: SystemMessage::ApplicationCreated, - }; - outcome.messages.push(message); - new_application = Some((id, instantiation_argument.clone())); - } - RequestApplication { - chain_id, - application_id, - } => { - let message = RawOutgoingMessage { - destination: Destination::Recipient(chain_id), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RequestApplication(application_id), - }; - outcome.messages.push(message); + ); + ensure!( + context.height == block_height, + SystemExecutionError::WrongBlockHeightForApplicationCreation( + block_height, + context.height + ) + ); + + let expected_operation_index = context + .operation_index + .expect("Operation index must be set!"); + ensure!( + operation_index == expected_operation_index, + SystemExecutionError::WrongBlockEffectIndexForApplicationCreation( + expected_operation_index, + operation_index + ) + ); + + for required_application_id in required_application_ids { + txn_tracker.replay_oracle_response(OracleResponse::Blob(BlobId::new( + required_application_id.application_description_hash, + BlobType::ApplicationDescription, + )))?; + } + + new_application = Some((application_id, instantiation_argument.clone())); } PublishDataBlob { blob_hash } => { txn_tracker.replay_oracle_response(OracleResponse::Blob(BlobId::new( @@ -747,7 +720,6 @@ where &mut self, context: MessageContext, message: SystemMessage, - txn_tracker: &mut TransactionTracker, ) -> Result, SystemExecutionError> { let mut outcome = RawExecutionOutcome::default(); use SystemMessage::*; @@ -815,36 +787,8 @@ where SystemExecutionError::InvalidCommitteeRemoval ); } - RegisterApplications { applications } => { - for application in applications { - self.check_and_record_bytecode_blobs(&application.bytecode_id, txn_tracker) - .await?; - self.registry - .register_application(application.clone()) - .await?; - } - } - RequestApplication(application_id) => { - let applications = self - .registry - .describe_applications_with_dependencies( - vec![application_id], - &Default::default(), - ) - .await?; - let message = RawOutgoingMessage { - destination: Destination::Recipient(context.message_id.chain_id), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { applications }, - }; - outcome.messages.push(message); - } // These messages are executed immediately when cross-chain requests are received. Subscribe { .. } | Unsubscribe { .. } | OpenChain(_) => {} - // This message is only a placeholder: Its ID is part of the application ID. - ApplicationCreated => {} } Ok(outcome) } @@ -996,7 +940,30 @@ where } } - async fn check_and_record_bytecode_blobs( + pub async fn check_and_record_application_blob( + &mut self, + application_id: &ApplicationId, + txn_tracker: &mut TransactionTracker, + ) -> Result<(), SystemExecutionError> { + self.check_and_record_bytecode_blobs(&application_id.bytecode_id, txn_tracker) + .await?; + + let application_blob_id = BlobId::new( + application_id.application_description_hash, + BlobType::ApplicationDescription, + ); + ensure!( + self.context() + .extra() + .contains_blob(application_blob_id) + .await?, + SystemExecutionError::BlobNotFoundOnRead(application_blob_id) + ); + txn_tracker.replay_oracle_response(OracleResponse::Blob(application_blob_id))?; + Ok(()) + } + + pub async fn check_and_record_bytecode_blobs( &mut self, bytecode_id: &BytecodeId, txn_tracker: &mut TransactionTracker, @@ -1027,10 +994,7 @@ where #[cfg(test)] mod tests { - use linera_base::{ - data_types::{Blob, BlockHeight, Bytecode}, - identifiers::ApplicationId, - }; + use linera_base::data_types::{Blob, BlockHeight, Bytecode, UserApplicationDescription}; use linera_views::context::MemoryContext; use super::*; @@ -1048,7 +1012,8 @@ mod tests { authenticated_signer: None, authenticated_caller_id: None, height: BlockHeight::from(7), - index: Some(2), + txn_index: Some(2), + operation_index: Some(2), }; let state = SystemExecutionState { description: Some(description), @@ -1066,13 +1031,25 @@ mod tests { let (mut view, context) = new_view_and_context().await; let contract = Bytecode::new(b"contract".into()); let service = Bytecode::new(b"service".into()); - let contract_blob = Blob::new_contract_bytecode(contract.compress()); - let service_blob = Blob::new_service_bytecode(service.compress()); + let contract_blob = Blob::new_contract_bytecode(contract.compress()).unwrap(); + let service_blob = Blob::new_service_bytecode(service.compress()).unwrap(); let bytecode_id = BytecodeId::new(contract_blob.id().hash, service_blob.id().hash); - let operation = SystemOperation::CreateApplication { + let application_description = UserApplicationDescription { bytecode_id, + creator_chain_id: context.chain_id, + block_height: context.height, + operation_index: context.operation_index.unwrap(), + required_application_ids: vec![], parameters: vec![], + }; + + let application_id = UserApplicationId::try_from(&application_description).unwrap(); + let operation = SystemOperation::CreateApplication { + application_id, + creator_chain_id: application_description.creator_chain_id, + block_height: application_description.block_height, + operation_index: application_description.operation_index, instantiation_argument: vec![], required_application_ids: vec![], }; @@ -1084,23 +1061,7 @@ mod tests { .execute_operation(context, operation, &mut txn_tracker) .await .unwrap(); - let [ExecutionOutcome::System(result)] = &txn_tracker.destructure().unwrap().0[..] else { - panic!("Unexpected outcome"); - }; - assert_eq!( - result.messages[CREATE_APPLICATION_MESSAGE_INDEX as usize].message, - SystemMessage::ApplicationCreated - ); - let creation = MessageId { - chain_id: context.chain_id, - height: context.height, - index: CREATE_APPLICATION_MESSAGE_INDEX, - }; - let id = ApplicationId { - bytecode_id, - creation, - }; - assert_eq!(new_application, Some((id, vec![]))); + assert_eq!(new_application, Some((application_id, vec![]))); } #[tokio::test] diff --git a/linera-execution/src/test_utils/mod.rs b/linera-execution/src/test_utils/mod.rs index 576e5e9a4f9..afadd50a809 100644 --- a/linera-execution/src/test_utils/mod.rs +++ b/linera-execution/src/test_utils/mod.rs @@ -12,8 +12,8 @@ use std::{sync::Arc, thread, vec}; use linera_base::{ crypto::{BcsSignable, CryptoHash}, - data_types::BlockHeight, - identifiers::{BlobId, BytecodeId, ChainId, MessageId}, + data_types::{Blob, BlockHeight, CompressedBytecode, OracleResponse}, + identifiers::{ApplicationId, BlobId, BlobType, BytecodeId, ChainId, MessageId}, }; use linera_views::{ context::Context, @@ -26,25 +26,36 @@ pub use self::{ system_execution_state::SystemExecutionState, }; use crate::{ - ApplicationRegistryView, ExecutionRequest, ExecutionRuntimeContext, ExecutionStateView, - QueryContext, ServiceRuntimeEndpoint, ServiceRuntimeRequest, ServiceSyncRuntime, + execution::ServiceRuntimeEndpoint, ExecutionRequest, ExecutionRuntimeContext, + ExecutionStateView, QueryContext, ServiceRuntimeRequest, ServiceSyncRuntime, TestExecutionRuntimeContext, UserApplicationDescription, UserApplicationId, }; -pub fn create_dummy_user_application_description(index: u64) -> UserApplicationDescription { +pub fn create_dummy_user_application_description( + index: u64, +) -> (UserApplicationDescription, Blob, Blob) { let chain_id = ChainId::root(1); - let contract_blob_hash = CryptoHash::new(&FakeBlob(String::from("contract"))); - let service_blob_hash = CryptoHash::new(&FakeBlob(String::from("service"))); - UserApplicationDescription { - bytecode_id: BytecodeId::new(contract_blob_hash, service_blob_hash), - creation: MessageId { - chain_id, - height: BlockHeight(index), - index: 1, + let contract_blob = Blob::new_contract_bytecode(CompressedBytecode { + compressed_bytes: String::from("contract").as_bytes().to_vec(), + }) + .unwrap(); + let service_blob = Blob::new_service_bytecode(CompressedBytecode { + compressed_bytes: String::from("service").as_bytes().to_vec(), + }) + .unwrap(); + + ( + UserApplicationDescription { + bytecode_id: BytecodeId::new(contract_blob.id().hash, service_blob.id().hash), + creator_chain_id: chain_id, + block_height: BlockHeight(index), + operation_index: 0, + required_application_ids: vec![], + parameters: vec![], }, - required_application_ids: vec![], - parameters: vec![], - } + contract_blob, + service_blob, + ) } #[derive(Deserialize, Serialize)] @@ -59,52 +70,90 @@ impl BcsSignable for FakeBlob {} pub async fn register_mock_applications( state: &mut ExecutionStateView, count: u64, -) -> anyhow::Result> +) -> anyhow::Result> where C: Context + Clone + Send + Sync + 'static, C::Extra: ExecutionRuntimeContext, { - let mock_applications: Vec<_> = - create_dummy_user_application_registrations(&mut state.system.registry, count) - .await? - .into_iter() - .map(|(id, _description)| (id, MockApplication::default())) - .collect(); + let mock_applications: Vec<_> = create_dummy_user_applications(count) + .await? + .into_iter() + .map(|(id, description, contract_blob, service_blob)| { + ( + id, + description, + MockApplication::default(), + contract_blob, + service_blob, + ) + }) + .collect(); let extra = state.context().extra(); - for (id, mock_application) in &mock_applications { + for (id, description, mock_application, contract_blob, service_blob) in &mock_applications { extra .user_contracts() .insert(*id, mock_application.clone().into()); extra .user_services() .insert(*id, mock_application.clone().into()); + + extra + .blobs() + .insert(contract_blob.id(), contract_blob.clone()); + extra + .blobs() + .insert(service_blob.id(), service_blob.clone()); + + let app_blob = Blob::new_application_description(description.clone())?; + extra.blobs().insert(app_blob.id(), app_blob); } - Ok(mock_applications.into_iter()) + Ok(mock_applications + .into_iter() + .map(|(id, _, mock_application, contract_blob, service_blob)| { + (id, mock_application, contract_blob, service_blob) + }) + .collect::>() + .into_iter()) } -pub async fn create_dummy_user_application_registrations( - registry: &mut ApplicationRegistryView, +pub async fn create_dummy_user_applications( count: u64, -) -> anyhow::Result> -where - C: Context + Clone + Send + Sync + 'static, -{ +) -> anyhow::Result> { let mut ids = Vec::with_capacity(count as usize); for index in 0..count { - let description = create_dummy_user_application_description(index); - let id = registry.register_application(description.clone()).await?; - - assert_eq!(registry.describe_application(id).await?, description); - - ids.push((id, description)); + let (description, contract_blob, service_blob) = + create_dummy_user_application_description(index); + ids.push(( + UserApplicationId::try_from(&description)?, + description, + contract_blob, + service_blob, + )); } Ok(ids) } +pub fn get_application_blob_oracle_responses(app_id: &ApplicationId) -> Vec { + vec![ + OracleResponse::Blob(BlobId::new( + app_id.bytecode_id.contract_blob_hash, + BlobType::ContractBytecode, + )), + OracleResponse::Blob(BlobId::new( + app_id.bytecode_id.service_blob_hash, + BlobType::ServiceBytecode, + )), + OracleResponse::Blob(BlobId::new( + app_id.application_description_hash, + BlobType::ApplicationDescription, + )), + ] +} + impl QueryContext { /// Spawns a thread running the [`ServiceSyncRuntime`] actor. /// diff --git a/linera-execution/src/test_utils/system_execution_state.rs b/linera-execution/src/test_utils/system_execution_state.rs index 3bb5c885515..a492de9dd92 100644 --- a/linera-execution/src/test_utils/system_execution_state.rs +++ b/linera-execution/src/test_utils/system_execution_state.rs @@ -20,7 +20,6 @@ use linera_views::{ }; use crate::{ - applications::ApplicationRegistry, committee::{Committee, Epoch}, execution::UserAction, system::SystemChannel, @@ -41,7 +40,6 @@ pub struct SystemExecutionState { pub balance: Amount, pub balances: BTreeMap, pub timestamp: Timestamp, - pub registry: ApplicationRegistry, pub closed: bool, pub application_permissions: ApplicationPermissions, } @@ -98,7 +96,6 @@ impl SystemExecutionState { balance, balances, timestamp, - registry, closed, application_permissions, } = self; @@ -133,10 +130,6 @@ impl SystemExecutionState { .expect("insertion of balances should not fail"); } view.system.timestamp.set(timestamp); - view.system - .registry - .import(registry) - .expect("serialization of registry components should not fail"); view.system.closed.set(closed); view.system .application_permissions diff --git a/linera-execution/src/transaction_tracker.rs b/linera-execution/src/transaction_tracker.rs index ad505c277bb..8922d5eed8b 100644 --- a/linera-execution/src/transaction_tracker.rs +++ b/linera-execution/src/transaction_tracker.rs @@ -1,10 +1,10 @@ // Copyright (c) Zefchain Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::vec; +use std::{collections::BTreeMap, sync::Arc, vec}; use linera_base::{ - data_types::{Amount, ArithmeticError, OracleResponse}, + data_types::{Amount, ArithmeticError, OracleResponse, UserApplicationDescription}, ensure, identifiers::ApplicationId, }; @@ -21,18 +21,31 @@ pub struct TransactionTracker { oracle_responses: Vec, outcomes: Vec, next_message_index: u32, + pending_applications: Arc>, } impl TransactionTracker { - pub fn new(next_message_index: u32, oracle_responses: Option>) -> Self { + pub fn new( + next_message_index: u32, + oracle_responses: Option>, + pending_applications: Arc>, + ) -> Self { TransactionTracker { replaying_oracle_responses: oracle_responses.map(Vec::into_iter), next_message_index, oracle_responses: Vec::new(), outcomes: Vec::new(), + pending_applications, } } + pub fn get_pending_application_description( + &self, + application_id: ApplicationId, + ) -> Option { + self.pending_applications.get(&application_id).cloned() + } + pub fn next_message_index(&self) -> u32 { self.next_message_index } @@ -116,6 +129,7 @@ impl TransactionTracker { oracle_responses, outcomes, next_message_index, + pending_applications: _pending_blobs, } = self; if let Some(mut responses) = replaying_oracle_responses { ensure!( @@ -125,8 +139,4 @@ impl TransactionTracker { } Ok((outcomes, oracle_responses, next_message_index)) } - - pub(crate) fn outcomes_mut(&mut self) -> &mut Vec { - &mut self.outcomes - } } diff --git a/linera-execution/src/unit_tests/applications_tests.rs b/linera-execution/src/unit_tests/applications_tests.rs deleted file mode 100644 index 6803e23a26b..00000000000 --- a/linera-execution/src/unit_tests/applications_tests.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Zefchain Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use linera_base::{ - crypto::CryptoHash, - data_types::BlockHeight, - identifiers::{BytecodeId, ChainId, MessageId}, -}; - -use super::{ - ApplicationRegistry, ApplicationRegistryView, UserApplicationDescription, UserApplicationId, -}; - -fn message_id(index: u32) -> MessageId { - MessageId { - chain_id: ChainId::root(0), - height: BlockHeight::ZERO, - index, - } -} - -fn bytecode_id() -> BytecodeId { - BytecodeId::new( - CryptoHash::test_hash("contract"), - CryptoHash::test_hash("service"), - ) -} - -fn app_id(index: u32) -> UserApplicationId { - UserApplicationId { - bytecode_id: bytecode_id(), - creation: message_id(index), - } -} - -fn app_description(index: u32, deps: Vec) -> UserApplicationDescription { - UserApplicationDescription { - bytecode_id: bytecode_id(), - creation: message_id(index), - parameters: vec![], - required_application_ids: deps.into_iter().map(app_id).collect(), - } -} - -fn registry(graph: impl IntoIterator)>) -> ApplicationRegistry { - let mut registry = ApplicationRegistry::default(); - for (index, deps) in graph { - let description = app_description(index, deps); - registry - .known_applications - .insert((&description).into(), description); - } - registry -} - -#[tokio::test] -async fn test_topological_sort() { - let mut view = ApplicationRegistryView::new().await; - view.import(registry([(1, vec![2, 3])])).unwrap(); - assert!(view - .find_dependencies(vec![app_id(1)], &Default::default()) - .await - .is_err()); - view.import(registry([(3, vec![2]), (2, vec![]), (0, vec![1])])) - .unwrap(); - let app_ids = view - .find_dependencies(vec![app_id(1)], &Default::default()) - .await - .unwrap(); - assert_eq!(app_ids, Vec::from_iter([2, 3, 1].into_iter().map(app_id))); - let app_ids = view - .find_dependencies(vec![app_id(0)], &Default::default()) - .await - .unwrap(); - assert_eq!( - app_ids, - Vec::from_iter([2, 3, 1, 0].into_iter().map(app_id)) - ); - let app_ids = view - .find_dependencies( - vec![app_id(0), app_id(5)], - &vec![ - (app_id(5), app_description(5, vec![4])), - (app_id(4), app_description(5, vec![2])), - ] - .into_iter() - .collect(), - ) - .await - .unwrap(); - assert_eq!( - app_ids, - Vec::from_iter([2, 4, 5, 3, 1, 0].into_iter().map(app_id)) - ); -} - -#[tokio::test] -async fn test_topological_sort_with_loop() { - let mut view = ApplicationRegistryView::new().await; - view.import(registry([ - (1, vec![2, 3]), - (3, vec![1, 2]), - (2, vec![]), - (0, vec![1]), - ])) - .unwrap(); - let app_ids = view - .find_dependencies(vec![app_id(1)], &Default::default()) - .await - .unwrap(); - assert_eq!(app_ids, Vec::from_iter([2, 3, 1].into_iter().map(app_id))); - let app_ids = view - .find_dependencies(vec![app_id(0)], &Default::default()) - .await - .unwrap(); - assert_eq!( - app_ids, - Vec::from_iter([2, 3, 1, 0].into_iter().map(app_id)) - ); -} diff --git a/linera-execution/src/unit_tests/runtime_tests.rs b/linera-execution/src/unit_tests/runtime_tests.rs index 3cc24dafb5f..b34dfd5713f 100644 --- a/linera-execution/src/unit_tests/runtime_tests.rs +++ b/linera-execution/src/unit_tests/runtime_tests.rs @@ -7,6 +7,7 @@ use std::{ any::Any, + collections::BTreeMap, sync::{Arc, Mutex}, }; @@ -14,7 +15,7 @@ use futures::{channel::mpsc, StreamExt}; use linera_base::{ crypto::CryptoHash, data_types::{BlockHeight, Timestamp}, - identifiers::{ApplicationId, BytecodeId, ChainDescription, MessageId}, + identifiers::{ApplicationId, BytecodeId, ChainDescription, ChainId}, }; use linera_views::batch::Batch; @@ -184,7 +185,7 @@ fn create_runtime() -> ( execution_state_sender, None, resource_controller, - TransactionTracker::new(0, Some(Vec::new())), + TransactionTracker::new(0, Some(Vec::new()), Arc::new(BTreeMap::new())), ); (runtime, execution_state_receiver) @@ -198,24 +199,19 @@ fn create_dummy_application() -> ApplicationStatus { parameters: vec![], signer: None, outcome: RawExecutionOutcome::default(), + creator_chain_id: ChainId::root(0), } } /// Creates a dummy [`ApplicationId`]. fn create_dummy_application_id() -> ApplicationId { - let chain_id = ChainDescription::Root(1).into(); - - ApplicationId { - bytecode_id: BytecodeId::new( + ApplicationId::new( + CryptoHash::test_hash("application description hash"), + BytecodeId::new( CryptoHash::test_hash("contract"), CryptoHash::test_hash("service"), ), - creation: MessageId { - chain_id, - height: BlockHeight(1), - index: 1, - }, - } + ) } /// Creates a fake application instance that's just a reference to the `runtime`. @@ -227,5 +223,6 @@ fn create_fake_application_with_runtime( LoadedApplication { instance: Arc::new(Mutex::new(fake_instance)), parameters: vec![], + creator_chain_id: ChainId::root(0), } } diff --git a/linera-execution/tests/fee_consumption.rs b/linera-execution/tests/fee_consumption.rs index 293ec5c1518..36127f64f4d 100644 --- a/linera-execution/tests/fee_consumption.rs +++ b/linera-execution/tests/fee_consumption.rs @@ -5,12 +5,12 @@ #![allow(clippy::items_after_test_module)] -use std::{sync::Arc, vec}; +use std::{collections::BTreeMap, sync::Arc, vec}; use linera_base::{ crypto::{CryptoHash, PublicKey}, - data_types::{Amount, BlockHeight, Timestamp}, - identifiers::{Account, ChainDescription, ChainId, MessageId, Owner}, + data_types::{Amount, BlockHeight, OracleResponse, Timestamp}, + identifiers::{Account, BlobId, BlobType, ChainDescription, ChainId, MessageId, Owner}, }; use linera_execution::{ test_utils::{register_mock_applications, ExpectedCall, SystemExecutionState}, @@ -130,7 +130,7 @@ async fn test_fee_consumption( } let mut applications = register_mock_applications(&mut view, 1).await.unwrap(); - let (application_id, application) = applications + let (application_id, application, contract_blob, service_blob) = applications .next() .expect("Caller mock application should be registered"); @@ -194,7 +194,18 @@ async fn test_fee_consumption( message_id: MessageId::default(), }; let mut grant = initial_grant.unwrap_or_default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(vec![ + OracleResponse::Blob(contract_blob.id()), + OracleResponse::Blob(service_blob.id()), + OracleResponse::Blob(BlobId::new( + application_id.application_description_hash, + BlobType::ApplicationDescription, + )), + ]), + Arc::new(BTreeMap::new()), + ); view.execute_message( context, Timestamp::from(0), diff --git a/linera-execution/tests/test_execution.rs b/linera-execution/tests/test_execution.rs index 36377bb1092..05d8436366f 100644 --- a/linera-execution/tests/test_execution.rs +++ b/linera-execution/tests/test_execution.rs @@ -3,14 +3,13 @@ #![allow(clippy::field_reassign_with_default)] -use std::{collections::BTreeMap, vec}; +use std::{collections::BTreeMap, sync::Arc, vec}; use assert_matches::assert_matches; -use futures::{stream, StreamExt, TryStreamExt}; use linera_base::{ crypto::PublicKey, data_types::{ - Amount, ApplicationPermissions, BlockHeight, Resources, SendMessageRequest, Timestamp, + Amount, ApplicationPermissions, Blob, BlockHeight, Resources, SendMessageRequest, Timestamp, }, identifiers::{Account, ChainDescription, ChainId, Destination, MessageId, Owner}, ownership::ChainOwnership, @@ -19,20 +18,21 @@ use linera_execution::{ committee::{Committee, Epoch}, system::SystemMessage, test_utils::{ - create_dummy_user_application_registrations, register_mock_applications, ExpectedCall, - SystemExecutionState, + create_dummy_user_applications, get_application_blob_oracle_responses, + register_mock_applications, ExpectedCall, SystemExecutionState, }, - BaseRuntime, ContractRuntime, ExecutionError, ExecutionOutcome, MessageKind, Operation, - OperationContext, Query, QueryContext, RawExecutionOutcome, RawOutgoingMessage, + BaseRuntime, ContractRuntime, ExecutionError, ExecutionOutcome, ExecutionRuntimeContext, + Operation, OperationContext, Query, QueryContext, RawExecutionOutcome, RawOutgoingMessage, ResourceControlPolicy, ResourceController, Response, SystemOperation, TransactionTracker, }; -use linera_views::batch::Batch; +use linera_views::{batch::Batch, context::Context, views::View}; fn make_operation_context() -> OperationContext { OperationContext { chain_id: ChainId::root(0), height: BlockHeight(0), - index: Some(0), + txn_index: Some(0), + operation_index: Some(0), authenticated_signer: None, authenticated_caller_id: None, } @@ -44,8 +44,22 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { state.description = Some(ChainDescription::Root(0)); let mut view = state.into_view().await; - let (app_id, app_desc) = - &create_dummy_user_application_registrations(&mut view.system.registry, 1).await?[0]; + let (app_id, app_desc, contract_blob, service_blob) = + &create_dummy_user_applications(1).await?[0]; + view.context() + .extra() + .blobs() + .insert(contract_blob.id(), contract_blob.clone()); + view.context() + .extra() + .blobs() + .insert(service_blob.id(), service_blob.clone()); + + let app_blob = Blob::new_application_description(app_desc.clone())?; + view.context() + .extra() + .blobs() + .insert(app_blob.id(), app_blob); let context = make_operation_context(); let mut controller = ResourceController::default(); @@ -57,7 +71,11 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { application_id: *app_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(app_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -77,10 +95,10 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -145,7 +163,11 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { ..make_operation_context() }; let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -264,10 +286,10 @@ async fn test_simulated_session() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -324,7 +346,11 @@ async fn test_simulated_session() -> anyhow::Result<()> { let context = make_operation_context(); let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -377,10 +403,10 @@ async fn test_simulated_session_leak() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -434,7 +460,11 @@ async fn test_simulated_session_leak() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -451,7 +481,7 @@ async fn test_rejecting_block_from_finalize() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 1).await?; - let (id, application) = applications + let (id, application, _, _) = applications .next() .expect("Mock application should be registered"); @@ -475,7 +505,11 @@ async fn test_rejecting_block_from_finalize() -> anyhow::Result<()> { application_id: id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -492,16 +526,16 @@ async fn test_rejecting_block_from_called_applications_finalize() -> anyhow::Res let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 4).await?; - let (first_id, first_application) = applications + let (first_id, first_application, _, _) = applications .next() .expect("First mock application should be registered"); - let (second_id, second_application) = applications + let (second_id, second_application, _, _) = applications .next() .expect("Second mock application should be registered"); - let (third_id, third_application) = applications + let (third_id, third_application, _, _) = applications .next() .expect("Third mock application should be registered"); - let (fourth_id, fourth_application) = applications + let (fourth_id, fourth_application, _, _) = applications .next() .expect("Fourth mock application should be registered"); @@ -546,7 +580,11 @@ async fn test_rejecting_block_from_called_applications_finalize() -> anyhow::Res application_id: first_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&first_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -563,16 +601,16 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 4).await?; - let (first_id, first_application) = applications + let (first_id, first_application, _, _) = applications .next() .expect("First mock application should be registered"); - let (second_id, second_application) = applications + let (second_id, second_application, _, _) = applications .next() .expect("Second mock application should be registered"); - let (third_id, third_application) = applications + let (third_id, third_application, _, _) = applications .next() .expect("Third mock application should be registered"); - let (fourth_id, fourth_application) = applications + let (fourth_id, fourth_application, _, _) = applications .next() .expect("Fourth mock application should be registered"); @@ -655,7 +693,11 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { let context = make_operation_context(); let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&first_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -667,20 +709,7 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { &mut controller, ) .await?; - view.update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await?; - - let applications = stream::iter([third_id, first_id]) - .then(|id| view.system.registry.describe_application(id)) - .try_collect() - .await?; - let registration_message = RawOutgoingMessage { - destination: Destination::from(destination_chain), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { applications }, - }; + let account = Account { chain_id: ChainId::root(0), owner: None, @@ -690,9 +719,6 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { assert_eq!( outcomes, vec![ - ExecutionOutcome::System( - RawExecutionOutcome::default().with_message(registration_message) - ), ExecutionOutcome::User( fourth_id, RawExecutionOutcome::default().with_refund_grant_to(Some(account)) @@ -745,10 +771,10 @@ async fn test_cross_application_call_from_finalize() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, _target_application) = applications + let (target_id, _target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -773,7 +799,11 @@ async fn test_cross_application_call_from_finalize() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -798,10 +828,10 @@ async fn test_cross_application_call_from_finalize_of_called_application() -> an let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -833,7 +863,11 @@ async fn test_cross_application_call_from_finalize_of_called_application() -> an application_id: caller_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -857,10 +891,10 @@ async fn test_calling_application_again_from_finalize() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -892,7 +926,11 @@ async fn test_calling_application_again_from_finalize() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await; @@ -919,10 +957,10 @@ async fn test_cross_application_error() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -949,7 +987,10 @@ async fn test_cross_application_error() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new())), &mut controller, ) .await, @@ -968,7 +1009,7 @@ async fn test_simple_message() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 1).await?; - let (application_id, application) = applications + let (application_id, application, _, _) = applications .next() .expect("Caller mock application should be registered"); @@ -995,7 +1036,11 @@ async fn test_simple_message() -> anyhow::Result<()> { let context = make_operation_context(); let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&application_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -1007,23 +1052,7 @@ async fn test_simple_message() -> anyhow::Result<()> { &mut controller, ) .await?; - view.update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await?; - let application_description = view - .system - .registry - .describe_application(application_id) - .await?; - let registration_message = RawOutgoingMessage { - destination: Destination::from(destination_chain), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { - applications: vec![application_description], - }, - }; let account = Account { chain_id: ChainId::root(0), owner: None, @@ -1033,9 +1062,6 @@ async fn test_simple_message() -> anyhow::Result<()> { assert_eq!( outcomes, &[ - ExecutionOutcome::System( - RawExecutionOutcome::default().with_message(registration_message) - ), ExecutionOutcome::User( application_id, RawExecutionOutcome::default() @@ -1061,10 +1087,10 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 2).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -1100,7 +1126,11 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { let context = make_operation_context(); let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -1112,19 +1142,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { &mut controller, ) .await?; - view.update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await?; - let target_description = view.system.registry.describe_application(target_id).await?; - let registration_message = RawOutgoingMessage { - destination: Destination::from(destination_chain), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { - applications: vec![target_description], - }, - }; let account = Account { chain_id: ChainId::root(0), owner: None, @@ -1134,9 +1152,6 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { assert_eq!( outcomes, &[ - ExecutionOutcome::System( - RawExecutionOutcome::default().with_message(registration_message) - ), ExecutionOutcome::User( target_id, RawExecutionOutcome::default() @@ -1169,13 +1184,13 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 3).await?; - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); - let (middle_id, middle_application) = applications + let (middle_id, middle_application, _, _) = applications .next() .expect("Middle mock application should be registered"); - let (target_id, target_application) = applications + let (target_id, target_application, _, _) = applications .next() .expect("Target mock application should be registered"); @@ -1219,7 +1234,11 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { let context = make_operation_context(); let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -1232,18 +1251,6 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { ) .await?; - let target_description = view.system.registry.describe_application(target_id).await?; - let registration_message = RawOutgoingMessage { - destination: Destination::from(destination_chain), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { - applications: vec![target_description], - }, - }; - view.update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await?; let account = Account { chain_id: ChainId::root(0), owner: None, @@ -1252,9 +1259,6 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { assert_eq!( outcomes, &[ - ExecutionOutcome::System( - RawExecutionOutcome::default().with_message(registration_message) - ), ExecutionOutcome::User( target_id, RawExecutionOutcome::default() @@ -1300,15 +1304,15 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< let mut applications = register_mock_applications(&mut view, 3).await?; // The entrypoint application, which sends a message and calls other applications - let (caller_id, caller_application) = applications + let (caller_id, caller_application, _, _) = applications .next() .expect("Caller mock application should be registered"); // An application that does not send any messages - let (silent_target_id, silent_target_application) = applications + let (silent_target_id, silent_target_application, _, _) = applications .next() .expect("Target mock application that doesn't send messages should be registered"); // An application that sends a message when handling a cross-application call - let (sending_target_id, sending_target_application) = applications + let (sending_target_id, sending_target_application, _, _) = applications .next() .expect("Target mock application that sends a message should be registered"); @@ -1383,7 +1387,11 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< // Execute the operation, starting the test scenario let context = make_operation_context(); let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&caller_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -1395,38 +1403,6 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< &mut controller, ) .await?; - view.update_execution_outcomes_with_app_registrations(&mut txn_tracker) - .await?; - - // Describe the two applications that sent messages, and will therefore handle them in the - // other chains - let caller_description = view.system.registry.describe_application(caller_id).await?; - let sending_target_description = view - .system - .registry - .describe_application(sending_target_id) - .await?; - - // The registration message for the first destination chain - let first_registration_message = RawOutgoingMessage { - destination: Destination::from(first_destination_chain), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { - applications: vec![sending_target_description.clone(), caller_description], - }, - }; - // The registration message for the second destination chain - let second_registration_message = RawOutgoingMessage { - destination: Destination::from(second_destination_chain), - authenticated: false, - grant: Amount::ZERO, - kind: MessageKind::Simple, - message: SystemMessage::RegisterApplications { - applications: vec![sending_target_description], - }, - }; let account = Account { chain_id: ChainId::root(0), @@ -1438,11 +1414,6 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< assert_eq!( outcomes, &[ - ExecutionOutcome::System( - RawExecutionOutcome::default() - .with_message(first_registration_message) - .with_message(second_registration_message) - ), ExecutionOutcome::User( silent_target_id, RawExecutionOutcome::default().with_refund_grant_to(Some(account)), @@ -1493,7 +1464,7 @@ async fn test_open_chain() { }; let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 1).await.unwrap(); - let (application_id, application) = applications.next().unwrap(); + let (application_id, application, _, _) = applications.next().unwrap(); let context = OperationContext { height: BlockHeight(1), @@ -1531,7 +1502,11 @@ async fn test_open_chain() { application_id, bytes: vec![], }; - let mut txn_tracker = TransactionTracker::new(first_message_index, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + first_message_index, + Some(get_application_blob_oracle_responses(&application_id)), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), @@ -1595,7 +1570,7 @@ async fn test_close_chain() { }; let mut view = state.into_view().await; let mut applications = register_mock_applications(&mut view, 1).await.unwrap(); - let (application_id, application) = applications.next().unwrap(); + let (application_id, application, _, _) = applications.next().unwrap(); // The application is not authorized to close the chain. let context = make_operation_context(); @@ -1619,7 +1594,11 @@ async fn test_close_chain() { context, Timestamp::from(0), operation, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&application_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await @@ -1633,7 +1612,7 @@ async fn test_close_chain() { context, Timestamp::from(0), operation.into(), - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new(0, Some(Vec::new()), Arc::new(BTreeMap::new())), &mut controller, ) .await @@ -1655,7 +1634,11 @@ async fn test_close_chain() { context, Timestamp::from(0), operation, - &mut TransactionTracker::new(0, Some(Vec::new())), + &mut TransactionTracker::new( + 0, + Some(get_application_blob_oracle_responses(&application_id)), + Arc::new(BTreeMap::new()), + ), &mut controller, ) .await diff --git a/linera-execution/tests/test_system_execution.rs b/linera-execution/tests/test_system_execution.rs index 6dbfeb85d39..12909005bee 100644 --- a/linera-execution/tests/test_system_execution.rs +++ b/linera-execution/tests/test_system_execution.rs @@ -3,6 +3,8 @@ #![allow(clippy::field_reassign_with_default)] +use std::{collections::BTreeMap, sync::Arc}; + use linera_base::{ crypto::CryptoHash, data_types::{Amount, BlockHeight, Timestamp}, @@ -28,12 +30,13 @@ async fn test_simple_system_operation() -> anyhow::Result<()> { let context = OperationContext { chain_id: ChainId::root(0), height: BlockHeight(0), - index: Some(0), + txn_index: Some(0), + operation_index: Some(0), authenticated_signer: None, authenticated_caller_id: None, }; let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new()), Arc::new(BTreeMap::new())); view.execute_operation( context, Timestamp::from(0), @@ -82,7 +85,7 @@ async fn test_simple_system_message() -> anyhow::Result<()> { refund_grant_to: None, }; let mut controller = ResourceController::default(); - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new()), Arc::new(BTreeMap::new())); view.execute_message( context, Timestamp::from(0), diff --git a/linera-execution/tests/wasm.rs b/linera-execution/tests/wasm.rs index f4a5d4e0859..6501a0bd943 100644 --- a/linera-execution/tests/wasm.rs +++ b/linera-execution/tests/wasm.rs @@ -3,12 +3,12 @@ #![cfg(with_wasm_runtime)] -use std::sync::Arc; +use std::{collections::BTreeMap, sync::Arc}; use counter::CounterAbi; use linera_base::{ - data_types::{Amount, BlockHeight, Timestamp}, - identifiers::{Account, ChainDescription, ChainId}, + data_types::{Amount, Blob, BlockHeight, OracleResponse, Timestamp}, + identifiers::{Account, ChainDescription, ChainId, UserApplicationId}, }; use linera_execution::{ test_utils::{create_dummy_user_application_description, SystemExecutionState}, @@ -41,12 +41,10 @@ async fn test_fuel_for_counter_wasm_application( let mut view = state .into_view_with(ChainId::root(0), ExecutionRuntimeConfig::default()) .await; - let app_desc = create_dummy_user_application_description(1); - let app_id = view - .system - .registry - .register_application(app_desc.clone()) - .await?; + let (app_desc, contract_blob, service_blob) = create_dummy_user_application_description(1); + let app_id = UserApplicationId::try_from(&app_desc)?; + + let app_blob = Blob::new_application_description(app_desc)?; let contract = WasmContractModule::from_file("tests/fixtures/counter_contract.wasm", wasm_runtime).await?; @@ -62,12 +60,27 @@ async fn test_fuel_for_counter_wasm_application( .user_services() .insert(app_id, service.into()); + let contract_blob_id = contract_blob.id(); + let service_blob_id = service_blob.id(); + let app_blob_id = app_blob.id(); + + view.context() + .extra() + .blobs() + .insert(contract_blob_id, contract_blob); + view.context() + .extra() + .blobs() + .insert(service_blob_id, service_blob); + view.context().extra().blobs().insert(app_blob_id, app_blob); + let app_id = app_id.with_abi::(); let context = OperationContext { chain_id: ChainId::root(0), height: BlockHeight(0), - index: Some(0), + txn_index: Some(0), + operation_index: Some(0), authenticated_signer: None, authenticated_caller_id: None, }; @@ -83,12 +96,21 @@ async fn test_fuel_for_counter_wasm_application( tracker: ResourceTracker::default(), account: None, }; + for increment in &increments { let account = Account { chain_id: ChainId::root(0), owner: None, }; - let mut txn_tracker = TransactionTracker::new(0, Some(Vec::new())); + let mut txn_tracker = TransactionTracker::new( + 0, + Some(vec![ + OracleResponse::Blob(contract_blob_id), + OracleResponse::Blob(service_blob_id), + OracleResponse::Blob(app_blob_id), + ]), + Arc::new(BTreeMap::new()), + ); view.execute_operation( context, Timestamp::from(0), diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 84fa57d0432..f5bcfd8f76a 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -27,10 +27,10 @@ Amount: NEWTYPESTRUCT: U128 ApplicationId: STRUCT: + - application_description_hash: + TYPENAME: CryptoHash - bytecode_id: TYPENAME: BytecodeId - - creation: - TYPENAME: MessageId ApplicationPermissions: STRUCT: - execute_operations: @@ -56,6 +56,10 @@ BlobContent: ServiceBytecode: NEWTYPE: TYPENAME: CompressedBytecode + 3: + ApplicationDescription: + NEWTYPE: + TYPENAME: UserApplicationDescription BlobId: STRUCT: - hash: @@ -70,6 +74,8 @@ BlobType: ContractBytecode: UNIT 2: ServiceBytecode: UNIT + 3: + ApplicationDescription: UNIT Block: STRUCT: - chain_id: @@ -902,18 +908,6 @@ SystemMessage: TYPENAME: ChainId - subscription: TYPENAME: ChannelSubscription - 7: - ApplicationCreated: UNIT - 8: - RegisterApplications: - STRUCT: - - applications: - SEQ: - TYPENAME: UserApplicationDescription - 9: - RequestApplication: - NEWTYPE: - TYPENAME: ApplicationId SystemOperation: ENUM: 0: @@ -993,21 +987,19 @@ SystemOperation: 11: CreateApplication: STRUCT: - - bytecode_id: - TYPENAME: BytecodeId - - parameters: BYTES - - instantiation_argument: BYTES + - application_id: + TYPENAME: ApplicationId + - creator_chain_id: + TYPENAME: ChainId + - block_height: + TYPENAME: BlockHeight + - operation_index: U32 + - instantiation_argument: + SEQ: U8 - required_application_ids: SEQ: TYPENAME: ApplicationId 12: - RequestApplication: - STRUCT: - - chain_id: - TYPENAME: ChainId - - application_id: - TYPENAME: ApplicationId - 13: Admin: NEWTYPE: TYPENAME: AdminOperation @@ -1030,8 +1022,11 @@ UserApplicationDescription: STRUCT: - bytecode_id: TYPENAME: BytecodeId - - creation: - TYPENAME: MessageId + - creator_chain_id: + TYPENAME: ChainId + - block_height: + TYPENAME: BlockHeight + - operation_index: U32 - parameters: BYTES - required_application_ids: SEQ: diff --git a/linera-sdk/src/contract/conversions_from_wit.rs b/linera-sdk/src/contract/conversions_from_wit.rs index 63de217976e..c2436590a9a 100644 --- a/linera-sdk/src/contract/conversions_from_wit.rs +++ b/linera-sdk/src/contract/conversions_from_wit.rs @@ -30,10 +30,10 @@ impl From for MessageId { impl From for ApplicationId { fn from(application_id: wit_system_api::ApplicationId) -> Self { - ApplicationId { - bytecode_id: application_id.bytecode_id.into(), - creation: application_id.creation.into(), - } + ApplicationId::new( + application_id.application_description_hash.into(), + application_id.bytecode_id.into(), + ) } } diff --git a/linera-sdk/src/contract/conversions_to_wit.rs b/linera-sdk/src/contract/conversions_to_wit.rs index 738040834c1..501c105a840 100644 --- a/linera-sdk/src/contract/conversions_to_wit.rs +++ b/linera-sdk/src/contract/conversions_to_wit.rs @@ -81,8 +81,8 @@ impl From for wit_system_api::ChainId { impl From for wit_system_api::ApplicationId { fn from(application_id: ApplicationId) -> Self { wit_system_api::ApplicationId { + application_description_hash: application_id.application_description_hash.into(), bytecode_id: application_id.bytecode_id.into(), - creation: application_id.creation.into(), } } } diff --git a/linera-sdk/src/service/conversions_from_wit.rs b/linera-sdk/src/service/conversions_from_wit.rs index 77a43608d08..87f32f5b5c5 100644 --- a/linera-sdk/src/service/conversions_from_wit.rs +++ b/linera-sdk/src/service/conversions_from_wit.rs @@ -6,7 +6,7 @@ use linera_base::{ crypto::CryptoHash, data_types::{Amount, BlockHeight, Timestamp}, - identifiers::{ApplicationId, BytecodeId, ChainId, MessageId, Owner}, + identifiers::{ApplicationId, BytecodeId, ChainId, Owner}, }; use super::wit::service_system_api as wit_system_api; @@ -54,22 +54,12 @@ impl From for CryptoHash { } } -impl From for MessageId { - fn from(message_id: wit_system_api::MessageId) -> Self { - MessageId { - chain_id: message_id.chain_id.into(), - height: message_id.height.into(), - index: message_id.index, - } - } -} - impl From for ApplicationId { fn from(application_id: wit_system_api::ApplicationId) -> Self { - ApplicationId { - bytecode_id: application_id.bytecode_id.into(), - creation: application_id.creation.into(), - } + ApplicationId::new( + application_id.application_description_hash.into(), + application_id.bytecode_id.into(), + ) } } diff --git a/linera-sdk/src/service/conversions_to_wit.rs b/linera-sdk/src/service/conversions_to_wit.rs index 8319f2af0f0..db15871329f 100644 --- a/linera-sdk/src/service/conversions_to_wit.rs +++ b/linera-sdk/src/service/conversions_to_wit.rs @@ -6,7 +6,7 @@ use linera_base::{ crypto::CryptoHash, data_types::BlockHeight, - identifiers::{ApplicationId, BytecodeId, ChainId, MessageId, Owner}, + identifiers::{ApplicationId, BytecodeId, ChainId, Owner}, }; use super::wit::service_system_api as wit_system_api; @@ -63,8 +63,8 @@ impl From for wit_system_api::ChainId { impl From for wit_system_api::ApplicationId { fn from(application_id: ApplicationId) -> Self { wit_system_api::ApplicationId { + application_description_hash: application_id.application_description_hash.into(), bytecode_id: application_id.bytecode_id.into(), - creation: application_id.creation.into(), } } } @@ -77,13 +77,3 @@ impl From for wit_system_api::BytecodeId { } } } - -impl From for wit_system_api::MessageId { - fn from(message_id: MessageId) -> Self { - wit_system_api::MessageId { - chain_id: message_id.chain_id.into(), - height: message_id.height.into(), - index: message_id.index, - } - } -} diff --git a/linera-sdk/src/test/block.rs b/linera-sdk/src/test/block.rs index 7f3129bad12..712a794b3c5 100644 --- a/linera-sdk/src/test/block.rs +++ b/linera-sdk/src/test/block.rs @@ -100,17 +100,6 @@ impl BlockBuilder { self } - /// Adds a request to register an application on this chain. - pub fn with_request_for_application( - &mut self, - application: ApplicationId, - ) -> &mut Self { - self.with_system_operation(SystemOperation::RequestApplication { - chain_id: application.creation.chain_id, - application_id: application.forget_abi(), - }) - } - /// Adds an operation to change this chain's ownership. pub fn with_owner_change( &mut self, diff --git a/linera-sdk/src/test/chain.rs b/linera-sdk/src/test/chain.rs index 06ae191b713..e04fe0adebb 100644 --- a/linera-sdk/src/test/chain.rs +++ b/linera-sdk/src/test/chain.rs @@ -15,15 +15,12 @@ use std::{ use cargo_toml::Manifest; use linera_base::{ crypto::{KeyPair, PublicKey}, - data_types::{Blob, BlockHeight, Bytecode, CompressedBytecode}, - identifiers::{ApplicationId, BytecodeId, ChainDescription, ChainId, MessageId}, -}; -use linera_chain::{data_types::Certificate, ChainError, ChainExecutionContext}; -use linera_core::{data_types::ChainInfoQuery, worker::WorkerError}; -use linera_execution::{ - system::{SystemExecutionError, SystemOperation, CREATE_APPLICATION_MESSAGE_INDEX}, - ExecutionError, Query, Response, + data_types::{Blob, BlockHeight, Bytecode, CompressedBytecode, UserApplicationDescription}, + identifiers::{ApplicationId, BytecodeId, ChainDescription, ChainId, UserApplicationId}, }; +use linera_chain::data_types::Certificate; +use linera_core::data_types::ChainInfoQuery; +use linera_execution::{system::SystemOperation, Query, Response}; use serde::Serialize; use tokio::{fs, sync::Mutex}; @@ -171,8 +168,8 @@ impl ActiveChain { .expect("Failed to obtain absolute application repository path"); Self::build_bytecodes_in(&repository_path).await; let (contract, service) = self.find_bytecodes_in(&repository_path).await; - let contract_blob = Blob::new_contract_bytecode(contract); - let service_blob = Blob::new_service_bytecode(service); + let contract_blob = Blob::new_contract_bytecode(contract).unwrap(); + let service_blob = Blob::new_service_bytecode(service).unwrap(); let contract_blob_hash = contract_blob.id().hash; let service_blob_hash = service_blob.id().hash; @@ -316,7 +313,7 @@ impl ActiveChain { /// bytecode to use, and fetch it. /// /// The application is instantiated using the instantiation parameters, which consist of the - /// global static `parameters`, the one time `instantiation_argument` and the + /// global static `parameters` and the one time `instantiation_argument` and the /// `required_application_ids` of the applications that the new application will depend on. pub async fn create_application( &mut self, @@ -333,15 +330,30 @@ impl ActiveChain { let parameters = serde_json::to_vec(¶meters).unwrap(); let instantiation_argument = serde_json::to_vec(&instantiation_argument).unwrap(); - for &dependency in &required_application_ids { - self.register_application(dependency).await; - } + let next_block_height = self.get_tip_height().await.try_add_one().unwrap(); + let application_description = UserApplicationDescription { + bytecode_id: bytecode_id.forget_abi(), + creator_chain_id: self.id(), + block_height: next_block_height, + operation_index: 0, + required_application_ids: required_application_ids.clone(), + parameters, + }; + let app_blob = Blob::new_application_description(application_description.clone()).unwrap(); + + self.validator + .worker() + .cache_recent_blob(Cow::Borrowed(&app_blob)) + .await; + let application_id = UserApplicationId::try_from(&application_description).unwrap(); let creation_certificate = self .add_block(|block| { block.with_system_operation(SystemOperation::CreateApplication { - bytecode_id: bytecode_id.forget_abi(), - parameters, + application_id, + creator_chain_id: application_description.creator_chain_id, + block_height: application_description.block_height, + operation_index: application_description.operation_index, instantiation_argument, required_application_ids, }); @@ -353,16 +365,10 @@ impl ActiveChain { .executed_block() .expect("Failed to obtain executed block from certificate"); assert_eq!(executed_block.messages().len(), 1); - let creation = MessageId { - chain_id: executed_block.block.chain_id, - height: executed_block.block.height, - index: CREATE_APPLICATION_MESSAGE_INDEX, - }; + assert_eq!(executed_block.block.chain_id, self.id()); + assert_eq!(executed_block.block.height, next_block_height); - ApplicationId { - bytecode_id: bytecode_id.just_abi(), - creation, - } + application_id.with_abi() } /// Returns whether this chain has been closed. @@ -375,58 +381,6 @@ impl ActiveChain { .is_closed() } - /// Registers on this chain an application created on another chain. - pub async fn register_application(&self, application_id: ApplicationId) { - if self.needs_application_description(application_id).await { - let source_chain = self.validator.get_chain(&application_id.creation.chain_id); - - let request_certificate = self - .add_block(|block| { - block.with_request_for_application(application_id); - }) - .await; - - let register_certificate = source_chain - .add_block(|block| { - block.with_messages_from(&request_certificate); - }) - .await; - - let final_certificate = self - .add_block(|block| { - block.with_messages_from(®ister_certificate); - }) - .await; - - assert_eq!(final_certificate.outgoing_message_count(), 0); - } - } - - /// Checks if the `application_id` is missing from this microchain. - async fn needs_application_description(&self, application_id: ApplicationId) -> bool { - let description_result = self - .validator - .worker() - .describe_application(self.id(), application_id.forget_abi()) - .await; - - match description_result { - Ok(_) => false, - Err(WorkerError::ChainError(boxed_chain_error)) - if matches!( - &*boxed_chain_error, - ChainError::ExecutionError( - ExecutionError::SystemError(SystemExecutionError::UnknownApplicationId(_)), - ChainExecutionContext::DescribeApplication, - ) - ) => - { - true - } - Err(_) => panic!("Failed to check known bytecode locations"), - } - } - /// Executes a `query` on an `application`'s state on this microchain. /// /// Returns the deserialized response from the `application`. diff --git a/linera-sdk/wit/contract-system-api.wit b/linera-sdk/wit/contract-system-api.wit index 814d7bfdcfa..99d9b003c3f 100644 --- a/linera-sdk/wit/contract-system-api.wit +++ b/linera-sdk/wit/contract-system-api.wit @@ -41,8 +41,8 @@ interface contract-system-api { } record application-id { + application-description-hash: crypto-hash, bytecode-id: bytecode-id, - creation: message-id, } record application-permissions { diff --git a/linera-sdk/wit/service-system-api.wit b/linera-sdk/wit/service-system-api.wit index d6d0639d602..1f0d03d0f1c 100644 --- a/linera-sdk/wit/service-system-api.wit +++ b/linera-sdk/wit/service-system-api.wit @@ -25,8 +25,8 @@ interface service-system-api { } record application-id { + application-description-hash: crypto-hash, bytecode-id: bytecode-id, - creation: message-id, } record block-height { @@ -57,12 +57,6 @@ interface service-system-api { trace, } - record message-id { - chain-id: chain-id, - height: block-height, - index: u32, - } - record owner { inner0: crypto-hash, } diff --git a/linera-service-graphql-client/gql/service_requests.graphql b/linera-service-graphql-client/gql/service_requests.graphql index 05da43f6298..4d9cf9cb0f1 100644 --- a/linera-service-graphql-client/gql/service_requests.graphql +++ b/linera-service-graphql-client/gql/service_requests.graphql @@ -203,6 +203,14 @@ query Chain( } } +query Application($chainId: ChainId!, $appId: ApplicationId!) { + application(chainId: $chainId, appId: $appId) { + id + link + description + } +} + query Applications($chainId: ChainId!) { applications(chainId: $chainId) { id diff --git a/linera-service-graphql-client/gql/service_schema.graphql b/linera-service-graphql-client/gql/service_schema.graphql index a5000ed9c1c..08b5a0d139f 100644 --- a/linera-service-graphql-client/gql/service_schema.graphql +++ b/linera-service-graphql-client/gql/service_schema.graphql @@ -688,11 +688,6 @@ type MutationRoot { Creates a new application. """ createApplication(chainId: ChainId!, bytecodeId: BytecodeId!, parameters: String!, instantiationArgument: String!, requiredApplicationIds: [ApplicationId!]!): ApplicationId! - """ - Requests a `RegisterApplications` message from another chain so the application can be used - on this one. - """ - requestApplication(chainId: ChainId!, applicationId: ApplicationId!, targetChainId: ChainId): CryptoHash! } """ @@ -808,8 +803,9 @@ scalar PublicKey type QueryRoot { chain(chainId: ChainId!): ChainStateExtendedView! - applications(chainId: ChainId!): [ApplicationOverview!]! chains: Chains! + application(chainId: ChainId!, appId: ApplicationId!): ApplicationOverview! + applications(chainId: ChainId!): [ApplicationOverview!]! block(hash: CryptoHash, chainId: ChainId!): HashedCertificateValue blocks(from: CryptoHash, chainId: ChainId!, limit: Int): [HashedCertificateValue!]! """ diff --git a/linera-service-graphql-client/src/service.rs b/linera-service-graphql-client/src/service.rs index 5f54c0e879c..0d770722eea 100644 --- a/linera-service-graphql-client/src/service.rs +++ b/linera-service-graphql-client/src/service.rs @@ -96,6 +96,14 @@ pub struct Chains; )] pub struct Applications; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "gql/service_schema.graphql", + query_path = "gql/service_requests.graphql", + response_derives = "Debug, Serialize, Clone, PartialEq" +)] +pub struct Application; + #[derive(GraphQLQuery)] #[graphql( schema_path = "gql/service_schema.graphql", diff --git a/linera-service-graphql-client/tests/test.rs b/linera-service-graphql-client/tests/test.rs index 722e90bf365..12e395c8894 100644 --- a/linera-service-graphql-client/tests/test.rs +++ b/linera-service-graphql-client/tests/test.rs @@ -16,7 +16,7 @@ use linera_service::cli_wrappers::{ LineraNet, LineraNetConfig, Network, }; use linera_service_graphql_client::{ - applications, block, blocks, chains, request, transfer, Applications, Block, Blocks, Chains, + application, block, blocks, chains, request, transfer, Application, Block, Blocks, Chains, Transfer, }; use test_case::test_case; @@ -97,18 +97,19 @@ async fn test_end_to_end_queries(config: impl LineraNetConfig) { .chains; assert_eq!((chains.default, chains.list), node_chains); - // check applications query - let applications = request::( + // check application query + let application = request::( req_client, url, - applications::Variables { - chain_id: node_chains.0.unwrap(), + application::Variables { + chain_id, + app_id: application_id.forget_abi().to_string(), }, ) .await .unwrap() - .applications; - assert_eq!(applications[0].id, application_id.forget_abi().to_string()); + .application; + assert_eq!(application.id, application_id.forget_abi().to_string()); // check blocks query let blocks = request::( diff --git a/linera-service/src/cli_wrappers/wallet.rs b/linera-service/src/cli_wrappers/wallet.rs index 375c84744c8..a7dca19b0fe 100644 --- a/linera-service/src/cli_wrappers/wallet.rs +++ b/linera-service/src/cli_wrappers/wallet.rs @@ -361,25 +361,6 @@ impl ClientWrapper { Ok(stdout.trim().parse::()?.with_abi()) } - /// Runs `linera request-application` - pub async fn request_application( - &self, - application_id: ApplicationId, - requester_chain_id: ChainId, - target_chain_id: Option, - ) -> Result { - let mut command = self.command().await?; - command - .arg("request-application") - .arg(application_id.to_string()) - .args(["--requester-chain-id", &requester_chain_id.to_string()]); - if let Some(target_chain_id) = target_chain_id { - command.args(["--target-chain-id", &target_chain_id.to_string()]); - } - let stdout = command.spawn_and_wait_for_stdout().await?; - Ok(stdout.trim().parse()?) - } - /// Runs `linera service`. pub async fn run_node_service( &self, @@ -931,12 +912,10 @@ impl NodeService { chain_id: &ChainId, application_id: &ApplicationId, ) -> Result> { - let application_id = application_id.forget_abi().to_string(); - let values = self.try_get_applications_uri(chain_id).await?; - let Some(link) = values.get(&application_id) else { - bail!("Could not find application URI: {application_id}"); - }; - Ok(ApplicationWrapper::from(link.to_string())) + let link = self + .try_get_application_uri(chain_id, &application_id.forget_abi()) + .await?; + Ok(ApplicationWrapper::from(link)) } pub async fn try_get_applications_uri( @@ -963,6 +942,21 @@ impl NodeService { .collect() } + pub async fn try_get_application_uri( + &self, + chain_id: &ChainId, + app_id: &ApplicationId, + ) -> Result { + let query = format!( + "query {{ application(chainId: \"{chain_id}\", appId: \"{app_id}\") {{ link }}}}" + ); + let data = self.query_node(query).await?; + Ok(data["application"]["link"] + .as_str() + .context("missing link field in response")? + .to_string()) + } + pub async fn publish_data_blob( &self, chain_id: &ChainId, @@ -1092,23 +1086,6 @@ impl NodeService { .with_abi()) } - pub async fn request_application( - &self, - chain_id: &ChainId, - application_id: &ApplicationId, - ) -> Result { - let application_id = application_id.forget_abi(); - let query = format!( - "mutation {{ requestApplication(\ - chainId: \"{chain_id}\", \ - applicationId: \"{application_id}\") \ - }}" - ); - let data = self.query_node(query).await?; - serde_json::from_value(data["requestApplication"].clone()) - .context("missing requestApplication field in response") - } - pub async fn subscribe( &self, subscriber_chain_id: ChainId, diff --git a/linera-service/src/linera/main.rs b/linera-service/src/linera/main.rs index 322dc3de8fe..ce43acdc077 100644 --- a/linera-service/src/linera/main.rs +++ b/linera-service/src/linera/main.rs @@ -647,9 +647,7 @@ impl Runnable for Job { .await?; if let Some(id) = fungible_application_id { - context - .supply_fungible_tokens(&key_pairs, id, max_in_flight) - .await?; + context.supply_fungible_tokens(&key_pairs, id).await?; } // For this command, we create proposals and gather certificates without using @@ -915,29 +913,6 @@ impl Runnable for Job { println!("{}", application_id); } - RequestApplication { - application_id, - target_chain_id, - requester_chain_id, - } => { - let requester_chain_id = - requester_chain_id.unwrap_or_else(|| context.default_chain()); - info!("Requesting application for chain {}", requester_chain_id); - let chain_client = context.make_chain_client(requester_chain_id); - let certificate = context - .apply_client_command(&chain_client, |chain_client| { - let chain_client = chain_client.clone(); - async move { - chain_client - .request_application(application_id, target_chain_id) - .await - } - }) - .await - .context("Failed to request application")?; - debug!("{:?}", certificate); - } - Assign { key, message_id } => { let chain_id = ChainId::child(message_id); info!( @@ -1281,7 +1256,6 @@ fn log_file_name_for(command: &ClientCommand) -> Cow<'static, str> { | ClientCommand::ReadDataBlob { .. } | ClientCommand::CreateApplication { .. } | ClientCommand::PublishAndCreate { .. } - | ClientCommand::RequestApplication { .. } | ClientCommand::Keygen { .. } | ClientCommand::Assign { .. } | ClientCommand::Wallet { .. } diff --git a/linera-service/src/node_service.rs b/linera-service/src/node_service.rs index 4ca17be9bfa..3738e0547b1 100644 --- a/linera-service/src/node_service.rs +++ b/linera-service/src/node_service.rs @@ -13,17 +13,17 @@ use async_graphql::{ use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription}; use axum::{extract::Path, http::StatusCode, response, response::IntoResponse, Extension, Router}; use futures::{ - future::{self}, + future::{self, try_join_all}, lock::Mutex, Future, }; use linera_base::{ crypto::{CryptoError, CryptoHash, PublicKey}, data_types::{ - Amount, ApplicationPermissions, BlobBytes, Bytecode, TimeDelta, Timestamp, + Amount, ApplicationPermissions, Blob, BlobBytes, Bytecode, TimeDelta, Timestamp, UserApplicationDescription, }, - identifiers::{ApplicationId, BytecodeId, ChainId, Owner, UserApplicationId}, + identifiers::{ApplicationId, BlobId, BlobType, BytecodeId, ChainId, Owner, UserApplicationId}, ownership::{ChainOwnership, TimeoutConfig}, BcsHexParseError, }; @@ -644,29 +644,30 @@ where }) .await } +} - /// Requests a `RegisterApplications` message from another chain so the application can be used - /// on this one. - async fn request_application( - &self, - chain_id: ChainId, - application_id: UserApplicationId, - target_chain_id: Option, - ) -> Result { - loop { - let client = self.clients.try_client_lock(&chain_id).await?; - let result = client - .request_application(application_id, target_chain_id) - .await; - self.context.lock().await.update_wallet(&client).await?; - let timeout = match result? { - ClientOutcome::Committed(certificate) => return Ok(certificate.hash()), - ClientOutcome::WaitForTimeout(timeout) => timeout, - }; - let mut stream = client.subscribe().await?; - drop(client); - wait_for_next_round(&mut stream, timeout).await; - } +impl QueryRoot +where + P: ValidatorNodeProvider + Send + Sync + 'static, + S: Storage + Clone + Send + Sync + 'static, +{ + async fn get_application_blob( + app_id: &UserApplicationId, + client: &ChainClient, + ) -> Result { + let blob_id = BlobId::new( + app_id.application_description_hash, + BlobType::ApplicationDescription, + ); + + Ok( + if let Ok(blob) = client.storage_client().read_blob(blob_id).await { + blob + } else { + client.receive_certificate_for_blob(blob_id).await?; + client.storage_client().read_blob(blob_id).await? + }, + ) } } @@ -682,28 +683,53 @@ where Ok(ChainStateExtendedView::new(view)) } + async fn chains(&self) -> Result { + Ok(Chains { + list: self.clients.0.lock().await.keys().cloned().collect(), + default: self.default_chain, + }) + } + + async fn application( + &self, + chain_id: ChainId, + app_id: UserApplicationId, + ) -> Result { + let client = self.clients.try_client_lock(&chain_id).await?; + let blob = Self::get_application_blob(&app_id, &client).await?; + + Ok(ApplicationOverview::new( + app_id, + blob.into_inner_application_description() + .expect("Should be able to get the inner application description!"), + self.port, + chain_id, + )) + } + async fn applications(&self, chain_id: ChainId) -> Result, Error> { let client = self.clients.try_client_lock(&chain_id).await?; - let applications = client + let application_ids = client .chain_state_view() .await? .execution_state - .list_applications() + .users + .indices() .await?; - - let overviews = applications - .into_iter() - .map(|(id, description)| ApplicationOverview::new(id, description, self.port, chain_id)) - .collect(); - - Ok(overviews) - } - - async fn chains(&self) -> Result { - Ok(Chains { - list: self.clients.0.lock().await.keys().cloned().collect(), - default: self.default_chain, - }) + try_join_all(application_ids.into_iter().map(|id| { + let client = client.clone(); + async move { + let blob = Self::get_application_blob(&id, &client).await?; + Ok(ApplicationOverview::new( + id, + blob.into_inner_application_description() + .expect("Should be able to get the inner application description!"), + self.port, + chain_id, + )) + } + })) + .await } async fn block( diff --git a/linera-service/tests/linera_net_tests.rs b/linera-service/tests/linera_net_tests.rs index ab2e619216d..e79c0f0cb5e 100644 --- a/linera-service/tests/linera_net_tests.rs +++ b/linera-service/tests/linera_net_tests.rs @@ -607,16 +607,6 @@ async fn test_wasm_end_to_end_social_user_pub_sub(config: impl LineraNetConfig) .run_node_service(port2, ProcessInbox::Automatic) .await?; - // Request the application so chain 2 has it, too. - node_service2 - .request_application(&chain2, &application_id) - .await?; - - // First chain1 receives the request for application and then - // chain2 receives the requested application - node_service1.process_inbox(&chain1).await?; - node_service2.process_inbox(&chain2).await?; - let app2 = node_service2 .make_application(&chain2, &application_id) .await?; @@ -625,6 +615,7 @@ async fn test_wasm_end_to_end_social_user_pub_sub(config: impl LineraNetConfig) .await?; node_service1.process_inbox(&chain1).await?; + node_service2.process_inbox(&chain2).await?; // The returned hash should now be the latest one. let query = format!("query {{ chain(chainId: \"{chain2}\") {{ tipState {{ blockHash }} }} }}"); @@ -743,8 +734,12 @@ async fn test_wasm_end_to_end_fungible( .await?, ); - // Needed synchronization though removing it does not get error in 100% of cases. - assert_eq!(node_service1.process_inbox(&chain1).await?.len(), 1); + if example_name == "native-fungible" { + // native-fungible needs to do transfers on instantiation to properly set + // the initial balances of the accounts. So we'll have a `Credit` message being + // processed here. + assert_eq!(node_service1.process_inbox(&chain1).await?.len(), 1); + } let expected_balances = [ (account_owner1, Amount::from_tokens(5)), @@ -913,8 +908,12 @@ async fn test_wasm_end_to_end_same_wallet_fungible( .await?, ); - // Needed synchronization though removing it does not get error in 100% of cases. - assert_eq!(node_service.process_inbox(&chain1).await?.len(), 1); + if example_name == "native-fungible" { + // native-fungible needs to do transfers on instantiation to properly set + // the initial balances of the accounts. So we'll have a `Credit` message being + // processed here. + assert_eq!(node_service.process_inbox(&chain1).await?.len(), 1); + } let expected_balances = [ (account_owner1, Amount::from_tokens(5)), @@ -1305,7 +1304,7 @@ async fn test_wasm_end_to_end_crowd_funding(config: impl LineraNetConfig) -> Res // TODO(#723): This hack will disappear soon. &application_id_fungible, &state_crowd, - &[application_id_fungible.forget_abi()], + &[], None, ) .await?; @@ -1337,14 +1336,6 @@ async fn test_wasm_end_to_end_crowd_funding(config: impl LineraNetConfig) -> Res ) .await; - // Register the campaign on chain2. - node_service2 - .request_application(&chain2, &application_id_crowd) - .await?; - - // Chain2 requests the application from chain1, so chain1 has - // to receive the request and then chain2 receive the answer. - assert_eq!(node_service1.process_inbox(&chain1).await?.len(), 1); assert_eq!(node_service2.process_inbox(&chain2).await?.len(), 1); let app_crowd2 = node_service2 @@ -1402,8 +1393,7 @@ async fn test_wasm_end_to_end_matching_engine(config: impl LineraNetConfig) -> R client_b.wallet_init(&[], FaucetOption::None).await?; // Create initial server and client config. - let (contract_fungible_a, service_fungible_a) = client_a.build_example("fungible").await?; - let (contract_fungible_b, service_fungible_b) = client_b.build_example("fungible").await?; + let (contract_fungible, service_fungible) = client_a.build_example("fungible").await?; let (contract_matching, service_matching) = client_admin.build_example("matching-engine").await?; @@ -1425,63 +1415,48 @@ async fn test_wasm_end_to_end_matching_engine(config: impl LineraNetConfig) -> R accounts: accounts1, }; + // Now creating the service and exporting the applications + let port1 = get_node_port().await; + let port2 = get_node_port().await; + let port3 = get_node_port().await; + let mut node_service_admin = client_admin + .run_node_service(port1, ProcessInbox::Skip) + .await?; + let mut node_service_a = client_a.run_node_service(port2, ProcessInbox::Skip).await?; + let mut node_service_b = client_b.run_node_service(port3, ProcessInbox::Skip).await?; + // Setting up the application fungible on chain_a and chain_b + let fungible_bytecode_id = node_service_admin + .publish_bytecode::< + fungible::FungibleTokenAbi, + fungible::Parameters, + fungible::InitialState + >(&chain_admin, contract_fungible, service_fungible).await?; + let params0 = fungible::Parameters::new("ZERO"); - let token0 = client_a - .publish_and_create::( - contract_fungible_a, - service_fungible_a, + let token0 = node_service_a + .create_application( + &chain_a, + &fungible_bytecode_id, ¶ms0, &state_fungible0, &[], - None, ) .await?; let params1 = fungible::Parameters::new("ONE"); - let token1 = client_b - .publish_and_create::( - contract_fungible_b, - service_fungible_b, + let token1 = node_service_b + .create_application( + &chain_b, + &fungible_bytecode_id, ¶ms1, &state_fungible1, &[], - None, ) .await?; - // Now creating the service and exporting the applications - let port1 = get_node_port().await; - let port2 = get_node_port().await; - let port3 = get_node_port().await; - let mut node_service_admin = client_admin - .run_node_service(port1, ProcessInbox::Skip) - .await?; - let mut node_service_a = client_a.run_node_service(port2, ProcessInbox::Skip).await?; - let mut node_service_b = client_b.run_node_service(port3, ProcessInbox::Skip).await?; - - node_service_a - .request_application(&chain_a, &token1) - .await?; - node_service_b - .request_application(&chain_b, &token0) - .await?; - node_service_admin - .request_application(&chain_admin, &token0) - .await?; - node_service_admin - .request_application(&chain_admin, &token1) - .await?; - - // In an operation node_service_a.request_application(&chain_a, app_b) - // chain_b needs to process the request first and then chain_a - // the answer. - node_service_a.process_inbox(&chain_a).await?; - node_service_b.process_inbox(&chain_b).await?; - node_service_a.process_inbox(&chain_a).await?; - node_service_admin.process_inbox(&chain_admin).await?; - let app_fungible0_a = FungibleApp(node_service_a.make_application(&chain_a, &token0).await?); let app_fungible1_a = FungibleApp(node_service_a.make_application(&chain_a, &token1).await?); + let app_fungible0_b = FungibleApp(node_service_b.make_application(&chain_b, &token0).await?); let app_fungible1_b = FungibleApp(node_service_b.make_application(&chain_b, &token1).await?); app_fungible0_a @@ -1498,6 +1473,7 @@ async fn test_wasm_end_to_end_matching_engine(config: impl LineraNetConfig) -> R (owner_admin, Amount::ZERO), ]) .await; + let app_fungible0_admin = FungibleApp( node_service_admin .make_application(&chain_admin, &token0) @@ -1535,34 +1511,13 @@ async fn test_wasm_end_to_end_matching_engine(config: impl LineraNetConfig) -> R ) .await?; let application_id_matching = node_service_admin - .create_application( - &chain_admin, - &bytecode_id, - ¶meter, - &(), - &[token0.forget_abi(), token1.forget_abi()], - ) + .create_application(&chain_admin, &bytecode_id, ¶meter, &(), &[]) .await?; let app_matching_admin = MatchingEngineApp( node_service_admin .make_application(&chain_admin, &application_id_matching) .await?, ); - node_service_a - .request_application(&chain_a, &application_id_matching) - .await?; - node_service_b - .request_application(&chain_b, &application_id_matching) - .await?; - - // First chain_admin needs to process the two requests and - // then chain_a / chain_b the answers. - assert_eq!( - node_service_admin.process_inbox(&chain_admin).await?.len(), - 1 - ); - assert_eq!(node_service_a.process_inbox(&chain_a).await?.len(), 1); - assert_eq!(node_service_b.process_inbox(&chain_b).await?.len(), 1); let app_matching_a = MatchingEngineApp( node_service_a @@ -1876,13 +1831,7 @@ async fn test_wasm_end_to_end_amm(config: impl LineraNetConfig) -> Result<()> { .publish_bytecode::(&chain_amm, contract_amm, service_amm) .await?; let application_id_amm = node_service_amm - .create_application( - &chain_amm, - &bytecode_id, - ¶meters, - &(), - &[token0.forget_abi(), token1.forget_abi()], - ) + .create_application(&chain_amm, &bytecode_id, ¶meters, &(), &[]) .await?; let owner_amm_app = AccountOwner::Application(application_id_amm.forget_abi()); @@ -1893,18 +1842,6 @@ async fn test_wasm_end_to_end_amm(config: impl LineraNetConfig) -> Result<()> { .make_application(&chain_amm, &application_id_amm) .await?, ); - node_service0 - .request_application(&chain0, &application_id_amm) - .await?; - node_service1 - .request_application(&chain1, &application_id_amm) - .await?; - - // The chain_amm must first requests those two requests - // and then chain0 / chain1 must handle the answers. - assert_eq!(node_service_amm.process_inbox(&chain_amm).await?.len(), 1); - assert_eq!(node_service0.process_inbox(&chain0).await?.len(), 1); - assert_eq!(node_service1.process_inbox(&chain1).await?.len(), 1); let app_amm0 = AmmApp( node_service0 diff --git a/linera-storage/src/db_storage.rs b/linera-storage/src/db_storage.rs index 62ae2ae16b8..94294b582cb 100644 --- a/linera-storage/src/db_storage.rs +++ b/linera-storage/src/db_storage.rs @@ -215,6 +215,7 @@ pub struct DbStorage { wasm_runtime: Option, user_contracts: Arc>, user_services: Arc>, + blobs: Arc>, execution_runtime_config: ExecutionRuntimeConfig, } @@ -377,6 +378,7 @@ where execution_runtime_config: self.execution_runtime_config, user_contracts: self.user_contracts.clone(), user_services: self.user_services.clone(), + blobs: self.blobs.clone(), }; let root_key = bcs::to_bytes(&BaseKey::ChainState(chain_id))?; let store = self.store.clone_with_root_key(&root_key)?; @@ -467,9 +469,8 @@ where let maybe_blob_bytes = self.store.read_value::>(&blob_key).await?; #[cfg(with_metrics)] READ_BLOB_COUNTER.with_label_values(&[]).inc(); - let blob_bytes = - maybe_blob_bytes.ok_or_else(|| ViewError::not_found("value for blob ID", blob_id))?; - Ok(Blob::new_with_id_unchecked(blob_id, blob_bytes)) + let blob_bytes = maybe_blob_bytes.ok_or_else(|| ViewError::BlobNotFoundOnRead(blob_id))?; + Ok(Blob::new_with_id_unchecked(blob_id, blob_bytes)?) } async fn read_blobs(&self, blob_ids: &[BlobId]) -> Result>, ViewError> { @@ -487,9 +488,11 @@ where .iter() .zip(maybe_blob_bytes) .map(|(blob_id, maybe_blob_bytes)| { - maybe_blob_bytes.map(|blob_bytes| Blob::new_with_id_unchecked(*blob_id, blob_bytes)) + maybe_blob_bytes + .map(|blob_bytes| Blob::new_with_id_unchecked(*blob_id, blob_bytes)) + .transpose() }) - .collect()) + .collect::>, _>>()?) } async fn read_blob_state(&self, blob_id: BlobId) -> Result { @@ -679,7 +682,7 @@ where #[cfg(with_metrics)] WRITE_BLOB_COUNTER.with_label_values(&[]).inc(); let blob_key = bcs::to_bytes(&BaseKey::Blob(blob.id()))?; - batch.put_key_value(blob_key.to_vec(), &blob.inner_bytes())?; + batch.put_key_value(blob_key.to_vec(), &blob.inner_bytes()?)?; Ok(()) } @@ -719,6 +722,7 @@ where wasm_runtime, user_contracts: Arc::new(DashMap::new()), user_services: Arc::new(DashMap::new()), + blobs: Arc::new(DashMap::new()), execution_runtime_config: ExecutionRuntimeConfig::default(), } } diff --git a/linera-storage/src/lib.rs b/linera-storage/src/lib.rs index 927b982c15c..5149dacbfb0 100644 --- a/linera-storage/src/lib.rs +++ b/linera-storage/src/lib.rs @@ -296,7 +296,7 @@ pub trait Storage: Sized { ); let contract_blob = self.read_blob(contract_bytecode_blob_id).await?; let compressed_contract_bytecode = CompressedBytecode { - compressed_bytes: contract_blob.inner_bytes(), + compressed_bytes: contract_blob.inner_bytes()?, }; let contract_bytecode = linera_base::task::spawn_blocking(move || compressed_contract_bytecode.decompress()) @@ -335,7 +335,7 @@ pub trait Storage: Sized { ); let service_blob = self.read_blob(service_bytecode_blob_id).await?; let compressed_service_bytecode = CompressedBytecode { - compressed_bytes: service_blob.inner_bytes(), + compressed_bytes: service_blob.inner_bytes()?, }; let service_bytecode = linera_base::task::spawn_blocking(move || compressed_service_bytecode.decompress()) @@ -366,6 +366,7 @@ pub struct ChainRuntimeContext { execution_runtime_config: ExecutionRuntimeConfig, user_contracts: Arc>, user_services: Arc>, + blobs: Arc>, } #[async_trait] @@ -389,11 +390,18 @@ where &self.user_services } + fn blobs(&self) -> &Arc> { + &self.blobs + } + async fn get_user_contract( &self, description: &UserApplicationDescription, ) -> Result { - match self.user_contracts.entry(description.into()) { + match self + .user_contracts + .entry(UserApplicationId::try_from(description)?) + { Entry::Occupied(entry) => Ok(entry.get().clone()), Entry::Vacant(entry) => { let contract = self.storage.load_contract(description).await?; @@ -407,7 +415,10 @@ where &self, description: &UserApplicationDescription, ) -> Result { - match self.user_services.entry(description.into()) { + match self + .user_services + .entry(UserApplicationId::try_from(description)?) + { Entry::Occupied(entry) => Ok(entry.get().clone()), Entry::Vacant(entry) => { let service = self.storage.load_service(description).await?; @@ -418,11 +429,22 @@ where } async fn get_blob(&self, blob_id: BlobId) -> Result { - Ok(self.storage.read_blob(blob_id).await?) + match self.blobs.entry(blob_id) { + Entry::Occupied(entry) => Ok(entry.get().clone()), + Entry::Vacant(entry) => { + let blob = self.storage.read_blob(blob_id).await?; + entry.insert(blob.clone()); + Ok(blob) + } + } } async fn contains_blob(&self, blob_id: BlobId) -> Result { - self.storage.contains_blob(blob_id).await + if self.blobs.contains_key(&blob_id) { + Ok(true) + } else { + self.storage.contains_blob(blob_id).await + } } } diff --git a/linera-views/src/views/mod.rs b/linera-views/src/views/mod.rs index f644657ab2d..8dae9edffca 100644 --- a/linera-views/src/views/mod.rs +++ b/linera-views/src/views/mod.rs @@ -4,7 +4,7 @@ use std::{fmt::Debug, io::Write}; use async_trait::async_trait; -use linera_base::{crypto::CryptoHash, data_types::ArithmeticError}; +use linera_base::{crypto::CryptoHash, data_types::ArithmeticError, identifiers::BlobId}; pub use linera_views_derive::{ ClonableView, CryptoHashRootView, CryptoHashView, HashableView, RootView, View, }; @@ -154,6 +154,10 @@ pub enum ViewError { /// The value is too large for the client #[error("The value is too large for the client")] TooLargeValue, + + /// Blob not found when trying to read it. + #[error("Blob not found on storage read: {0}")] + BlobNotFoundOnRead(BlobId), } impl ViewError {