diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 131ff6ce3eab..79f326bdf39a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -64,7 +64,7 @@ jobs: cargo build --bin linera cargo run --bin linera -- resource-control-policy --block 0.0000001 cargo run --bin linera -- resource-control-policy --block 0.000000 - cargo run --bin linera -- faucet --amount 1000 --port 8079 a3edc33d8e951a1139333be8a4b56646b5598a8f51216e86592d881808972b07 & + cargo run --bin linera -- faucet --amount 1000 --port 8079 & - name: Run the remote-net tests run: | cargo test -p linera-service remote_net_grpc --features remote-net diff --git a/CLI.md b/CLI.md index 8e6f8ff29f16..26f98d941b58 100644 --- a/CLI.md +++ b/CLI.md @@ -720,12 +720,12 @@ Create an unassigned key pair Link an owner with a key pair in the wallet to a chain that was created for that owner -**Usage:** `linera assign --owner --message-id ` +**Usage:** `linera assign --owner --chain-id ` ###### **Options:** * `--owner ` — The owner to assign -* `--message-id ` — The ID of the message that created the chain. (This uniquely describes the chain and where it was created.) +* `--chain-id ` — The ID of the chain diff --git a/README.md b/README.md index 674336173487..8295c81ccd56 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ linera wallet init --faucet $FAUCET_URL INFO1=($(linera wallet request-chain --faucet $FAUCET_URL)) INFO2=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN1="${INFO1[0]}" -ACCOUNT1="${INFO1[3]}" +ACCOUNT1="${INFO1[2]}" CHAIN2="${INFO2[0]}" -ACCOUNT2="${INFO2[3]}" +ACCOUNT2="${INFO2[2]}" # Show the different chains tracked by the wallet. linera wallet show diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 125308c4ee89..cacb138c7e3c 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -2981,6 +2981,7 @@ name = "hex-game" version = "0.1.0" dependencies = [ "async-graphql", + "bcs", "linera-sdk", "log", "serde", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d20818583380..8efd74af6ad3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -2,15 +2,15 @@ resolver = "2" members = [ "amm", - "counter", "call-evm-counter", + "counter", "counter-no-graphql", "crowd-funding", "ethereum-tracker", "fungible", "gen-nft", - "how-to/perform-http-requests", "hex-game", + "how-to/perform-http-requests", "llm", "matching-engine", "meta-counter", diff --git a/examples/amm/README.md b/examples/amm/README.md index 4f986844048e..b0d8b9fdc61b 100644 --- a/examples/amm/README.md +++ b/examples/amm/README.md @@ -64,9 +64,9 @@ INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_AMM="${INFO_AMM[0]}" CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_AMM="${INFO_AMM[3]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_AMM="${INFO_AMM[2]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` Now we have to publish and create the fungible applications. The flag `--wait-for-outgoing-messages` waits until a quorum of validators has confirmed that all sent cross-chain messages have been delivered. diff --git a/examples/counter/README.md b/examples/counter/README.md index 0f3845991875..cf785ba7876d 100644 --- a/examples/counter/README.md +++ b/examples/counter/README.md @@ -49,7 +49,7 @@ linera wallet init --faucet $FAUCET_URL INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" -OWNER_1="${INFO_1[3]}" +OWNER_1="${INFO_1[2]}" ``` Now, compile the `counter` application WebAssembly binaries, publish and create an application instance. diff --git a/examples/crowd-funding/README.md b/examples/crowd-funding/README.md index b972b22b6c14..31d21b1216b7 100644 --- a/examples/crowd-funding/README.md +++ b/examples/crowd-funding/README.md @@ -77,8 +77,8 @@ INFO_1=($(linera --with-wallet 1 wallet request-chain --faucet $FAUCET_URL)) INFO_2=($(linera --with-wallet 2 wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` Note that `linera --with-wallet 1` is equivalent to `linera --wallet "$LINERA_WALLET_1" diff --git a/examples/fungible/README.md b/examples/fungible/README.md index 0d99a1138065..871fdc3d1aa9 100644 --- a/examples/fungible/README.md +++ b/examples/fungible/README.md @@ -64,10 +64,10 @@ linera wallet init --faucet $FAUCET_URL INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" -OWNER_1="${INFO_1[3]}" +OWNER_1="${INFO_1[2]}" INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_2="${INFO_2[0]}" -OWNER_2="${INFO_2[3]}" +OWNER_2="${INFO_2[2]}" ``` Now, compile the `fungible` application WebAssembly binaries, and publish them as an application diff --git a/examples/gen-nft/README.md b/examples/gen-nft/README.md index c68de253c40b..35c79f494dfb 100644 --- a/examples/gen-nft/README.md +++ b/examples/gen-nft/README.md @@ -61,8 +61,8 @@ INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL)) INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` Next, compile the `non-fungible` application WebAssembly binaries, and publish them as an application module: diff --git a/examples/hex-game/Cargo.toml b/examples/hex-game/Cargo.toml index c1a14941e66a..2d266ff39548 100644 --- a/examples/hex-game/Cargo.toml +++ b/examples/hex-game/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] async-graphql.workspace = true +bcs.workspace = true linera-sdk.workspace = true log.workspace = true serde.workspace = true diff --git a/examples/hex-game/README.md b/examples/hex-game/README.md index d7fdfbc1d4c6..3736f13a0244 100644 --- a/examples/hex-game/README.md +++ b/examples/hex-game/README.md @@ -60,8 +60,8 @@ INFO_1=($(linera --with-wallet 1 wallet request-chain --faucet $FAUCET_URL)) INFO_2=($(linera --with-wallet 2 wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` Note that `linera --with-wallet 1` or `linera -w1` is equivalent to `linera --wallet @@ -123,25 +123,24 @@ query { gameChains { entry(key: "$OWNER_1") { value { - messageId chainId + chainId } } } } ``` -Set the `QUERY_RESULT` variable to have the result returned by the previous query, and `HEX_CHAIN` and `MESSAGE_ID` will be properly set for you. -Alternatively you can set the variables to the `chainId` and `messageId` values, respectively, returned by the previous query yourself. -Using the message ID, we can assign the new chain to the key in each wallet: +Set the `QUERY_RESULT` variable to have the result returned by the previous query, and `HEX_CHAIN` will be properly set for you. +Alternatively you can set the variable to the `chainId`, returned by the previous query yourself. +Using the chain ID, we can assign the new chain to the key in each wallet: ```bash kill %% && sleep 1 # Kill the service so we can use CLI commands for wallet 0. HEX_CHAIN=$(echo "$QUERY_RESULT" | jq -r '.gameChains.entry.value[0].chainId') -MESSAGE_ID=$(echo "$QUERY_RESULT" | jq -r '.gameChains.entry.value[0].messageId') -linera -w1 assign --owner $OWNER_1 --message-id $MESSAGE_ID -linera -w2 assign --owner $OWNER_2 --message-id $MESSAGE_ID +linera -w1 assign --owner $OWNER_1 --chain-id $HEX_CHAIN +linera -w2 assign --owner $OWNER_2 --chain-id $HEX_CHAIN linera -w1 service --port 8080 & linera -w2 service --port 8081 & diff --git a/examples/hex-game/src/contract.rs b/examples/hex-game/src/contract.rs index 8e4281e69fa1..4f8306df6f76 100644 --- a/examples/hex-game/src/contract.rs +++ b/examples/hex-game/src/contract.rs @@ -155,17 +155,14 @@ impl HexContract { ); let app_id = self.runtime.application_id(); let permissions = ApplicationPermissions::new_single(app_id.forget_abi()); - let (message_id, chain_id) = self.runtime.open_chain(ownership, permissions, fee_budget); + let chain_id = self.runtime.open_chain(ownership, permissions, fee_budget); for owner in &players { self.state .game_chains .get_mut_or_default(owner) .await .unwrap() - .insert(GameChain { - message_id, - chain_id, - }); + .insert(GameChain { chain_id }); } self.runtime.send_message( chain_id, diff --git a/examples/hex-game/src/state.rs b/examples/hex-game/src/state.rs index 4185e7b6e4d8..d8e74f512338 100644 --- a/examples/hex-game/src/state.rs +++ b/examples/hex-game/src/state.rs @@ -6,7 +6,7 @@ use std::collections::BTreeSet; use async_graphql::SimpleObject; use hex_game::{Board, Clock, Timeouts}; use linera_sdk::{ - linera_base_types::{AccountOwner, ChainId, MessageId}, + linera_base_types::{AccountOwner, ChainId}, views::{linera_views, MapView, RegisterView, RootView, ViewStorageContext}, }; use serde::{Deserialize, Serialize}; @@ -14,8 +14,6 @@ use serde::{Deserialize, Serialize}; /// The IDs of a temporary chain for a single game of Hex. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, SimpleObject)] pub struct GameChain { - /// The ID of the `OpenChain` message that created the chain. - pub message_id: MessageId, /// The ID of the temporary game chain itself. pub chain_id: ChainId, } diff --git a/examples/hex-game/tests/hex_game.rs b/examples/hex-game/tests/hex_game.rs index 6ce0cb498950..069e0ac6a6fb 100644 --- a/examples/hex-game/tests/hex_game.rs +++ b/examples/hex-game/tests/hex_game.rs @@ -8,7 +8,7 @@ use hex_game::{HexAbi, Operation, Timeouts}; use linera_sdk::{ linera_base_types::{ - AccountSecretKey, Amount, ChainDescription, Secp256k1SecretKey, TimeDelta, + AccountSecretKey, Amount, BlobType, ChainDescription, Secp256k1SecretKey, TimeDelta, }, test::{ActiveChain, QueryOutcome, TestValidator}, }; @@ -34,8 +34,15 @@ async fn hex_game() { .await; let block = certificate.inner().block(); - let message_id = block.message_id_for_operation(0, 0).unwrap(); - let description = ChainDescription::Child(message_id); + let description = block + .created_blobs() + .into_iter() + .filter_map(|(blob_id, blob)| { + (blob_id.blob_type == BlobType::ChainDescription) + .then(|| bcs::from_bytes::(blob.content().bytes()).unwrap()) + }) + .next() + .unwrap(); let mut chain = ActiveChain::new(key_pair1.copy(), description, validator); chain @@ -108,8 +115,15 @@ async fn hex_game_clock() { .await; let block = certificate.inner().block(); - let message_id = block.message_id_for_operation(0, 0).unwrap(); - let description = ChainDescription::Child(message_id); + let description = block + .created_blobs() + .into_iter() + .filter_map(|(blob_id, blob)| { + (blob_id.blob_type == BlobType::ChainDescription) + .then(|| bcs::from_bytes::(blob.content().bytes()).unwrap()) + }) + .next() + .unwrap(); let mut chain = ActiveChain::new(key_pair1.copy(), description, validator.clone()); chain diff --git a/examples/how-to/perform-http-requests/tests/http_requests.rs b/examples/how-to/perform-http-requests/tests/http_requests.rs index 62d89dc121f0..24936664bb0c 100644 --- a/examples/how-to/perform-http-requests/tests/http_requests.rs +++ b/examples/how-to/perform-http-requests/tests/http_requests.rs @@ -22,7 +22,7 @@ async fn service_query_performs_http_request() -> anyhow::Result<()> { let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url, ()).await; validator @@ -89,7 +89,7 @@ async fn service_sends_valid_http_response_to_contract() -> anyhow::Result<()> { let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url, ()).await; validator @@ -119,7 +119,7 @@ async fn contract_rejects_invalid_http_response_from_service() { let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url.clone(), ()).await; validator @@ -151,7 +151,7 @@ async fn contract_accepts_valid_http_response_it_obtains_by_itself() -> anyhow:: let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url, ()).await; validator @@ -181,7 +181,7 @@ async fn contract_rejects_invalid_http_response_it_obtains_by_itself() { let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url, ()).await; validator @@ -214,7 +214,7 @@ async fn contract_accepts_valid_http_response_from_oracle() -> anyhow::Result<() let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url, ()).await; validator @@ -245,7 +245,7 @@ async fn contract_rejects_invalid_http_response_from_oracle() { let port = http_server.port(); let url = format!("http://localhost:{port}/"); - let (validator, application_id, chain) = + let (mut validator, application_id, chain) = TestValidator::with_current_application::(url, ()).await; validator diff --git a/examples/matching-engine/README.md b/examples/matching-engine/README.md index e069628f2406..14a476685599 100644 --- a/examples/matching-engine/README.md +++ b/examples/matching-engine/README.md @@ -75,9 +75,9 @@ INFO_3=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" CHAIN_3="${INFO_3[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" -OWNER_3="${INFO_3[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" +OWNER_3="${INFO_3[2]}" ``` Publish and create two `fungible` applications whose IDs will be used as a diff --git a/examples/native-fungible/README.md b/examples/native-fungible/README.md index d5865b4cd848..5d1c5e283765 100644 --- a/examples/native-fungible/README.md +++ b/examples/native-fungible/README.md @@ -46,8 +46,8 @@ INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL)) INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` Compile the `native-fungible` application WebAssembly binaries, and publish them as an application diff --git a/examples/native-fungible/tests/transfers.rs b/examples/native-fungible/tests/transfers.rs index e28ed64756b7..4803939b4164 100644 --- a/examples/native-fungible/tests/transfers.rs +++ b/examples/native-fungible/tests/transfers.rs @@ -9,7 +9,7 @@ use std::collections::{BTreeMap, HashMap}; use fungible::{self, FungibleTokenAbi}; use linera_sdk::{ - linera_base_types::{Account, AccountOwner, Amount, ChainId, CryptoHash}, + linera_base_types::{Account, AccountOwner, Amount, CryptoHash}, test::{ActiveChain, Recipient, TestValidator}, }; @@ -28,7 +28,7 @@ async fn chain_balance_transfers() { .await; let transfer_amount = Amount::ONE; - let funding_chain = validator.get_chain(&ChainId::root(0)); + let funding_chain = validator.get_chain(&validator.admin_chain_id()); let recipient = Recipient::chain(recipient_chain.id()); let transfer_certificate = funding_chain @@ -62,7 +62,7 @@ async fn transfer_to_owner() { .await; let transfer_amount = Amount::from_tokens(2); - let funding_chain = validator.get_chain(&ChainId::root(0)); + let funding_chain = validator.get_chain(&validator.admin_chain_id()); let owner = AccountOwner::from(CryptoHash::test_hash("owner")); let account = Account::new(recipient_chain.id(), owner); let recipient = Recipient::Account(account); @@ -98,7 +98,7 @@ async fn transfer_to_multiple_owners() { let number_of_owners = 10; let transfer_amounts = (1..=number_of_owners).map(Amount::from_tokens); - let funding_chain = validator.get_chain(&ChainId::root(0)); + let funding_chain = validator.get_chain(&validator.admin_chain_id()); let account_owners = (1..=number_of_owners) .map(|index| AccountOwner::from(CryptoHash::test_hash(format!("owner{index}")))) @@ -146,7 +146,7 @@ async fn emptied_account_disappears_from_queries() { .await; let transfer_amount = Amount::from_tokens(100); - let funding_chain = validator.get_chain(&ChainId::root(0)); + let funding_chain = validator.get_chain(&validator.admin_chain_id()); let owner = AccountOwner::from(recipient_chain.public_key()); let recipient = Recipient::Account(Account::new(recipient_chain.id(), owner)); diff --git a/examples/non-fungible/README.md b/examples/non-fungible/README.md index 243be7eef696..06f0983b9a04 100644 --- a/examples/non-fungible/README.md +++ b/examples/non-fungible/README.md @@ -61,8 +61,8 @@ INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL)) INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` ```bash diff --git a/examples/rfq/README.md b/examples/rfq/README.md index dc8c7a403ae3..ccd685ecdf53 100644 --- a/examples/rfq/README.md +++ b/examples/rfq/README.md @@ -77,8 +77,8 @@ INFO_1=($(linera --with-wallet 1 wallet request-chain --faucet $FAUCET_URL)) INFO_2=($(linera --with-wallet 2 wallet request-chain --faucet $FAUCET_URL)) CHAIN_1="${INFO_1[0]}" CHAIN_2="${INFO_2[0]}" -OWNER_1="${INFO_1[3]}" -OWNER_2="${INFO_2[3]}" +OWNER_1="${INFO_1[2]}" +OWNER_2="${INFO_2[2]}" ``` Note that `linera --with-wallet 1` is equivalent to `linera --wallet "$LINERA_WALLET_1" diff --git a/examples/rfq/src/contract.rs b/examples/rfq/src/contract.rs index 5ee166f3975b..a1c4586f19ee 100644 --- a/examples/rfq/src/contract.rs +++ b/examples/rfq/src/contract.rs @@ -334,7 +334,7 @@ impl RfqContract { ); let app_id = self.runtime.application_id(); let permissions = ApplicationPermissions::new_single(app_id.forget_abi()); - let (_, temp_chain_id) = self.runtime.open_chain(ownership, permissions, fee_budget); + let temp_chain_id = self.runtime.open_chain(ownership, permissions, fee_budget); // transfer tokens to the new chain let transfer = fungible::Operation::Transfer { diff --git a/linera-base/src/data_types.rs b/linera-base/src/data_types.rs index 204ef7c886c9..960eef0f48cb 100644 --- a/linera-base/src/data_types.rs +++ b/linera-base/src/data_types.rs @@ -9,6 +9,7 @@ use std::ops; #[cfg(with_metrics)] use std::sync::LazyLock; use std::{ + collections::BTreeMap, fmt::{self, Display}, fs, hash::Hash, @@ -37,6 +38,7 @@ use crate::{ ApplicationId, BlobId, BlobType, ChainId, EventId, GenericApplicationId, ModuleId, StreamId, }, limited_writer::{LimitedWriter, LimitedWriterError}, + ownership::ChainOwnership, time::{Duration, SystemTime}, vm::VmRuntime, }; @@ -706,6 +708,30 @@ impl Amount { } } +/// What created a chain. +#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize)] +pub enum ChainOrigin { + /// The chain was created by the genesis configuration. + Root(u32), + /// The chain was created by a call from another chain. + Child { + /// The parent of this chain. + parent: ChainId, + /// The block height in the parent at which this chain was created. + block_height: BlockHeight, + /// The index of this chain among chains created at the same block height in the parent + /// chain. + chain_index: u32, + }, +} + +impl ChainOrigin { + /// Whether the chain was created by another chain. + pub fn is_child(&self) -> bool { + matches!(self, ChainOrigin::Child { .. }) + } +} + /// A number identifying the configuration of the chain (aka the committee). #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug)] pub struct Epoch(pub u32); @@ -784,12 +810,77 @@ impl Epoch { } } +/// The initial configuration for a new chain. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub struct InitialChainConfig { + /// The ownership configuration of the new chain. + pub ownership: ChainOwnership, + /// The ID of the admin chain. + pub admin_id: Option, + /// The epoch in which the chain is created. + pub epoch: Epoch, + /// Serialized committees corresponding to epochs. + pub committees: BTreeMap>, + /// The initial chain balance. + pub balance: Amount, + /// The initial application permissions. + pub application_permissions: ApplicationPermissions, +} + +/// Initial chain configuration and chain origin. +#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize)] +pub struct ChainDescription { + origin: ChainOrigin, + timestamp: Timestamp, + config: InitialChainConfig, +} + +impl ChainDescription { + /// Creates a new [`ChainDescription`]. + pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self { + Self { + origin, + config, + timestamp, + } + } + + /// Returns the [`ChainId`] based on this [`ChainDescription`]. + pub fn id(&self) -> ChainId { + ChainId::from(self) + } + + /// Returns the [`ChainOrigin`] describing who created this chain. + pub fn origin(&self) -> ChainOrigin { + self.origin + } + + /// Returns a reference to the [`InitialChainConfig`] of the chain. + pub fn config(&self) -> &InitialChainConfig { + &self.config + } + + /// Returns the timestamp of when the chain was created. + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + + /// Whether the chain was created by another chain. + pub fn is_child(&self) -> bool { + self.origin.is_child() + } +} + +impl BcsHashable<'_> for ChainDescription {} + /// Permissions for applications on a chain. #[derive( Default, Debug, PartialEq, Eq, + PartialOrd, + Ord, Hash, Clone, Serialize, @@ -1137,6 +1228,13 @@ impl BlobContent { BlobContent::new(BlobType::Committee, committee) } + /// Creates a new chain description [`BlobContent`] from a [`ChainDescription`]. + pub fn new_chain_description(chain_description: &ChainDescription) -> Self { + let bytes = bcs::to_bytes(&chain_description) + .expect("Serializing a ChainDescription should not fail!"); + BlobContent::new(BlobType::ChainDescription, bytes) + } + /// Gets a reference to the blob's bytes. pub fn bytes(&self) -> &[u8] { &self.bytes @@ -1198,7 +1296,7 @@ impl Blob { Blob::new(BlobContent::new_data(bytes)) } - /// Creates a new contract bytecode [`BlobContent`] from the provided bytes. + /// Creates a new contract bytecode [`Blob`] from the provided bytes. pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self { Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode)) } @@ -1208,19 +1306,28 @@ impl Blob { Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode)) } - /// Creates a new service bytecode [`BlobContent`] from the provided bytes. + /// Creates a new service bytecode [`Blob`] from the provided bytes. pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self { Blob::new(BlobContent::new_service_bytecode(compressed_bytecode)) } - /// Creates a new application description [`BlobContent`] from the provided - /// description. + /// Creates a new application description [`Blob`] from the provided description. pub fn new_application_description(application_description: &ApplicationDescription) -> Self { Blob::new(BlobContent::new_application_description( application_description, )) } + /// Creates a new committee [`Blob`] from the provided bytes. + pub fn new_committee(committee: impl Into>) -> Self { + Blob::new(BlobContent::new_committee(committee)) + } + + /// Creates a new chain description [`Blob`] from a [`ChainDescription`]. + pub fn new_chain_description(chain_description: &ChainDescription) -> Self { + Blob::new(BlobContent::new_chain_description(chain_description)) + } + /// A content-addressed blob ID i.e. the hash of the `Blob`. pub fn id(&self) -> BlobId { BlobId { @@ -1352,6 +1459,10 @@ doc_scalar!( Round, "A number to identify successive attempts to decide a value in a consensus protocol." ); +doc_scalar!( + ChainDescription, + "Initial chain configuration and chain origin." +); doc_scalar!(OracleResponse, "A record of a single oracle response."); doc_scalar!(BlobContent, "A blob of binary data."); doc_scalar!( diff --git a/linera-base/src/identifiers.rs b/linera-base/src/identifiers.rs index 43acaa18c968..4c69c5535e1a 100644 --- a/linera-base/src/identifiers.rs +++ b/linera-base/src/identifiers.rs @@ -21,10 +21,10 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{ bcs_scalar, crypto::{ - AccountPublicKey, BcsHashable, CryptoError, CryptoHash, Ed25519PublicKey, EvmPublicKey, + AccountPublicKey, CryptoError, CryptoHash, Ed25519PublicKey, EvmPublicKey, Secp256k1PublicKey, }, - data_types::BlockHeight, + data_types::{BlobContent, BlockHeight, ChainDescription}, doc_scalar, hex_debug, vm::VmRuntime, }; @@ -112,22 +112,6 @@ impl FromStr for Account { } } -/// How to create a chain. -#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize)] -pub enum ChainDescription { - /// The chain was created by the genesis configuration. - Root(u32), - /// The chain was created by a message from another chain. - Child(MessageId), -} - -impl ChainDescription { - /// Whether the chain was created by another chain. - pub fn is_child(&self) -> bool { - matches!(self, ChainDescription::Child(_)) - } -} - /// The unique identifier (UID) of a chain. This is currently computed as the hash value /// of a [`ChainDescription`]. #[derive( @@ -181,6 +165,8 @@ pub enum BlobType { ApplicationDescription, /// A blob containing a committee of validators. Committee, + /// A blob containing a chain description. + ChainDescription, } impl Display for BlobType { @@ -964,33 +950,26 @@ impl fmt::Debug for ChainId { } } -impl From for ChainId { - fn from(description: ChainDescription) -> Self { - Self(CryptoHash::new(&description)) +impl<'a> From<&'a ChainDescription> for ChainId { + fn from(description: &'a ChainDescription) -> Self { + Self(CryptoHash::new(&BlobContent::new_chain_description( + description, + ))) } } -impl ChainId { - /// The chain ID representing the N-th chain created at genesis time. - pub fn root(index: u32) -> Self { - Self(CryptoHash::new(&ChainDescription::Root(index))) - } - - /// The chain ID representing the chain created by the given message. - pub fn child(id: MessageId) -> Self { - Self(CryptoHash::new(&ChainDescription::Child(id))) +impl From for ChainId { + fn from(description: ChainDescription) -> Self { + From::from(&description) } } -impl BcsHashable<'_> for ChainDescription {} - bcs_scalar!(ApplicationId, "A unique identifier for a user application"); doc_scalar!( GenericApplicationId, "A unique identifier for a user application or for the system application" ); bcs_scalar!(ModuleId, "A unique identifier for an application module"); -doc_scalar!(ChainDescription, "How to create a chain"); doc_scalar!( ChainId, "The unique identifier (UID) of a chain. This is currently computed as the hash value of a \ @@ -1014,31 +993,32 @@ mod tests { use assert_matches::assert_matches; - use super::{AccountOwner, BlobType, ChainId}; + use super::{AccountOwner, BlobType}; + use crate::{ + data_types::{Amount, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp}, + ownership::ChainOwnership, + }; - /// Verifies that chain IDs that are explicitly used in some example and test scripts don't - /// change. + /// Verifies that the way of computing chain IDs doesn't change. #[test] - fn chain_ids() { - assert_eq!( - &ChainId::root(0).to_string(), - "aee928d4bf3880353b4a3cd9b6f88e6cc6e5ed050860abae439e7782e9b2dfe8" - ); - assert_eq!( - &ChainId::root(1).to_string(), - "a3edc33d8e951a1139333be8a4b56646b5598a8f51216e86592d881808972b07" - ); - assert_eq!( - &ChainId::root(2).to_string(), - "678e9f66507069d38955b593e93ddf192a23a4087225fd307eadad44e5544ae3" - ); - assert_eq!( - &ChainId::root(9).to_string(), - "63620ea465af9e9e0e8e4dd8d21593cc3a719feac5f096df8440f90738f4dbd8" + fn chain_id_computing() { + let example_chain_origin = ChainOrigin::Root(0); + let example_chain_config = InitialChainConfig { + admin_id: None, + epoch: Epoch::ZERO, + ownership: ChainOwnership::single(AccountOwner::Reserved(0)), + balance: Amount::ZERO, + committees: [(Epoch::ZERO, vec![])].into_iter().collect(), + application_permissions: Default::default(), + }; + let description = ChainDescription::new( + example_chain_origin, + example_chain_config, + Timestamp::from(0), ); assert_eq!( - &ChainId::root(999).to_string(), - "5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844" + description.id().to_string(), + "f8c2f02adc0ac763ffd74a32d02aa37490d869605c356a2da55e1d09e7d253bf" ); } diff --git a/linera-base/src/unit_tests.rs b/linera-base/src/unit_tests.rs index c110b3069d54..a31c3dde0fb2 100644 --- a/linera-base/src/unit_tests.rs +++ b/linera-base/src/unit_tests.rs @@ -67,7 +67,7 @@ fn send_message_request_test_case() -> SendMessageRequest> { SendMessageRequest { authenticated: true, is_tracked: false, - destination: ChainId::root(0), + destination: ChainId(CryptoHash::test_hash("chain_id_0")), grant: Resources { bytes_to_read: 200, bytes_to_write: 0, @@ -91,7 +91,7 @@ fn send_message_request_test_case() -> SendMessageRequest> { /// Creates a dummy [`Account`] instance to use for the WIT roundtrip test. fn account_test_case() -> Account { Account { - chain_id: ChainId::root(10), + chain_id: ChainId(CryptoHash::test_hash("chain_id_10")), owner: AccountOwner::from(CryptoHash::test_hash("account")), } } @@ -99,7 +99,7 @@ fn account_test_case() -> Account { /// Creates a dummy [`MessageId`] instance to use for the WIT roundtrip test. fn message_id_test_case() -> MessageId { MessageId { - chain_id: ChainId::root(3), + chain_id: ChainId(CryptoHash::test_hash("chain_id_3")), height: BlockHeight(9_812_394), index: 7, } diff --git a/linera-chain/src/block.rs b/linera-chain/src/block.rs index bb275e7dee0c..5dc3848dbcac 100644 --- a/linera-chain/src/block.rs +++ b/linera-chain/src/block.rs @@ -13,16 +13,16 @@ use linera_base::{ crypto::{BcsHashable, CryptoHash}, data_types::{Blob, BlockHeight, Epoch, Event, OracleResponse, Timestamp}, hashed::Hashed, - identifiers::{AccountOwner, BlobId, ChainId, MessageId}, + identifiers::{AccountOwner, BlobId, BlobType, ChainId, MessageId}, }; -use linera_execution::{system::OpenChainConfig, BlobState, Operation, OutgoingMessage}; +use linera_execution::{BlobState, Operation, OutgoingMessage}; use serde::{ser::SerializeStruct, Deserialize, Serialize}; use thiserror::Error; use crate::{ data_types::{ - BlockExecutionOutcome, IncomingBundle, MessageAction, MessageBundle, OperationResult, - OutgoingMessageExt, PostedMessage, ProposedBlock, + BlockExecutionOutcome, IncomingBundle, MessageBundle, OperationResult, OutgoingMessageExt, + ProposedBlock, }, types::CertificateValue, }; @@ -497,6 +497,13 @@ impl Block { let mut blob_ids = self.oracle_blob_ids(); blob_ids.extend(self.published_blob_ids()); blob_ids.extend(self.created_blob_ids()); + if self.header.height == BlockHeight(0) { + // the initial block implicitly depends on the chain description blob + blob_ids.insert(BlobId::new( + self.header.chain_id.0, + BlobType::ChainDescription, + )); + } blob_ids } @@ -505,6 +512,9 @@ impl Block { self.oracle_blob_ids().contains(blob_id) || self.published_blob_ids().contains(blob_id) || self.created_blob_ids().contains(blob_id) + || (self.header.height == BlockHeight(0) + && (blob_id.blob_type == BlobType::ChainDescription + && blob_id.hash == self.header.chain_id.0)) } /// Returns all the published blob IDs in this block's operations. @@ -635,20 +645,6 @@ impl Block { .flatten() .map(|blob| (blob.id(), blob.clone())) } - - /// If the block's first message is `OpenChain`, returns the bundle, the message and - /// the configuration for the new chain. - pub fn starts_with_open_chain_message( - &self, - ) -> Option<(&IncomingBundle, &PostedMessage, &OpenChainConfig)> { - let in_bundle = self.body.incoming_bundles.first()?; - if in_bundle.action != MessageAction::Accept { - return None; - } - let posted_message = in_bundle.bundle.messages.first()?; - let config = posted_message.message.matches_open_chain()?; - Some((in_bundle, posted_message, config)) - } } impl BcsHashable<'_> for Block {} diff --git a/linera-chain/src/chain.rs b/linera-chain/src/chain.rs index 18fc6f136f13..853f3fc9f1eb 100644 --- a/linera-chain/src/chain.rs +++ b/linera-chain/src/chain.rs @@ -21,9 +21,9 @@ use linera_base::{ ownership::ChainOwnership, }; use linera_execution::{ - committee::Committee, system::OpenChainConfig, ExecutionRuntimeContext, ExecutionStateView, - Message, MessageContext, Operation, OperationContext, OutgoingMessage, Query, QueryContext, - QueryOutcome, ResourceController, ResourceTracker, ServiceRuntimeEndpoint, TransactionTracker, + committee::Committee, ExecutionRuntimeContext, ExecutionStateView, Message, MessageContext, + Operation, OperationContext, OutgoingMessage, Query, QueryContext, QueryOutcome, + ResourceController, ResourceTracker, ServiceRuntimeEndpoint, TransactionTracker, }; use linera_views::{ bucket_queue_view::BucketQueueView, @@ -38,7 +38,7 @@ use linera_views::{ use serde::{Deserialize, Serialize}; use crate::{ - block::{Block, ConfirmedBlock}, + block::ConfirmedBlock, data_types::{ BlockExecutionOutcome, ChainAndHeight, IncomingBundle, MessageAction, MessageBundle, OperationResult, PostedMessage, ProposedBlock, Transaction, @@ -47,7 +47,7 @@ use crate::{ manager::ChainManager, outbox::OutboxStateView, pending_blobs::PendingBlobsView, - ChainError, ChainExecutionContext, ExecutionResultExt, + ChainError, ChainExecutionContext, ExecutionError, ExecutionResultExt, }; #[cfg(test)] @@ -426,12 +426,31 @@ where } /// Invariant for the states of active chains. - pub fn ensure_is_active(&self) -> Result<(), ChainError> { - if self.is_active() { - Ok(()) - } else { - Err(ChainError::InactiveChain(self.chain_id())) + pub async fn ensure_is_active(&mut self, local_time: Timestamp) -> Result<(), ChainError> { + // Initialize ourselves. + if self + .execution_state + .system + .initialize_chain(self.chain_id()) + .await + .with_execution_context(ChainExecutionContext::Block)? + { + // the chain was already initialized + return Ok(()); } + // Recompute the state hash. + let hash = self.execution_state.crypto_hash().await?; + self.execution_state_hash.set(Some(hash)); + let maybe_committee = self.execution_state.system.current_committee().into_iter(); + // Last, reset the consensus state based on the current ownership. + self.manager.reset( + self.execution_state.system.ownership.get().clone(), + BlockHeight(0), + local_time, + maybe_committee.flat_map(|(_, committee)| committee.account_keys_and_weights()), + )?; + self.save().await?; + Ok(()) } /// Verifies that this chain is up-to-date and all the messages executed ahead of time @@ -505,16 +524,20 @@ where height: bundle.height, }; - // Handle immediate messages. - for posted_message in &bundle.messages { - if let Some(config) = posted_message.message.matches_open_chain() { - if self.execution_state.system.description.get().is_none() { - let message_id = chain_and_height.to_message_id(posted_message.index); - self.execute_init_message(message_id, config, bundle.timestamp, local_time) - .await?; - } + match self.ensure_is_active(local_time).await { + Ok(_) => (), + // if the only issue was that we couldn't initialize the chain because of a + // missing chain description blob, we might still want to update the inbox + Err(ChainError::ExecutionError(exec_err, _)) + if matches!(*exec_err, ExecutionError::BlobsNotFound(ref blobs) + if blobs.iter().all(|blob_id| { + blob_id.blob_type == BlobType::ChainDescription && blob_id.hash == chain_id.0 + })) => {} + err => { + return err; } } + // Process the inbox bundle and update the inbox state. let mut inbox = self.inboxes.try_load_entry_mut(origin).await?; #[cfg(with_metrics)] @@ -565,52 +588,6 @@ where } } - /// Verifies that the block's first message is `OpenChain`. Initializes the chain if necessary. - pub async fn execute_init_message_from( - &mut self, - block: &Block, - local_time: Timestamp, - ) -> Result<(), ChainError> { - let (in_bundle, posted_message, config) = block - .starts_with_open_chain_message() - .ok_or_else(|| ChainError::InactiveChain(block.header.chain_id))?; - if self.is_active() { - return Ok(()); // Already initialized. - } - let message_id = MessageId { - chain_id: in_bundle.origin, - height: in_bundle.bundle.height, - index: posted_message.index, - }; - self.execute_init_message(message_id, config, block.header.timestamp, local_time) - .await - } - - /// Initializes the chain using the given configuration. - async fn execute_init_message( - &mut self, - message_id: MessageId, - config: &OpenChainConfig, - timestamp: Timestamp, - local_time: Timestamp, - ) -> Result<(), ChainError> { - // Initialize ourself. - self.execution_state - .system - .initialize_chain(message_id, timestamp, config.clone()); - // Recompute the state hash. - let hash = self.execution_state.crypto_hash().await?; - self.execution_state_hash.set(Some(hash)); - let maybe_committee = self.execution_state.system.current_committee().into_iter(); - // Last, reset the consensus state based on the current ownership. - self.manager.reset( - self.execution_state.system.ownership.get().clone(), - BlockHeight(0), - local_time, - maybe_committee.flat_map(|(_, committee)| committee.account_keys_and_weights()), - ) - } - pub fn current_committee(&self) -> Result<(Epoch, &Committee), ChainError> { self.execution_state .system @@ -697,7 +674,7 @@ where /// Executes a block: first the incoming messages, then the main operation. /// Does not update chain state other than the execution state. #[expect(clippy::too_many_arguments)] - pub async fn execute_block_inner( + async fn execute_block_inner( chain: &mut ExecutionStateView, confirmed_log: &LogView, previous_message_blocks_view: &MapView, @@ -710,13 +687,13 @@ where #[cfg(with_metrics)] let _execution_latency = BLOCK_EXECUTION_LATENCY.measure_latency(); - assert_eq!(block.chain_id, chain.context().extra().chain_id()); - ensure!( *chain.system.timestamp.get() <= block.timestamp, ChainError::InvalidBlockTimestamp ); + chain.system.timestamp.set(block.timestamp); + let (_, committee) = chain .system .current_committee() @@ -766,6 +743,7 @@ where let mut replaying_oracle_responses = replaying_oracle_responses.map(Vec::into_iter); let mut next_message_index = 0; let mut next_application_index = 0; + let mut next_chain_index = 0; let mut oracle_responses = Vec::new(); let mut events = Vec::new(); let mut blobs = Vec::new(); @@ -786,6 +764,7 @@ where txn_index, next_message_index, next_application_index, + next_chain_index, maybe_responses, ); match transaction { @@ -819,6 +798,7 @@ where round, authenticated_signer: block.authenticated_signer, authenticated_caller_id: None, + timestamp: block.timestamp, }; Box::pin(chain.execute_operation( context, @@ -841,6 +821,7 @@ where .with_execution_context(chain_execution_context)?; next_message_index = txn_outcome.next_message_index; next_application_index = txn_outcome.next_application_index; + next_chain_index = txn_outcome.next_chain_index; if matches!( transaction, @@ -953,6 +934,13 @@ where published_blobs: &[Blob], replaying_oracle_responses: Option>>, ) -> Result { + assert_eq!( + block.chain_id, + self.execution_state.context().extra().chain_id() + ); + + self.ensure_is_active(local_time).await?; + Self::execute_block_inner( &mut self.execution_state, &self.confirmed_log, @@ -1030,6 +1018,7 @@ where message_id, authenticated_signer: posted_message.authenticated_signer, refund_grant_to: posted_message.refund_grant_to, + timestamp: block.timestamp, }; let mut grant = posted_message.grant; match incoming_bundle.action { @@ -1196,7 +1185,9 @@ where #[test] fn empty_block_size() { let size = bcs::serialized_size(&crate::block::Block::new( - crate::test::make_first_block(ChainId::root(0)), + crate::test::make_first_block( + linera_execution::test_utils::dummy_chain_description(0).id(), + ), crate::data_types::BlockExecutionOutcome::default(), )) .unwrap(); diff --git a/linera-chain/src/data_types.rs b/linera-chain/src/data_types.rs index 7c686fa3212f..3951b6ecfb04 100644 --- a/linera-chain/src/data_types.rs +++ b/linera-chain/src/data_types.rs @@ -16,9 +16,7 @@ use linera_base::{ doc_scalar, ensure, hex_debug, identifiers::{Account, AccountOwner, BlobId, ChainId, MessageId}, }; -use linera_execution::{ - committee::Committee, Message, MessageKind, Operation, OutgoingMessage, SystemMessage, -}; +use linera_execution::{committee::Committee, Message, MessageKind, Operation, OutgoingMessage}; use serde::{Deserialize, Serialize}; use crate::{ @@ -184,25 +182,6 @@ impl IncomingBundle { (message_id, posted_message) }) } - - /// Rearranges the messages in the bundle so that the first message is an `OpenChain` message. - /// Returns whether the `OpenChain` message was found at all. - pub fn put_openchain_at_front(bundles: &mut [IncomingBundle]) -> bool { - let Some(index) = bundles.iter().position(|msg| { - matches!( - msg.bundle.messages.first(), - Some(PostedMessage { - message: Message::System(SystemMessage::OpenChain(_)), - .. - }) - ) - }) else { - return false; - }; - - bundles[0..=index].rotate_right(1); - true - } } impl BcsHashable<'_> for IncomingBundle {} diff --git a/linera-chain/src/unit_tests/chain_tests.rs b/linera-chain/src/unit_tests/chain_tests.rs index 976705bcb2df..c59f15f52334 100644 --- a/linera-chain/src/unit_tests/chain_tests.rs +++ b/linera-chain/src/unit_tests/chain_tests.rs @@ -12,23 +12,22 @@ use std::{ use assert_matches::assert_matches; use axum::{routing::get, Router}; use linera_base::{ - crypto::{AccountPublicKey, CryptoHash, ValidatorPublicKey}, + crypto::{AccountPublicKey, ValidatorPublicKey}, data_types::{ - Amount, ApplicationDescription, ApplicationPermissions, Blob, BlockHeight, Bytecode, Epoch, - Timestamp, + Amount, ApplicationDescription, ApplicationPermissions, Blob, BlockHeight, Bytecode, + ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp, }, http, - identifiers::{AccountOwner, ApplicationId, ChainId, MessageId, ModuleId}, + identifiers::{AccountOwner, ApplicationId, ChainId, ModuleId}, ownership::ChainOwnership, vm::VmRuntime, }; use linera_execution::{ committee::{Committee, ValidatorState}, - system::{OpenChainConfig, Recipient}, + system::Recipient, test_utils::{ExpectedCall, MockApplication}, BaseRuntime, ContractRuntime, ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, - Message, MessageKind, Operation, ResourceControlPolicy, ServiceRuntime, SystemMessage, - SystemOperation, TestExecutionRuntimeContext, + Operation, ResourceControlPolicy, ServiceRuntime, SystemOperation, TestExecutionRuntimeContext, }; use linera_views::{ context::{Context as _, MemoryContext, ViewContext}, @@ -39,10 +38,8 @@ use test_case::test_case; use crate::{ block::{Block, ConfirmedBlock}, - data_types::{ - BlockExecutionOutcome, IncomingBundle, MessageAction, MessageBundle, ProposedBlock, - }, - test::{make_child_block, make_first_block, BlockTestExt, HttpServer, MessageTestExt}, + data_types::{BlockExecutionOutcome, ProposedBlock}, + test::{make_child_block, make_first_block, BlockTestExt, HttpServer}, ChainError, ChainExecutionContext, ChainStateView, }; @@ -63,70 +60,108 @@ where } } -fn make_app_description() -> (ApplicationDescription, 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 vm_runtime = VmRuntime::Wasm; - - let module_id = ModuleId::new(contract_blob.id().hash, service_blob.id().hash, vm_runtime); - ( - ApplicationDescription { - module_id, - creator_chain_id: admin_id(), - block_height: BlockHeight(2), - application_index: 0, - required_application_ids: vec![], - parameters: vec![], - }, - contract_blob, - service_blob, - ) +struct TestEnvironment { + admin_chain_description: ChainDescription, + created_descriptions: BTreeMap, } -fn admin_id() -> ChainId { - ChainId::root(0) -} +impl TestEnvironment { + fn new() -> Self { + let committee = Committee::make_simple(vec![( + ValidatorPublicKey::test_key(1), + AccountPublicKey::test_key(1), + )]); + let config = InitialChainConfig { + ownership: ChainOwnership::single(AccountPublicKey::test_key(0).into()), + admin_id: None, + epoch: Epoch::ZERO, + committees: iter::once(( + Epoch::ZERO, + bcs::to_bytes(&committee).expect("serializing a committee should not fail"), + )) + .collect(), + balance: Amount::from_tokens(10), + application_permissions: Default::default(), + }; + let origin = ChainOrigin::Root(0); + let admin_chain_description = ChainDescription::new(origin, config, Default::default()); + let admin_id = admin_chain_description.id(); + Self { + admin_chain_description: admin_chain_description.clone(), + created_descriptions: [(admin_id, admin_chain_description)].into_iter().collect(), + } + } -fn make_admin_message_id(height: BlockHeight) -> MessageId { - MessageId { - chain_id: admin_id(), - height, - index: 0, + fn admin_id(&self) -> ChainId { + self.admin_chain_description.id() + } + + fn description_blobs(&self) -> impl Iterator + '_ { + self.created_descriptions + .values() + .map(Blob::new_chain_description) + } + + fn make_open_chain_config(&self) -> InitialChainConfig { + self.admin_chain_description.config().clone() + } + + fn make_app_description(&self) -> (ApplicationDescription, 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 vm_runtime = VmRuntime::Wasm; + + let module_id = ModuleId::new(contract_blob.id().hash, service_blob.id().hash, vm_runtime); + ( + ApplicationDescription { + module_id, + creator_chain_id: self.admin_id(), + block_height: BlockHeight(2), + application_index: 0, + required_application_ids: vec![], + parameters: vec![], + }, + contract_blob, + service_blob, + ) } -} -fn make_open_chain_config() -> OpenChainConfig { - let committee = Committee::make_simple(vec![( - ValidatorPublicKey::test_key(1), - AccountPublicKey::test_key(1), - )]); - OpenChainConfig { - ownership: ChainOwnership::single(AccountPublicKey::test_key(0).into()), - admin_id: admin_id(), - epoch: Epoch::ZERO, - committees: iter::once((Epoch::ZERO, committee)).collect(), - balance: Amount::from_tokens(10), - application_permissions: Default::default(), + fn make_child_chain_description_with_config( + &mut self, + height: u64, + config: InitialChainConfig, + ) -> ChainDescription { + let origin = ChainOrigin::Child { + parent: self.admin_id(), + block_height: BlockHeight(height), + chain_index: 0, + }; + let config = InitialChainConfig { + admin_id: Some(self.admin_id()), + ..config + }; + let description = ChainDescription::new(origin, config, Timestamp::from(0)); + self.created_descriptions + .insert(description.id(), description.clone()); + description } } #[tokio::test] -async fn test_block_size_limit() { +async fn test_block_size_limit() -> anyhow::Result<()> { + let mut env = TestEnvironment::new(); + let time = Timestamp::from(0); - let message_id = make_admin_message_id(BlockHeight(3)); - let chain_id = ChainId::child(message_id); - let mut chain = ChainStateView::new(chain_id).await; // The size of the executed valid block below. - let maximum_block_size = 856; + let maximum_block_size = 260; - // Initialize the chain. - let mut config = make_open_chain_config(); + let mut config = env.make_open_chain_config(); config.committees.insert( Epoch(0), - Committee::new( + bcs::to_bytes(&Committee::new( BTreeMap::from([( ValidatorPublicKey::test_key(1), ValidatorState { @@ -139,34 +174,45 @@ async fn test_block_size_limit() { maximum_block_size, ..ResourceControlPolicy::default() }, - ), + )) + .expect("serializing a committee should not fail"), ); - chain - .execute_init_message(message_id, &config, time, time) - .await + let chain_desc = env.make_child_chain_description_with_config(3, config); + let chain_id = chain_desc.id(); + let owner = chain_desc + .config() + .ownership + .all_owners() + .next() + .copied() .unwrap(); - let open_chain_bundle = IncomingBundle { - origin: admin_id(), - bundle: MessageBundle { - certificate_hash: CryptoHash::test_hash("certificate"), - height: BlockHeight(1), - transaction_index: 0, - timestamp: time, - messages: vec![Message::System(SystemMessage::OpenChain(Box::new(config))) - .to_posted(0, MessageKind::Protected)], - }, - action: MessageAction::Accept, - }; - let valid_block = make_first_block(chain_id).with_incoming_bundle(open_chain_bundle.clone()); + let mut chain = ChainStateView::new(chain_id).await; + chain + .context() + .extra() + .add_blobs(env.description_blobs()) + .await?; + + // Initialize the chain. + + chain.ensure_is_active(time).await.unwrap(); + + let valid_block = make_first_block(chain_id) + .with_authenticated_signer(Some(owner)) + .with_operation(SystemOperation::Transfer { + owner: AccountOwner::CHAIN, + recipient: Recipient::chain(env.admin_id()), + amount: Amount::ONE, + }); // Any block larger than the valid block is rejected. let invalid_block = valid_block .clone() .with_operation(SystemOperation::Transfer { owner: AccountOwner::CHAIN, - recipient: Recipient::root(0), + recipient: Recipient::chain(env.admin_id()), amount: Amount::ONE, }); @@ -193,23 +239,35 @@ async fn test_block_size_limit() { bcs::serialized_size(&block).unwrap(), maximum_block_size as usize ); + + Ok(()) } #[tokio::test] async fn test_application_permissions() -> anyhow::Result<()> { + let mut env = TestEnvironment::new(); + let time = Timestamp::from(0); - let message_id = make_admin_message_id(BlockHeight(3)); - let chain_id = ChainId::child(message_id); - let mut chain = ChainStateView::new(chain_id).await; // Create a mock application. - let (app_description, contract_blob, service_blob) = make_app_description(); + let (app_description, contract_blob, service_blob) = env.make_app_description(); let application_id = ApplicationId::from(&app_description); let application = MockApplication::default(); + + let config = InitialChainConfig { + application_permissions: ApplicationPermissions::new_single(application_id), + ..env.make_open_chain_config() + }; + let chain_desc = env.make_child_chain_description_with_config(3, config); + let chain_id = chain_desc.id(); + + let mut chain = ChainStateView::new(chain_id).await; + let extra = &chain.context().extra(); extra .user_contracts() .insert(application_id, application.clone().into()); + extra.add_blobs(env.description_blobs()).await?; extra .add_blobs([ contract_blob, @@ -219,32 +277,10 @@ async fn test_application_permissions() -> anyhow::Result<()> { .await?; // Initialize the chain, with a chain application. - let config = OpenChainConfig { - application_permissions: ApplicationPermissions::new_single(application_id), - ..make_open_chain_config() - }; - chain - .execute_init_message(message_id, &config, time, time) - .await?; - let open_chain_message = Message::System(SystemMessage::OpenChain(Box::new(config))); - - // The OpenChain message must be included in the first block. Also register the app. - let bundle = IncomingBundle { - origin: admin_id(), - bundle: MessageBundle { - certificate_hash: CryptoHash::test_hash("certificate"), - height: BlockHeight(1), - transaction_index: 0, - timestamp: Timestamp::from(0), - messages: vec![open_chain_message.to_posted(0, MessageKind::Protected)], - }, - action: MessageAction::Accept, - }; + chain.ensure_is_active(time).await?; // An operation that doesn't belong to the app isn't allowed. - let invalid_block = make_first_block(chain_id) - .with_incoming_bundle(bundle.clone()) - .with_simple_transfer(chain_id, Amount::ONE); + let invalid_block = make_first_block(chain_id).with_simple_transfer(chain_id, Amount::ONE); let result = chain .execute_block(&invalid_block, time, None, &[], None) .await; @@ -259,9 +295,7 @@ async fn test_application_permissions() -> anyhow::Result<()> { application_id, bytes: b"foo".to_vec(), }; - let valid_block = make_first_block(chain_id) - .with_incoming_bundle(bundle) - .with_operation(app_operation.clone()); + let valid_block = make_first_block(chain_id).with_operation(app_operation.clone()); let outcome = chain .execute_block(&valid_block, time, None, &[], None) .await?; @@ -628,15 +662,13 @@ async fn prepare_test_with_dummy_mock_application( ProposedBlock, Timestamp, )> { + let mut env = TestEnvironment::new(); let time = Timestamp::from(0); - let message_id = make_admin_message_id(BlockHeight(3)); - let chain_id = ChainId::child(message_id); - let mut chain = ChainStateView::new(chain_id).await; - let mut config = make_open_chain_config(); + let mut config = env.make_open_chain_config(); config.committees.insert( Epoch(0), - Committee::new( + bcs::to_bytes(&Committee::new( BTreeMap::from([( ValidatorPublicKey::test_key(1), ValidatorState { @@ -646,15 +678,24 @@ async fn prepare_test_with_dummy_mock_application( }, )]), policy, - ), + )) + .expect("serializing a committee should not fail"), ); + let chain_desc = env.make_child_chain_description_with_config(3, config); + let chain_id = chain_desc.id(); + let mut chain = ChainStateView::new(chain_id).await; + chain - .execute_init_message(message_id, &config, time, time) + .context() + .extra() + .add_blobs(env.description_blobs()) .await?; + chain.ensure_is_active(time).await?; + // Create a mock application. - let (app_description, contract_blob, service_blob) = make_app_description(); + let (app_description, contract_blob, service_blob) = env.make_app_description(); let application_id = ApplicationId::from(&app_description); let application = MockApplication::default(); let extra = &chain.context().extra(); diff --git a/linera-chain/src/unit_tests/data_types_tests.rs b/linera-chain/src/unit_tests/data_types_tests.rs index f251e6f7bf03..3425cc6dee7f 100644 --- a/linera-chain/src/unit_tests/data_types_tests.rs +++ b/linera-chain/src/unit_tests/data_types_tests.rs @@ -13,6 +13,10 @@ use crate::{ test::{make_first_block, BlockTestExt}, }; +fn dummy_chain_id(index: u32) -> ChainId { + ChainId(CryptoHash::test_hash(format!("chain{}", index))) +} + #[test] fn test_signed_values() { let validator1_key_pair = ValidatorKeypair::generate(); @@ -27,7 +31,7 @@ fn test_signed_values() { blobs: vec![Vec::new()], operation_results: vec![OperationResult::default()], } - .with(make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(2), Amount::ONE)); + .with(make_first_block(dummy_chain_id(1)).with_simple_transfer(dummy_chain_id(2), Amount::ONE)); let confirmed_value = ConfirmedBlock::new(block.clone()); let confirmed_vote = LiteVote::new( @@ -97,7 +101,7 @@ fn test_certificates() { blobs: vec![Vec::new()], operation_results: vec![OperationResult::default()], } - .with(make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(1), Amount::ONE)); + .with(make_first_block(dummy_chain_id(1)).with_simple_transfer(dummy_chain_id(1), Amount::ONE)); let value = ConfirmedBlock::new(block); let v1 = LiteVote::new( diff --git a/linera-client/src/chain_listener.rs b/linera-client/src/chain_listener.rs index 0244d4336ae9..b5349a684bd7 100644 --- a/linera-client/src/chain_listener.rs +++ b/linera-client/src/chain_listener.rs @@ -15,8 +15,8 @@ use futures::{ }; use linera_base::{ crypto::{AccountSecretKey, CryptoHash}, - data_types::Timestamp, - identifiers::ChainId, + data_types::{ChainDescription, Timestamp}, + identifiers::{BlobType, ChainId}, task::NonBlockingFuture, }; use linera_core::{ @@ -25,7 +25,6 @@ use linera_core::{ worker::{Notification, Reason}, Environment, }; -use linera_execution::{Message, OutgoingMessage, SystemMessage}; use linera_storage::{Clock as _, Storage as _}; use tokio_util::sync::CancellationToken; use tracing::{debug, info, instrument, warn, Instrument as _}; @@ -70,7 +69,8 @@ pub trait ClientContext: 'static { fn storage(&self) -> &::Storage; - fn make_chain_client(&self, chain_id: ChainId) -> Result, Error>; + async fn make_chain_client(&self, chain_id: ChainId) + -> Result, Error>; async fn update_wallet_for_new_chain( &mut self, @@ -81,10 +81,10 @@ pub trait ClientContext: 'static { async fn update_wallet(&mut self, client: &ContextChainClient) -> Result<(), Error>; - fn clients(&self) -> Result>, Error> { + async fn clients(&self) -> Result>, Error> { let mut clients = vec![]; for chain_id in &self.wallet().chain_ids() { - clients.push(self.make_chain_client(*chain_id)?); + clients.push(self.make_chain_client(*chain_id).await?); } Ok(clients) } @@ -215,17 +215,14 @@ impl ChainListener { /// add them to the wallet and start listening for notifications. async fn add_new_chains(&mut self, hash: CryptoHash) -> Result<(), Error> { let block = self.storage.read_confirmed_block(hash).await?.into_block(); - let messages = block.messages().iter().flatten(); - let new_chains = messages - .filter_map(|outgoing_message| { - if let OutgoingMessage { - destination: new_id, - message: Message::System(SystemMessage::OpenChain(open_chain_config)), - .. - } = outgoing_message - { - let owners = open_chain_config.ownership.all_owners().cloned(); - Some((new_id, owners.collect::>())) + let blobs = block.created_blobs().into_iter(); + let new_chains = blobs + .filter_map(|(blob_id, blob)| { + if blob_id.blob_type == BlobType::ChainDescription { + let chain_desc: ChainDescription = bcs::from_bytes(blob.content().bytes()) + .expect("ChainDescription should deserialize correctly"); + let owners = chain_desc.config().ownership.all_owners().cloned(); + Some((ChainId(blob_id.hash), owners.collect::>())) } else { None } @@ -242,9 +239,9 @@ impl ChainListener { .find_map(|owner| context_guard.wallet().key_pair_for_owner(owner)); if key_pair.is_some() { context_guard - .update_wallet_for_new_chain(*new_id, key_pair, block.header.timestamp) + .update_wallet_for_new_chain(new_id, key_pair, block.header.timestamp) .await?; - new_ids.insert(*new_id); + new_ids.insert(new_id); } } drop(context_guard); @@ -279,7 +276,12 @@ impl ChainListener { if self.listening.contains_key(&chain_id) { return Ok(BTreeSet::new()); } - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let (listener, abort_handle, notification_stream) = client.listen().await?; if client.is_tracked() { client.synchronize_from_validators().await?; diff --git a/linera-client/src/client_context.rs b/linera-client/src/client_context.rs index c396055036b5..d1d0b9aa9116 100644 --- a/linera-client/src/client_context.rs +++ b/linera-client/src/client_context.rs @@ -8,23 +8,23 @@ use std::{collections::HashSet, sync::Arc}; use async_trait::async_trait; use futures::Future; use linera_base::{ - crypto::{AccountSecretKey, CryptoHash, ValidatorPublicKey}, - data_types::{BlockHeight, Timestamp}, - identifiers::{Account, AccountOwner, ChainId, MessageId}, + crypto::{AccountSecretKey, CryptoHash}, + data_types::{BlockHeight, ChainDescription, Timestamp}, + identifiers::{Account, AccountOwner, BlobId, BlobType, ChainId}, ownership::ChainOwnership, time::{Duration, Instant}, }; use linera_chain::types::ConfirmedBlockCertificate; use linera_core::{ client::{BlanketMessagePolicy, ChainClient, Client, MessagePolicy, PendingProposal}, - data_types::{ChainInfoQuery, ClientOutcome}, + data_types::ClientOutcome, join_set_ext::JoinSet, - node::{CrossChainMessageDelivery, ValidatorNodeProvider}, - remote_node::RemoteNode, + node::CrossChainMessageDelivery, Environment, JoinSetExt, }; -use linera_execution::{Message, SystemMessage}; use linera_rpc::node_provider::{NodeOptions, NodeProvider}; +use linera_storage::Storage; +use linera_views::views::ViewError; use thiserror_context::Context; use tracing::{debug, info}; #[cfg(feature = "benchmark")] @@ -38,7 +38,7 @@ use { linera_core::client::ChainClientError, linera_execution::{ committee::Committee, - system::{OpenChainConfig, SystemOperation, OPEN_CHAIN_MESSAGE_INDEX}, + system::{OpenChainConfig, SystemOperation}, Operation, }, std::{collections::HashMap, iter}, @@ -84,7 +84,7 @@ pub struct ClientContext { #[cfg_attr(web, async_trait(?Send))] impl chain_listener::ClientContext for ClientContext where - W: Persist + 'static, + W: Persist + Sync + 'static, { type Environment = Env; @@ -96,8 +96,8 @@ where self.client.storage_client() } - fn make_chain_client(&self, chain_id: ChainId) -> Result, Error> { - self.make_chain_client(chain_id) + async fn make_chain_client(&self, chain_id: ChainId) -> Result, Error> { + self.make_chain_client(chain_id).await } async fn update_wallet_for_new_chain( @@ -250,24 +250,25 @@ where .expect("No chain specified in wallet with no default chain") } - fn make_chain_client(&self, chain_id: ChainId) -> Result, Error> { + async fn make_chain_client(&self, chain_id: ChainId) -> Result, Error> { // We only create clients for chains we have in the wallet, or for the admin chain. let chain = match self.wallet.get(chain_id) { Some(chain) => chain.clone(), None => UserChain::make_other(chain_id, Timestamp::from(0)), }; let known_key_pairs = chain.key_pair.into_iter().collect(); - Ok(self.make_chain_client_internal( + self.make_chain_client_internal( chain_id, known_key_pairs, chain.block_hash, chain.timestamp, chain.next_block_height, chain.pending_proposal, - )) + ) + .await } - fn make_chain_client_internal( + async fn make_chain_client_internal( &self, chain_id: ChainId, known_key_pairs: Vec, @@ -275,21 +276,24 @@ where timestamp: Timestamp, next_block_height: BlockHeight, pending_proposal: Option, - ) -> ChainClient { - let mut chain_client = self.client.create_chain_client( - chain_id, - known_key_pairs, - self.wallet.genesis_admin_chain(), - block_hash, - timestamp, - next_block_height, - pending_proposal, - ); + ) -> Result, Error> { + let mut chain_client = self + .client + .create_chain_client( + chain_id, + known_key_pairs, + self.wallet.genesis_admin_chain(), + block_hash, + timestamp, + next_block_height, + pending_proposal, + ) + .await?; chain_client.options_mut().message_policy = MessagePolicy::new( self.blanket_message_policy, self.restrict_chain_ids_to.clone(), ); - chain_client + Ok(chain_client) } pub fn make_node_provider(&self) -> NodeProvider { @@ -401,59 +405,33 @@ where pub async fn assign_new_chain_to_key( &mut self, chain_id: ChainId, - message_id: MessageId, owner: AccountOwner, - validators: Option>, ) -> Result<(), Error> { - let node_provider = self.make_node_provider(); self.client.track_chain(chain_id); - - // Take the latest committee we know of. - let admin_chain_id = self.wallet.genesis_admin_chain(); - let query = ChainInfoQuery::new(admin_chain_id).with_committees(); - let nodes: Vec<_> = if let Some(validators) = validators { - node_provider - .make_nodes_from_list(validators)? - .map(|(public_key, node)| RemoteNode { public_key, node }) - .collect() - } else { - let info = self - .client - .local_node() - .handle_chain_info_query(query) - .await?; - let committee = info - .latest_committee() - .ok_or(error::Inner::ChainInfoResponseMissingCommittee)?; - node_provider - .make_nodes(committee)? - .map(|(public_key, node)| RemoteNode { public_key, node }) - .collect() - }; - - // Download the parent chain. - let target_height = message_id.height.try_add_one()?; - self.client - .download_certificates(&nodes, message_id.chain_id, target_height) - .await - .context("downloading parent chain")?; - - // The initial timestamp for the new chain is taken from the block with the message. - let certificate = self + let chain_description_blob_id = BlobId::new(chain_id.0, BlobType::ChainDescription); + let chain_description_blob = match self .client - .local_node() - .certificate_for(&message_id) + .storage_client() + .read_blob(chain_description_blob_id) .await - .context("looking for `OpenChain` message")?; - let block = certificate.block(); - let message = block.message_by_id(&message_id).map(|msg| &msg.message); - let Some(Message::System(SystemMessage::OpenChain(config))) = message else { - tracing::error!( - "The message with the ID returned by the faucet is not OpenChain. \ - Please make sure you are connecting to a genuine faucet." - ); - return Err(error::Inner::InvalidOpenMessage(message.cloned().map(Box::new)).into()); + { + Ok(blob) => blob, + Err(ViewError::BlobsNotFound(blob_ids)) if blob_ids == [chain_description_blob_id] => { + // we're missing the blob describing the chain we're assigning - try to + // get it + self.client + .ensure_has_chain_description(chain_id, self.wallet.genesis_admin_chain()) + .await? + } + Err(err) => { + return Err(err.into()); + } }; + let chain_description: ChainDescription = + bcs::from_bytes(&chain_description_blob.into_bytes()) + .map_err(|e| error::Inner::Persistence(Box::new(e)))?; + + let config = chain_description.config(); if !config.ownership.verify_owner(&owner) { tracing::error!( @@ -464,7 +442,7 @@ where } self.wallet_mut() - .mutate(|w| w.assign_new_chain_to_owner(owner, chain_id, block.header.timestamp)) + .mutate(|w| w.assign_new_chain_to_owner(owner, chain_id, chain_description.timestamp())) .await .map_err(|e| error::Inner::Persistence(Box::new(e)))? .context("assigning new chain")?; @@ -517,7 +495,7 @@ where ownership_config: ChainOwnershipConfig, ) -> Result<(), Error> { let chain_id = chain_id.unwrap_or_else(|| self.default_chain()); - let chain_client = self.make_chain_client(chain_id)?; + let chain_client = self.make_chain_client(chain_id).await?; info!("Changing ownership for chain {}", chain_id); let time_start = Instant::now(); let ownership = ChainOwnership::try_from(ownership_config)?; @@ -689,7 +667,7 @@ where .wallet .default_chain() .expect("should have default chain"); - let default_chain_client = self.make_chain_client(default_chain_id)?; + let default_chain_client = self.make_chain_client(default_chain_id).await?; let (epoch, mut committees) = default_chain_client .epoch_and_committees(default_chain_id) .await?; @@ -744,15 +722,14 @@ where } async fn process_inboxes_and_force_validator_updates(&mut self) { - let chain_clients = self - .wallet - .owned_chain_ids() - .iter() - .map(|chain_id| { + let mut chain_clients = vec![]; + for chain_id in &self.wallet.owned_chain_ids() { + chain_clients.push( self.make_chain_client(*chain_id) - .expect("chains in the wallet must exist") - }) - .collect::>(); + .await + .expect("chains in the wallet must exist"), + ); + } let mut join_set = task::JoinSet::new(); for chain_client in chain_clients { @@ -811,7 +788,7 @@ where .get(chain_id) .and_then(|chain| chain.key_pair.as_ref().map(|kp| kp.copy())) .unwrap(); - let chain_client = self.make_chain_client(chain_id)?; + let chain_client = self.make_chain_client(chain_id).await?; let ownership = chain_client.chain_info().await?.manager.ownership; if !ownership.owners.is_empty() || ownership.super_owners.len() != 1 { continue; @@ -840,8 +817,7 @@ where key_pairs.push(self.wallet.generate_key_pair()); } let mut key_pairs_iter = key_pairs.into_iter(); - let admin_id = self.wallet.genesis_admin_chain(); - let default_chain_client = self.make_chain_client(default_chain_id)?; + let default_chain_client = self.make_chain_client(default_chain_id).await?; for i in (0..num_chains_to_create).step_by(operations_per_block) { let num_new_chains = operations_per_block.min(num_chains_to_create - i); @@ -852,28 +828,30 @@ where &default_chain_client, balance, &key_pair, - admin_id, ) .await?; info!("Block executed successfully"); let block = certificate.block(); for i in 0..num_new_chains { - let message_id = block - .message_id_for_operation(i, OPEN_CHAIN_MESSAGE_INDEX) - .expect("failed to create new chain"); - let chain_id = ChainId::child(message_id); + let chain_id = block.body.blobs[i] + .iter() + .find(|blob| blob.id().blob_type == BlobType::ChainDescription) + .map(|blob| ChainId(blob.id().hash)) + .expect("failed to create a new chain"); benchmark_chains.insert(chain_id, key_pair.copy()); self.client.track_chain(chain_id); - let chain_client = self.make_chain_client_internal( - chain_id, - vec![key_pair.copy()], - None, - certificate.block().header.timestamp, - BlockHeight::ZERO, - None, - ); + let chain_client = self + .make_chain_client_internal( + chain_id, + vec![key_pair.copy()], + None, + certificate.block().header.timestamp, + BlockHeight::ZERO, + None, + ) + .await?; chain_client.process_inbox().await?; chain_clients.insert(chain_id, chain_client); } @@ -906,16 +884,9 @@ where chain_client: &ChainClient, balance: Amount, key_pair: &AccountSecretKey, - admin_id: ChainId, ) -> Result { - let chain_id = chain_client.chain_id(); - let (epoch, committees) = chain_client.epoch_and_committees(chain_id).await?; - let epoch = epoch.expect("default chain should be active"); let config = OpenChainConfig { ownership: ChainOwnership::single_super(key_pair.public().into()), - committees, - admin_id, - epoch, balance, application_permissions: Default::default(), }; @@ -962,7 +933,7 @@ where ) }) .collect(); - let chain_client = self.make_chain_client(default_chain_id)?; + let chain_client = self.make_chain_client(default_chain_id).await?; // Put at most 1000 fungible token operations in each block. for operation_chunk in operations.chunks(1000) { chain_client diff --git a/linera-client/src/config.rs b/linera-client/src/config.rs index a1ff05d6a8bd..158867a86c38 100644 --- a/linera-client/src/config.rs +++ b/linera-client/src/config.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ + collections::BTreeMap, iter::IntoIterator, ops::{Deref, DerefMut}, }; @@ -12,8 +13,9 @@ use linera_base::{ AccountPublicKey, AccountSecretKey, BcsSignable, CryptoHash, CryptoRng, Ed25519SecretKey, ValidatorPublicKey, ValidatorSecretKey, }, - data_types::{Amount, Timestamp}, - identifiers::{ChainDescription, ChainId}, + data_types::{Amount, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp}, + identifiers::ChainId, + ownership::ChainOwnership, }; use linera_execution::{ committee::{Committee, ValidatorState}, @@ -222,18 +224,28 @@ impl GenesisConfig { S: Storage + Clone + Send + Sync + 'static, { let committee = self.create_committee(); + let committees: BTreeMap<_, _> = [( + Epoch::ZERO, + bcs::to_bytes(&committee).expect("serializing a committee should not fail"), + )] + .into_iter() + .collect(); for (chain_number, (public_key, balance)) in (0..).zip(&self.chains) { - let description = ChainDescription::Root(chain_number); - storage - .create_chain( - committee.clone(), - self.admin_id, - description, - (*public_key).into(), - *balance, - self.timestamp, - ) - .await?; + let origin = ChainOrigin::Root(chain_number); + let config = InitialChainConfig { + admin_id: if chain_number == 0 { + None + } else { + Some(self.admin_id) + }, + application_permissions: Default::default(), + balance: *balance, + committees: committees.clone(), + epoch: Epoch::ZERO, + ownership: ChainOwnership::single((*public_key).into()), + }; + let description = ChainDescription::new(origin, config, self.timestamp); + storage.create_chain(description).await?; } Ok(()) } diff --git a/linera-client/src/error.rs b/linera-client/src/error.rs index 419e3db8cc2c..2a229d82cafc 100644 --- a/linera-client/src/error.rs +++ b/linera-client/src/error.rs @@ -28,12 +28,8 @@ pub(crate) enum Inner { LocalNode(#[from] linera_core::local_node::LocalNodeError), #[error("remote node operation failed: {0}")] RemoteNode(#[from] linera_core::node::NodeError), - #[error("chain info response missing latest committee")] - ChainInfoResponseMissingCommittee, #[error("arithmetic error: {0}")] Arithmetic(#[from] linera_base::data_types::ArithmeticError), - #[error("invalid open message")] - InvalidOpenMessage(Option>), #[error("incorrect chain ownership")] ChainOwnership, #[cfg(feature = "benchmark")] diff --git a/linera-client/src/unit_tests/chain_listener.rs b/linera-client/src/unit_tests/chain_listener.rs index 49fd05b2a456..5dfd798b9360 100644 --- a/linera-client/src/unit_tests/chain_listener.rs +++ b/linera-client/src/unit_tests/chain_listener.rs @@ -49,7 +49,7 @@ impl chain_listener::ClientContext for ClientContext { self.client.storage_client() } - fn make_chain_client( + async fn make_chain_client( &self, chain_id: ChainId, ) -> Result, Error> { @@ -63,15 +63,18 @@ impl chain_listener::ClientContext for ClientContext { .map(|kp| kp.copy()) .into_iter() .collect(); - Ok(self.client.create_chain_client( - chain_id, - known_key_pairs, - self.wallet.genesis_admin_chain(), - chain.block_hash, - chain.timestamp, - chain.next_block_height, - chain.pending_proposal.clone(), - )) + Ok(self + .client + .create_chain_client( + chain_id, + known_key_pairs, + self.wallet.genesis_admin_chain(), + chain.block_hash, + chain.timestamp, + chain.next_block_height, + chain.pending_proposal.clone(), + ) + .await?) } async fn update_wallet_for_new_chain( diff --git a/linera-client/src/unit_tests/util.rs b/linera-client/src/unit_tests/util.rs index 8a898c1eef28..a993344545a6 100644 --- a/linera-client/src/unit_tests/util.rs +++ b/linera-client/src/unit_tests/util.rs @@ -3,7 +3,6 @@ use linera_base::data_types::Timestamp; use linera_core::test_utils::{MemoryStorageBuilder, TestBuilder}; -use linera_execution::ResourceControlPolicy; use linera_rpc::{ config::{NetworkProtocol, ValidatorPublicNetworkPreConfig}, simple::TransportProtocol, @@ -31,7 +30,7 @@ pub fn make_genesis_config(builder: &TestBuilder) -> Genes CommitteeConfig { validators }, builder.admin_id(), Timestamp::from(0), - ResourceControlPolicy::default(), + builder.initial_committee.policy().clone(), "test network".to_string(), ); genesis_config.chains.extend(builder.genesis_chains()); diff --git a/linera-client/src/unit_tests/wallet.rs b/linera-client/src/unit_tests/wallet.rs index f7492bbcb8d5..263c9ac34c54 100644 --- a/linera-client/src/unit_tests/wallet.rs +++ b/linera-client/src/unit_tests/wallet.rs @@ -5,7 +5,6 @@ use anyhow::anyhow; use linera_base::{ crypto::{AccountSecretKey, Ed25519SecretKey}, data_types::{Amount, Blob, BlockHeight, Epoch}, - identifiers::{ChainDescription, ChainId}, }; use linera_chain::data_types::ProposedBlock; use linera_core::{ @@ -28,8 +27,8 @@ async fn test_save_wallet_with_pending_blobs() -> anyhow::Result<()> { let storage_builder = MemoryStorageBuilder::default(); let clock = storage_builder.clock().clone(); let mut builder = TestBuilder::new(storage_builder, 4, 1).await?; - let chain_id = ChainId::root(0); builder.add_root_chain(0, Amount::ONE).await?; + let chain_id = builder.admin_id(); let storage = builder.make_storage().await?; let genesis_config = make_genesis_config(&builder); @@ -52,7 +51,7 @@ async fn test_save_wallet_with_pending_blobs() -> anyhow::Result<()> { wallet .add_chains(Some(UserChain::make_initial( key_pair, - ChainDescription::Root(0), + builder.admin_description().unwrap().clone(), clock.current_time(), ))) .await?; diff --git a/linera-client/src/wallet.rs b/linera-client/src/wallet.rs index d17a5c61817f..afc2d8493fe0 100644 --- a/linera-client/src/wallet.rs +++ b/linera-client/src/wallet.rs @@ -8,9 +8,9 @@ use std::{ use linera_base::{ crypto::{AccountSecretKey, CryptoHash, CryptoRng}, - data_types::{BlockHeight, Timestamp}, + data_types::{BlockHeight, ChainDescription, Timestamp}, ensure, - identifiers::{AccountOwner, ChainDescription, ChainId}, + identifiers::{AccountOwner, ChainId}, }; use linera_core::{ client::{ChainClient, PendingProposal}, diff --git a/linera-core/src/chain_worker/state/attempted_changes.rs b/linera-core/src/chain_worker/state/attempted_changes.rs index 3c46d4e2dbaa..d48adcb9949f 100644 --- a/linera-core/src/chain_worker/state/attempted_changes.rs +++ b/linera-core/src/chain_worker/state/attempted_changes.rs @@ -68,7 +68,7 @@ where ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> { // Check that the chain is active and ready for this timeout. // Verify the certificate. Returns a catch-all error to make client code more robust. - self.state.ensure_is_active()?; + self.state.ensure_is_active().await?; let (chain_epoch, committee) = self.state.chain.current_committee()?; ensure!( certificate.inner().epoch() == chain_epoch, @@ -208,7 +208,7 @@ where let height = header.height; // Check that the chain is active and ready for this validated block. // Verify the certificate. Returns a catch-all error to make client code more robust. - self.state.ensure_is_active()?; + self.state.ensure_is_active().await?; let (epoch, committee) = self.state.chain.current_committee()?; check_block_epoch(epoch, header.chain_id, header.epoch)?; certificate.check(committee)?; @@ -303,13 +303,7 @@ where } let local_time = self.state.storage.clock().current_time(); // TODO(#2351): This sets the committee and then checks that committee's signatures. - if tip.is_first_block() && self.state.chain.is_child() { - self.state - .chain - .execute_init_message_from(block, local_time) - .await?; - } - self.state.ensure_is_active()?; + self.state.ensure_is_active().await?; // Verify the certificate. let (epoch, committee) = self.state.chain.current_committee()?; check_block_epoch(epoch, chain_id, block.header.epoch)?; diff --git a/linera-core/src/chain_worker/state/mod.rs b/linera-core/src/chain_worker/state/mod.rs index 84f7768f2307..524775dcbac4 100644 --- a/linera-core/src/chain_worker/state/mod.rs +++ b/linera-core/src/chain_worker/state/mod.rs @@ -17,7 +17,7 @@ use linera_base::{ data_types::{ApplicationDescription, Blob, BlockHeight, Epoch}, ensure, hashed::Hashed, - identifiers::{ApplicationId, BlobId, ChainId}, + identifiers::{ApplicationId, BlobId, BlobType, ChainId}, }; use linera_chain::{ data_types::{BlockExecutionOutcome, BlockProposal, MessageBundle, ProposedBlock}, @@ -26,8 +26,7 @@ use linera_chain::{ ChainError, ChainStateView, }; use linera_execution::{ - ExecutionStateView, Message, Query, QueryContext, QueryOutcome, ServiceRuntimeEndpoint, - SystemMessage, + ExecutionStateView, Query, QueryContext, QueryOutcome, ServiceRuntimeEndpoint, }; use linera_storage::{Clock as _, Storage}; use linera_views::views::{ClonableView, ViewError}; @@ -212,7 +211,7 @@ where &mut self, proposal: BlockProposal, ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> { - self.ensure_is_active()?; + self.ensure_is_active().await?; if ChainWorkerStateWithTemporaryChanges::new(&mut *self) .await .check_proposed_block(&proposal) @@ -346,9 +345,10 @@ where } /// Ensures that the current chain is active, returning an error otherwise. - fn ensure_is_active(&mut self) -> Result<(), WorkerError> { + async fn ensure_is_active(&mut self) -> Result<(), WorkerError> { if !self.knows_chain_is_active { - self.chain.ensure_is_active()?; + let local_time = self.storage.clock().current_time(); + self.chain.ensure_is_active(local_time).await?; self.knows_chain_is_active = true; } Ok(()) @@ -430,17 +430,11 @@ where { return; // The parent chain is not tracked; don't track the child. } - let messages = outcome.messages.iter().flatten(); - let open_chain_message_indices = - messages - .enumerate() - .filter_map(|(index, outgoing_message)| match outgoing_message.message { - Message::System(SystemMessage::OpenChain(_)) => Some(index), - _ => None, - }); - let open_chain_message_ids = - open_chain_message_indices.map(|index| proposed_block.message_id(index as u32)); - let new_chain_ids = open_chain_message_ids.map(ChainId::child); + let new_chain_ids = outcome + .created_blobs_ids() + .into_iter() + .filter(|blob_id| blob_id.blob_type == BlobType::ChainDescription) + .map(|blob_id| ChainId(blob_id.hash)); tracked_chains .write() diff --git a/linera-core/src/chain_worker/state/temporary_changes.rs b/linera-core/src/chain_worker/state/temporary_changes.rs index 48c6eec6b661..a8e3b0b6e8e2 100644 --- a/linera-core/src/chain_worker/state/temporary_changes.rs +++ b/linera-core/src/chain_worker/state/temporary_changes.rs @@ -59,7 +59,7 @@ where &mut self, height: BlockHeight, ) -> Result, WorkerError> { - self.0.ensure_is_active()?; + self.0.ensure_is_active().await?; let certificate_hash = match self.0.chain.confirmed_log.get(height.try_into()?).await? { Some(hash) => hash, None => return Ok(None), @@ -77,7 +77,7 @@ where height: BlockHeight, index: u32, ) -> Result, WorkerError> { - self.0.ensure_is_active()?; + self.0.ensure_is_active().await?; let mut inbox = self.0.chain.inboxes.try_load_entry_mut(&inbox_id).await?; let mut bundles = inbox.added_bundles.iter_mut().await?; @@ -96,7 +96,7 @@ where &mut self, query: Query, ) -> Result { - self.0.ensure_is_active()?; + self.0.ensure_is_active().await?; let local_time = self.0.storage.clock().current_time(); let outcome = self .0 @@ -111,7 +111,7 @@ where &mut self, application_id: ApplicationId, ) -> Result { - self.0.ensure_is_active()?; + self.0.ensure_is_active().await?; let response = self.0.chain.describe_application(application_id).await?; Ok(response) } @@ -123,6 +123,7 @@ where round: Option, published_blobs: &[Blob], ) -> Result<(Block, ChainInfoResponse), WorkerError> { + self.0.ensure_is_active().await?; let local_time = self.0.storage.clock().current_time(); let signer = block.authenticated_signer; let (_, committee) = self.0.chain.current_committee()?; @@ -241,6 +242,7 @@ where &mut self, query: ChainInfoQuery, ) -> Result { + self.0.ensure_is_active().await?; let chain = &self.0.chain; let mut info = ChainInfo::from(chain); if query.request_committees { diff --git a/linera-core/src/client/mod.rs b/linera-core/src/client/mod.rs index 4d31f2b14e5d..00aa263e62a7 100644 --- a/linera-core/src/client/mod.rs +++ b/linera-core/src/client/mod.rs @@ -33,8 +33,8 @@ use linera_base::{ }, ensure, identifiers::{ - Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, EventId, MessageId, - ModuleId, StreamId, + Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, EventId, ModuleId, + StreamId, }, ownership::{ChainOwnership, TimeoutConfig}, }; @@ -55,7 +55,7 @@ use linera_execution::{ committee::Committee, system::{ AdminOperation, OpenChainConfig, Recipient, SystemOperation, EPOCH_STREAM_NAME, - OPEN_CHAIN_MESSAGE_INDEX, REMOVED_EPOCH_STREAM_NAME, + REMOVED_EPOCH_STREAM_NAME, }, ExecutionError, Operation, Query, QueryOutcome, QueryResponse, SystemQuery, SystemResponse, }; @@ -240,7 +240,7 @@ impl Client { /// Creates a new `ChainClient`. #[instrument(level = "trace", skip_all, fields(chain_id, next_block_height))] #[expect(clippy::too_many_arguments)] - pub fn create_chain_client( + pub async fn create_chain_client( self: &Arc, chain_id: ChainId, known_key_pairs: Vec, @@ -249,7 +249,7 @@ impl Client { timestamp: Timestamp, next_block_height: BlockHeight, pending_proposal: Option, - ) -> ChainClient { + ) -> Result, ChainClientError> { // If the entry already exists we assume that the entry is more up to date than // the arguments: If they were read from the wallet file, they might be stale. if let dashmap::mapref::entry::Entry::Vacant(e) = self.chains.entry(chain_id) { @@ -262,7 +262,11 @@ impl Client { )); } - ChainClient { + let _ = self + .ensure_has_chain_description(chain_id, admin_id) + .await?; + + Ok(ChainClient { client: self.clone(), chain_id, admin_id, @@ -273,6 +277,26 @@ impl Client { grace_period: self.grace_period, blob_download_timeout: self.blob_download_timeout, }, + }) + } + + pub async fn chain_info( + &self, + chain_id: ChainId, + validators: &[RemoteNode], + ) -> Result, LocalNodeError> { + match self.local_node.chain_info(chain_id).await { + Ok(info) => Ok(info), + Err(LocalNodeError::BlobsNotFound(blob_ids)) => { + // TODO(#2351): make sure the blobs are legitimate! + let blobs = + RemoteNode::download_blobs(&blob_ids, validators, self.blob_download_timeout) + .await + .ok_or(LocalNodeError::BlobsNotFound(blob_ids))?; + self.local_node.storage_client().write_blobs(&blobs).await?; + self.local_node.chain_info(chain_id).await + } + err => err, } } @@ -285,10 +309,10 @@ impl Client { target_next_block_height: BlockHeight, ) -> Result, ChainClientError> { // Sequentially try each validator in random order. - let mut validators = validators.iter().collect::>(); - validators.shuffle(&mut rand::thread_rng()); - for remote_node in validators { - let info = self.local_node.chain_info(chain_id).await?; + let mut validators_vec = validators.iter().collect::>(); + validators_vec.shuffle(&mut rand::thread_rng()); + for remote_node in validators_vec { + let info = self.chain_info(chain_id, validators).await?; if target_next_block_height <= info.next_block_height { return Ok(info); } @@ -300,7 +324,7 @@ impl Client { ) .await?; } - let info = self.local_node.chain_info(chain_id).await?; + let info = self.chain_info(chain_id, validators).await?; if target_next_block_height <= info.next_block_height { Ok(info) } else { @@ -392,6 +416,65 @@ impl Client { .handle_certificate(certificate.clone(), &self.notifier) .await } + + /// Obtains the current epoch of the given chain as well as its set of trusted committees. + pub async fn epoch_and_committees( + &self, + chain_id: ChainId, + ) -> Result<(Option, BTreeMap), LocalNodeError> { + let query = ChainInfoQuery::new(chain_id).with_committees(); + let info = self.local_node.handle_chain_info_query(query).await?.info; + let epoch = info.epoch; + let committees = info + .requested_committees + .ok_or(LocalNodeError::InvalidChainInfoResponse)?; + Ok((epoch, committees)) + } + + fn make_nodes( + &self, + committee: &Committee, + ) -> Result>, NodeError> { + Ok(self + .validator_node_provider() + .make_nodes(committee)? + .map(|(public_key, node)| RemoteNode { public_key, node }) + .collect()) + } + + /// Ensures that the client has the `ChainDescription` blob corresponding to this + /// client's `ChainId`. + pub async fn ensure_has_chain_description( + &self, + chain_id: ChainId, + admin_id: ChainId, + ) -> Result { + let chain_desc_id = BlobId::new(chain_id.0, BlobType::ChainDescription); + if let Ok(blob) = self + .local_node + .storage_client() + .read_blob(chain_desc_id) + .await + { + // We have the blob - return it. + return Ok(blob); + } + // We can't get the committee from the chain we're assigned to because we don't + // have the description - use the admin chain. + let (admin_epoch, admin_committees) = self.epoch_and_committees(admin_id).await?; + let admin_epoch = admin_epoch.ok_or(ChainClientError::CommitteeSynchronizationError)?; + let remote_committee = admin_committees + .get(&admin_epoch) + .ok_or_else(|| ChainClientError::CommitteeDeprecationError)?; + // Recover history from the network. + // TODO(#2351): make sure that the blob is legitimately created! + let nodes = self.make_nodes(remote_committee)?; + let blob = RemoteNode::download_blob(&nodes, chain_desc_id, self.blob_download_timeout) + .await + .ok_or(LocalNodeError::BlobsNotFound(vec![chain_desc_id]))?; + self.local_node.storage_client().write_blob(&blob).await?; + Ok(blob) + } } /// Policies for automatically handling incoming messages. @@ -814,6 +897,11 @@ impl ChainClient { /// local chain. #[instrument(level = "trace")] async fn pending_message_bundles(&self) -> Result, ChainClientError> { + if self.options.message_policy.is_ignore() { + // Ignore all messages. + return Ok(Vec::new()); + } + let query = ChainInfoQuery::new(self.chain_id).with_pending_message_bundles(); let info = self .client @@ -829,35 +917,8 @@ impl ChainClient { ChainClientError::WalletSynchronizationError ); } - if info.next_block_height != BlockHeight::ZERO && self.options.message_policy.is_ignore() { - return Ok(Vec::new()); // OpenChain is already received, others are ignored. - } - let mut rearranged = false; - let mut pending_message_bundles = info.requested_pending_message_bundles; - - // The first incoming message of any child chain must be `OpenChain`. We must have it in - // our inbox, and include it before all other messages. - if info.next_block_height == BlockHeight::ZERO - && info - .description - .ok_or_else(|| LocalNodeError::InactiveChain(self.chain_id))? - .is_child() - { - // The first incoming message of any child chain must be `OpenChain`. We must have it in - // our inbox, and include it before all other messages. - rearranged = IncomingBundle::put_openchain_at_front(&mut pending_message_bundles); - ensure!(rearranged, LocalNodeError::InactiveChain(self.chain_id)); - } - - if self.options.message_policy.is_ignore() { - // Ignore messages other than OpenChain. - if rearranged { - return Ok(pending_message_bundles[0..1].to_vec()); - } else { - return Ok(Vec::new()); - } - } + let pending_message_bundles = info.requested_pending_message_bundles; Ok(pending_message_bundles .into_iter() @@ -922,31 +983,20 @@ impl ChainClient { &self, chain_id: ChainId, ) -> Result<(Option, BTreeMap), LocalNodeError> { - let query = ChainInfoQuery::new(chain_id).with_committees(); - let info = self - .client - .local_node - .handle_chain_info_query(query) - .await? - .info; - let epoch = info.epoch; - let committees = info - .requested_committees - .ok_or(LocalNodeError::InvalidChainInfoResponse)?; - Ok((epoch, committees)) + self.client.epoch_and_committees(chain_id).await } /// Obtains the epochs of the committees trusted by the local chain. #[instrument(level = "trace")] pub async fn epochs(&self) -> Result, LocalNodeError> { - let (_epoch, committees) = self.epoch_and_committees(self.chain_id).await?; + let (_epoch, committees) = self.client.epoch_and_committees(self.chain_id).await?; Ok(committees.into_keys().collect()) } /// Obtains the committee for the current epoch of the local chain. #[instrument(level = "trace")] pub async fn local_committee(&self) -> Result { - let (epoch, mut committees) = self.epoch_and_committees(self.chain_id).await?; + let (epoch, mut committees) = self.client.epoch_and_committees(self.chain_id).await?; committees .remove( epoch @@ -971,33 +1021,27 @@ impl ChainClient { async fn known_committees( &self, ) -> Result<(BTreeMap, Epoch), LocalNodeError> { - let (epoch, mut committees) = self.epoch_and_committees(self.chain_id).await?; - let (admin_epoch, admin_committees) = self.epoch_and_committees(self.admin_id).await?; + let (epoch, mut committees) = match self.client.epoch_and_committees(self.chain_id).await { + Ok(result) => result, + // We might not be initialized due to a missing blob - just treat this case as + // no committees. + Err(LocalNodeError::BlobsNotFound(_)) => (None, BTreeMap::new()), + err => err?, + }; + let (admin_epoch, admin_committees) = + self.client.epoch_and_committees(self.admin_id).await?; committees.extend(admin_committees); let epoch = std::cmp::max(epoch.unwrap_or_default(), admin_epoch.unwrap_or_default()); Ok((committees, epoch)) } - #[instrument(level = "trace")] - fn make_nodes( - &self, - committee: &Committee, - ) -> Result>, NodeError> { - Ok(self - .client - .validator_node_provider() - .make_nodes(committee)? - .map(|(public_key, node)| RemoteNode { public_key, node }) - .collect()) - } - /// Obtains the validators for the latest epoch. #[instrument(level = "trace")] async fn validator_nodes( &self, ) -> Result>, ChainClientError> { let committee = self.latest_committee().await?; - Ok(self.make_nodes(&committee)?) + Ok(self.client.make_nodes(&committee)?) } /// Obtains the current epoch of the local chain. @@ -1193,7 +1237,7 @@ impl ChainClient { delivery: CrossChainMessageDelivery, ) -> Result<(), ChainClientError> { let local_node = self.client.local_node.clone(); - let nodes = self.make_nodes(committee)?; + let nodes = self.client.make_nodes(committee)?; let n_validators = nodes.len(); let chain_worker_count = std::cmp::max(1, self.client.max_loaded_chains.get() / n_validators); @@ -1232,7 +1276,7 @@ impl ChainClient { value: T, ) -> Result, ChainClientError> { let local_node = self.client.local_node.clone(); - let nodes = self.make_nodes(committee)?; + let nodes = self.client.make_nodes(committee)?; let n_validators = nodes.len(); let chain_worker_count = std::cmp::max(1, self.client.max_loaded_chains.get() / n_validators); @@ -1331,7 +1375,7 @@ impl ChainClient { nodes } else { // We assume that the committee that signed the certificate is still active. - self.make_nodes(remote_committee)? + self.client.make_nodes(remote_committee)? }; self.client .download_certificates(&nodes, block.header.chain_id, block.header.height) @@ -1388,6 +1432,12 @@ impl ChainClient { let remote_max_heights = Self::max_height_per_chain(&remote_log); // Obtain the next block height we need in the local node, for each chain. + // But first, ensure we have the chain descriptions! + for chain in remote_max_heights.keys() { + self.client + .ensure_has_chain_description(*chain, self.admin_id) + .await?; + } let local_next_heights = self .client .local_node @@ -1633,7 +1683,7 @@ impl ChainClient { // Use network information from the local chain. let chain_id = self.chain_id; let local_committee = self.local_committee().await?; - let nodes = self.make_nodes(&local_committee)?; + let nodes = self.client.make_nodes(&local_committee)?; let client = self.clone(); // Proceed to downloading received certificates. Split the available chain workers so that // the tasks don't use more than the limit in total. @@ -1793,11 +1843,11 @@ impl ChainClient { #[cfg(with_metrics)] let _latency = metrics::SYNCHRONIZE_CHAIN_STATE_LATENCY.measure_latency(); - let (epoch, mut committees) = self.epoch_and_committees(chain_id).await?; + let (epoch, mut committees) = self.client.epoch_and_committees(chain_id).await?; let committee = committees .remove(&epoch.ok_or(LocalNodeError::InvalidChainInfoResponse)?) .ok_or(LocalNodeError::InvalidChainInfoResponse)?; - let validators = self.make_nodes(&committee)?; + let validators = self.client.make_nodes(&committee)?; communicate_with_quorum( &validators, &committee, @@ -2910,15 +2960,10 @@ impl ChainClient { ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - ) -> Result, ChainClientError> { + ) -> Result, ChainClientError> { loop { - let (epoch, committees) = self.epoch_and_committees(self.chain_id).await?; - let epoch = epoch.ok_or(LocalNodeError::InactiveChain(self.chain_id))?; let config = OpenChainConfig { ownership: ownership.clone(), - committees, - admin_id: self.admin_id, - epoch, balance, application_permissions: application_permissions.clone(), }; @@ -2931,17 +2976,20 @@ impl ChainClient { } }; // The first message of the only operation created the new chain. - let message_id = certificate + let chain_blob_id = certificate .block() - .message_id_for_operation(0, OPEN_CHAIN_MESSAGE_INDEX) - .ok_or_else(|| ChainClientError::InternalError("Failed to create new chain"))?; + .created_blob_ids() + .into_iter() + .next() + .ok_or_else(|| ChainClientError::InternalError("Failed to create a new chain"))?; + let chain_id = ChainId(chain_blob_id.hash); // Add the new chain to the list of tracked chains - self.client.track_chain(ChainId::child(message_id)); + self.client.track_chain(chain_id); self.client .local_node .retry_pending_cross_chain_requests(self.chain_id) .await?; - return Ok(ClientOutcome::Committed((message_id, certificate))); + return Ok(ClientOutcome::Committed((chain_id, certificate))); } } @@ -3242,7 +3290,7 @@ impl ChainClient { &self, ) -> Result, ChainClientError> { self.prepare_chain().await?; - let (current_epoch, committees) = self.epoch_and_committees(self.chain_id).await?; + let (current_epoch, committees) = self.client.epoch_and_committees(self.chain_id).await?; let current_epoch = current_epoch.ok_or(LocalNodeError::InactiveChain(self.chain_id))?; let operations = committees .keys() @@ -3344,19 +3392,39 @@ impl ChainClient { ) { match notification.reason { Reason::NewIncomingBundle { origin, height } => { + if let Err(error) = self + .client + .ensure_has_chain_description(origin, self.admin_id) + .await + { + error!( + chain_id = %self.chain_id, + "NewIncomingBundle: could not find blob for sender's chain: {error}" + ); + return; + } if self.local_next_block_height(origin, &mut local_node).await > Some(height) { - debug!("Accepting redundant notification for new message"); + debug!( + chain_id = %self.chain_id, + "Accepting redundant notification for new message" + ); return; } if let Err(error) = self .find_received_certificates_from_validator(remote_node) .await { - error!("Fail to process notification: {error}"); + error!( + chain_id = %self.chain_id, + "NewIncomingBundle: Fail to process notification: {error}" + ); return; } if self.local_next_block_height(origin, &mut local_node).await <= Some(height) { - error!("Fail to synchronize new message after notification"); + error!( + chain_id = %self.chain_id, + "NewIncomingBundle: Fail to synchronize new message after notification" + ); } } Reason::NewBlock { height, .. } => { @@ -3366,28 +3434,37 @@ impl ChainClient { .await > Some(height) { - debug!("Accepting redundant notification for new block"); + debug!( + chain_id = %self.chain_id, + "Accepting redundant notification for new block" + ); return; } if let Err(error) = self .try_synchronize_chain_state_from(&remote_node, chain_id) .await { - error!("Fail to process notification: {error}"); + error!( + chain_id = %self.chain_id, + "NewBlock: Fail to process notification: {error}" + ); return; } let local_height = self .local_next_block_height(chain_id, &mut local_node) .await; if local_height <= Some(height) { - error!("Fail to synchronize new block after notification"); + error!("NewBlock: Fail to synchronize new block after notification"); } } Reason::NewRound { height, round } => { let chain_id = notification.chain_id; if let Some(info) = self.local_chain_info(chain_id, &mut local_node).await { if (info.next_block_height, info.manager.current_round) >= (height, round) { - debug!("Accepting redundant notification for new round"); + debug!( + chain_id = %self.chain_id, + "Accepting redundant notification for new round" + ); return; } } @@ -3395,15 +3472,24 @@ impl ChainClient { .try_synchronize_chain_state_from(&remote_node, chain_id) .await { - error!("Fail to process notification: {error}"); + error!( + chain_id = %self.chain_id, + "NewRound: Fail to process notification: {error}" + ); return; } let Some(info) = self.local_chain_info(chain_id, &mut local_node).await else { - error!("Fail to read local chain info for {chain_id}"); + error!( + chain_id = %self.chain_id, + "NewRound: Fail to read local chain info for {chain_id}" + ); return; }; if (info.next_block_height, info.manager.current_round) < (height, round) { - error!("Fail to synchronize new block after notification"); + error!( + chain_id = %self.chain_id, + "NewRound: Fail to synchronize new block after notification" + ); } } } diff --git a/linera-core/src/data_types.rs b/linera-core/src/data_types.rs index 2cd7d5b9b81a..cc993a7affb1 100644 --- a/linera-core/src/data_types.rs +++ b/linera-core/src/data_types.rs @@ -10,8 +10,8 @@ use linera_base::{ BcsSignable, CryptoError, CryptoHash, ValidatorPublicKey, ValidatorSecretKey, ValidatorSignature, }, - data_types::{Amount, BlockHeight, Epoch, Round, Timestamp}, - identifiers::{AccountOwner, ChainDescription, ChainId}, + data_types::{Amount, BlockHeight, ChainDescription, Epoch, Round, Timestamp}, + identifiers::{AccountOwner, ChainId}, }; use linera_chain::{ data_types::{ChainAndHeight, IncomingBundle, MessageBundle}, @@ -271,7 +271,7 @@ where ChainInfo { chain_id: view.chain_id(), epoch: *system_state.epoch.get(), - description: *system_state.description.get(), + description: system_state.description.get().clone(), manager: Box::new(ChainManagerInfo::from(&view.manager)), chain_balance: *system_state.balance.get(), block_hash: tip_state.block_hash, diff --git a/linera-core/src/local_node.rs b/linera-core/src/local_node.rs index cae6dec8a031..ef7162300d4d 100644 --- a/linera-core/src/local_node.rs +++ b/linera-core/src/local_node.rs @@ -11,7 +11,7 @@ use futures::{future::Either, stream, StreamExt as _, TryStreamExt as _}; use linera_base::{ crypto::ValidatorPublicKey, data_types::{ApplicationDescription, ArithmeticError, Blob, BlockHeight}, - identifiers::{ApplicationId, BlobId, ChainId, MessageId}, + identifiers::{ApplicationId, BlobId, ChainId}, }; use linera_chain::{ data_types::{BlockProposal, ProposedBlock}, @@ -296,12 +296,13 @@ where /// Obtains the certificate containing the specified message. #[instrument(level = "trace", skip(self))] - pub async fn certificate_for( + pub async fn certificate_for_block( &self, - message_id: &MessageId, + chain_id: ChainId, + block_height: BlockHeight, ) -> Result { - let query = ChainInfoQuery::new(message_id.chain_id) - .with_sent_certificate_hashes_in_range(BlockHeightRange::single(message_id.height)); + let query = ChainInfoQuery::new(chain_id) + .with_sent_certificate_hashes_in_range(BlockHeightRange::single(block_height)); let info = self.handle_chain_info_query(query).await?.info; let certificates = self .storage_client() @@ -309,9 +310,15 @@ where .await?; let certificate = certificates .into_iter() - .find(|certificate| certificate.has_message(message_id)) + .find(|certificate| { + certificate.block().header.chain_id == chain_id + && certificate.block().header.height == block_height + }) .ok_or_else(|| { - ViewError::not_found("could not find certificate with message {}", message_id) + ViewError::not_found( + "could not find certificate with block chain ID and height {}", + (chain_id, block_height), + ) })?; Ok(certificate) } diff --git a/linera-core/src/notifier.rs b/linera-core/src/notifier.rs index cdd09f00bcab..2421f3fc852a 100644 --- a/linera-core/src/notifier.rs +++ b/linera-core/src/notifier.rs @@ -119,14 +119,16 @@ pub mod tests { time::Duration, }; + use linera_execution::test_utils::dummy_chain_description; + use super::*; #[test] fn test_concurrent() { let notifier = ChannelNotifier::default(); - let chain_a = ChainId::root(0); - let chain_b = ChainId::root(1); + let chain_a = dummy_chain_description(0).id(); + let chain_b = dummy_chain_description(1).id(); let a_rec = Arc::new(std::sync::atomic::AtomicUsize::new(0)); let b_rec = Arc::new(std::sync::atomic::AtomicUsize::new(0)); @@ -195,10 +197,10 @@ pub mod tests { fn test_eviction() { let notifier = ChannelNotifier::default(); - let chain_a = ChainId::root(0); - let chain_b = ChainId::root(1); - let chain_c = ChainId::root(2); - let chain_d = ChainId::root(3); + let chain_a = dummy_chain_description(0).id(); + let chain_b = dummy_chain_description(1).id(); + let chain_c = dummy_chain_description(2).id(); + let chain_d = dummy_chain_description(3).id(); // Chain A -> Notify A, Notify B // Chain B -> Notify A, Notify B diff --git a/linera-core/src/remote_node.rs b/linera-core/src/remote_node.rs index f7a16071a537..7c3f85c55362 100644 --- a/linera-core/src/remote_node.rs +++ b/linera-core/src/remote_node.rs @@ -313,8 +313,10 @@ impl RemoteNode { self.node.download_certificates(hashes).await } + /// Downloads a blob, but does not verify if it has actually been published and + /// accepted by a quorum of validators. #[instrument(level = "trace", skip(validators))] - async fn download_blob( + pub async fn download_blob( validators: &[Self], blob_id: BlobId, timeout: Duration, diff --git a/linera-core/src/unit_tests/client_tests.rs b/linera-core/src/unit_tests/client_tests.rs index 84682ba12917..b464ec0c1694 100644 --- a/linera-core/src/unit_tests/client_tests.rs +++ b/linera-core/src/unit_tests/client_tests.rs @@ -11,7 +11,7 @@ use futures::StreamExt; use linera_base::{ crypto::{AccountSecretKey, CryptoHash}, data_types::*, - identifiers::{Account, AccountOwner, ApplicationId, ChainId, MessageId}, + identifiers::{Account, AccountOwner, ApplicationId}, ownership::{ChainOwnership, TimeoutConfig}, }; use linera_chain::{ @@ -95,6 +95,7 @@ where .await? .with_policy(ResourceControlPolicy::fuel_and_block()); let sender = builder.add_root_chain(1, Amount::from_tokens(4)).await?; + let chain_2 = builder.add_root_chain(2, Amount::ZERO).await?; // Listen to the notifications on the sender chain. let mut notifications = sender.subscribe().await?; let (listener, _listen_handle, _) = sender.listen().await?; @@ -104,7 +105,7 @@ where .transfer_to_account( AccountOwner::CHAIN, Amount::from_tokens(3), - Account::chain(ChainId::root(2)), + Account::chain(chain_2.chain_id()), ) .await .unwrap() @@ -128,7 +129,7 @@ where Some(Notification { reason: Reason::NewBlock { height, .. }, chain_id, - }) if chain_id == ChainId::root(1) && height == BlockHeight::ZERO + }) if chain_id == sender.chain_id() && height == BlockHeight::ZERO ); Ok(()) } @@ -207,7 +208,7 @@ where .claim( owner, receiver_id, - Recipient::root(1), + Recipient::chain(sender.chain_id()), Amount::from_tokens(5), ) .await @@ -217,7 +218,7 @@ where .claim( owner, receiver_id, - Recipient::root(1), + Recipient::chain(sender.chain_id()), Amount::from_tokens(2), ) .await @@ -468,7 +469,7 @@ where let sender = builder.add_root_chain(1, Amount::from_tokens(4)).await?; let new_key_pair = AccountSecretKey::generate(); // Open the new chain. - let (message_id, certificate) = sender + let (new_id, certificate) = sender .open_chain( ChainOwnership::single(new_key_pair.public().into()), ApplicationPermissions::default(), @@ -478,21 +479,10 @@ where .unwrap() .unwrap(); - assert_eq!( - sender - .client - .local_node() - .certificate_for(&message_id) - .await - .unwrap(), - certificate - ); - assert_eq!(sender.next_block_height(), BlockHeight::from(1)); assert!(sender.pending_proposal().is_none()); assert!(sender.key_pair().await.is_ok()); // Make a client to try the new chain. - let new_id = ChainId::child(message_id); let client = builder .make_client(new_id, new_key_pair, None, BlockHeight::ZERO) .await?; @@ -515,17 +505,35 @@ async fn test_transfer_then_open_chain(storage_builder: B) -> anyhow::Result< where B: StorageBuilder, { + let clock = storage_builder.clock().clone(); let mut builder = TestBuilder::new(storage_builder, 4, 1).await?; // New chains use the admin chain to verify their creation certificate. let _admin = builder.add_root_chain(0, Amount::ZERO).await?; let sender = builder.add_root_chain(1, Amount::from_tokens(4)).await?; let parent = builder.add_root_chain(2, Amount::ZERO).await?; let new_key_pair = AccountSecretKey::generate(); - let new_id = ChainId::child(MessageId { - chain_id: parent.chain_id(), - height: BlockHeight::ZERO, - index: 0, - }); + + let new_chain_config = InitialChainConfig { + ownership: ChainOwnership::single(new_key_pair.public().into()), + admin_id: Some(builder.admin_id()), + epoch: Epoch::ZERO, + committees: builder + .admin_description() + .unwrap() + .config() + .committees + .clone(), + balance: Amount::ZERO, + application_permissions: Default::default(), + }; + let new_chain_origin = ChainOrigin::Child { + parent: parent.chain_id(), + block_height: BlockHeight::ZERO, + chain_index: 0, + }; + let new_id = + ChainDescription::new(new_chain_origin, new_chain_config, clock.current_time()).id(); + // Transfer before creating the chain. The validators will ignore the cross-chain messages. sender .transfer_to_account( @@ -536,7 +544,7 @@ where .await .unwrap(); // Open the new chain. - let (open_chain_message_id, certificate) = parent + let (new_id2, certificate) = parent .open_chain( ChainOwnership::single(new_key_pair.public().into()), ApplicationPermissions::default(), @@ -545,15 +553,13 @@ where .await .unwrap() .unwrap(); - let new_id2 = ChainId::child(open_chain_message_id); assert_eq!(new_id, new_id2); assert_eq!(sender.next_block_height(), BlockHeight::from(1)); assert_eq!(parent.next_block_height(), BlockHeight::from(1)); assert!(sender.pending_proposal().is_none()); assert!(sender.key_pair().await.is_ok()); assert_matches!( - certificate.block().body.operations[open_chain_message_id.index as usize] - .as_system_operation(), + certificate.block().body.operations[0].as_system_operation(), Some(SystemOperation::OpenChain(_)), "Unexpected certificate value", ); @@ -598,87 +604,6 @@ where Ok(()) } -#[test_case(MemoryStorageBuilder::default(); "memory")] -#[cfg_attr(feature = "storage-service", test_case(ServiceStorageBuilder::new().await; "storage_service"))] -#[cfg_attr(feature = "rocksdb", test_case(RocksDbStorageBuilder::new().await; "rocks_db"))] -#[cfg_attr(feature = "dynamodb", test_case(DynamoDbStorageBuilder::default(); "dynamo_db"))] -#[cfg_attr(feature = "scylladb", test_case(ScyllaDbStorageBuilder::default(); "scylla_db"))] -#[test_log::test(tokio::test)] -async fn test_open_chain_must_be_first(storage_builder: B) -> anyhow::Result<()> -where - B: StorageBuilder, -{ - let mut builder = TestBuilder::new(storage_builder, 4, 1).await?; - // New chains use the admin chain to verify their creation certificate. - let _admin = builder.add_root_chain(0, Amount::ZERO).await?; - let sender = builder.add_root_chain(1, Amount::from_tokens(4)).await?; - let new_key_pair = AccountSecretKey::generate(); - let new_id = ChainId::child(MessageId { - chain_id: sender.chain_id(), - height: BlockHeight::from(1), - index: 0, - }); - // Transfer before creating the chain. - sender - .transfer_to_account( - AccountOwner::CHAIN, - Amount::from_tokens(3), - Account::chain(new_id), - ) - .await - .unwrap(); - // Open the new chain. - let (open_chain_message_id, certificate) = sender - .open_chain( - ChainOwnership::single(new_key_pair.public().into()), - ApplicationPermissions::default(), - Amount::ZERO, - ) - .await - .unwrap() - .unwrap(); - let new_id2 = ChainId::child(open_chain_message_id); - assert_eq!(new_id, new_id2); - assert_eq!(sender.next_block_height(), BlockHeight::from(2)); - assert!(sender.pending_proposal().is_none()); - assert!(sender.key_pair().await.is_ok()); - assert_matches!( - certificate.block().body.operations[open_chain_message_id.index as usize] - .as_system_operation(), - Some(SystemOperation::OpenChain(_)), - "Unexpected certificate value", - ); - assert_eq!( - builder - .check_that_validators_have_certificate(sender.chain_id, BlockHeight::from(1), 3) - .await - .unwrap(), - certificate - ); - // Make a client to try the new chain. - let client = builder - .make_client(new_id, new_key_pair, None, BlockHeight::ZERO) - .await?; - client - .receive_certificate_and_update_validators(certificate) - .await - .unwrap(); - let result = client - .burn(AccountOwner::CHAIN, Amount::from_tokens(3)) - .await; - assert_matches!( - result, - Err(ChainClientError::LocalNodeError( - LocalNodeError::WorkerError(WorkerError::ChainError(error)) - )) if matches!(&*error, ChainError::CannotSkipMessage { bundle, .. } - if matches!(&**bundle, MessageBundle { messages, .. } - if matches!(messages[..], [PostedMessage { - message: Message::System(SystemMessage::Credit { .. }), .. - }]))) - ); - Ok(()) -} - #[test_case(MemoryStorageBuilder::default(); "memory")] #[cfg_attr(feature = "storage-service", test_case(ServiceStorageBuilder::new().await; "storage_service"))] #[cfg_attr(feature = "rocksdb", test_case(RocksDbStorageBuilder::new().await; "rocks_db"))] @@ -697,12 +622,11 @@ where // Open the new chain. We are both regular and super owner. let ownership = ChainOwnership::single(new_key_pair.public().into()) .with_regular_owner(new_key_pair.public().into(), 100); - let (message_id, creation_certificate) = sender + let (new_id, creation_certificate) = sender .open_chain(ownership, ApplicationPermissions::default(), Amount::ZERO) .await .unwrap() .unwrap(); - let new_id = ChainId::child(message_id); // Transfer after creating the chain. let transfer_certificate = sender .transfer_to_account( @@ -853,17 +777,20 @@ where { let mut builder = TestBuilder::new(storage_builder, 4, 2).await?; let sender = builder.add_root_chain(1, Amount::from_tokens(4)).await?; + let chain_2 = builder.add_root_chain(2, Amount::from_tokens(4)).await?; let result = sender .transfer_to_account_unsafe_unconfirmed( AccountOwner::CHAIN, Amount::from_tokens(3), - Account::chain(ChainId::root(2)), + Account::chain(chain_2.chain_id()), ) .await; + // The faulty validators will not have a chain with this ID (as the initial balance + // being zero changes it) - they will fail with `BlobsNotFound`. assert_matches!( result, Err(ChainClientError::CommunicationError( - CommunicationError::Trusted(crate::node::NodeError::ArithmeticError { .. }) + CommunicationError::Trusted(crate::node::NodeError::BlobsNotFound(_)) )), "unexpected result" ); @@ -1862,6 +1789,7 @@ where let client = builder.add_root_chain(1, Amount::from_tokens(3)).await?; let observer = builder.add_root_chain(2, Amount::ZERO).await?; let chain_id = client.chain_id(); + let observer_id = observer.chain_id(); let owner0 = client.public_key().await.unwrap().into(); let owner1 = AccountSecretKey::generate().public().into(); @@ -1925,7 +1853,11 @@ where // The other owner is leader now. Trying to submit a block should return `WaitForTimeout`. let result = client - .transfer(AccountOwner::CHAIN, Amount::ONE, Recipient::root(2)) + .transfer( + AccountOwner::CHAIN, + Amount::ONE, + Recipient::chain(observer_id), + ) .await .unwrap(); let timeout = match result { @@ -1952,7 +1884,11 @@ where // Now we are the leader, and the transfer should succeed. let _certificate = client - .transfer(AccountOwner::CHAIN, Amount::ONE, Recipient::root(2)) + .transfer( + AccountOwner::CHAIN, + Amount::ONE, + Recipient::chain(observer_id), + ) .await .unwrap() .unwrap(); @@ -1969,6 +1905,9 @@ where Ok(()) } +// TODO(#3860): this test is currently intermittently failing if the faulty validators respond to +// client0 before the correct ones. Un-ignore when this issue is fixed. +#[ignore] #[test_case(MemoryStorageBuilder::default(); "memory")] #[cfg_attr(feature = "storage-service", test_case(ServiceStorageBuilder::new().await; "storage_service"))] #[cfg_attr(feature = "rocksdb", test_case(RocksDbStorageBuilder::new().await; "rocks_db"))] diff --git a/linera-core/src/unit_tests/test_utils.rs b/linera-core/src/unit_tests/test_utils.rs index 84abd63ed36f..54347e4b7d54 100644 --- a/linera-core/src/unit_tests/test_utils.rs +++ b/linera-core/src/unit_tests/test_utils.rs @@ -20,7 +20,8 @@ use linera_base::{ AccountPublicKey, AccountSecretKey, CryptoHash, ValidatorKeypair, ValidatorPublicKey, }, data_types::*, - identifiers::{BlobId, ChainDescription, ChainId}, + identifiers::{BlobId, ChainId}, + ownership::ChainOwnership, }; use linera_chain::{ data_types::BlockProposal, @@ -658,7 +659,7 @@ where pub struct TestBuilder { storage_builder: B, pub initial_committee: Committee, - admin_id: ChainId, + admin_description: Option, genesis_storage_builder: GenesisStorageBuilder, validator_clients: Vec>, validator_storages: HashMap, @@ -682,37 +683,23 @@ struct GenesisStorageBuilder { struct GenesisAccount { description: ChainDescription, public_key: AccountPublicKey, - balance: Amount, } impl GenesisStorageBuilder { - fn add( - &mut self, - description: ChainDescription, - public_key: AccountPublicKey, - balance: Amount, - ) { + fn add(&mut self, description: ChainDescription, public_key: AccountPublicKey) { self.accounts.push(GenesisAccount { description, public_key, - balance, }) } - async fn build(&self, storage: S, initial_committee: Committee, admin_id: ChainId) -> S + async fn build(&self, storage: S) -> S where S: Storage + Clone + Send + Sync + 'static, { for account in &self.accounts { storage - .create_chain( - initial_committee.clone(), - admin_id, - account.description, - account.public_key.into(), - account.balance, - Timestamp::from(0), - ) + .create_chain(account.description.clone()) .await .unwrap(); } @@ -769,7 +756,7 @@ where Ok(Self { storage_builder, initial_committee, - admin_id: ChainId::root(0), + admin_description: None, genesis_storage_builder: GenesisStorageBuilder::default(), validator_clients, validator_storages, @@ -807,60 +794,60 @@ where balance: Amount, ) -> anyhow::Result> { // Make sure the admin chain is initialized. - if self.genesis_storage_builder.accounts.is_empty() && index != 0 { + if self.admin_description.is_none() && index != 0 { Box::pin(self.add_root_chain(0, Amount::ZERO)).await?; } - let description = ChainDescription::Root(index); + let origin = ChainOrigin::Root(index); + let mut committees = BTreeMap::new(); + committees.insert( + Epoch(0), + bcs::to_bytes(&self.initial_committee) + .expect("Serializing a committee should not fail!"), + ); let key_pair = AccountSecretKey::generate(); let public_key = key_pair.public(); + let open_chain_config = InitialChainConfig { + ownership: ChainOwnership::single(public_key.into()), + admin_id: if index == 0 { + None + } else { + Some(self.admin_id()) + }, + epoch: Epoch(0), + committees, + balance, + application_permissions: ApplicationPermissions::default(), + }; + let description = ChainDescription::new(origin, open_chain_config, Timestamp::from(0)); + if index == 0 { + self.admin_description = Some(description.clone()); + } // Remember what's in the genesis store for future clients to join. self.genesis_storage_builder - .add(description, public_key, balance); + .add(description.clone(), public_key); for validator in &self.validator_clients { let storage = self .validator_storages .get_mut(&validator.public_key) .unwrap(); if validator.fault_type().await == FaultType::Malicious { + let origin = description.origin(); + let config = InitialChainConfig { + balance: Amount::ZERO, + ..description.config().clone() + }; storage - .create_chain( - self.initial_committee.clone(), - self.admin_id, - description, - public_key.into(), - Amount::ZERO, - Timestamp::from(0), - ) + .create_chain(ChainDescription::new(origin, config, Timestamp::from(0))) .await .unwrap(); } else { - storage - .create_chain( - self.initial_committee.clone(), - self.admin_id, - description, - public_key.into(), - balance, - Timestamp::from(0), - ) - .await - .unwrap(); + storage.create_chain(description.clone()).await.unwrap(); } } for storage in self.chain_client_storages.iter_mut() { - storage - .create_chain( - self.initial_committee.clone(), - self.admin_id, - description, - public_key.into(), - balance, - Timestamp::from(0), - ) - .await - .unwrap(); + storage.create_chain(description.clone()).await.unwrap(); } - self.make_client(description.into(), key_pair, None, BlockHeight::ZERO) + self.make_client(description.id(), key_pair, None, BlockHeight::ZERO) .await } @@ -868,16 +855,26 @@ where let mut result = Vec::new(); for (i, genesis_account) in self.genesis_storage_builder.accounts.iter().enumerate() { assert_eq!( - genesis_account.description, - ChainDescription::Root(i as u32) + genesis_account.description.origin(), + ChainOrigin::Root(i as u32) ); - result.push((genesis_account.public_key, genesis_account.balance)); + result.push(( + genesis_account.public_key, + genesis_account.description.config().balance, + )); } result } pub fn admin_id(&self) -> ChainId { - self.admin_id + self.admin_description + .as_ref() + .expect("admin chain not initialized") + .id() + } + + pub fn admin_description(&self) -> Option<&ChainDescription> { + self.admin_description.as_ref() } pub fn make_node_provider(&self) -> NodeProvider { @@ -891,11 +888,7 @@ where pub async fn make_storage(&mut self) -> anyhow::Result { Ok(self .genesis_storage_builder - .build( - self.storage_builder.build().await?, - self.initial_committee.clone(), - self.admin_id, - ) + .build(self.storage_builder.build().await?) .await) } @@ -924,15 +917,17 @@ where DEFAULT_GRACE_PERIOD, Duration::from_secs(1), )); - Ok(builder.create_chain_client( - chain_id, - vec![key_pair], - self.admin_id, - block_hash, - Timestamp::from(0), - block_height, - None, - )) + Ok(builder + .create_chain_client( + chain_id, + vec![key_pair], + self.admin_id(), + block_hash, + Timestamp::from(0), + block_height, + None, + ) + .await?) } /// Tries to find a (confirmation) certificate for the given chain_id and block height. diff --git a/linera-core/src/unit_tests/wasm_worker_tests.rs b/linera-core/src/unit_tests/wasm_worker_tests.rs index 7daf086a6a15..e295529134c5 100644 --- a/linera-core/src/unit_tests/wasm_worker_tests.rs +++ b/linera-core/src/unit_tests/wasm_worker_tests.rs @@ -19,7 +19,7 @@ use linera_base::{ Amount, ApplicationDescription, Blob, BlockHeight, Bytecode, Epoch, OracleResponse, Timestamp, }, - identifiers::{ChainDescription, ChainId, ModuleId}, + identifiers::ModuleId, ownership::ChainOwnership, vm::VmRuntime, }; @@ -46,7 +46,7 @@ use linera_views::{ }; use test_case::test_case; -use super::{init_worker_with_chains, make_certificate}; +use super::TestEnvironment; use crate::worker::WorkerError; #[cfg_attr(feature = "wasmer", test_case(WasmRuntime::Wasmer ; "wasmer"))] @@ -100,19 +100,11 @@ where S: Storage + Clone + Send + Sync + 'static, { let vm_runtime = VmRuntime::Wasm; - let admin_id = ChainDescription::Root(0); let publisher_owner = AccountSecretKey::generate().public().into(); - let publisher_chain = ChainDescription::Root(1); let creator_owner = AccountSecretKey::generate().public().into(); - let creator_chain = ChainDescription::Root(2); - let (committee, worker) = init_worker_with_chains( - storage.clone(), - vec![ - (publisher_chain, publisher_owner, Amount::ZERO), - (creator_chain, creator_owner, Amount::ZERO), - ], - ) - .await; + let mut env = TestEnvironment::new(storage.clone(), false, false).await; + let publisher_chain = env.add_root_chain(1, publisher_owner, Amount::ZERO).await; + let creator_chain = env.add_root_chain(2, creator_owner, Amount::ZERO).await; // Load the bytecode files for a module. let (contract_path, service_path) = @@ -134,15 +126,17 @@ where // Publish the module. let publish_operation = SystemOperation::PublishModule { module_id }; - let publish_block = make_first_block(publisher_chain.into()) + let publish_block = make_first_block(publisher_chain.id()) .with_timestamp(1) .with_operation(publish_operation); let publisher_system_state = SystemExecutionState { - committees: [(Epoch::ZERO, committee.clone())].into_iter().collect(), + committees: [(Epoch::ZERO, env.committee().clone())] + .into_iter() + .collect(), ownership: ChainOwnership::single(publisher_owner), timestamp: Timestamp::from(1), used_blobs: BTreeSet::from([contract_blob_id, service_blob_id]), - ..SystemExecutionState::new(Epoch::ZERO, publisher_chain, admin_id) + ..SystemExecutionState::new(publisher_chain.clone()) }; let publisher_state_hash = publisher_system_state.clone().into_hash().await; let publish_block_proposal = ConfirmedBlock::new( @@ -157,10 +151,10 @@ where } .with(publish_block), ); - let publish_certificate = make_certificate(&committee, &worker, publish_block_proposal); + let publish_certificate = env.make_certificate(publish_block_proposal); assert_matches!( - worker + env.worker() .fully_handle_certificate_with_notifications(publish_certificate.clone(), &()) .await, Err(WorkerError::BlobsNotFound(_)) @@ -168,12 +162,13 @@ where storage .write_blobs(&[contract_blob.clone(), service_blob.clone()]) .await?; - let info = worker + let info = env + .worker() .fully_handle_certificate_with_notifications(publish_certificate.clone(), &()) .await .unwrap() .info; - assert_eq!(ChainId::from(publisher_chain), info.chain_id); + assert_eq!(publisher_chain.id(), info.chain_id); assert_eq!(Amount::ZERO, info.chain_balance); assert_eq!(BlockHeight::from(1), info.next_block_height); assert_eq!(Timestamp::from(1), info.timestamp); @@ -181,10 +176,12 @@ where assert!(info.manager.pending.is_none()); let mut creator_system_state = SystemExecutionState { - committees: [(Epoch::ZERO, committee.clone())].into_iter().collect(), + committees: [(Epoch::ZERO, env.committee().clone())] + .into_iter() + .collect(), ownership: ChainOwnership::single(creator_owner), timestamp: Timestamp::from(1), - ..SystemExecutionState::new(Epoch::ZERO, creator_chain, admin_id) + ..SystemExecutionState::new(creator_chain.clone()) }; // Create an application. @@ -199,7 +196,7 @@ where }; let application_description = ApplicationDescription { module_id, - creator_chain_id: creator_chain.into(), + creator_chain_id: creator_chain.id(), block_height: BlockHeight::from(0), application_index: 0, required_application_ids: vec![], @@ -208,7 +205,7 @@ where let application_description_blob = Blob::new_application_description(&application_description); let application_description_blob_id = application_description_blob.id(); let application_id = From::from(&application_description); - let create_block = make_first_block(creator_chain.into()) + let create_block = make_first_block(creator_chain.id()) .with_timestamp(2) .with_operation(create_operation); creator_system_state.timestamp = Timestamp::from(2); @@ -238,7 +235,7 @@ where } .with(create_block), ); - let create_certificate = make_certificate(&committee, &worker, create_block_proposal); + let create_certificate = env.make_certificate(create_block_proposal); storage .write_blobs(&[application_description_blob.clone()]) @@ -248,12 +245,13 @@ where .extra() .add_blobs([application_description_blob]) .await?; - let info = worker + let info = env + .worker() .fully_handle_certificate_with_notifications(create_certificate.clone(), &()) .await .unwrap() .info; - assert_eq!(ChainId::root(2), info.chain_id); + assert_eq!(creator_chain.id(), info.chain_id); assert_eq!(Amount::ZERO, info.chain_balance); assert_eq!(BlockHeight::from(1), info.next_block_height); assert_eq!(Timestamp::from(2), info.timestamp); @@ -270,11 +268,12 @@ where bytes: user_operation.clone(), }); let operation_context = OperationContext { - chain_id: creator_chain.into(), + chain_id: creator_chain.id(), authenticated_signer: None, authenticated_caller_id: None, height: run_block.height, round: Some(0), + timestamp: Timestamp::from(3), }; let mut controller = ResourceController::default(); creator_state @@ -289,6 +288,7 @@ where 0, 0, 0, + 0, Some(vec![OracleResponse::Blob(application_description_blob_id)]), ), &mut controller, @@ -311,14 +311,15 @@ where } .with(run_block), ); - let run_certificate = make_certificate(&committee, &worker, run_block_proposal); + let run_certificate = env.make_certificate(run_block_proposal); - let info = worker + let info = env + .worker() .fully_handle_certificate_with_notifications(run_certificate.clone(), &()) .await .unwrap() .info; - assert_eq!(ChainId::root(2), info.chain_id); + assert_eq!(creator_chain.id(), info.chain_id); assert_eq!(Amount::ZERO, info.chain_balance); assert_eq!(BlockHeight::from(2), info.next_block_height); assert_eq!(Some(run_certificate.hash()), info.block_hash); diff --git a/linera-core/src/unit_tests/worker_tests.rs b/linera-core/src/unit_tests/worker_tests.rs index e8ff693b0bda..22bccaef5a83 100644 --- a/linera-core/src/unit_tests/worker_tests.rs +++ b/linera-core/src/unit_tests/worker_tests.rs @@ -22,7 +22,7 @@ use linera_base::{ Secp256k1SecretKey, ValidatorKeypair, }, data_types::*, - identifiers::{Account, AccountOwner, ChainDescription, ChainId, EventId, MessageId, StreamId}, + identifiers::{Account, AccountOwner, ChainId, EventId, StreamId}, ownership::{ChainOwnership, TimeoutConfig}, }; use linera_chain::{ @@ -45,7 +45,9 @@ use linera_execution::{ AdminOperation, OpenChainConfig, Recipient, SystemMessage, SystemOperation, EPOCH_STREAM_NAME as NEW_EPOCH_STREAM_NAME, REMOVED_EPOCH_STREAM_NAME, }, - test_utils::{ExpectedCall, RegisterMockApplication, SystemExecutionState}, + test_utils::{ + dummy_chain_description, ExpectedCall, RegisterMockApplication, SystemExecutionState, + }, ExecutionError, Message, MessageKind, OutgoingMessage, Query, QueryContext, QueryOutcome, QueryResponse, SystemQuery, SystemResponse, }; @@ -79,294 +81,355 @@ use crate::{ /// The test worker accepts blocks with a timestamp this far in the future. const TEST_GRACE_PERIOD_MICROS: u64 = 500_000; -/// Instantiates the protocol with a single validator. Returns the corresponding committee -/// and the (non-sharded, in-memory) "worker" that we can interact with. -fn init_worker( - storage: S, - is_client: bool, - has_long_lived_services: bool, -) -> (Committee, WorkerState) -where - S: Storage + Clone + Send + Sync + 'static, -{ - let validator_keypair = ValidatorKeypair::generate(); - let account_secret = AccountSecretKey::generate(); - let committee = Committee::make_simple(vec![( - validator_keypair.public_key, - account_secret.public(), - )]); - let worker = WorkerState::new( - "Single validator node".to_string(), - Some(validator_keypair.secret_key), - storage, - NonZeroUsize::new(10).expect("Chain worker limit should not be zero"), - ) - .with_allow_inactive_chains(is_client) - .with_allow_messages_from_deprecated_epochs(is_client) - .with_long_lived_services(has_long_lived_services) - .with_grace_period(Duration::from_micros(TEST_GRACE_PERIOD_MICROS)); - (committee, worker) +fn serialize_committees( + committees: impl IntoIterator, +) -> BTreeMap> { + committees + .into_iter() + .map(|(epoch, committee)| { + ( + epoch, + bcs::to_bytes(&committee).expect("serializing a committee should not fail"), + ) + }) + .collect() +} + +struct TestEnvironment { + committee: Committee, + worker: WorkerState, + admin_keypair: AccountSecretKey, + admin_description: ChainDescription, + other_chains: BTreeMap, } -/// Same as `init_worker` but also instantiates some initial chains. -async fn init_worker_with_chains(storage: S, balances: I) -> (Committee, WorkerState) +impl TestEnvironment where - I: IntoIterator, S: Storage + Clone + Send + Sync + 'static, { - let (committee, worker) = init_worker( - storage, /* is_client */ false, /* has_long_lived_services */ false, - ); - for (description, owner, balance) in balances { - worker + async fn new(storage: S, is_client: bool, has_long_lived_services: bool) -> Self { + Self::new_with_amount( + storage, + is_client, + has_long_lived_services, + Amount::from_tokens(1_000_000), + ) + .await + } + + async fn new_with_amount( + storage: S, + is_client: bool, + has_long_lived_services: bool, + amount: Amount, + ) -> Self { + let validator_keypair = ValidatorKeypair::generate(); + let account_secret = AccountSecretKey::generate(); + let committee = Committee::make_simple(vec![( + validator_keypair.public_key, + account_secret.public(), + )]); + + let origin = ChainOrigin::Root(0); + let config = InitialChainConfig { + admin_id: None, + balance: amount, + ownership: ChainOwnership::single(account_secret.public().into()), + epoch: Epoch::ZERO, + committees: serialize_committees([(Epoch::ZERO, committee.clone())]), + application_permissions: Default::default(), + }; + let admin_description = ChainDescription::new(origin, config, Timestamp::from(0)); + storage + .write_blob(&Blob::new_chain_description(&admin_description)) + .await + .expect("writing a blob should not fail"); + + let worker = WorkerState::new( + "Single validator node".to_string(), + Some(validator_keypair.secret_key), + storage, + NonZeroUsize::new(10).expect("Chain worker limit should not be zero"), + ) + .with_allow_inactive_chains(is_client) + .with_allow_messages_from_deprecated_epochs(is_client) + .with_long_lived_services(has_long_lived_services) + .with_grace_period(Duration::from_micros(TEST_GRACE_PERIOD_MICROS)); + Self { + committee, + worker, + admin_description, + admin_keypair: account_secret, + other_chains: BTreeMap::new(), + } + } + + fn admin_id(&self) -> ChainId { + self.admin_description.id() + } + + fn committee(&self) -> &Committee { + &self.committee + } + + fn worker(&self) -> &WorkerState { + &self.worker + } + + fn admin_public_key(&self) -> AccountPublicKey { + self.admin_keypair.public() + } + + async fn add_root_chain( + &mut self, + index: u32, + owner: AccountOwner, + balance: Amount, + ) -> ChainDescription { + let origin = ChainOrigin::Root(index); + let config = InitialChainConfig { + admin_id: Some(self.admin_id()), + epoch: self.admin_description.config().epoch, + ownership: ChainOwnership::single(owner), + committees: self.admin_description.config().committees.clone(), + balance, + application_permissions: Default::default(), + }; + let description = ChainDescription::new(origin, config, Timestamp::from(0)); + self.other_chains + .insert(description.id(), description.clone()); + self.worker .storage - .create_chain( - committee.clone(), - ChainId::root(0), - description, - owner, - balance, - Timestamp::from(0), - ) + .create_chain(description.clone()) .await .unwrap(); + description } - (committee, worker) -} -/// Same as `init_worker` but also instantiate a single initial chain. -async fn init_worker_with_chain( - storage: S, - description: ChainDescription, - owner: AccountOwner, - balance: Amount, -) -> (Committee, WorkerState) -where - S: Storage + Clone + Send + Sync + 'static, -{ - init_worker_with_chains(storage, [(description, owner, balance)]).await -} + async fn add_child_chain( + &mut self, + parent_id: ChainId, + owner: AccountOwner, + balance: Amount, + ) -> ChainDescription { + let origin = ChainOrigin::Child { + parent: parent_id, + block_height: BlockHeight(0), + chain_index: 0, + }; + let config = InitialChainConfig { + admin_id: Some(self.admin_id()), + epoch: self.admin_description.config().epoch, + ownership: ChainOwnership::single(owner), + committees: self.admin_description.config().committees.clone(), + balance, + application_permissions: Default::default(), + }; + let description = ChainDescription::new(origin, config, Timestamp::from(0)); + self.other_chains + .insert(description.id(), description.clone()); + self.worker + .storage + .create_chain(description.clone()) + .await + .unwrap(); + description + } -fn make_certificate( - committee: &Committee, - worker: &WorkerState, - value: T, -) -> GenericCertificate -where - S: Storage, - T: CertificateValue, -{ - make_certificate_with_round(committee, worker, value, Round::MultiLeader(0)) -} + fn make_certificate(&self, value: T) -> GenericCertificate + where + T: CertificateValue, + { + self.make_certificate_with_round(value, Round::MultiLeader(0)) + } -fn make_certificate_with_round( - committee: &Committee, - worker: &WorkerState, - value: T, - round: Round, -) -> GenericCertificate -where - S: Storage, - T: CertificateValue, -{ - let vote = LiteVote::new( - LiteValue::new(&value), - round, - worker.chain_worker_config.key_pair().unwrap(), - ); - let mut builder = SignatureAggregator::new(value, round, committee); - builder - .append(vote.public_key, vote.signature) - .unwrap() - .unwrap() -} + fn make_certificate_with_round(&self, value: T, round: Round) -> GenericCertificate + where + T: CertificateValue, + { + let vote = LiteVote::new( + LiteValue::new(&value), + round, + self.worker.chain_worker_config.key_pair().unwrap(), + ); + let mut builder = SignatureAggregator::new(value, round, &self.committee); + builder + .append(vote.public_key, vote.signature) + .unwrap() + .unwrap() + } -#[expect(clippy::too_many_arguments)] -async fn make_simple_transfer_certificate( - chain_description: ChainDescription, - key_pair: &AccountSecretKey, - target_id: ChainId, - amount: Amount, - incoming_bundles: Vec, - committee: &Committee, - balance: Amount, - worker: &WorkerState, - previous_confirmed_block: Option<&ConfirmedBlockCertificate>, -) -> ConfirmedBlockCertificate -where - S: Storage, -{ - make_transfer_certificate_for_epoch( - chain_description, - key_pair, - Some(key_pair.public().into()), - AccountOwner::CHAIN, - Recipient::chain(target_id), - amount, - incoming_bundles, - Epoch::ZERO, - committee, - balance, - BTreeMap::new(), - worker, - previous_confirmed_block, - ) - .await -} + #[expect(clippy::too_many_arguments)] + async fn make_simple_transfer_certificate( + &self, + chain_description: ChainDescription, + key_pair: &AccountSecretKey, + target_id: ChainId, + amount: Amount, + incoming_bundles: Vec, + balance: Amount, + previous_confirmed_block: Option<&ConfirmedBlockCertificate>, + ) -> ConfirmedBlockCertificate { + self.make_transfer_certificate_for_epoch( + chain_description, + key_pair, + Some(key_pair.public().into()), + AccountOwner::CHAIN, + Recipient::chain(target_id), + amount, + incoming_bundles, + Epoch::ZERO, + balance, + BTreeMap::new(), + previous_confirmed_block, + ) + .await + } -#[expect(clippy::too_many_arguments)] -async fn make_transfer_certificate( - chain_description: ChainDescription, - key_pair: &AccountSecretKey, - authenticated_signer: Option, - source: AccountOwner, - recipient: Recipient, - amount: Amount, - incoming_bundles: Vec, - committee: &Committee, - balance: Amount, - balances: BTreeMap, - worker: &WorkerState, - previous_confirmed_block: Option<&ConfirmedBlockCertificate>, -) -> ConfirmedBlockCertificate -where - S: Storage, -{ - make_transfer_certificate_for_epoch( - chain_description, - key_pair, - authenticated_signer, - source, - recipient, - amount, - incoming_bundles, - Epoch::ZERO, - committee, - balance, - balances, - worker, - previous_confirmed_block, - ) - .await -} + #[expect(clippy::too_many_arguments)] + async fn make_transfer_certificate( + &self, + chain_description: ChainDescription, + key_pair: &AccountSecretKey, + authenticated_signer: Option, + source: AccountOwner, + recipient: Recipient, + amount: Amount, + incoming_bundles: Vec, + balance: Amount, + balances: BTreeMap, + previous_confirmed_block: Option<&ConfirmedBlockCertificate>, + ) -> ConfirmedBlockCertificate { + self.make_transfer_certificate_for_epoch( + chain_description, + key_pair, + authenticated_signer, + source, + recipient, + amount, + incoming_bundles, + Epoch::ZERO, + balance, + balances, + previous_confirmed_block, + ) + .await + } -/// Creates a certificate with a transfer. -/// -/// This does not work for blocks with ancestors that sent a message to the same recipient, unless -/// the `previous_confirmed_block` also did. -#[expect(clippy::too_many_arguments)] -async fn make_transfer_certificate_for_epoch( - chain_description: ChainDescription, - key_pair: &AccountSecretKey, - authenticated_signer: Option, - source: AccountOwner, - recipient: Recipient, - amount: Amount, - incoming_bundles: Vec, - epoch: Epoch, - committee: &Committee, - balance: Amount, - balances: BTreeMap, - worker: &WorkerState, - previous_confirmed_block: Option<&ConfirmedBlockCertificate>, -) -> ConfirmedBlockCertificate -where - S: Storage, -{ - let chain_id = chain_description.into(); - let system_state = SystemExecutionState { - committees: [(epoch, committee.clone())].into_iter().collect(), - ownership: ChainOwnership::single(key_pair.public().into()), - balance, - balances, - ..SystemExecutionState::new(epoch, chain_description, ChainId::root(0)) - }; - let block_template = match &previous_confirmed_block { - None => make_first_block(chain_id), - Some(cert) => make_child_block(cert.value()), - }; + /// Creates a certificate with a transfer. + /// + /// This does not work for blocks with ancestors that sent a message to the same recipient, unless + /// the `previous_confirmed_block` also did. + #[expect(clippy::too_many_arguments)] + async fn make_transfer_certificate_for_epoch( + &self, + chain_description: ChainDescription, + key_pair: &AccountSecretKey, + authenticated_signer: Option, + source: AccountOwner, + recipient: Recipient, + amount: Amount, + incoming_bundles: Vec, + epoch: Epoch, + balance: Amount, + balances: BTreeMap, + previous_confirmed_block: Option<&ConfirmedBlockCertificate>, + ) -> ConfirmedBlockCertificate { + let chain_id = chain_description.id(); + let system_state = SystemExecutionState { + committees: [(epoch, self.committee.clone())].into_iter().collect(), + ownership: ChainOwnership::single(key_pair.public().into()), + balance, + balances, + ..SystemExecutionState::new(chain_description) + }; + let block_template = match &previous_confirmed_block { + None => make_first_block(chain_id), + Some(cert) => make_child_block(cert.value()), + }; - let mut messages = incoming_bundles - .iter() - .flat_map(|incoming_bundle| { - incoming_bundle - .bundle - .messages - .iter() - .map(|posted_message| { - if matches!(incoming_bundle.action, MessageAction::Reject) - && matches!(posted_message.kind, MessageKind::Tracked) - { - vec![OutgoingMessage { - authenticated_signer: posted_message.authenticated_signer, - destination: incoming_bundle.origin, - grant: Amount::ZERO, - refund_grant_to: None, - kind: MessageKind::Bouncing, - message: posted_message.message.clone(), - }] - } else { - Vec::new() - } - }) - }) - .collect::>(); + let mut messages = incoming_bundles + .iter() + .flat_map(|incoming_bundle| { + incoming_bundle + .bundle + .messages + .iter() + .map(|posted_message| { + if matches!(incoming_bundle.action, MessageAction::Reject) + && matches!(posted_message.kind, MessageKind::Tracked) + { + vec![OutgoingMessage { + authenticated_signer: posted_message.authenticated_signer, + destination: incoming_bundle.origin, + grant: Amount::ZERO, + refund_grant_to: None, + kind: MessageKind::Bouncing, + message: posted_message.message.clone(), + }] + } else { + Vec::new() + } + }) + }) + .collect::>(); - let block = ProposedBlock { - epoch, - incoming_bundles, - authenticated_signer, - ..block_template - } - .with_transfer(source, recipient, amount); - match recipient { - Recipient::Account(account) => { - messages.push(vec![direct_outgoing_message( - account.chain_id, - MessageKind::Tracked, - SystemMessage::Credit { - source, - target: account.owner, - amount, - }, - )]); + let block = ProposedBlock { + epoch, + incoming_bundles, + authenticated_signer, + ..block_template } - Recipient::Burn => messages.push(Vec::new()), - } - let tx_count = block.operations.len() + block.incoming_bundles.len(); - let oracle_responses = iter::repeat_with(Vec::new).take(tx_count).collect(); - let events = iter::repeat_with(Vec::new).take(tx_count).collect(); - let blobs = iter::repeat_with(Vec::new).take(tx_count).collect(); - let operation_results = iter::repeat_with(Vec::new) - .map(OperationResult) - .take(block.operations.len()) - .collect(); - let state_hash = system_state.into_hash().await; - let previous_message_blocks = messages - .iter() - .flatten() - .map(|message| message.destination) - .filter(|recipient| { - previous_confirmed_block - .iter() - .flat_map(|block| block.inner().block().body.messages.iter().flatten()) - .any(|message| message.destination == *recipient) - }) - .map(|recipient| (recipient, previous_confirmed_block.unwrap().hash())) - .collect(); - let value = ConfirmedBlock::new( - BlockExecutionOutcome { - messages, - previous_message_blocks, - events, - blobs, - state_hash, - oracle_responses, - operation_results, + .with_transfer(source, recipient, amount); + match recipient { + Recipient::Account(account) => { + messages.push(vec![direct_outgoing_message( + account.chain_id, + MessageKind::Tracked, + SystemMessage::Credit { + source, + target: account.owner, + amount, + }, + )]); + } + Recipient::Burn => messages.push(Vec::new()), } - .with(block), - ); - make_certificate(committee, worker, value) + let tx_count = block.operations.len() + block.incoming_bundles.len(); + let oracle_responses = iter::repeat_with(Vec::new).take(tx_count).collect(); + let events = iter::repeat_with(Vec::new).take(tx_count).collect(); + let blobs = iter::repeat_with(Vec::new).take(tx_count).collect(); + let operation_results = iter::repeat_with(Vec::new) + .map(OperationResult) + .take(block.operations.len()) + .collect(); + let state_hash = system_state.into_hash().await; + let previous_message_blocks = messages + .iter() + .flatten() + .map(|message| message.destination) + .filter(|recipient| { + previous_confirmed_block + .iter() + .flat_map(|block| block.inner().block().body.messages.iter().flatten()) + .any(|message| message.destination == *recipient) + }) + .map(|recipient| (recipient, previous_confirmed_block.unwrap().hash())) + .collect(); + let value = ConfirmedBlock::new( + BlockExecutionOutcome { + messages, + previous_message_blocks, + events, + blobs, + state_hash, + oracle_responses, + operation_results, + } + .with(block), + ); + self.make_certificate(value) + } } fn direct_outgoing_message( @@ -438,35 +501,29 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (_, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; - let block_proposal = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(5)) + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let block_proposal = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::from_tokens(5)) .into_first_proposal(&sender_key_pair); let unknown_key_pair = AccountSecretKey::generate(); let mut bad_signature_block_proposal = block_proposal.clone(); bad_signature_block_proposal.signature = unknown_key_pair.sign(&block_proposal.content); assert_matches!( - worker + env.worker() .handle_block_proposal(bad_signature_block_proposal) .await, - Err(WorkerError::CryptoError(error)) if matches!(error, linera_base::crypto::CryptoError::InvalidSignature {..}) + Err(WorkerError::CryptoError(error)) + if matches!(error, linera_base::crypto::CryptoError::InvalidSignature {..}) ); - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.confirmed_vote().is_none()); assert!(chain.manager.validated_vote().is_none()); @@ -483,29 +540,22 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (_, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); // test block non-positive amount - let zero_amount_block_proposal = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::ZERO) + let zero_amount_block_proposal = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::ZERO) .with_authenticated_signer(Some(sender_key_pair.public().into())) .into_first_proposal(&sender_key_pair); assert_matches!( - worker + env.worker() .handle_block_proposal(zero_amount_block_proposal) .await, Err( @@ -514,7 +564,7 @@ where execution_error, ChainExecutionContext::Operation(_) ) if matches!(**execution_error, ExecutionError::IncorrectTransferAmount)) ); - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.confirmed_vote().is_none()); assert!(chain.manager.validated_vote().is_none()); @@ -534,35 +584,38 @@ where let clock = storage_builder.clock(); let key_pair = AccountSecretKey::generate(); let balance = Amount::from_tokens(5); - let balances = vec![(ChainDescription::Root(1), key_pair.public().into(), balance)]; + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env + .add_root_chain(1, key_pair.public().into(), balance) + .await; let epoch = Epoch::ZERO; - let (committee, worker) = init_worker_with_chains(storage, balances).await; + let chain_id = chain_1_desc.id(); { - let block_proposal = make_first_block(ChainId::root(1)) + let block_proposal = make_first_block(chain_id) .with_timestamp(Timestamp::from(TEST_GRACE_PERIOD_MICROS + 1_000_000)) .into_first_proposal(&key_pair); // Timestamp too far in the future assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err(WorkerError::InvalidTimestamp) ); } let block_0_time = Timestamp::from(TEST_GRACE_PERIOD_MICROS); let certificate = { - let block = make_first_block(ChainId::root(1)).with_timestamp(block_0_time); + let block = make_first_block(chain_id).with_timestamp(block_0_time); let block_proposal = block.clone().into_first_proposal(&key_pair); - let future = worker.handle_block_proposal(block_proposal); + let future = env.worker().handle_block_proposal(block_proposal); clock.set(block_0_time); future.await?; let system_state = SystemExecutionState { - committees: [(epoch, committee.clone())].into_iter().collect(), + committees: [(epoch, env.committee().clone())].into_iter().collect(), ownership: ChainOwnership::single(key_pair.public().into()), balance, timestamp: block_0_time, - ..SystemExecutionState::new(epoch, ChainDescription::Root(1), ChainId::root(0)) + ..SystemExecutionState::new(chain_1_desc.clone()) }; let state_hash = system_state.into_hash().await; let value = ConfirmedBlock::new( @@ -572,9 +625,9 @@ where } .with(block), ); - make_certificate(&committee, &worker, value) + env.make_certificate(value) }; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; @@ -584,7 +637,7 @@ where .into_first_proposal(&key_pair); // Timestamp older than previous one assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::InvalidBlockTimestamp) ); @@ -602,33 +655,26 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (_, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); let unknown_key = AccountSecretKey::generate(); - let unknown_sender_block_proposal = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(5)) + let unknown_sender_block_proposal = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::from_tokens(5)) .into_first_proposal(&unknown_key); assert_matches!( - worker + env.worker() .handle_block_proposal(unknown_sender_block_proposal) .await, Err(WorkerError::InvalidOwner) ); - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.confirmed_vote().is_none()); assert!(chain.manager.validated_vote().is_none()); @@ -645,35 +691,36 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chain( - storage_builder.build().await?, - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ) - .await; - let block_proposal0 = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::ONE) + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_1 = chain_desc.id(); + let chain_2 = env + .add_root_chain(2, sender_key_pair.public().into(), Amount::ZERO) + .await + .id(); + let block_proposal0 = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::ONE) .with_authenticated_signer(Some(sender_key_pair.public().into())) .into_first_proposal(&sender_key_pair); - let certificate0 = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::ONE, - Vec::new(), - &committee, - Amount::from_tokens(4), - &worker, - None, - ) - .await; + let certificate0 = env + .make_simple_transfer_certificate( + chain_desc.clone(), + &sender_key_pair, + chain_2, + Amount::ONE, + Vec::new(), + Amount::from_tokens(4), + None, + ) + .await; let block_proposal1 = make_child_block(certificate0.value()) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(2)) + .with_simple_transfer(chain_2, Amount::from_tokens(2)) .into_first_proposal(&sender_key_pair); assert_matches!( - worker.handle_block_proposal(block_proposal1.clone()).await, + env.worker().handle_block_proposal(block_proposal1.clone()).await, Err(WorkerError::ChainError(error)) if matches!( *error, ChainError::UnexpectedBlockHeight { @@ -681,32 +728,29 @@ where found_block_height: BlockHeight(1) }) ); - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.confirmed_vote().is_none()); assert!(chain.manager.validated_vote().is_none()); drop(chain); - worker + env.worker() .handle_block_proposal(block_proposal0.clone()) .await?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); let block = chain.manager.validated_vote().unwrap().value().block(); // Multi-leader round - it's not confirmed yet. assert!(block.matches_proposed_block(&block_proposal0.content.block)); assert!(chain.manager.confirmed_vote().is_none()); - let block_certificate0 = make_certificate( - &committee, - &worker, - chain.manager.validated_vote().unwrap().value().clone(), - ); + let block_certificate0 = + env.make_certificate(chain.manager.validated_vote().unwrap().value().clone()); drop(chain); - worker + env.worker() .handle_validated_certificate(block_certificate0) .await?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); let block = chain.manager.confirmed_vote().unwrap().value().block(); @@ -715,24 +759,24 @@ where assert!(chain.manager.validated_vote().is_none()); drop(chain); - worker + env.worker() .handle_confirmed_certificate(certificate0, None) .await?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; drop(chain); - worker + env.worker() .handle_block_proposal(block_proposal1.clone()) .await?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); let block = chain.manager.validated_vote().unwrap().value().block(); assert!(block.matches_proposed_block(&block_proposal1.content.block)); assert!(chain.manager.confirmed_vote().is_none()); drop(chain); assert_matches!( - worker.handle_block_proposal(block_proposal0).await, + env.worker().handle_block_proposal(block_proposal0).await, Err(WorkerError::ChainError(error)) if matches!( *error, ChainError::UnexpectedBlockHeight { @@ -756,92 +800,70 @@ where { let sender_key_pair = AccountSecretKey::generate(); let recipient_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(6), - ), - ( - ChainDescription::Root(2), - recipient_key_pair.public().into(), - Amount::ZERO, - ), - ], - ) - .await; - - let epoch = Epoch::ZERO; - let admin_id = ChainId::root(0); - let certificate0 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![ - vec![direct_credit_message(ChainId::root(2), Amount::ONE)], - vec![direct_credit_message( - ChainId::root(2), - Amount::from_tokens(2), - )], - ], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new(); 2], - blobs: vec![Vec::new(); 2], - state_hash: SystemExecutionState { - committees: [(epoch, committee.clone())].into_iter().collect(), - ownership: ChainOwnership::single(sender_key_pair.public().into()), - balance: Amount::from_tokens(3), - ..SystemExecutionState::new(epoch, ChainDescription::Root(1), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![Vec::new(); 2], - operation_results: vec![OperationResult::default(); 2], + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(6)) + .await; + let chain_2_desc = env + .add_root_chain(2, recipient_key_pair.public().into(), Amount::ZERO) + .await; + let chain_3_desc = env + .add_root_chain(3, recipient_key_pair.public().into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let chain_3 = chain_3_desc.id(); + + let certificate0 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![ + vec![direct_credit_message(chain_2, Amount::ONE)], + vec![direct_credit_message(chain_2, Amount::from_tokens(2))], + ], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new(); 2], + blobs: vec![Vec::new(); 2], + state_hash: SystemExecutionState { + balance: Amount::from_tokens(3), + ..SystemExecutionState::new(chain_1_desc.clone()) } - .with( - make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::ONE) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(2)) - .with_authenticated_signer(Some(sender_key_pair.public().into())), - ), + .into_hash() + .await, + oracle_responses: vec![Vec::new(); 2], + operation_results: vec![OperationResult::default(); 2], + } + .with( + make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::ONE) + .with_simple_transfer(chain_2, Amount::from_tokens(2)) + .with_authenticated_signer(Some(sender_key_pair.public().into())), ), - ); + )); - let certificate1 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![vec![direct_credit_message( - ChainId::root(2), - Amount::from_tokens(3), - )]], - previous_message_blocks: BTreeMap::from([(ChainId::root(2), certificate0.hash())]), - events: vec![Vec::new()], - blobs: vec![Vec::new()], - state_hash: SystemExecutionState { - committees: [(epoch, committee.clone())].into_iter().collect(), - ownership: ChainOwnership::single(sender_key_pair.public().into()), - ..SystemExecutionState::new(epoch, ChainDescription::Root(1), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![Vec::new()], - operation_results: vec![OperationResult::default()], + let certificate1 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![vec![direct_credit_message(chain_2, Amount::from_tokens(3))]], + previous_message_blocks: BTreeMap::from([(chain_2, certificate0.hash())]), + events: vec![Vec::new()], + blobs: vec![Vec::new()], + state_hash: SystemExecutionState { + balance: Amount::ZERO, + ..SystemExecutionState::new(chain_1_desc.clone()) } - .with( - make_child_block(&certificate0.clone().into_value()) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(3)) - .with_authenticated_signer(Some(sender_key_pair.public().into())), - ), + .into_hash() + .await, + oracle_responses: vec![Vec::new()], + operation_results: vec![OperationResult::default()], + } + .with( + make_child_block(&certificate0.clone().into_value()) + .with_simple_transfer(chain_2, Amount::from_tokens(3)) + .with_authenticated_signer(Some(sender_key_pair.public().into())), ), - ); + )); // Missing earlier blocks assert_matches!( - worker + env.worker() .handle_confirmed_certificate(certificate1.clone(), None) .await, Err(WorkerError::MissingEarlierBlocks { .. }) @@ -849,53 +871,53 @@ where // Run transfers let notifications = Arc::new(Mutex::new(Vec::new())); - worker + env.worker() .fully_handle_certificate_with_notifications(certificate0.clone(), ¬ifications) .await?; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate1.clone(), ¬ifications) .await?; assert_eq!( *notifications.lock().unwrap(), vec![ Notification { - chain_id: ChainId::root(1), + chain_id: chain_1, reason: NewBlock { height: BlockHeight(0), hash: certificate0.hash(), } }, Notification { - chain_id: ChainId::root(2), + chain_id: chain_2, reason: NewIncomingBundle { - origin: ChainId::root(1), + origin: chain_1, height: BlockHeight(0) } }, Notification { - chain_id: ChainId::root(1), + chain_id: chain_1, reason: NewBlock { height: BlockHeight(1), hash: certificate1.hash(), } }, Notification { - chain_id: ChainId::root(2), + chain_id: chain_2, reason: NewIncomingBundle { - origin: ChainId::root(1), + origin: chain_1, height: BlockHeight(1) } } ] ); { - let block_proposal = make_first_block(ChainId::root(2)) - .with_simple_transfer(ChainId::root(3), Amount::from_tokens(6)) + let block_proposal = make_first_block(chain_2) + .with_simple_transfer(chain_3, Amount::from_tokens(6)) .with_authenticated_signer(Some(recipient_key_pair.public().into())) .into_first_proposal(&recipient_key_pair); // Insufficient funding assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err( WorkerError::ChainError(error) ) if matches!(&*error, ChainError::ExecutionError( @@ -904,10 +926,10 @@ where ); } { - let block_proposal = make_first_block(ChainId::root(2)) - .with_simple_transfer(ChainId::root(3), Amount::from_tokens(5)) + let block_proposal = make_first_block(chain_2) + .with_simple_transfer(chain_3, Amount::from_tokens(5)) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::ZERO, @@ -920,7 +942,7 @@ where action: MessageAction::Accept, }) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::ZERO, @@ -932,7 +954,7 @@ where action: MessageAction::Accept, }) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate1.hash(), height: BlockHeight::from(1), @@ -949,16 +971,16 @@ where .into_first_proposal(&recipient_key_pair); // Inconsistent received messages. assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err(WorkerError::ChainError(chain_error)) if matches!(*chain_error, ChainError::UnexpectedMessage { .. }) ); } { - let block_proposal = make_first_block(ChainId::root(2)) - .with_simple_transfer(ChainId::root(3), Amount::from_tokens(6)) + let block_proposal = make_first_block(chain_2) + .with_simple_transfer(chain_3, Amount::from_tokens(6)) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::ZERO, @@ -973,16 +995,16 @@ where .into_first_proposal(&recipient_key_pair); // Skipped message. assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err(WorkerError::ChainError(chain_error)) if matches!(*chain_error, ChainError::CannotSkipMessage { .. }) ); } { - let block_proposal = make_first_block(ChainId::root(2)) - .with_simple_transfer(ChainId::root(3), Amount::from_tokens(6)) + let block_proposal = make_first_block(chain_2) + .with_simple_transfer(chain_3, Amount::from_tokens(6)) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate1.hash(), height: BlockHeight::from(1), @@ -994,7 +1016,7 @@ where action: MessageAction::Accept, }) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::ZERO, @@ -1007,7 +1029,7 @@ where action: MessageAction::Accept, }) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::ZERO, @@ -1022,16 +1044,16 @@ where .into_first_proposal(&recipient_key_pair); // Inconsistent order in received messages (heights). assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err(WorkerError::ChainError(chain_error)) if matches!(*chain_error, ChainError::CannotSkipMessage { .. }) ); } { - let block_proposal = make_first_block(ChainId::root(2)) - .with_simple_transfer(ChainId::root(3), Amount::ONE) + let block_proposal = make_first_block(chain_2) + .with_simple_transfer(chain_3, Amount::ONE) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::ZERO, @@ -1046,41 +1068,35 @@ where .with_authenticated_signer(Some(recipient_key_pair.public().into())) .into_first_proposal(&recipient_key_pair); // Taking the first message only is ok. - worker.handle_block_proposal(block_proposal.clone()).await?; - let certificate: ConfirmedBlockCertificate = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![ - Vec::new(), - vec![direct_credit_message(ChainId::root(3), Amount::ONE)], - ], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new(); 2], - blobs: vec![Vec::new(); 2], - state_hash: SystemExecutionState { - committees: [(epoch, committee.clone())].into_iter().collect(), - ownership: ChainOwnership::single(recipient_key_pair.public().into()), - ..SystemExecutionState::new(epoch, ChainDescription::Root(2), admin_id) - } + env.worker() + .handle_block_proposal(block_proposal.clone()) + .await?; + let certificate: ConfirmedBlockCertificate = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![ + Vec::new(), + vec![direct_credit_message(chain_3, Amount::ONE)], + ], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new(); 2], + blobs: vec![Vec::new(); 2], + state_hash: SystemExecutionState::new(chain_2_desc.clone()) .into_hash() .await, - oracle_responses: vec![Vec::new(); 2], - operation_results: vec![OperationResult::default()], - } - .with(block_proposal.content.block), - ), - ); - worker + oracle_responses: vec![Vec::new(); 2], + operation_results: vec![OperationResult::default()], + } + .with(block_proposal.content.block), + )); + env.worker() .handle_confirmed_certificate(certificate.clone(), None) .await?; // Then receive the next two messages. let block_proposal = make_child_block(&certificate.into_value()) - .with_simple_transfer(ChainId::root(3), Amount::from_tokens(3)) + .with_simple_transfer(chain_3, Amount::from_tokens(3)) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate0.hash(), height: BlockHeight::from(0), @@ -1092,7 +1108,7 @@ where action: MessageAction::Accept, }) .with_incoming_bundle(IncomingBundle { - origin: ChainId::root(1), + origin: chain_1, bundle: MessageBundle { certificate_hash: certificate1.hash(), height: BlockHeight::from(1), @@ -1104,7 +1120,9 @@ where action: MessageAction::Accept, }) .into_first_proposal(&recipient_key_pair); - worker.handle_block_proposal(block_proposal.clone()).await?; + env.worker() + .handle_block_proposal(block_proposal.clone()) + .await?; } Ok(()) } @@ -1119,35 +1137,28 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (_, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; - let block_proposal = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(1000)) + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let block_proposal = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::from_tokens(1000)) .with_authenticated_signer(Some(sender_key_pair.public().into())) .into_first_proposal(&sender_key_pair); assert_matches!( - worker.handle_block_proposal(block_proposal).await, + env.worker().handle_block_proposal(block_proposal).await, Err( WorkerError::ChainError(error) ) if matches!(&*error, ChainError::ExecutionError( execution_error, ChainExecutionContext::Operation(_) ) if matches!(**execution_error, ExecutionError::InsufficientFunding { .. })) ); - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.confirmed_vote().is_none()); assert!(chain.manager.validated_vote().is_none()); @@ -1164,38 +1175,37 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - )], - ) - .await; - let block_proposal = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(5)) + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let block_proposal = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::from_tokens(5)) .with_authenticated_signer(Some(sender_key_pair.public().into())) .into_first_proposal(&sender_key_pair); - let (chain_info_response, _actions) = worker.handle_block_proposal(block_proposal).await?; - chain_info_response.check(&worker.public_key())?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let (chain_info_response, _actions) = + env.worker().handle_block_proposal(block_proposal).await?; + chain_info_response.check(&env.worker().public_key())?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.confirmed_vote().is_none()); // It was a multi-leader // round. - let validated_certificate = make_certificate( - &committee, - &worker, - chain.manager.validated_vote().unwrap().value().clone(), - ); + let validated_certificate = + env.make_certificate(chain.manager.validated_vote().unwrap().value().clone()); drop(chain); - let (chain_info_response, _actions) = worker + let (chain_info_response, _actions) = env + .worker() .handle_validated_certificate(validated_certificate) .await?; - chain_info_response.check(&worker.public_key())?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + chain_info_response.check(&env.worker().public_key())?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert!(chain.manager.validated_vote().is_none()); // Should be confirmed by now. let pending_vote = chain.manager.confirmed_vote().unwrap().lite(); @@ -1216,30 +1226,26 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (_, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; - let block_proposal = make_first_block(ChainId::root(1)) - .with_simple_transfer(ChainId::root(2), Amount::from_tokens(5)) + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let block_proposal = make_first_block(chain_1) + .with_simple_transfer(chain_2, Amount::from_tokens(5)) .with_authenticated_signer(Some(sender_key_pair.public().into())) .into_first_proposal(&sender_key_pair); - let (response, _actions) = worker.handle_block_proposal(block_proposal.clone()).await?; - response.check(&worker.public_key())?; - let (replay_response, _actions) = worker.handle_block_proposal(block_proposal).await?; + let (response, _actions) = env + .worker() + .handle_block_proposal(block_proposal.clone()) + .await?; + response.check(&env.worker().public_key())?; + let (replay_response, _actions) = env.worker().handle_block_proposal(block_proposal).await?; // Workaround lack of equality. assert_eq!( CryptoHash::new(&*response.info), @@ -1258,32 +1264,29 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - )], - ) - .await; - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::from_tokens(5), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_2_desc = env + .add_root_chain(2, sender_key_pair.public().into(), Amount::ZERO) + .await; + let chain_2 = chain_2_desc.id(); + let chain_1_desc = dummy_chain_description(1); + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + chain_2, + Amount::from_tokens(5), + Vec::new(), + Amount::from_tokens(5), + None, + ) + .await; assert_matches!( - worker + env.worker() .fully_handle_certificate_with_notifications(certificate, &()) .await, - Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::InactiveChain {..}) + Err(WorkerError::BlobsNotFound(error)) + if error == vec![Blob::new_chain_description(&chain_1_desc).id()] ); Ok(()) } @@ -1298,67 +1301,31 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - )], - ) - .await; - let admin_id = ChainId::root(0); - let epoch = Epoch::ZERO; - let description = ChainDescription::Child(MessageId { - chain_id: ChainId::root(3), - height: BlockHeight::ZERO, - index: 0, - }); - let chain_id = ChainId::from(description); - let ownership = ChainOwnership::single(sender_key_pair.public().into()); - let committees = BTreeMap::from_iter([(epoch, committee.clone())]); + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; let balance = Amount::from_tokens(42); - let state = SystemExecutionState { - committees: committees.clone(), - ownership: ownership.clone(), - balance, - ..SystemExecutionState::new(epoch, description, admin_id) - }; - let open_chain_message = IncomingBundle { - origin: ChainId::root(3), - bundle: MessageBundle { - certificate_hash: CryptoHash::test_hash("certificate"), - height: BlockHeight::ZERO, - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![ - Message::System(SystemMessage::OpenChain(Box::new(OpenChainConfig { - ownership, - admin_id, - epoch, - committees, - balance, - application_permissions: Default::default(), - }))) - .to_posted(0, MessageKind::Protected), - ], - }, - action: MessageAction::Accept, - }; + let description = env + .add_child_chain(chain_2_desc.id(), sender_key_pair.public().into(), balance) + .await; + let chain_id = description.id(); + let state = SystemExecutionState::new(description); let value = ConfirmedBlock::new( BlockExecutionOutcome { - messages: vec![Vec::new()], + messages: vec![], previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new()], - blobs: vec![Vec::new()], + events: vec![], + blobs: vec![], state_hash: state.into_hash().await, - oracle_responses: vec![Vec::new()], + oracle_responses: vec![], operation_results: vec![], } - .with(make_first_block(chain_id).with_incoming_bundle(open_chain_message)), + .with(make_first_block(chain_id)), ); - let certificate = make_certificate(&committee, &worker, value); - let info = worker + let certificate = env.make_certificate(value); + let info = env + .worker() .fully_handle_certificate_with_notifications(certificate, &()) .await? .info; @@ -1377,35 +1344,30 @@ where { let sender_key_pair = AccountSecretKey::generate(); let chain_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![( - ChainDescription::Root(2), - chain_key_pair.public().into(), + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_2_desc = env + .add_root_chain(2, chain_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2 = chain_2_desc.id(); + let certificate = env + .make_transfer_certificate_for_epoch( + chain_2_desc.clone(), + &sender_key_pair, + Some(chain_key_pair.public().into()), + AccountOwner::CHAIN, + Recipient::chain(chain_2), Amount::from_tokens(5), - )], - ) - .await; - let certificate = make_transfer_certificate_for_epoch( - ChainDescription::Root(2), - &sender_key_pair, - Some(chain_key_pair.public().into()), - AccountOwner::CHAIN, - Recipient::chain(ChainId::root(2)), - Amount::from_tokens(5), - Vec::new(), - Epoch::ZERO, - &committee, - Amount::ZERO, - BTreeMap::new(), - &worker, - None, - ) - .await; + Vec::new(), + Epoch::ZERO, + Amount::ZERO, + BTreeMap::new(), + None, + ) + .await; // This fails because `make_simple_transfer_certificate` uses `sender_key_pair.public()` to // compute the hash of the execution state. assert_matches!( - worker + env.worker() .fully_handle_certificate_with_notifications(certificate, &()) .await, Err(WorkerError::IncorrectOutcome { .. }) @@ -1423,39 +1385,30 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::from_tokens(5), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_2 = chain_2_desc.id(); + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + chain_2, + Amount::from_tokens(5), + Vec::new(), + Amount::ZERO, + None, + ) + .await; // Replays are ignored. - worker + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate, &()) .await?; Ok(()) @@ -1473,50 +1426,46 @@ where B: StorageBuilder, { let key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ZERO, - ), - ], - ) - .await; - - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &key_pair, - ChainId::root(2), - Amount::from_tokens(1000), - vec![IncomingBundle { - origin: ChainId::root(3), - bundle: MessageBundle { - certificate_hash: CryptoHash::test_hash("certificate"), - height: BlockHeight::ZERO, - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![system_credit_message(Amount::from_tokens(995)) - .to_posted(0, MessageKind::Tracked)], - }, - action: MessageAction::Accept, - }], - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; - worker + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ZERO) + .await; + let chain_3_desc = env + .add_root_chain(3, AccountPublicKey::test_key(3).into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let chain_3 = chain_3_desc.id(); + + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &key_pair, + chain_2, + Amount::from_tokens(1000), + vec![IncomingBundle { + origin: chain_3, + bundle: MessageBundle { + certificate_hash: CryptoHash::test_hash("certificate"), + height: BlockHeight::ZERO, + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![system_credit_message(Amount::from_tokens(995)) + .to_posted(0, MessageKind::Tracked)], + }, + action: MessageAction::Accept, + }], + Amount::ZERO, + None, + ) + .await; + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert_eq!(Amount::ZERO, *chain.execution_state.system.balance.get()); assert_eq!( @@ -1525,9 +1474,9 @@ where ); let inbox = chain .inboxes - .try_load_entry(&ChainId::root(3)) + .try_load_entry(&chain_3) .await? - .expect("Missing inbox for `ChainId::root(3)` in `ChainId::root(1)`"); + .expect("Missing inbox for `chain_3` in `ChainId::root(1)`"); assert_eq!(BlockHeight::ZERO, inbox.next_block_height_to_receive()?); assert_eq!(inbox.added_bundles.count(), 0); assert_matches!( @@ -1557,7 +1506,7 @@ where ); assert_eq!(chain.confirmed_log.count(), 1); assert_eq!(Some(certificate.hash()), chain.tip_state.get().block_hash); - let chain = worker.chain_state_view(ChainId::root(2)).await?; + let chain = env.worker().chain_state_view(chain_2).await?; assert!(chain.is_active()); Ok(()) } @@ -1574,39 +1523,31 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::ONE, - ), - ( - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::MAX, - ), - ], - ) - .await; - - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::ONE, - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; - worker + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::ONE) + .await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::MAX) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + chain_2, + Amount::ONE, + Vec::new(), + Amount::ZERO, + None, + ) + .await; + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; - let new_sender_chain = worker.chain_state_view(ChainId::root(1)).await?; + let new_sender_chain = env.worker().chain_state_view(chain_1).await?; assert!(new_sender_chain.is_active()); assert_eq!( Amount::ZERO, @@ -1621,7 +1562,7 @@ where Some(certificate.hash()), new_sender_chain.tip_state.get().block_hash ); - let new_recipient_chain = worker.chain_state_view(ChainId::root(2)).await?; + let new_recipient_chain = env.worker().chain_state_view(chain_2).await?; assert!(new_recipient_chain.is_active()); assert_eq!( Amount::MAX, @@ -1644,30 +1585,30 @@ where let storage = storage_builder.build().await?; let key_pair = AccountSecretKey::generate(); let owner = key_pair.public().into(); - let (committee, worker) = - init_worker_with_chain(storage, ChainDescription::Root(1), owner, Amount::ONE).await; - - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &key_pair, - ChainId::root(1), - Amount::ONE, - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; - worker + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env.add_root_chain(1, owner, Amount::ONE).await; + let chain_1 = chain_1_desc.id(); + + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &key_pair, + chain_1, + Amount::ONE, + Vec::new(), + Amount::ZERO, + None, + ) + .await; + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker().chain_state_view(chain_1).await?; assert!(chain.is_active()); assert_eq!(Amount::ZERO, *chain.execution_state.system.balance.get()); let inbox = chain .inboxes - .try_load_entry(&ChainId::root(1)) + .try_load_entry(&chain_1) .await? .expect("Missing inbox for `ChainId::root(1)` in `ChainId::root(1)`"); assert_eq!(BlockHeight::from(1), inbox.next_block_height_to_receive()?); @@ -1711,40 +1652,36 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chain( - storage_builder.build().await?, - ChainDescription::Root(2), - AccountPublicKey::test_key(2).into(), - Amount::ONE, - ) - .await; - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::from_tokens(10), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; - worker - .handle_cross_chain_request(update_recipient_direct( - ChainId::root(2), - &certificate.clone(), - )) + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_2_desc = env + .add_root_chain(2, AccountPublicKey::test_key(2).into(), Amount::ONE) + .await; + let chain_2 = chain_2_desc.id(); + let chain_1_desc = dummy_chain_description(1); + let chain_1 = chain_1_desc.id(); + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc, + &sender_key_pair, + chain_2, + Amount::from_tokens(10), + Vec::new(), + Amount::ZERO, + None, + ) + .await; + env.worker() + .handle_cross_chain_request(update_recipient_direct(chain_2, &certificate.clone())) .await?; - let chain = worker.chain_state_view(ChainId::root(2)).await?; + let chain = env.worker().chain_state_view(chain_2).await?; assert!(chain.is_active()); assert_eq!(Amount::ONE, *chain.execution_state.system.balance.get()); assert_eq!(BlockHeight::ZERO, chain.tip_state.get().next_block_height); let inbox = chain .inboxes - .try_load_entry(&ChainId::root(1)) + .try_load_entry(&chain_1) .await? - .expect("Missing inbox for `ChainId::root(1)` in `ChainId::root(2)`"); + .expect("Missing inbox for `ChainId::root(1)` in `chain_2`"); assert_eq!(BlockHeight::from(1), inbox.next_block_height_to_receive()?); assert_matches!( inbox @@ -1790,27 +1727,29 @@ where { let storage = storage_builder.build().await?; let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker( - storage, /* is_client */ false, /* has_long_lived_services */ false, - ); - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::from_tokens(10), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; - assert!(worker - .handle_cross_chain_request(update_recipient_direct(ChainId::root(2), &certificate)) + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(10)) + .await; + let chain_2 = dummy_chain_description(2).id(); + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc, + &sender_key_pair, + chain_2, + Amount::from_tokens(10), + Vec::new(), + Amount::ZERO, + None, + ) + .await; + assert!(env + .worker() + .handle_cross_chain_request(update_recipient_direct(chain_2, &certificate)) .await? .cross_chain_requests .is_empty()); - let chain = worker.chain_state_view(ChainId::root(2)).await?; + let chain = env.worker().chain_state_view(chain_2).await?; // The target chain did not receive the message assert!(chain.inboxes.indices().await?.is_empty()); Ok(()) @@ -1829,24 +1768,27 @@ where { let storage = storage_builder.build().await?; let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker( - storage, /* is_client */ true, /* has_long_lived_services */ false, - ); - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::from_tokens(10), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; + let mut env = TestEnvironment::new(storage, true, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(10)) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = dummy_chain_description(2).id(); + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc, + &sender_key_pair, + chain_2, + Amount::from_tokens(10), + Vec::new(), + Amount::ZERO, + None, + ) + .await; // An inactive target chain is created and it acknowledges the message. - let actions = worker - .handle_cross_chain_request(update_recipient_direct(ChainId::root(2), &certificate)) + let actions = env + .worker() + .handle_cross_chain_request(update_recipient_direct(chain_2, &certificate)) .await?; assert_matches!( actions.cross_chain_requests.as_slice(), @@ -1855,14 +1797,14 @@ where assert_eq!( actions.notifications, vec![Notification { - chain_id: ChainId::root(2), + chain_id: chain_2, reason: Reason::NewIncomingBundle { - origin: ChainId::root(1), + origin: chain_1, height: BlockHeight::ZERO, } }] ); - let chain = worker.chain_state_view(ChainId::root(2)).await?; + let chain = env.worker().chain_state_view(chain_2).await?; assert!(!chain.inboxes.indices().await?.is_empty()); Ok(()) } @@ -1880,76 +1822,73 @@ where { let sender_key_pair = AccountSecretKey::generate(); let recipient_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ), - ( - ChainDescription::Root(2), - recipient_key_pair.public().into(), - Amount::ZERO, - ), - ], - ) - .await; + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_2_desc = env + .add_root_chain(2, recipient_key_pair.public().into(), Amount::ZERO) + .await; + let chain_3_desc = env + .add_root_chain(3, recipient_key_pair.public().into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + let chain_3 = chain_3_desc.id(); assert_eq!( - worker - .query_application(ChainId::root(1), Query::System(SystemQuery)) + env.worker() + .query_application(chain_1, Query::System(SystemQuery)) .await?, QueryOutcome { response: QueryResponse::System(SystemResponse { - chain_id: ChainId::root(1), + chain_id: chain_1, balance: Amount::from_tokens(5), }), operations: vec![], } ); assert_eq!( - worker - .query_application(ChainId::root(2), Query::System(SystemQuery)) + env.worker() + .query_application(chain_2, Query::System(SystemQuery)) .await?, QueryOutcome { response: QueryResponse::System(SystemResponse { - chain_id: ChainId::root(2), + chain_id: chain_2, balance: Amount::ZERO, }), operations: vec![], } ); - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), - Amount::from_tokens(5), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + chain_2, + Amount::from_tokens(5), + Vec::new(), + Amount::ZERO, + None, + ) + .await; - let info = worker + let info = env + .worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await? .info; - assert_eq!(ChainId::root(1), info.chain_id); + assert_eq!(chain_1, info.chain_id); assert_eq!(Amount::ZERO, info.chain_balance); assert_eq!(BlockHeight::from(1), info.next_block_height); assert_eq!(Some(certificate.hash()), info.block_hash); assert!(info.manager.pending.is_none()); assert_eq!( - worker - .query_application(ChainId::root(1), Query::System(SystemQuery)) + env.worker() + .query_application(chain_1, Query::System(SystemQuery)) .await?, QueryOutcome { response: QueryResponse::System(SystemResponse { - chain_id: ChainId::root(1), + chain_id: chain_1, balance: Amount::ZERO, }), operations: vec![], @@ -1957,40 +1896,39 @@ where ); // Try to use the money. This requires selecting the incoming message in a next block. - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(2), - &recipient_key_pair, - ChainId::root(3), - Amount::ONE, - vec![IncomingBundle { - origin: ChainId::root(1), - bundle: MessageBundle { - certificate_hash: certificate.hash(), - height: BlockHeight::ZERO, - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![system_credit_message(Amount::from_tokens(5)) - .to_posted(0, MessageKind::Tracked)], - }, - action: MessageAction::Accept, - }], - &committee, - Amount::from_tokens(4), - &worker, - None, - ) - .await; - worker + let certificate = env + .make_simple_transfer_certificate( + chain_2_desc.clone(), + &recipient_key_pair, + chain_3, + Amount::ONE, + vec![IncomingBundle { + origin: chain_1, + bundle: MessageBundle { + certificate_hash: certificate.hash(), + height: BlockHeight::ZERO, + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![system_credit_message(Amount::from_tokens(5)) + .to_posted(0, MessageKind::Tracked)], + }, + action: MessageAction::Accept, + }], + Amount::from_tokens(4), + None, + ) + .await; + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; assert_eq!( - worker - .query_application(ChainId::root(2), Query::System(SystemQuery)) + env.worker() + .query_application(chain_2, Query::System(SystemQuery)) .await?, QueryOutcome { response: QueryResponse::System(SystemResponse { - chain_id: ChainId::root(2), + chain_id: chain_2, balance: Amount::from_tokens(4), }), operations: vec![], @@ -1998,7 +1936,7 @@ where ); { - let recipient_chain = worker.chain_state_view(ChainId::root(2)).await?; + let recipient_chain = env.worker().chain_state_view(chain_2).await?; assert!(recipient_chain.is_active()); assert_eq!( *recipient_chain.execution_state.system.balance.get(), @@ -2019,13 +1957,13 @@ where ); assert_eq!(recipient_chain.received_log.count(), 1); } - let query = ChainInfoQuery::new(ChainId::root(2)).with_received_log_excluding_first_n(0); - let (response, _actions) = worker.handle_chain_info_query(query).await?; + let query = ChainInfoQuery::new(chain_2).with_received_log_excluding_first_n(0); + let (response, _actions) = env.worker().handle_chain_info_query(query).await?; assert_eq!(response.info.requested_received_log.len(), 1); assert_eq!( response.info.requested_received_log[0], ChainAndHeight { - chain_id: ChainId::root(1), + chain_id: chain_1, height: BlockHeight::ZERO } ); @@ -2044,31 +1982,30 @@ where B: StorageBuilder, { let sender_key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chain( - storage_builder.build().await?, - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(5), - ) - .await; - let certificate = make_simple_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - ChainId::root(2), // the recipient chain does not exist - Amount::from_tokens(5), - Vec::new(), - &committee, - Amount::ZERO, - &worker, - None, - ) - .await; + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(5)) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = dummy_chain_description(2).id(); + let certificate = env + .make_simple_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + chain_2, // the recipient chain does not exist + Amount::from_tokens(5), + Vec::new(), + Amount::ZERO, + None, + ) + .await; - let info = worker + let info = env + .worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await? .info; - assert_eq!(ChainId::root(1), info.chain_id); + assert_eq!(chain_1, info.chain_id); assert_eq!(Amount::ZERO, info.chain_balance); assert_eq!(BlockHeight::from(1), info.next_block_height); assert_eq!(Some(certificate.hash()), info.block_hash); @@ -2089,237 +2026,225 @@ where { let sender_key_pair = AccountSecretKey::generate(); let sender = AccountOwner::from(sender_key_pair.public()); - let sender_account = Account { - chain_id: ChainId::root(1), - owner: sender, - }; let recipient_key_pair = AccountSecretKey::generate(); let recipient = AccountOwner::from(sender_key_pair.public()); + + let mut env = TestEnvironment::new(storage_builder.build().await?, false, false).await; + let chain_1_desc = env + .add_root_chain(1, sender_key_pair.public().into(), Amount::from_tokens(6)) + .await; + let chain_2_desc = env + .add_root_chain(2, recipient_key_pair.public().into(), Amount::ZERO) + .await; + let chain_1 = chain_1_desc.id(); + let chain_2 = chain_2_desc.id(); + + let sender_account = Account { + chain_id: chain_1, + owner: sender, + }; let recipient_account = Account { - chain_id: ChainId::root(2), + chain_id: chain_2, owner: recipient, }; - let (committee, worker) = init_worker_with_chains( - storage_builder.build().await?, - vec![ - ( - ChainDescription::Root(1), - sender_key_pair.public().into(), - Amount::from_tokens(6), - ), - ( - ChainDescription::Root(2), - recipient_key_pair.public().into(), - Amount::ZERO, - ), - ], - ) - .await; - // First move the money from the public balance to the sender's account. // This takes two certificates (sending, receiving) sadly. - let certificate00 = make_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - Some(AccountOwner::from(sender_key_pair.public())), - AccountOwner::CHAIN, - Recipient::Account(sender_account), - Amount::from_tokens(5), - Vec::new(), - &committee, - Amount::ONE, - BTreeMap::new(), - &worker, - None, - ) - .await; + let certificate00 = env + .make_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + Some(AccountOwner::from(sender_key_pair.public())), + AccountOwner::CHAIN, + Recipient::Account(sender_account), + Amount::from_tokens(5), + Vec::new(), + Amount::ONE, + BTreeMap::new(), + None, + ) + .await; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate00.clone(), &()) .await?; - let certificate01 = make_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - Some(AccountOwner::from(sender_key_pair.public())), - AccountOwner::CHAIN, - Recipient::Burn, - Amount::ONE, - vec![IncomingBundle { - origin: ChainId::root(1), - bundle: MessageBundle { - certificate_hash: certificate00.hash(), - height: BlockHeight::from(0), - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![Message::System(SystemMessage::Credit { - source: AccountOwner::CHAIN, - target: sender, - amount: Amount::from_tokens(5), - }) - .to_posted(0, MessageKind::Tracked)], - }, - action: MessageAction::Accept, - }], - &committee, - Amount::ZERO, - BTreeMap::from_iter([(sender, Amount::from_tokens(5))]), - &worker, - Some(&certificate00), - ) - .await; + let certificate01 = env + .make_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + Some(AccountOwner::from(sender_key_pair.public())), + AccountOwner::CHAIN, + Recipient::Burn, + Amount::ONE, + vec![IncomingBundle { + origin: chain_1, + bundle: MessageBundle { + certificate_hash: certificate00.hash(), + height: BlockHeight::from(0), + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![Message::System(SystemMessage::Credit { + source: AccountOwner::CHAIN, + target: sender, + amount: Amount::from_tokens(5), + }) + .to_posted(0, MessageKind::Tracked)], + }, + action: MessageAction::Accept, + }], + Amount::ZERO, + BTreeMap::from_iter([(sender, Amount::from_tokens(5))]), + Some(&certificate00), + ) + .await; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate01.clone(), &()) .await?; { - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker.chain_state_view(chain_1).await?; assert!(chain.is_active()); chain.validate_incoming_bundles().await?; } - // Then, make two transfers to the recipient. - let certificate1 = make_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - Some(sender), - sender, - Recipient::Account(recipient_account), - Amount::from_tokens(3), - Vec::new(), - &committee, - Amount::ZERO, - BTreeMap::from_iter([(sender, Amount::from_tokens(2))]), - &worker, - Some(&certificate01), - ) - .await; - - worker + // Then, make two transfers to the recipient. + let certificate1 = env + .make_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + Some(sender), + sender, + Recipient::Account(recipient_account), + Amount::from_tokens(3), + Vec::new(), + Amount::ZERO, + BTreeMap::from_iter([(sender, Amount::from_tokens(2))]), + Some(&certificate01), + ) + .await; + + env.worker() .fully_handle_certificate_with_notifications(certificate1.clone(), &()) .await?; - let certificate2 = make_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - Some(sender), - sender, - Recipient::Account(recipient_account), - Amount::from_tokens(2), - Vec::new(), - &committee, - Amount::ZERO, - BTreeMap::new(), - &worker, - Some(&certificate1), - ) - .await; + let certificate2 = env + .make_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + Some(sender), + sender, + Recipient::Account(recipient_account), + Amount::from_tokens(2), + Vec::new(), + Amount::ZERO, + BTreeMap::new(), + Some(&certificate1), + ) + .await; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate2.clone(), &()) .await?; // Reject the first transfer and try to use the money of the second one. - let certificate = make_transfer_certificate( - ChainDescription::Root(2), - &recipient_key_pair, - Some(recipient), - recipient, - Recipient::Burn, - Amount::ONE, - vec![ - IncomingBundle { - origin: ChainId::root(1), - bundle: MessageBundle { - certificate_hash: certificate1.hash(), - height: BlockHeight::from(2), - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![Message::System(SystemMessage::Credit { - source: sender, - target: recipient, - amount: Amount::from_tokens(3), - }) - .to_posted(0, MessageKind::Tracked)], + let certificate = env + .make_transfer_certificate( + chain_2_desc.clone(), + &recipient_key_pair, + Some(recipient), + recipient, + Recipient::Burn, + Amount::ONE, + vec![ + IncomingBundle { + origin: chain_1, + bundle: MessageBundle { + certificate_hash: certificate1.hash(), + height: BlockHeight::from(2), + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![Message::System(SystemMessage::Credit { + source: sender, + target: recipient, + amount: Amount::from_tokens(3), + }) + .to_posted(0, MessageKind::Tracked)], + }, + action: MessageAction::Reject, }, - action: MessageAction::Reject, - }, - IncomingBundle { - origin: ChainId::root(1), - bundle: MessageBundle { - certificate_hash: certificate2.hash(), - height: BlockHeight::from(3), - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![Message::System(SystemMessage::Credit { - source: sender, - target: recipient, - amount: Amount::from_tokens(2), - }) - .to_posted(0, MessageKind::Tracked)], + IncomingBundle { + origin: chain_1, + bundle: MessageBundle { + certificate_hash: certificate2.hash(), + height: BlockHeight::from(3), + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![Message::System(SystemMessage::Credit { + source: sender, + target: recipient, + amount: Amount::from_tokens(2), + }) + .to_posted(0, MessageKind::Tracked)], + }, + action: MessageAction::Accept, }, - action: MessageAction::Accept, - }, - ], - &committee, - Amount::ZERO, - BTreeMap::from_iter([(recipient, Amount::from_tokens(1))]), - &worker, - None, - ) - .await; + ], + Amount::ZERO, + BTreeMap::from_iter([(recipient, Amount::from_tokens(1))]), + None, + ) + .await; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate.clone(), &()) .await?; { - let chain = worker.chain_state_view(ChainId::root(2)).await?; + let chain = env.worker().chain_state_view(chain_2).await?; assert!(chain.is_active()); chain.validate_incoming_bundles().await?; } // Process the bounced message and try to use the refund. - let certificate3 = make_transfer_certificate( - ChainDescription::Root(1), - &sender_key_pair, - Some(sender), - sender, - Recipient::Burn, - Amount::from_tokens(3), - vec![IncomingBundle { - origin: ChainId::root(2), - bundle: MessageBundle { - certificate_hash: certificate.hash(), - height: BlockHeight::from(0), - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![Message::System(SystemMessage::Credit { - source: sender, - target: recipient, - amount: Amount::from_tokens(3), - }) - .to_posted(0, MessageKind::Bouncing)], - }, - action: MessageAction::Accept, - }], - &committee, - Amount::ZERO, - BTreeMap::new(), - &worker, - Some(&certificate2), - ) - .await; + let certificate3 = env + .make_transfer_certificate( + chain_1_desc.clone(), + &sender_key_pair, + Some(sender), + sender, + Recipient::Burn, + Amount::from_tokens(3), + vec![IncomingBundle { + origin: chain_2, + bundle: MessageBundle { + certificate_hash: certificate.hash(), + height: BlockHeight::from(0), + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![Message::System(SystemMessage::Credit { + source: sender, + target: recipient, + amount: Amount::from_tokens(3), + }) + .to_posted(0, MessageKind::Bouncing)], + }, + action: MessageAction::Accept, + }], + Amount::ZERO, + BTreeMap::new(), + Some(&certificate2), + ) + .await; - worker + env.worker() .fully_handle_certificate_with_notifications(certificate3.clone(), &()) .await?; { - let chain = worker.chain_state_view(ChainId::root(1)).await?; + let chain = env.worker.chain_state_view(chain_1).await?; assert!(chain.is_active()); chain.validate_incoming_bundles().await?; } @@ -2338,74 +2263,48 @@ where B: StorageBuilder, { let storage = storage_builder.build().await?; - let key_pair = AccountSecretKey::generate(); - let (committee, worker) = init_worker_with_chain( - storage.clone(), - ChainDescription::Root(0), - key_pair.public().into(), - Amount::from_tokens(2), - ) - .await; + let mut env = + TestEnvironment::new_with_amount(storage.clone(), false, false, Amount::from_tokens(2)) + .await; let mut committees = BTreeMap::new(); + let committee = env.committee().clone(); committees.insert(Epoch::ZERO, committee.clone()); - let admin_id = ChainId::root(0); + let admin_id = env.admin_id(); // Have the admin chain create a user chain. - let user_description = ChainDescription::Child(MessageId { - chain_id: admin_id, - height: BlockHeight::ZERO, - index: 0, - }); - let user_id = ChainId::from(user_description); - let certificate0 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![vec![direct_outgoing_message( - user_id, - MessageKind::Protected, - SystemMessage::OpenChain(Box::new(OpenChainConfig { - ownership: ChainOwnership::single(key_pair.public().into()), - epoch: Epoch::ZERO, - committees: committees.clone(), - admin_id, - balance: Amount::ZERO, - application_permissions: Default::default(), - })), - )]], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new()], - blobs: vec![Vec::new()], - state_hash: SystemExecutionState { - committees: committees.clone(), - ownership: ChainOwnership::single(key_pair.public().into()), - balance: Amount::from_tokens(2), - ..SystemExecutionState::new(Epoch::ZERO, ChainDescription::Root(0), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![Vec::new()], - operation_results: vec![OperationResult::default()], + let user_description = env + .add_child_chain(admin_id, env.admin_public_key().into(), Amount::ZERO) + .await; + let user_id = user_description.id(); + let certificate0 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![vec![]], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new()], + blobs: vec![vec![Blob::new_chain_description(&user_description)]], + state_hash: SystemExecutionState { + admin_id: Some(admin_id), + ..SystemExecutionState::new(env.admin_description.clone()) } - .with( - make_first_block(admin_id) - .with_operation(SystemOperation::OpenChain(OpenChainConfig { - ownership: ChainOwnership::single(key_pair.public().into()), - epoch: Epoch::ZERO, - committees: committees.clone(), - admin_id, - balance: Amount::ZERO, - application_permissions: Default::default(), - })) - .with_authenticated_signer(Some(key_pair.public().into())), - ), + .into_hash() + .await, + oracle_responses: vec![Vec::new()], + operation_results: vec![OperationResult::default()], + } + .with( + make_first_block(admin_id) + .with_operation(SystemOperation::OpenChain(OpenChainConfig { + ownership: ChainOwnership::single(env.admin_public_key().into()), + balance: Amount::ZERO, + application_permissions: Default::default(), + })) + .with_authenticated_signer(Some(env.admin_public_key().into())), ), - ); - worker + )); + env.worker() .fully_handle_certificate_with_notifications(certificate0.clone(), &()) .await?; { - let admin_chain = worker.chain_state_view(admin_id).await?; + let admin_chain = env.worker().chain_state_view(admin_id).await?; assert!(admin_chain.is_active()); admin_chain.validate_incoming_bundles().await?; assert_eq!( @@ -2434,53 +2333,51 @@ where // just write it directly to storage here for simplicity. storage.write_blob(&committee_blob).await?; let blob_hash = committee_blob.id().hash; - let certificate1 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![ - vec![], - vec![direct_credit_message(user_id, Amount::from_tokens(2))], - ], - previous_message_blocks: BTreeMap::from([(user_id, certificate0.hash())]), - events: vec![ - vec![Event { - stream_id: event_id.stream_id.clone(), - index: event_id.index, - value: bcs::to_bytes(&blob_hash).unwrap(), - }], - Vec::new(), - ], - blobs: vec![Vec::new(); 2], - state_hash: SystemExecutionState { - // The root chain knows both committees at the end. - committees: committees2.clone(), - ownership: ChainOwnership::single(key_pair.public().into()), - used_blobs: BTreeSet::from([committee_blob.id()]), - ..SystemExecutionState::new(Epoch::from(1), ChainDescription::Root(0), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![vec![OracleResponse::Blob(committee_blob.id())], vec![]], - operation_results: vec![OperationResult::default(); 2], + let certificate1 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![ + vec![], + vec![direct_credit_message(user_id, Amount::from_tokens(2))], + ], + previous_message_blocks: BTreeMap::new(), + events: vec![ + vec![Event { + stream_id: event_id.stream_id.clone(), + index: event_id.index, + value: bcs::to_bytes(&blob_hash).unwrap(), + }], + Vec::new(), + ], + blobs: vec![Vec::new(); 2], + state_hash: SystemExecutionState { + // The root chain knows both committees at the end. + committees: committees2.clone(), + used_blobs: BTreeSet::from([committee_blob.id()]), + epoch: Some(Epoch::from(1)), + admin_id: Some(admin_id), + balance: Amount::ZERO, + ..SystemExecutionState::new(env.admin_description.clone()) } - .with( - make_child_block(&certificate0.clone().into_value()) - .with_operation(SystemOperation::Admin(AdminOperation::CreateCommittee { - epoch: Epoch::from(1), - blob_hash, - })) - .with_simple_transfer(user_id, Amount::from_tokens(2)), - ), + .into_hash() + .await, + oracle_responses: vec![vec![OracleResponse::Blob(committee_blob.id())], vec![]], + operation_results: vec![OperationResult::default(); 2], + } + .with( + make_child_block(&certificate0.clone().into_value()) + .with_operation(SystemOperation::Admin(AdminOperation::CreateCommittee { + epoch: Epoch::from(1), + blob_hash, + })) + .with_simple_transfer(user_id, Amount::from_tokens(2)), ), - ); - worker + )); + env.worker() .fully_handle_certificate_with_notifications(certificate1.clone(), &()) .await?; { // The child is active and has not migrated yet. - let user_chain = worker.chain_state_view(user_id).await?; + let user_chain = env.worker().chain_state_view(user_id).await?; assert!(user_chain.is_active()); assert_eq!( BlockHeight::ZERO, @@ -2500,96 +2397,68 @@ where .added_bundles .read_front(10) .await?[..], - [bundle1, bundle2] + [bundle1] if matches!(bundle1.messages[..], [PostedMessage { - message: Message::System(SystemMessage::OpenChain(_)), .. - }]) && matches!(bundle2.messages[..], [PostedMessage { message: Message::System(SystemMessage::Credit { .. }), .. }]) ); assert_eq!(user_chain.execution_state.system.committees.get().len(), 1); } // Make the child receive the pending messages. - let certificate3 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![Vec::new(); 3], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new(); 3], - blobs: vec![Vec::new(); 3], - state_hash: SystemExecutionState { - // Finally the child knows about both committees and has the money. - committees: committees2.clone(), - ownership: ChainOwnership::single(key_pair.public().into()), - balance: Amount::from_tokens(2), - used_blobs: BTreeSet::from([committee_blob.id()]), - ..SystemExecutionState::new(Epoch::from(1), user_description, admin_id) - } - .into_hash() - .await, - oracle_responses: vec![ - vec![], - vec![], - vec![ - OracleResponse::Event( - EventId { - chain_id: admin_id, - stream_id: StreamId::system(NEW_EPOCH_STREAM_NAME), - index: 1, - }, - bcs::to_bytes(&blob_hash).unwrap(), - ), - OracleResponse::Blob(committee_blob.id()), - ], - ], - operation_results: vec![OperationResult::default()], + let certificate3 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![Vec::new(); 2], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new(); 2], + blobs: vec![Vec::new(); 2], + state_hash: SystemExecutionState { + // Finally the child knows about both committees and has the money. + committees: committees2.clone(), + balance: Amount::from_tokens(2), + used_blobs: BTreeSet::from([committee_blob.id()]), + epoch: Some(Epoch::from(1)), + ..SystemExecutionState::new(user_description) } - .with( - make_first_block(user_id) - .with_incoming_bundle(IncomingBundle { - origin: admin_id, - bundle: MessageBundle { - certificate_hash: certificate0.hash(), - height: BlockHeight::from(0), - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![Message::System(SystemMessage::OpenChain(Box::new( - OpenChainConfig { - ownership: ChainOwnership::single(key_pair.public().into()), - epoch: Epoch::from(0), - committees: committees.clone(), - admin_id, - balance: Amount::ZERO, - application_permissions: Default::default(), - }, - ))) - .to_posted(0, MessageKind::Protected)], - }, - action: MessageAction::Accept, - }) - .with_incoming_bundle(IncomingBundle { - origin: admin_id, - bundle: MessageBundle { - certificate_hash: certificate1.hash(), - height: BlockHeight::from(1), - timestamp: Timestamp::from(0), - transaction_index: 1, - messages: vec![system_credit_message(Amount::from_tokens(2)) - .to_posted(0, MessageKind::Tracked)], + .into_hash() + .await, + oracle_responses: vec![ + vec![], + vec![ + OracleResponse::Event( + EventId { + chain_id: admin_id, + stream_id: StreamId::system(NEW_EPOCH_STREAM_NAME), + index: 1, }, - action: MessageAction::Accept, - }) - .with_operation(SystemOperation::ProcessNewEpoch(Epoch::from(1))), - ), + bcs::to_bytes(&blob_hash).unwrap(), + ), + OracleResponse::Blob(committee_blob.id()), + ], + ], + operation_results: vec![OperationResult::default()], + } + .with( + make_first_block(user_id) + .with_incoming_bundle(IncomingBundle { + origin: admin_id, + bundle: MessageBundle { + certificate_hash: certificate1.hash(), + height: BlockHeight::from(1), + timestamp: Timestamp::from(0), + transaction_index: 1, + messages: vec![system_credit_message(Amount::from_tokens(2)) + .to_posted(0, MessageKind::Tracked)], + }, + action: MessageAction::Accept, + }) + .with_operation(SystemOperation::ProcessNewEpoch(Epoch::from(1))), ), - ); - worker + )); + env.worker() .fully_handle_certificate_with_notifications(certificate3, &()) .await?; { - let user_chain = worker.chain_state_view(user_id).await?; + let user_chain = env.worker().chain_state_view(user_id).await?; assert!(user_chain.is_active()); assert_eq!( BlockHeight::from(1), @@ -2614,50 +2483,40 @@ async fn test_transfers_and_committee_creation(mut storage_builder: B) -> any where B: StorageBuilder, { - let owner0 = AccountSecretKey::generate().public().into(); let owner1 = AccountSecretKey::generate().public().into(); let storage = storage_builder.build().await?; - let (committee, worker) = init_worker_with_chains( - storage.clone(), - vec![ - (ChainDescription::Root(0), owner0, Amount::ZERO), - (ChainDescription::Root(1), owner1, Amount::from_tokens(3)), - ], - ) - .await; + let mut env = TestEnvironment::new(storage.clone(), false, false).await; + let chain_1_desc = env.add_root_chain(1, owner1, Amount::from_tokens(3)).await; let mut committees = BTreeMap::new(); + let committee = env.committee().clone(); committees.insert(Epoch::ZERO, committee.clone()); - let admin_id = ChainId::root(0); - let user_id = ChainId::root(1); + let admin_id = env.admin_id(); + let user_id = chain_1_desc.id(); // Have the user chain start a transfer to the admin chain. - let certificate0 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![vec![direct_credit_message(admin_id, Amount::ONE)]], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new()], - blobs: vec![Vec::new()], - state_hash: SystemExecutionState { - committees: committees.clone(), - ownership: ChainOwnership::single(owner1), - balance: Amount::from_tokens(2), - ..SystemExecutionState::new(Epoch::ZERO, ChainDescription::Root(1), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![Vec::new()], - operation_results: vec![OperationResult::default()], + let certificate0 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![vec![direct_credit_message(admin_id, Amount::ONE)]], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new()], + blobs: vec![Vec::new()], + state_hash: SystemExecutionState { + committees: committees.clone(), + ownership: ChainOwnership::single(owner1), + balance: Amount::from_tokens(2), + ..SystemExecutionState::new(chain_1_desc.clone()) } - .with( - make_first_block(user_id) - .with_simple_transfer(admin_id, Amount::ONE) - .with_authenticated_signer(Some(owner1)), - ), + .into_hash() + .await, + oracle_responses: vec![Vec::new()], + operation_results: vec![OperationResult::default()], + } + .with( + make_first_block(user_id) + .with_simple_transfer(admin_id, Amount::ONE) + .with_authenticated_signer(Some(owner1)), ), - ); + )); // Have the admin chain create a new epoch without retiring the old one. let committees2 = BTreeMap::from_iter([ (Epoch::ZERO, committee.clone()), @@ -2666,51 +2525,48 @@ where let committee_blob = Blob::new(BlobContent::new_committee(bcs::to_bytes(&committee)?)); let blob_hash = committee_blob.id().hash; storage.write_blob(&committee_blob).await?; - let certificate1 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![vec![]], - previous_message_blocks: BTreeMap::new(), - events: vec![vec![Event { - stream_id: StreamId::system(NEW_EPOCH_STREAM_NAME), - index: 1, - value: bcs::to_bytes(&committee_blob.id().hash).unwrap(), - }]], - blobs: vec![Vec::new()], - state_hash: SystemExecutionState { - committees: committees2.clone(), - ownership: ChainOwnership::single(owner0), - used_blobs: BTreeSet::from([committee_blob.id()]), - ..SystemExecutionState::new(Epoch::from(1), ChainDescription::Root(0), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![vec![OracleResponse::Blob(committee_blob.id())]], - operation_results: vec![OperationResult::default()], + let certificate1 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![vec![]], + previous_message_blocks: BTreeMap::new(), + events: vec![vec![Event { + stream_id: StreamId::system(NEW_EPOCH_STREAM_NAME), + index: 1, + value: bcs::to_bytes(&committee_blob.id().hash).unwrap(), + }]], + blobs: vec![Vec::new()], + state_hash: SystemExecutionState { + committees: committees2.clone(), + used_blobs: BTreeSet::from([committee_blob.id()]), + admin_id: Some(admin_id), + epoch: Some(Epoch::from(1)), + ..SystemExecutionState::new(env.admin_description.clone()) } - .with( - make_first_block(admin_id).with_operation(SystemOperation::Admin( - AdminOperation::CreateCommittee { - epoch: Epoch::from(1), - blob_hash, - }, - )), - ), + .into_hash() + .await, + oracle_responses: vec![vec![OracleResponse::Blob(committee_blob.id())]], + operation_results: vec![OperationResult::default()], + } + .with( + make_first_block(admin_id).with_operation(SystemOperation::Admin( + AdminOperation::CreateCommittee { + epoch: Epoch::from(1), + blob_hash, + }, + )), ), - ); - worker + )); + env.worker() .fully_handle_certificate_with_notifications(certificate1.clone(), &()) .await?; // Try to execute the transfer. - worker + env.worker() .fully_handle_certificate_with_notifications(certificate0.clone(), &()) .await?; // The transfer was started.. - let user_chain = worker.chain_state_view(user_id).await?; + let user_chain = env.worker().chain_state_view(user_id).await?; assert!(user_chain.is_active()); assert_eq!( BlockHeight::from(1), @@ -2726,7 +2582,7 @@ where ); // .. and the message has gone through. - let admin_chain = worker.chain_state_view(admin_id).await?; + let admin_chain = env.worker().chain_state_view(admin_id).await?; assert!(admin_chain.is_active()); assert_eq!(admin_chain.inboxes.indices().await?.len(), 1); matches!( @@ -2755,110 +2611,98 @@ async fn test_transfers_and_committee_removal(mut storage_builder: B) -> anyh where B: StorageBuilder, { - let owner0 = AccountSecretKey::generate().public().into(); - let owner1 = AccountSecretKey::generate().public().into(); let storage = storage_builder.build().await?; - let (committee, worker) = init_worker_with_chains( - storage.clone(), - vec![ - (ChainDescription::Root(0), owner0, Amount::ZERO), - (ChainDescription::Root(1), owner1, Amount::from_tokens(3)), - ], - ) - .await; + let mut env = + TestEnvironment::new_with_amount(storage.clone(), false, false, Amount::ZERO).await; + let owner1 = AccountSecretKey::generate().public().into(); + let chain_1_desc = env.add_root_chain(1, owner1, Amount::from_tokens(3)).await; let mut committees = BTreeMap::new(); + let committee = env.committee().clone(); committees.insert(Epoch::ZERO, committee.clone()); - let admin_id = ChainId::root(0); - let user_id = ChainId::root(1); + let admin_id = env.admin_id(); + let user_id = chain_1_desc.id(); // Have the user chain start a transfer to the admin chain. - let certificate0 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![vec![direct_credit_message(admin_id, Amount::ONE)]], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new()], - blobs: vec![Vec::new()], - state_hash: SystemExecutionState { - committees: committees.clone(), - ownership: ChainOwnership::single(owner1), - balance: Amount::from_tokens(2), - ..SystemExecutionState::new(Epoch::ZERO, ChainDescription::Root(1), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![Vec::new()], - operation_results: vec![OperationResult::default()], + let certificate0 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![vec![direct_credit_message(admin_id, Amount::ONE)]], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new()], + blobs: vec![Vec::new()], + state_hash: SystemExecutionState { + balance: Amount::from_tokens(2), + ..SystemExecutionState::new(chain_1_desc.clone()) } - .with( - make_first_block(user_id) - .with_simple_transfer(admin_id, Amount::ONE) - .with_authenticated_signer(Some(owner1)), - ), + .into_hash() + .await, + oracle_responses: vec![Vec::new()], + operation_results: vec![OperationResult::default()], + } + .with( + make_first_block(user_id) + .with_simple_transfer(admin_id, Amount::ONE) + .with_authenticated_signer(Some(owner1)), ), - ); + )); // Have the admin chain create a new epoch and retire the old one immediately. let committees3 = BTreeMap::from_iter([(Epoch::from(1), committee.clone())]); let committee_blob = Blob::new(BlobContent::new_committee(bcs::to_bytes(&committee)?)); let blob_hash = committee_blob.id().hash; storage.write_blob(&committee_blob).await?; - let certificate1 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![vec![]; 2], - previous_message_blocks: BTreeMap::new(), - events: vec![ - vec![Event { - stream_id: StreamId::system(NEW_EPOCH_STREAM_NAME), - index: 1, - value: bcs::to_bytes(&committee_blob.id().hash).unwrap(), - }], - vec![Event { - stream_id: StreamId::system(REMOVED_EPOCH_STREAM_NAME), - index: 0, - value: Vec::new(), - }], - ], - blobs: vec![Vec::new(); 2], - state_hash: SystemExecutionState { - committees: committees3.clone(), - ownership: ChainOwnership::single(owner0), - used_blobs: BTreeSet::from([committee_blob.id()]), - ..SystemExecutionState::new(Epoch::from(1), ChainDescription::Root(0), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![vec![OracleResponse::Blob(committee_blob.id())], vec![]], - operation_results: vec![OperationResult::default(); 2], + + let certificate1 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![vec![]; 2], + previous_message_blocks: BTreeMap::new(), + events: vec![ + vec![Event { + stream_id: StreamId::system(NEW_EPOCH_STREAM_NAME), + index: 1, + value: bcs::to_bytes(&committee_blob.id().hash).unwrap(), + }], + vec![Event { + stream_id: StreamId::system(REMOVED_EPOCH_STREAM_NAME), + index: 0, + value: Vec::new(), + }], + ], + blobs: vec![Vec::new(); 2], + state_hash: SystemExecutionState { + committees: committees3.clone(), + used_blobs: BTreeSet::from([committee_blob.id()]), + epoch: Some(Epoch::from(1)), + admin_id: Some(admin_id), + ..SystemExecutionState::new(env.admin_description.clone()) } - .with( - make_first_block(admin_id) - .with_operation(SystemOperation::Admin(AdminOperation::CreateCommittee { - epoch: Epoch::from(1), - blob_hash, - })) - .with_operation(SystemOperation::Admin(AdminOperation::RemoveCommittee { - epoch: Epoch::ZERO, - })), - ), + .into_hash() + .await, + oracle_responses: vec![vec![OracleResponse::Blob(committee_blob.id())], vec![]], + operation_results: vec![OperationResult::default(); 2], + } + .with( + make_first_block(admin_id) + .with_operation(SystemOperation::Admin(AdminOperation::CreateCommittee { + epoch: Epoch::from(1), + blob_hash, + })) + .with_operation(SystemOperation::Admin(AdminOperation::RemoveCommittee { + epoch: Epoch::ZERO, + })), ), - ); - worker + )); + + env.worker() .fully_handle_certificate_with_notifications(certificate1.clone(), &()) .await?; // Try to execute the transfer from the user chain to the admin chain. - worker + env.worker() .fully_handle_certificate_with_notifications(certificate0.clone(), &()) .await?; { // The transfer was started.. - let user_chain = worker.chain_state_view(user_id).await?; + let user_chain = env.worker().chain_state_view(user_id).await?; assert!(user_chain.is_active()); assert_eq!( BlockHeight::from(1), @@ -2874,58 +2718,56 @@ where ); // .. but the message hasn't gone through. - let admin_chain = worker.chain_state_view(admin_id).await?; + let admin_chain = env.worker().chain_state_view(admin_id).await?; assert!(admin_chain.is_active()); assert!(admin_chain.inboxes.indices().await?.is_empty()); } // Force the admin chain to receive the money nonetheless by anticipation. - let certificate2 = make_certificate( - &committee, - &worker, - ConfirmedBlock::new( - BlockExecutionOutcome { - messages: vec![Vec::new()], - previous_message_blocks: BTreeMap::new(), - events: vec![Vec::new()], - blobs: vec![Vec::new()], - state_hash: SystemExecutionState { - committees: committees3.clone(), - ownership: ChainOwnership::single(owner0), - balance: Amount::ONE, - used_blobs: BTreeSet::from([committee_blob.id()]), - ..SystemExecutionState::new(Epoch::from(1), ChainDescription::Root(0), admin_id) - } - .into_hash() - .await, - oracle_responses: vec![Vec::new()], - operation_results: vec![], + let certificate2 = env.make_certificate(ConfirmedBlock::new( + BlockExecutionOutcome { + messages: vec![Vec::new()], + previous_message_blocks: BTreeMap::new(), + events: vec![Vec::new()], + blobs: vec![Vec::new()], + state_hash: SystemExecutionState { + committees: committees3.clone(), + balance: Amount::ONE, + used_blobs: BTreeSet::from([committee_blob.id()]), + admin_id: Some(admin_id), + epoch: Some(Epoch::from(1)), + ..SystemExecutionState::new(env.admin_description.clone()) } - .with( - make_child_block(&certificate1.into_value()) - .with_epoch(1) - .with_incoming_bundle(IncomingBundle { - origin: user_id, - bundle: MessageBundle { - certificate_hash: certificate0.hash(), - height: BlockHeight::ZERO, - timestamp: Timestamp::from(0), - transaction_index: 0, - messages: vec![system_credit_message(Amount::ONE) - .to_posted(0, MessageKind::Tracked)], - }, - action: MessageAction::Accept, - }), - ), + .into_hash() + .await, + oracle_responses: vec![Vec::new()], + operation_results: vec![], + } + .with( + make_child_block(&certificate1.into_value()) + .with_epoch(1) + .with_incoming_bundle(IncomingBundle { + origin: user_id, + bundle: MessageBundle { + certificate_hash: certificate0.hash(), + height: BlockHeight::ZERO, + timestamp: Timestamp::from(0), + transaction_index: 0, + messages: vec![ + system_credit_message(Amount::ONE).to_posted(0, MessageKind::Tracked) + ], + }, + action: MessageAction::Accept, + }), ), - ); - worker + )); + env.worker() .fully_handle_certificate_with_notifications(certificate2.clone(), &()) .await?; { // The admin chain has an anticipated message. - let admin_chain = worker.chain_state_view(admin_id).await?; + let admin_chain = env.worker().chain_state_view(admin_id).await?; assert!(admin_chain.is_active()); assert_matches!( admin_chain.validate_incoming_bundles().await, @@ -2935,13 +2777,13 @@ where // Try again to execute the transfer from the user chain to the admin chain. // This time, the epoch verification should be overruled. - worker + env.worker() .fully_handle_certificate_with_notifications(certificate0.clone(), &()) .await?; { // The admin chain has no more anticipated messages. - let admin_chain = worker.chain_state_view(admin_id).await?; + let admin_chain = env.worker().chain_state_view(admin_id).await?; assert!(admin_chain.is_active()); admin_chain.validate_incoming_bundles().await?; } @@ -2950,7 +2792,6 @@ where #[test(tokio::test)] async fn test_cross_chain_helper() -> anyhow::Result<()> { - // Make a committee and worker (only used for signing certificates) let store_config = MemoryStore::new_test_config().await?; let namespace = generate_test_namespace(); let store = DbStorage::::new_for_testing( @@ -2960,78 +2801,77 @@ async fn test_cross_chain_helper() -> anyhow::Result<()> { TestClock::new(), ) .await?; - let (committee, worker) = init_worker(store, true, false); - let committees = BTreeMap::from_iter([(Epoch::from(1), committee.clone())]); + let env = TestEnvironment::new(store, true, false).await; + let committees = BTreeMap::from([(Epoch::from(1), env.committee().clone())]); + + let chain_0 = env.admin_description.clone(); + let chain_1 = dummy_chain_description(1); let key_pair0 = AccountSecretKey::generate(); - let id0 = ChainId::root(0); - let id1 = ChainId::root(1); - - let certificate0 = make_transfer_certificate_for_epoch( - ChainDescription::Root(0), - &key_pair0, - Some(key_pair0.public().into()), - AccountOwner::CHAIN, - Recipient::chain(id1), - Amount::ONE, - Vec::new(), - Epoch::ZERO, - &committee, - Amount::ONE, - BTreeMap::new(), - &worker, - None, - ) - .await; - let certificate1 = make_transfer_certificate_for_epoch( - ChainDescription::Root(0), - &key_pair0, - Some(key_pair0.public().into()), - AccountOwner::CHAIN, - Recipient::chain(id1), - Amount::ONE, - Vec::new(), - Epoch::ZERO, - &committee, - Amount::ONE, - BTreeMap::new(), - &worker, - Some(&certificate0), - ) - .await; - let certificate2 = make_transfer_certificate_for_epoch( - ChainDescription::Root(0), - &key_pair0, - Some(key_pair0.public().into()), - AccountOwner::CHAIN, - Recipient::chain(id1), - Amount::ONE, - Vec::new(), - Epoch::from(1), - &committee, - Amount::ONE, - BTreeMap::new(), - &worker, - Some(&certificate1), - ) - .await; + let id0 = chain_0.id(); + let id1 = chain_1.id(); + + let certificate0 = env + .make_transfer_certificate_for_epoch( + chain_0.clone(), + &key_pair0, + Some(key_pair0.public().into()), + AccountOwner::CHAIN, + Recipient::chain(id1), + Amount::ONE, + Vec::new(), + Epoch::ZERO, + Amount::ONE, + BTreeMap::new(), + None, + ) + .await; + let certificate1 = env + .make_transfer_certificate_for_epoch( + chain_0.clone(), + &key_pair0, + Some(key_pair0.public().into()), + AccountOwner::CHAIN, + Recipient::chain(id1), + Amount::ONE, + Vec::new(), + Epoch::ZERO, + Amount::ONE, + BTreeMap::new(), + Some(&certificate0), + ) + .await; + let certificate2 = env + .make_transfer_certificate_for_epoch( + chain_0.clone(), + &key_pair0, + Some(key_pair0.public().into()), + AccountOwner::CHAIN, + Recipient::chain(id1), + Amount::ONE, + Vec::new(), + Epoch::from(1), + Amount::ONE, + BTreeMap::new(), + Some(&certificate1), + ) + .await; // Weird case: epoch going backward. - let certificate3 = make_transfer_certificate_for_epoch( - ChainDescription::Root(0), - &key_pair0, - Some(key_pair0.public().into()), - AccountOwner::CHAIN, - Recipient::chain(id1), - Amount::ONE, - Vec::new(), - Epoch::ZERO, - &committee, - Amount::ONE, - BTreeMap::new(), - &worker, - Some(&certificate2), - ) - .await; + let certificate3 = env + .make_transfer_certificate_for_epoch( + chain_0.clone(), + &key_pair0, + Some(key_pair0.public().into()), + AccountOwner::CHAIN, + Recipient::chain(id1), + Amount::ONE, + Vec::new(), + Epoch::ZERO, + Amount::ONE, + BTreeMap::new(), + Some(&certificate2), + ) + .await; let bundles0 = certificate0.message_bundles_for(id1).collect::>(); let bundles1 = certificate1.message_bundles_for(id1).collect::>(); let bundles2 = certificate2.message_bundles_for(id1).collect::>(); @@ -3135,15 +2975,15 @@ where { let storage = storage_builder.build().await?; let clock = storage_builder.clock(); - let chain_id = ChainId::root(0); let key_pairs = generate_key_pairs(2); let owner0 = AccountOwner::from(key_pairs[0].public()); let owner1 = AccountOwner::from(key_pairs[1].public()); - let balances = vec![(ChainDescription::Root(0), owner0, Amount::from_tokens(2))]; - let (committee, worker) = init_worker_with_chains(storage, balances).await; + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env.add_root_chain(1, owner0, Amount::from_tokens(2)).await; + let chain_1 = chain_1_desc.id(); // Add another owner and use the leader-based protocol in all rounds. - let proposed_block0 = make_first_block(chain_id) + let proposed_block0 = make_first_block(chain_1) .with_operation(SystemOperation::ChangeOwnership { super_owners: Vec::new(), owners: vec![(owner0, 100), (owner1, 100)], @@ -3152,12 +2992,14 @@ where timeout_config: TimeoutConfig::default(), }) .with_authenticated_signer(Some(owner0)); - let (block0, _) = worker + let (block0, _) = env + .worker() .stage_block_execution(proposed_block0, None, vec![]) .await?; let value0 = ConfirmedBlock::new(block0); - let certificate0 = make_certificate(&committee, &worker, value0.clone()); - let response = worker + let certificate0 = env.make_certificate(value0.clone()); + let response = env + .worker() .fully_handle_certificate_with_notifications(certificate0, &()) .await?; @@ -3167,28 +3009,28 @@ where // So owner 0 cannot propose a block in this round. And the next round hasn't started yet. let proposal = make_child_block(&value0.clone()) .into_proposal_with_round(&key_pairs[0], Round::SingleLeader(0)); - let result = worker.handle_block_proposal(proposal).await; + let result = env.worker().handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::InvalidOwner)); let proposal = make_child_block(&value0.clone()) .into_proposal_with_round(&key_pairs[0], Round::SingleLeader(1)); - let result = worker.handle_block_proposal(proposal).await; + let result = env.worker().handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::ChainError(ref error)) if matches!(**error, ChainError::WrongRound(Round::SingleLeader(0))) ); // The round hasn't timed out yet, so the validator won't sign a leader timeout vote yet. - let query = ChainInfoQuery::new(chain_id).with_timeout(); - let (response, _) = worker.handle_chain_info_query(query).await?; + let query = ChainInfoQuery::new(chain_1).with_timeout(); + let (response, _) = env.worker().handle_chain_info_query(query).await?; assert!(response.info.manager.timeout_vote.is_none()); // Set the clock to the end of the round. clock.set(response.info.manager.round_timeout.unwrap()); // Now the validator will sign a leader timeout vote. - let query = ChainInfoQuery::new(chain_id).with_timeout(); - let (response, _) = worker.handle_chain_info_query(query).await?; + let query = ChainInfoQuery::new(chain_1).with_timeout(); + let (response, _) = env.worker().handle_chain_info_query(query).await?; let vote = response.info.manager.timeout_vote.clone().unwrap(); - let value_timeout = Timeout::new(chain_id, BlockHeight::from(1), Epoch::from(0)); + let value_timeout = Timeout::new(chain_1, BlockHeight::from(1), Epoch::from(0)); // Once we provide the validator with a timeout certificate, the next round starts, where owner // 0 happens to be the leader. @@ -3196,32 +3038,38 @@ where .with_value(value_timeout.clone()) .unwrap() .into_certificate(); - let (response, _) = worker + let (response, _) = env + .worker() .handle_timeout_certificate(certificate_timeout) .await?; assert_eq!(response.info.manager.leader, Some(owner0)); // Now owner 0 can propose a block, but owner 1 can't. let proposed_block1 = make_child_block(&value0.clone()); - let (block1, _) = worker + let (block1, _) = env + .worker() .stage_block_execution(proposed_block1.clone(), None, vec![]) .await?; let proposal1_wrong_owner = proposed_block1 .clone() .with_authenticated_signer(Some(owner1)) .into_proposal_with_round(&key_pairs[1], Round::SingleLeader(1)); - let result = worker.handle_block_proposal(proposal1_wrong_owner).await; + let result = env + .worker() + .handle_block_proposal(proposal1_wrong_owner) + .await; assert_matches!(result, Err(WorkerError::InvalidOwner)); let proposal1 = proposed_block1 .clone() .into_proposal_with_round(&key_pairs[0], Round::SingleLeader(1)); - let (response, _) = worker.handle_block_proposal(proposal1).await?; + let (response, _) = env.worker().handle_block_proposal(proposal1).await?; let value1 = ValidatedBlock::new(block1.clone()); // If we send the validated block certificate to the worker, it votes to confirm. let vote = response.info.manager.pending.clone().unwrap(); let certificate1 = vote.with_value(value1.clone()).unwrap().into_certificate(); - let (response, _) = worker + let (response, _) = env + .worker() .handle_validated_certificate(certificate1.clone()) .await?; let vote = response.info.manager.pending.as_ref().unwrap(); @@ -3229,13 +3077,10 @@ where assert_eq!(vote.value, LiteValue::new(&value)); // Instead of submitting the confirmed block certificate, let rounds 2 to 4 time out, too. - let certificate_timeout = make_certificate_with_round( - &committee, - &worker, - value_timeout.clone(), - Round::SingleLeader(4), - ); - let (response, _) = worker + let certificate_timeout = + env.make_certificate_with_round(value_timeout.clone(), Round::SingleLeader(4)); + let (response, _) = env + .worker() .handle_timeout_certificate(certificate_timeout) .await?; assert_eq!(response.info.manager.leader, Some(owner1)); @@ -3243,19 +3088,23 @@ where // Create block2, also at height 1, but different from block 1. let amount = Amount::from_tokens(1); - let proposed_block2 = - make_child_block(&value0.clone()).with_simple_transfer(ChainId::root(1), amount); - let (block2, _) = worker + let proposed_block2 = make_child_block(&value0.clone()).with_simple_transfer(chain_1, amount); + let (block2, _) = env + .worker() .stage_block_execution(proposed_block2.clone(), None, vec![]) .await?; // Since round 3 is already over, the validator won't vote for a validated block from round 3. let value2 = ValidatedBlock::new(block2.clone()); - let certificate = - make_certificate_with_round(&committee, &worker, value2.clone(), Round::SingleLeader(2)); - worker.handle_validated_certificate(certificate).await?; - let query_values = ChainInfoQuery::new(chain_id).with_manager_values(); - let (response, _) = worker.handle_chain_info_query(query_values.clone()).await?; + let certificate = env.make_certificate_with_round(value2.clone(), Round::SingleLeader(2)); + env.worker() + .handle_validated_certificate(certificate) + .await?; + let query_values = ChainInfoQuery::new(chain_1).with_manager_values(); + let (response, _) = env + .worker() + .handle_chain_info_query(query_values.clone()) + .await?; let manager = response.info.manager; assert_eq!( manager.requested_confirmed.unwrap().block(), @@ -3267,19 +3116,21 @@ where .clone() .with_authenticated_signer(Some(owner1)) .into_proposal_with_round(&key_pairs[1], Round::SingleLeader(5)); - let result = worker.handle_block_proposal(proposal.clone()).await; + let result = env.worker().handle_block_proposal(proposal.clone()).await; assert_matches!(result, Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::HasIncompatibleConfirmedVote(_, _)) ); // But with the validated block certificate for block2, it is allowed. - let certificate2 = - make_certificate_with_round(&committee, &worker, value2.clone(), Round::SingleLeader(4)); + let certificate2 = env.make_certificate_with_round(value2.clone(), Round::SingleLeader(4)); let proposal = BlockProposal::new_retry(Round::SingleLeader(5), certificate2.clone(), &key_pairs[1]); let lite_value2 = LiteValue::new(&value2); - let (_, _) = worker.handle_block_proposal(proposal).await?; - let (response, _) = worker.handle_chain_info_query(query_values.clone()).await?; + let (_, _) = env.worker().handle_block_proposal(proposal).await?; + let (response, _) = env + .worker() + .handle_chain_info_query(query_values.clone()) + .await?; assert_eq!( response.info.manager.requested_locking, Some(Box::new(LockingBlock::Regular(certificate2))) @@ -3289,13 +3140,10 @@ where assert_eq!(vote.round, Round::SingleLeader(5)); // Let round 5 time out, too. - let certificate_timeout = make_certificate_with_round( - &committee, - &worker, - value_timeout.clone(), - Round::SingleLeader(5), - ); - let (response, _) = worker + let certificate_timeout = + env.make_certificate_with_round(value_timeout.clone(), Round::SingleLeader(5)); + let (response, _) = env + .worker() .handle_timeout_certificate(certificate_timeout) .await?; assert_eq!(response.info.manager.leader, Some(owner0)); @@ -3303,23 +3151,23 @@ where // Since the validator now voted for block2, it can't vote for block1 anymore. let proposal = proposed_block1.into_proposal_with_round(&key_pairs[0], Round::SingleLeader(6)); - let result = worker.handle_block_proposal(proposal.clone()).await; + let result = env.worker().handle_block_proposal(proposal.clone()).await; assert_matches!(result, Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::HasIncompatibleConfirmedVote(_, _)) ); // Let rounds 6 and 7 time out. let certificate_timeout = - make_certificate_with_round(&committee, &worker, value_timeout, Round::SingleLeader(7)); - let (response, _) = worker + env.make_certificate_with_round(value_timeout, Round::SingleLeader(7)); + let (response, _) = env + .worker() .handle_timeout_certificate(certificate_timeout) .await?; assert_eq!(response.info.manager.current_round, Round::SingleLeader(8)); // The worker updates its locking block even if it's from a past round. - let certificate = - make_certificate_with_round(&committee, &worker, value1, Round::SingleLeader(7)); - let worker = worker.with_key_pair(None).await; // Forget validator keys. + let certificate = env.make_certificate_with_round(value1, Round::SingleLeader(7)); + let worker = env.worker().clone().with_key_pair(None).await; // Forget validator keys. worker .handle_validated_certificate(certificate.clone()) .await?; @@ -3342,12 +3190,12 @@ where { let storage = storage_builder.build().await?; let clock = storage_builder.clock(); - let chain_id = ChainId::root(0); let key_pairs = generate_key_pairs(2); let owner0 = AccountOwner::from(key_pairs[0].public()); let owner1 = AccountOwner::from(key_pairs[1].public()); - let balances = vec![(ChainDescription::Root(0), owner0, Amount::from_tokens(2))]; - let (committee, worker) = init_worker_with_chains(storage, balances).await; + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env.add_root_chain(1, owner0, Amount::from_tokens(2)).await; + let chain_id = chain_1_desc.id(); // Add another owner and configure two multi-leader rounds. let proposed_block0 = @@ -3361,12 +3209,14 @@ where ..TimeoutConfig::default() }, }); - let (block0, _) = worker + let (block0, _) = env + .worker() .stage_block_execution(proposed_block0, None, vec![]) .await?; let value0 = ConfirmedBlock::new(block0); - let certificate0 = make_certificate(&committee, &worker, value0.clone()); - let response = worker + let certificate0 = env.make_certificate(value0.clone()); + let response = env + .worker() .fully_handle_certificate_with_notifications(certificate0, &()) .await?; @@ -3376,18 +3226,18 @@ where // So owner 1 cannot propose a block in this round. And the next round hasn't started yet. let proposal = make_child_block(&value0).into_proposal_with_round(&key_pairs[1], Round::Fast); - let result = worker.handle_block_proposal(proposal).await; + let result = env.worker().handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::InvalidOwner)); let proposal = make_child_block(&value0).into_proposal_with_round(&key_pairs[1], Round::MultiLeader(0)); - let result = worker.handle_block_proposal(proposal).await; + let result = env.worker().handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::ChainError(ref error)) if matches!(**error, ChainError::WrongRound(Round::Fast)) ); // The round hasn't timed out yet, so the validator won't sign a leader timeout vote yet. let query = ChainInfoQuery::new(chain_id).with_timeout(); - let (response, _) = worker.handle_chain_info_query(query).await?; + let (response, _) = env.worker().handle_chain_info_query(query).await?; assert!(response.info.manager.timeout_vote.is_none()); // Set the clock to the end of the round. @@ -3395,7 +3245,7 @@ where // Now the validator will sign a leader timeout vote. let query = ChainInfoQuery::new(chain_id).with_timeout(); - let (response, _) = worker.handle_chain_info_query(query).await?; + let (response, _) = env.worker().handle_chain_info_query(query).await?; let vote = response.info.manager.timeout_vote.clone().unwrap(); let value_timeout = Timeout::new(chain_id, BlockHeight::from(1), Epoch::from(0)); @@ -3404,7 +3254,8 @@ where .with_value(value_timeout.clone()) .unwrap() .into_certificate(); - let (response, _) = worker + let (response, _) = env + .worker() .handle_timeout_certificate(certificate_timeout) .await?; assert_eq!(response.info.manager.current_round, Round::MultiLeader(0)); @@ -3416,9 +3267,9 @@ where .clone() .with_authenticated_signer(Some(owner1)) .into_proposal_with_round(&key_pairs[1], Round::MultiLeader(1)); - let _ = worker.handle_block_proposal(proposal1).await?; + let _ = env.worker().handle_block_proposal(proposal1).await?; let query_values = ChainInfoQuery::new(chain_id).with_manager_values(); - let (response, _) = worker.handle_chain_info_query(query_values).await?; + let (response, _) = env.worker().handle_chain_info_query(query_values).await?; assert_eq!(response.info.manager.current_round, Round::MultiLeader(1)); Ok(()) } @@ -3433,12 +3284,11 @@ where B: StorageBuilder, { let storage = storage_builder.build().await?; - let chain_id = ChainId::root(0); let key_pair = AccountSecretKey::generate(); let owner = key_pair.public().into(); - let description = ChainDescription::Root(0); - let (committee, worker) = - init_worker_with_chain(storage, description, owner, Amount::from_tokens(2)).await; + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env.add_root_chain(1, owner, Amount::from_tokens(2)).await; + let chain_id = chain_1_desc.id(); // Configure open multi-leader rounds. let change_ownership_block = @@ -3452,13 +3302,13 @@ where ..TimeoutConfig::default() }, }); - let (change_ownership_block, _) = worker + let (change_ownership_block, _) = env + .worker() .stage_block_execution(change_ownership_block, None, vec![]) .await?; let change_ownership_value = ConfirmedBlock::new(change_ownership_block); - let change_ownership_certificate = - make_certificate(&committee, &worker, change_ownership_value.clone()); - worker + let change_ownership_certificate = env.make_certificate(change_ownership_value.clone()); + env.worker() .fully_handle_certificate_with_notifications(change_ownership_certificate, &()) .await?; @@ -3467,7 +3317,7 @@ where let proposal = make_child_block(&change_ownership_value) .with_transfer(AccountOwner::CHAIN, Recipient::Burn, Amount::from_tokens(1)) .into_proposal_with_round(&AccountSecretKey::generate(), Round::MultiLeader(0)); - let result = worker.handle_block_proposal(proposal).await; + let result = env.worker().handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::ChainError(error)) if matches!(&*error, ChainError::ExecutionError(error, _) if matches!(&**error, ExecutionError::UnauthenticatedTransferOwner @@ -3476,11 +3326,12 @@ where // Without the transfer, a random key pair can propose a block. let proposal = make_child_block(&change_ownership_value) .into_proposal_with_round(&AccountSecretKey::generate(), Round::MultiLeader(0)); - let (block, _) = worker + let (block, _) = env + .worker() .stage_block_execution(proposal.content.block.clone(), None, vec![]) .await?; let value = ConfirmedBlock::new(block); - let (response, _) = worker.handle_block_proposal(proposal).await?; + let (response, _) = env.worker().handle_block_proposal(proposal).await?; let vote = response.info.manager.pending.unwrap(); assert_eq!(vote.round, Round::MultiLeader(0)); assert_eq!(vote.value.value_hash, value.hash()); @@ -3498,12 +3349,12 @@ where { let storage = storage_builder.build().await?; let clock = storage_builder.clock(); - let chain_id = ChainId::root(0); let key_pairs = generate_key_pairs(2); let owner0 = AccountOwner::from(key_pairs[0].public()); let owner1 = AccountOwner::from(key_pairs[1].public()); - let balances = vec![(ChainDescription::Root(0), owner0, Amount::from_tokens(2))]; - let (committee, worker) = init_worker_with_chains(storage, balances).await; + let mut env = TestEnvironment::new(storage, false, false).await; + let chain_1_desc = env.add_root_chain(1, owner0, Amount::from_tokens(2)).await; + let chain_id = chain_1_desc.id(); // Add another owner and configure two multi-leader rounds. let proposed_block0 = @@ -3517,12 +3368,14 @@ where ..TimeoutConfig::default() }, }); - let (block0, _) = worker + let (block0, _) = env + .worker() .stage_block_execution(proposed_block0, None, vec![]) .await?; let value0 = ConfirmedBlock::new(block0); - let certificate0 = make_certificate(&committee, &worker, value0.clone()); - let response = worker + let certificate0 = env.make_certificate(value0.clone()); + let response = env + .worker() .fully_handle_certificate_with_notifications(certificate0, &()) .await?; @@ -3535,11 +3388,12 @@ where let proposal1 = proposed_block1 .clone() .into_proposal_with_round(&key_pairs[0], Round::Fast); - let (block1, _) = worker + let (block1, _) = env + .worker() .stage_block_execution(proposed_block1.clone(), None, vec![]) .await?; let value1 = ConfirmedBlock::new(block1); - let (response, _) = worker.handle_block_proposal(proposal1).await?; + let (response, _) = env.worker().handle_block_proposal(proposal1).await?; let vote = response.info.manager.pending.as_ref().unwrap(); assert_eq!(vote.round, Round::Fast); assert_eq!(vote.value.value_hash, value1.hash()); @@ -3549,9 +3403,9 @@ where // Once we provide the validator with a timeout certificate, the next round starts. let value_timeout = Timeout::new(chain_id, BlockHeight::from(1), Epoch::from(0)); - let certificate_timeout = - make_certificate_with_round(&committee, &worker, value_timeout.clone(), Round::Fast); - let (response, _) = worker + let certificate_timeout = env.make_certificate_with_round(value_timeout.clone(), Round::Fast); + let (response, _) = env + .worker() .handle_timeout_certificate(certificate_timeout) .await?; assert_eq!(response.info.manager.current_round, Round::MultiLeader(0)); @@ -3561,40 +3415,40 @@ where let proposal1b = proposed_block1 .clone() .into_proposal_with_round(&key_pairs[1], Round::MultiLeader(0)); - let (response, _) = worker.handle_block_proposal(proposal1b).await?; + let (response, _) = env.worker().handle_block_proposal(proposal1b).await?; let vote = response.info.manager.pending.as_ref().unwrap(); assert_eq!(vote.round, Round::MultiLeader(0)); assert_eq!(vote.value.value_hash, value1.hash()); // Proposing a different block is not. let proposed_block2 = make_child_block(&value0) - .with_simple_transfer(ChainId::root(1), Amount::ONE) + .with_simple_transfer(chain_id, Amount::ONE) .with_authenticated_signer(Some(owner1)); let proposal2 = proposed_block2 .clone() .into_proposal_with_round(&key_pairs[1], Round::MultiLeader(1)); - let result = worker.handle_block_proposal(proposal2).await; + let result = env.worker().handle_block_proposal(proposal2).await; assert_matches!(result, Err(WorkerError::ChainError(err)) if matches!(*err, ChainError::HasIncompatibleConfirmedVote(_, Round::Fast)) ); let proposal3 = proposed_block1 .clone() .into_proposal_with_round(&key_pairs[1], Round::MultiLeader(2)); - worker.handle_block_proposal(proposal3).await?; + env.worker().handle_block_proposal(proposal3).await?; // A validated block certificate from a later round can override the locked fast block. - let (block2, _) = worker + let (block2, _) = env + .worker() .stage_block_execution(proposed_block2.clone(), None, vec![]) .await?; let value2 = ValidatedBlock::new(block2.clone()); - let certificate2 = - make_certificate_with_round(&committee, &worker, value2.clone(), Round::MultiLeader(0)); + let certificate2 = env.make_certificate_with_round(value2.clone(), Round::MultiLeader(0)); let proposal = BlockProposal::new_retry(Round::MultiLeader(3), certificate2.clone(), &key_pairs[1]); let lite_value2 = LiteValue::new(&value2); - let (_, _) = worker.handle_block_proposal(proposal).await?; + let (_, _) = env.worker().handle_block_proposal(proposal).await?; let query_values = ChainInfoQuery::new(chain_id).with_manager_values(); - let (response, _) = worker.handle_chain_info_query(query_values).await?; + let (response, _) = env.worker().handle_chain_info_query(query_values).await?; assert_eq!( response.info.manager.requested_locking, Some(Box::new(LockingBlock::Regular(certificate2))) @@ -3616,17 +3470,19 @@ where { let storage = storage_builder.build().await?; let clock = storage_builder.clock(); - let chain_id = ChainId::root(1); let key_pair = AccountSecretKey::generate(); + let mut env = TestEnvironment::new(storage, false, false).await; let balance = Amount::from_tokens(5); - let balances = vec![(ChainDescription::Root(1), key_pair.public().into(), balance)]; - let (committee, worker) = init_worker_with_chains(storage, balances).await; + let chain_1_desc = env + .add_root_chain(1, key_pair.public().into(), balance) + .await; + let chain_id = chain_1_desc.id(); // At time 0 we don't vote for fallback mode. let query = ChainInfoQuery::new(chain_id) .with_fallback() .with_committees(); - let (response, _) = worker.handle_chain_info_query(query.clone()).await?; + let (response, _) = env.worker().handle_chain_info_query(query.clone()).await?; let manager = response.info.manager; assert!(manager.fallback_vote.is_none()); assert_eq!(manager.current_round, Round::MultiLeader(0)); @@ -3635,39 +3491,40 @@ where // Even if a long time passes: Without an incoming message there's no fallback mode. clock.add(fallback_duration); - let (response, _) = worker.handle_chain_info_query(query.clone()).await?; + let (response, _) = env.worker().handle_chain_info_query(query.clone()).await?; assert!(response.info.manager.fallback_vote.is_none()); // Make a tracked message to ourselves. It's in the inbox now. let proposed_block = make_first_block(chain_id) .with_simple_transfer(chain_id, Amount::ONE) .with_authenticated_signer(Some(key_pair.public().into())); - let (block, _) = worker + let (block, _) = env + .worker() .stage_block_execution(proposed_block, None, vec![]) .await?; let value = ConfirmedBlock::new(block); - let certificate = make_certificate(&committee, &worker, value); - worker + let certificate = env.make_certificate(value); + env.worker() .fully_handle_certificate_with_notifications(certificate, &()) .await?; // The message only just arrived: No fallback mode. - let (response, _) = worker.handle_chain_info_query(query.clone()).await?; + let (response, _) = env.worker().handle_chain_info_query(query.clone()).await?; assert!(response.info.manager.fallback_vote.is_none()); // If for a long time the message isn't handled, we vote for fallback mode. clock.add(fallback_duration); - let (response, _) = worker.handle_chain_info_query(query.clone()).await?; + let (response, _) = env.worker().handle_chain_info_query(query.clone()).await?; let vote = response.info.manager.fallback_vote.unwrap(); let value = Timeout::new(chain_id, BlockHeight(1), Epoch::ZERO); let round = Round::SingleLeader(u32::MAX); assert_eq!(vote.value.value_hash, value.hash()); assert_eq!(vote.round, round); - let certificate = make_certificate_with_round(&committee, &worker, value, round); - worker.handle_timeout_certificate(certificate).await?; + let certificate = env.make_certificate_with_round(value, round); + env.worker().handle_timeout_certificate(certificate).await?; // Now we are in fallback mode, and the validator is the leader. - let (response, _) = worker.handle_chain_info_query(query.clone()).await?; + let (response, _) = env.worker().handle_chain_info_query(query.clone()).await?; let manager = response.info.manager; let expected_key = response .info @@ -3702,32 +3559,19 @@ where let storage = storage_builder.build().await?; let clock = storage_builder.clock(); - let chain_description = ChainDescription::Root(1); - let chain_id = ChainId::from(chain_description); - let key_pair = AccountSecretKey::generate(); - let balance = Amount::ZERO; - - let (committee, worker) = init_worker( - storage.clone(), - /* is_client */ false, - /* has_long_lived_services */ true, - ); - worker - .storage - .create_chain( - committee, - ChainId::root(0), - chain_description, - key_pair.public().into(), - balance, - Timestamp::from(0), + let mut env = TestEnvironment::new(storage, false, true).await; + let chain_description = env + .add_root_chain( + 1, + AccountSecretKey::generate().public().into(), + Amount::from_tokens(2), ) - .await - .unwrap(); + .await; + let chain_id = chain_description.id(); let (application_id, application); { - let mut chain = storage.load_chain(chain_id).await?; + let mut chain = env.worker().storage.load_chain(chain_id).await?; (application_id, application, _) = chain.execution_state.register_mock_application(0).await?; chain.save().await?; @@ -3755,7 +3599,9 @@ where clock.set(query_time); assert_eq!( - worker.query_application(chain_id, query.clone()).await?, + env.worker() + .query_application(chain_id, query.clone()) + .await?, QueryOutcome { response: QueryResponse::User(vec![]), operations: vec![], @@ -3763,7 +3609,7 @@ where ); } - drop(worker); + drop(env); linera_base::time::timer::sleep(Duration::from_millis(10)).await; application.assert_no_more_expected_calls(); application.assert_no_active_instances(); @@ -3789,28 +3635,14 @@ where let storage = storage_builder.build().await?; let clock = storage_builder.clock(); - let chain_description = ChainDescription::Root(1); - let chain_id = ChainId::from(chain_description); let key_pair = AccountSecretKey::generate(); let balance = Amount::ZERO; - let (committee, worker) = init_worker( - storage.clone(), - /* is_client */ false, - /* has_long_lived_services */ true, - ); - worker - .storage - .create_chain( - committee.clone(), - ChainId::root(0), - chain_description, - key_pair.public().into(), - balance, - Timestamp::from(0), - ) - .await - .unwrap(); + let mut env = TestEnvironment::new(storage.clone(), false, true).await; + let chain_description = env + .add_root_chain(1, key_pair.public().into(), balance) + .await; + let chain_id = chain_description.id(); let (application_id, application); { @@ -3863,7 +3695,9 @@ where clock.set(local_time); assert_eq!( - worker.query_application(chain_id, query.clone()).await?, + env.worker() + .query_application(chain_id, query.clone()) + .await?, QueryOutcome { response: QueryResponse::User(vec![]), operations: vec![], @@ -3875,13 +3709,15 @@ where let block = make_first_block(chain_id).with_timestamp(Timestamp::from(BLOCK_TIMESTAMP)); let block_proposal = block.clone().into_first_proposal(&key_pair); - let _ = worker.handle_block_proposal(block_proposal).await?; + let _ = env.worker().handle_block_proposal(block_proposal).await?; for local_time in queries_before_confirmation { clock.set(local_time); assert_eq!( - worker.query_application(chain_id, query.clone()).await?, + env.worker() + .query_application(chain_id, query.clone()) + .await?, QueryOutcome { response: QueryResponse::User(vec![]), operations: vec![], @@ -3889,14 +3725,9 @@ where ); } - let epoch = Epoch::ZERO; - let admin_id = ChainId::root(0); let mut state = SystemExecutionState { - committees: BTreeMap::from_iter([(epoch, committee.clone())]), - ownership: ChainOwnership::single(key_pair.public().into()), - balance, timestamp: Timestamp::from(BLOCK_TIMESTAMP), - ..SystemExecutionState::new(epoch, chain_description, admin_id) + ..SystemExecutionState::new(chain_description) } .into_view() .await; @@ -3914,8 +3745,8 @@ where } .with(block), ); - let certificate = make_certificate(&committee, &worker, value); - worker + let certificate = env.make_certificate(value); + env.worker() .handle_confirmed_certificate(certificate, None) .await?; @@ -3930,7 +3761,9 @@ where clock.set(local_time); assert_eq!( - worker.query_application(chain_id, query.clone()).await?, + env.worker() + .query_application(chain_id, query.clone()) + .await?, QueryOutcome { response: QueryResponse::User(vec![]), operations: vec![], @@ -3938,7 +3771,7 @@ where ); } - drop(worker); + drop(env); linera_base::time::timer::sleep(Duration::from_millis(10)).await; application.assert_no_more_expected_calls(); application.assert_no_active_instances(); diff --git a/linera-execution/src/committee.rs b/linera-execution/src/committee.rs index c85af8dc7a95..09be0737e909 100644 --- a/linera-execution/src/committee.rs +++ b/linera-execution/src/committee.rs @@ -207,6 +207,12 @@ impl std::str::FromStr for ValidatorName { } } +impl From for ValidatorName { + fn from(value: ValidatorPublicKey) -> Self { + Self(value) + } +} + impl Committee { pub fn new( validators: BTreeMap, diff --git a/linera-execution/src/execution.rs b/linera-execution/src/execution.rs index 16d94251ddd8..8fa358e8158f 100644 --- a/linera-execution/src/execution.rs +++ b/linera-execution/src/execution.rs @@ -35,7 +35,7 @@ use crate::{ ApplicationId, ContractSyncRuntime, ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, Message, MessageContext, MessageKind, Operation, OperationContext, OutgoingMessage, ProcessStreamsContext, Query, QueryContext, QueryOutcome, ServiceSyncRuntime, - SystemMessage, TransactionTracker, + SystemMessage, Timestamp, TransactionTracker, }; /// A view accessing the execution state of a chain. @@ -80,11 +80,13 @@ where authenticated_caller_id: None, height: application_description.block_height, round: None, + timestamp: local_time, }; let action = UserAction::Instantiate(context, instantiation_argument); let next_message_index = 0; let next_application_index = application_description.application_index + 1; + let next_chain_index = 0; let application_id = From::from(&application_description); let blob = Blob::new_application_description(&application_description); @@ -119,6 +121,7 @@ where 0, next_message_index, next_application_index, + next_chain_index, None, ); txn_tracker.add_created_blob(blob); @@ -170,6 +173,15 @@ impl UserAction { UserAction::Message(context, _) => context.round, } } + + pub(crate) fn timestamp(&self) -> Timestamp { + match self { + UserAction::Instantiate(context, _) => context.timestamp, + UserAction::Operation(context, _) => context.timestamp, + UserAction::ProcessStreams(context, _) => context.timestamp, + UserAction::Message(context, _) => context.timestamp, + } + } } impl ExecutionStateView diff --git a/linera-execution/src/execution_state_actor.rs b/linera-execution/src/execution_state_actor.rs index 5dec3a5ac65c..f31f2f60f158 100644 --- a/linera-execution/src/execution_state_actor.rs +++ b/linera-execution/src/execution_state_actor.rs @@ -19,7 +19,7 @@ use linera_base::{ Amount, ApplicationPermissions, ArithmeticError, BlobContent, BlockHeight, Timestamp, }, ensure, hex_debug, hex_vec_debug, http, - identifiers::{Account, AccountOwner, BlobId, BlobType, ChainId, EventId, MessageId, StreamId}, + identifiers::{Account, AccountOwner, BlobId, BlobType, ChainId, EventId, StreamId}, ownership::ChainOwnership, }; use linera_views::{batch::Batch, context::Context, views::View}; @@ -289,20 +289,23 @@ where OpenChain { ownership, balance, - next_message_id, + parent_id, + block_height, application_permissions, + timestamp, callback, + mut txn_tracker, } => { - let inactive_err = || ExecutionError::InactiveChain; let config = OpenChainConfig { ownership, - admin_id: self.system.admin_id.get().ok_or_else(inactive_err)?, - epoch: self.system.epoch.get().ok_or_else(inactive_err)?, - committees: self.system.committees.get().clone(), balance, application_permissions, }; - callback.respond(self.system.open_chain(config, next_message_id).await?); + let chain_id = self + .system + .open_chain(config, parent_id, block_height, timestamp, &mut txn_tracker) + .await?; + callback.respond((chain_id, txn_tracker)); } CloseChain { @@ -697,10 +700,14 @@ pub enum ExecutionRequest { ownership: ChainOwnership, #[debug(skip_if = Amount::is_zero)] balance: Amount, - next_message_id: MessageId, + parent_id: ChainId, + block_height: BlockHeight, application_permissions: ApplicationPermissions, + timestamp: Timestamp, #[debug(skip)] - callback: Sender, + txn_tracker: TransactionTracker, + #[debug(skip)] + callback: Sender<(ChainId, TransactionTracker)>, }, CloseChain { diff --git a/linera-execution/src/graphql.rs b/linera-execution/src/graphql.rs index cd1d66a91bd6..a58fdbe0d79d 100644 --- a/linera-execution/src/graphql.rs +++ b/linera-execution/src/graphql.rs @@ -5,9 +5,9 @@ use std::collections::BTreeMap; use linera_base::{ crypto::ValidatorPublicKey, - data_types::{Amount, Epoch, Timestamp}, + data_types::{Amount, ChainDescription, Epoch, Timestamp}, doc_scalar, - identifiers::{AccountOwner, ChainDescription, ChainId}, + identifiers::{AccountOwner, ChainId}, ownership::ChainOwnership, }; use linera_views::{context::Context, map_view::MapView}; diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index e13b884d0e3b..75ed88f442a3 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -47,7 +47,7 @@ use linera_base::{ }; use linera_views::{batch::Batch, views::ViewError}; use serde::{Deserialize, Serialize}; -use system::{AdminOperation, OpenChainConfig}; +use system::AdminOperation; use thiserror::Error; #[cfg(with_revm)] @@ -433,6 +433,8 @@ pub struct OperationContext { pub height: BlockHeight, /// The consensus round number, if this is a block that gets validated in a multi-leader round. pub round: Option, + /// The timestamp of the block containing the operation. + pub timestamp: Timestamp, } #[derive(Clone, Copy, Debug)] @@ -451,6 +453,8 @@ pub struct MessageContext { pub height: BlockHeight, /// The consensus round number, if this is a block that gets validated in a multi-leader round. pub round: Option, + /// The timestamp of the block executing the message. + pub timestamp: Timestamp, /// The ID of the message (based on the operation height and index in the remote /// certificate). pub message_id: MessageId, @@ -464,6 +468,8 @@ pub struct ProcessStreamsContext { pub height: BlockHeight, /// The consensus round number, if this is a block that gets validated in a multi-leader round. pub round: Option, + /// The timestamp of the current block. + pub timestamp: Timestamp, } impl From for ProcessStreamsContext { @@ -472,6 +478,7 @@ impl From for ProcessStreamsContext { chain_id: context.chain_id, height: context.height, round: context.round, + timestamp: context.timestamp, } } } @@ -482,6 +489,7 @@ impl From for ProcessStreamsContext { chain_id: context.chain_id, height: context.height, round: context.round, + timestamp: context.timestamp, } } } @@ -786,7 +794,7 @@ pub trait ContractRuntime: BaseRuntime { ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - ) -> Result<(MessageId, ChainId), ExecutionError>; + ) -> Result; /// Closes the current chain. fn close_chain(&mut self) -> Result<(), ExecutionError>; @@ -1019,14 +1027,6 @@ impl OperationContext { owner, }) } - - fn next_message_id(&self, next_message_index: u32) -> MessageId { - MessageId { - chain_id: self.chain_id, - height: self.height, - index: next_message_index, - } - } } #[cfg(with_testing)] @@ -1259,13 +1259,6 @@ impl Message { Self::User { application_id, .. } => GenericApplicationId::User(*application_id), } } - - pub fn matches_open_chain(&self) -> Option<&OpenChainConfig> { - match self { - Message::System(SystemMessage::OpenChain(config)) => Some(config), - _ => None, - } - } } impl From for Query { diff --git a/linera-execution/src/policy.rs b/linera-execution/src/policy.rs index a3024c8e8796..f512fa6c2df6 100644 --- a/linera-execution/src/policy.rs +++ b/linera-execution/src/policy.rs @@ -397,7 +397,10 @@ impl ResourceControlPolicy { ExecutionError::BytecodeTooLarge ); } - BlobType::Data | BlobType::ApplicationDescription | BlobType::Committee => {} + BlobType::Data + | BlobType::ApplicationDescription + | BlobType::Committee + | BlobType::ChainDescription => {} } Ok(()) } diff --git a/linera-execution/src/runtime.rs b/linera-execution/src/runtime.rs index 2182a0ed249b..f43f20872e62 100644 --- a/linera-execution/src/runtime.rs +++ b/linera-execution/src/runtime.rs @@ -43,8 +43,25 @@ use crate::{ #[path = "unit_tests/runtime_tests.rs"] mod tests; +pub trait WithContext { + type UserContext; +} + +impl WithContext for UserContractInstance { + type UserContext = Timestamp; +} + +impl WithContext for UserServiceInstance { + type UserContext = (); +} + +#[cfg(test)] +impl WithContext for Arc { + type UserContext = (); +} + #[derive(Debug)] -pub struct SyncRuntime(Option>); +pub struct SyncRuntime(Option>); pub type ContractSyncRuntime = SyncRuntime; @@ -54,14 +71,16 @@ pub struct ServiceSyncRuntime { } #[derive(Debug)] -pub struct SyncRuntimeHandle(Arc>>); +pub struct SyncRuntimeHandle( + Arc>>, +); pub type ContractSyncRuntimeHandle = SyncRuntimeHandle; pub type ServiceSyncRuntimeHandle = SyncRuntimeHandle; /// Runtime data tracked during the execution of a transaction on the synchronous thread. #[derive(Debug)] -pub struct SyncRuntimeInternal { +pub struct SyncRuntimeInternal { /// The current chain ID. chain_id: ChainId, /// The height of the next block that will be added to this chain. During operations @@ -110,6 +129,8 @@ pub struct SyncRuntimeInternal { refund_grant_to: Option, /// Controller to track fuel and storage consumption. resource_controller: ResourceController, + /// Additional context for the runtime. + user_context: UserInstance::UserContext, } /// The runtime status of an application. @@ -257,7 +278,7 @@ impl ViewUserState { } } -impl Deref for SyncRuntime { +impl Deref for SyncRuntime { type Target = SyncRuntimeHandle; fn deref(&self) -> &Self::Target { @@ -267,7 +288,7 @@ impl Deref for SyncRuntime { } } -impl DerefMut for SyncRuntime { +impl DerefMut for SyncRuntime { fn deref_mut(&mut self) -> &mut Self::Target { self.0.as_mut().expect( "`SyncRuntime` should not be used after its `inner` contents have been moved out", @@ -275,7 +296,7 @@ impl DerefMut for SyncRuntime { } } -impl Drop for SyncRuntime { +impl Drop for SyncRuntime { fn drop(&mut self) { // Ensure the `loaded_applications` are cleared to prevent circular references in // the runtime @@ -285,7 +306,7 @@ impl Drop for SyncRuntime { } } -impl SyncRuntimeInternal { +impl SyncRuntimeInternal { #[expect(clippy::too_many_arguments)] fn new( chain_id: ChainId, @@ -298,6 +319,7 @@ impl SyncRuntimeInternal { refund_grant_to: Option, resource_controller: ResourceController, transaction_tracker: TransactionTracker, + user_context: UserInstance::UserContext, ) -> Self { Self { chain_id, @@ -317,6 +339,7 @@ impl SyncRuntimeInternal { resource_controller, transaction_tracker, scheduled_operations: Vec::new(), + user_context, } } @@ -441,12 +464,14 @@ impl SyncRuntimeInternal { _ => None, }; let authenticated_caller_id = authenticated.then_some(caller_id); + let timestamp = self.user_context; let callee_context = OperationContext { chain_id: self.chain_id, authenticated_signer, authenticated_caller_id, height: self.height, round: self.round, + timestamp, }; self.push_application(ApplicationStatus { caller_id: authenticated_caller_id, @@ -548,7 +573,7 @@ impl SyncRuntimeInternal { } } -impl SyncRuntime { +impl SyncRuntime { fn into_inner(mut self) -> Option> { let handle = self.0.take().expect( "`SyncRuntime` should not be used after its `inner` contents have been moved out", @@ -560,13 +585,15 @@ impl SyncRuntime { } } -impl From> for SyncRuntimeHandle { +impl From> + for SyncRuntimeHandle +{ fn from(runtime: SyncRuntimeInternal) -> Self { SyncRuntimeHandle(Arc::new(Mutex::new(runtime))) } } -impl SyncRuntimeHandle { +impl SyncRuntimeHandle { fn inner(&self) -> std::sync::MutexGuard<'_, SyncRuntimeInternal> { self.0 .try_lock() @@ -574,7 +601,7 @@ impl SyncRuntimeHandle { } } -impl BaseRuntime for SyncRuntimeHandle +impl BaseRuntime for SyncRuntimeHandle where Self: ContractOrServiceRuntime, { @@ -941,7 +968,7 @@ impl ContractOrServiceRuntime for ServiceSyncRuntimeHandle { const LIMIT_HTTP_RESPONSE_SIZE_TO_ORACLE_RESPONSE_SIZE: bool = false; } -impl Clone for SyncRuntimeHandle { +impl Clone for SyncRuntimeHandle { fn clone(&self) -> Self { SyncRuntimeHandle(self.0.clone()) } @@ -972,6 +999,7 @@ impl ContractSyncRuntime { refund_grant_to, resource_controller, txn_tracker, + action.timestamp(), ), ))) } @@ -1431,27 +1459,31 @@ impl ContractRuntime for ContractSyncRuntimeHandle { ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - ) -> Result<(MessageId, ChainId), ExecutionError> { - let mut this = self.inner(); - let message_id = MessageId { - chain_id: this.chain_id, - height: this.height, - index: this.transaction_tracker.next_message_index(), - }; - let chain_id = ChainId::child(message_id); - let open_chain_message = this + ) -> Result { + let parent_id = self.inner().chain_id; + let block_height = self.block_height()?; + + let txn_tracker_moved = mem::take(&mut self.inner().transaction_tracker); + let timestamp = self.inner().user_context; + + let (chain_id, txn_tracker_moved) = self + .inner() .execution_state_sender .send_request(|callback| ExecutionRequest::OpenChain { ownership, balance, - next_message_id: message_id, + parent_id, + block_height, + timestamp, application_permissions, callback, + txn_tracker: txn_tracker_moved, })? .recv_response()?; - this.transaction_tracker - .add_outgoing_message(open_chain_message)?; - Ok((message_id, chain_id)) + + self.inner().transaction_tracker = txn_tracker_moved; + + Ok(chain_id) } fn close_chain(&mut self) -> Result<(), ExecutionError> { @@ -1590,6 +1622,7 @@ impl ServiceSyncRuntime { None, ResourceController::default(), txn_tracker, + (), ) .into(), )); diff --git a/linera-execution/src/system.rs b/linera-execution/src/system.rs index 2143d477d8d1..ae604453c117 100644 --- a/linera-execution/src/system.rs +++ b/linera-execution/src/system.rs @@ -17,14 +17,11 @@ use custom_debug_derive::Debug; use linera_base::{ crypto::CryptoHash, data_types::{ - Amount, ApplicationPermissions, ArithmeticError, Blob, BlobContent, BlockHeight, Epoch, - OracleResponse, Timestamp, + Amount, ApplicationPermissions, ArithmeticError, Blob, BlobContent, BlockHeight, + ChainDescription, ChainOrigin, Epoch, InitialChainConfig, OracleResponse, Timestamp, }, ensure, hex_debug, - identifiers::{ - Account, AccountOwner, BlobId, BlobType, ChainDescription, ChainId, EventId, MessageId, - ModuleId, StreamId, - }, + identifiers::{Account, AccountOwner, BlobId, BlobType, ChainId, EventId, ModuleId, StreamId}, ownership::{ChainOwnership, TimeoutConfig}, }; use linera_views::{ @@ -46,8 +43,6 @@ use crate::{ QueryContext, QueryOutcome, ResourceController, TransactionTracker, }; -/// The relative index of the `OpenChain` message created by the `OpenChain` operation. -pub static OPEN_CHAIN_MESSAGE_INDEX: u32 = 0; /// The event stream name for new epochs and committees. pub static EPOCH_STREAM_NAME: &[u8] = &[0]; /// The event stream name for removed epochs. @@ -105,17 +100,37 @@ pub struct EventSubscriptions { pub applications: BTreeSet, } -/// The configuration for a new chain. +/// The initial configuration for a new chain. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub struct OpenChainConfig { + /// The ownership configuration of the new chain. pub ownership: ChainOwnership, - pub admin_id: ChainId, - pub epoch: Epoch, - pub committees: BTreeMap, + /// The initial chain balance. pub balance: Amount, + /// The initial application permissions. pub application_permissions: ApplicationPermissions, } +impl OpenChainConfig { + /// Creates an [`InitialChainConfig`] based on this [`OpenChainConfig`] and additional + /// parameters. + pub fn init_chain_config( + &self, + epoch: Epoch, + admin_id: Option, + committees: BTreeMap>, + ) -> InitialChainConfig { + InitialChainConfig { + admin_id, + application_permissions: self.application_permissions.clone(), + balance: self.balance, + committees, + epoch, + ownership: self.ownership.clone(), + } + } +} + /// A system operation. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub enum SystemOperation { @@ -221,8 +236,6 @@ pub enum SystemMessage { amount: Amount, recipient: Recipient, }, - /// Creates (or activates) a new chain. - OpenChain(Box), /// Notifies that a new application was created. ApplicationCreated, } @@ -252,12 +265,6 @@ impl Recipient { pub fn chain(chain_id: ChainId) -> Recipient { Recipient::Account(Account::chain(chain_id)) } - - /// Returns the default recipient for the root chain with the given index. - #[cfg(with_testing)] - pub fn root(index: u32) -> Recipient { - Recipient::chain(ChainId::root(index)) - } } /// Optional user message attached to a transfer. @@ -318,6 +325,19 @@ where Some((*epoch, committee)) } + /// Returns a map of epochs to serialized_committees. + pub fn get_committees(&self) -> BTreeMap> { + self.committees + .get() + .iter() + .map(|(epoch, committee)| { + let serialized_committee = + bcs::to_bytes(committee).expect("Serializing a committee should not fail!"); + (*epoch, serialized_committee) + }) + .collect() + } + /// Executes the sender's side of an operation and returns a list of actions to be /// taken. pub async fn execute_operation( @@ -331,9 +351,15 @@ where let mut new_application = None; match operation { OpenChain(config) => { - let next_message_id = context.next_message_id(txn_tracker.next_message_index()); - let message = self.open_chain(config, next_message_id).await?; - txn_tracker.add_outgoing_message(message)?; + let _chain_id = self + .open_chain( + config, + context.chain_id, + context.height, + context.timestamp, + txn_tracker, + ) + .await?; #[cfg(with_metrics)] OPEN_CHAIN_COUNT.with_label_values(&[]).inc(); } @@ -712,8 +738,6 @@ where Recipient::Burn => (), } } - // These messages are executed immediately when cross-chain requests are received. - OpenChain(_) => {} // This message is only a placeholder: Its ID is part of the application ID. ApplicationCreated => {} } @@ -721,33 +745,42 @@ where } /// Initializes the system application state on a newly opened chain. - pub fn initialize_chain( - &mut self, - message_id: MessageId, - timestamp: Timestamp, - config: OpenChainConfig, - ) { - // Guaranteed under BFT assumptions. - assert!(self.description.get().is_none()); - assert!(!self.ownership.get().is_active()); - assert!(self.committees.get().is_empty()); - let OpenChainConfig { + /// Returns `Ok(true)` if the chain was already initialized, `Ok(false)` if it wasn't. + pub async fn initialize_chain(&mut self, chain_id: ChainId) -> Result { + if self.description.get().is_some() { + // already initialized + return Ok(true); + } + let description_blob = self + .read_blob_content(BlobId::new(chain_id.0, BlobType::ChainDescription)) + .await?; + let description: ChainDescription = bcs::from_bytes(description_blob.bytes())?; + let InitialChainConfig { ownership, admin_id, epoch, committees, balance, application_permissions, - } = config; - let description = ChainDescription::Child(message_id); + } = description.config().clone(); + self.timestamp.set(description.timestamp()); self.description.set(Some(description)); self.epoch.set(Some(epoch)); + let committees = committees + .into_iter() + .map(|(epoch, serialized_committee)| { + let committee = bcs::from_bytes(&serialized_committee) + .expect("Deserializing a committee shouldn't fail"); + (epoch, committee) + }) + .collect(); self.committees.set(committees); - self.admin_id.set(Some(admin_id)); + // If `admin_id` is `None`, this chain is its own admin chain. + self.admin_id.set(admin_id.or(Some(chain_id))); self.ownership.set(ownership); - self.timestamp.set(timestamp); self.balance.set(balance); self.application_permissions.set(application_permissions); + Ok(false) } pub async fn handle_query( @@ -770,27 +803,29 @@ where pub async fn open_chain( &mut self, config: OpenChainConfig, - next_message_id: MessageId, - ) -> Result { - let child_id = ChainId::child(next_message_id); - ensure!( - self.admin_id.get().as_ref() == Some(&config.admin_id), - ExecutionError::InvalidNewChainAdminId(child_id) - ); - ensure!( - self.committees.get() == &config.committees, - ExecutionError::InvalidCommittees - ); - ensure!( - self.epoch.get().as_ref() == Some(&config.epoch), - ExecutionError::InvalidEpoch { - chain_id: child_id, - epoch: config.epoch, - } + parent: ChainId, + block_height: BlockHeight, + timestamp: Timestamp, + txn_tracker: &mut TransactionTracker, + ) -> Result { + let chain_index = txn_tracker.next_chain_index(); + let chain_origin = ChainOrigin::Child { + parent, + block_height, + chain_index, + }; + let committees = self.get_committees(); + let init_chain_config = config.init_chain_config( + (*self.epoch.get()).ok_or(ExecutionError::InactiveChain)?, + *self.admin_id.get(), + committees, ); + let chain_description = ChainDescription::new(chain_origin, init_chain_config, timestamp); + let child_id = chain_description.id(); self.debit(&AccountOwner::CHAIN, config.balance).await?; - let message = SystemMessage::OpenChain(Box::new(config)); - Ok(OutgoingMessage::new(child_id, message).with_kind(MessageKind::Protected)) + let blob = Blob::new_chain_description(&chain_description); + txn_tracker.add_created_blob(blob); + Ok(child_id) } pub async fn close_chain(&mut self) -> Result<(), ExecutionError> { diff --git a/linera-execution/src/test_utils/mod.rs b/linera-execution/src/test_utils/mod.rs index e39596d92f24..63a495903ce3 100644 --- a/linera-execution/src/test_utils/mod.rs +++ b/linera-execution/src/test_utils/mod.rs @@ -13,9 +13,13 @@ mod system_execution_state; use std::{collections::BTreeMap, sync::Arc, thread, vec}; use linera_base::{ - crypto::{BcsSignable, CryptoHash}, - data_types::{Amount, Blob, BlockHeight, CompressedBytecode, OracleResponse, Timestamp}, + crypto::{AccountPublicKey, BcsSignable, CryptoHash, ValidatorPublicKey}, + data_types::{ + Amount, Blob, BlockHeight, ChainDescription, ChainOrigin, CompressedBytecode, Epoch, + InitialChainConfig, OracleResponse, Timestamp, + }, identifiers::{AccountOwner, ApplicationId, BlobId, BlobType, ChainId, MessageId, ModuleId}, + ownership::ChainOwnership, vm::VmRuntime, }; use linera_views::{ @@ -30,16 +34,63 @@ pub use self::{ system_execution_state::SystemExecutionState, }; use crate::{ - ApplicationDescription, ExecutionRequest, ExecutionRuntimeContext, ExecutionStateView, - MessageContext, OperationContext, QueryContext, ServiceRuntimeEndpoint, ServiceRuntimeRequest, - ServiceSyncRuntime, SystemExecutionStateView, TestExecutionRuntimeContext, + committee::Committee, ApplicationDescription, ExecutionRequest, ExecutionRuntimeContext, + ExecutionStateView, MessageContext, OperationContext, QueryContext, ServiceRuntimeEndpoint, + ServiceRuntimeRequest, ServiceSyncRuntime, SystemExecutionStateView, + TestExecutionRuntimeContext, }; +pub fn dummy_chain_description_with_ownership_and_balance( + index: u32, + ownership: ChainOwnership, + balance: Amount, +) -> ChainDescription { + let committee = Committee::make_simple(vec![( + ValidatorPublicKey::test_key(index as u8), + AccountPublicKey::test_key(2 * (index % 128) as u8), + )]); + let committees = BTreeMap::from([( + Epoch::ZERO, + bcs::to_bytes(&committee).expect("serializing a committee shouldn't fail"), + )]); + let origin = ChainOrigin::Root(index); + let config = InitialChainConfig { + admin_id: if index == 0 { + None + } else { + Some( + dummy_chain_description_with_ownership_and_balance(0, ownership.clone(), balance) + .id(), + ) + }, + application_permissions: Default::default(), + balance, + committees, + epoch: Epoch::ZERO, + ownership, + }; + ChainDescription::new(origin, config, Timestamp::default()) +} + +pub fn dummy_chain_description_with_owner(index: u32, owner: AccountOwner) -> ChainDescription { + dummy_chain_description_with_ownership_and_balance( + index, + ChainOwnership::single(owner), + Amount::MAX, + ) +} + +pub fn dummy_chain_description(index: u32) -> ChainDescription { + let chain_key = AccountPublicKey::test_key(2 * (index % 128) as u8 + 1); + let ownership = ChainOwnership::single(chain_key.into()); + dummy_chain_description_with_ownership_and_balance(index, ownership, Amount::MAX) +} + /// Creates a dummy [`ApplicationDescription`] for use in tests. pub fn create_dummy_user_application_description( index: u32, ) -> (ApplicationDescription, Blob, Blob) { - let chain_id = ChainId::root(1); + let chain_id = dummy_chain_description(1).id(); let mut contract_bytes = b"contract".to_vec(); let mut service_bytes = b"service".to_vec(); contract_bytes.push(index as u8); @@ -67,37 +118,42 @@ pub fn create_dummy_user_application_description( } /// Creates a dummy [`OperationContext`] to use in tests. -pub fn create_dummy_operation_context() -> OperationContext { +pub fn create_dummy_operation_context(chain_id: ChainId) -> OperationContext { OperationContext { - chain_id: ChainId::root(0), + chain_id, height: BlockHeight(0), round: Some(0), authenticated_signer: None, authenticated_caller_id: None, + timestamp: Default::default(), } } /// Creates a dummy [`MessageContext`] to use in tests. -pub fn create_dummy_message_context(authenticated_signer: Option) -> MessageContext { +pub fn create_dummy_message_context( + chain_id: ChainId, + authenticated_signer: Option, +) -> MessageContext { MessageContext { - chain_id: ChainId::root(0), + chain_id, is_bouncing: false, authenticated_signer, refund_grant_to: None, height: BlockHeight(0), round: Some(0), message_id: MessageId { - chain_id: ChainId::root(0), + chain_id, height: BlockHeight(0), index: 0, }, + timestamp: Default::default(), } } /// Creates a dummy [`QueryContext`] to use in tests. pub fn create_dummy_query_context() -> QueryContext { QueryContext { - chain_id: ChainId::root(0), + chain_id: dummy_chain_description(0).id(), next_block_height: BlockHeight(0), local_time: Timestamp::from(0), } @@ -169,7 +225,7 @@ where C::Extra: ExecutionRuntimeContext, { fn creator_chain_id(&self) -> ChainId { - self.description.get().expect( + self.description.get().as_ref().expect( "Can't register applications on a system state with no associated `ChainDescription`", ).into() } diff --git a/linera-execution/src/test_utils/system_execution_state.rs b/linera-execution/src/test_utils/system_execution_state.rs index 8026dfb3a7a8..58372ecd7872 100644 --- a/linera-execution/src/test_utils/system_execution_state.rs +++ b/linera-execution/src/test_utils/system_execution_state.rs @@ -10,8 +10,8 @@ use std::{ use custom_debug_derive::Debug; use linera_base::{ crypto::CryptoHash, - data_types::{Amount, ApplicationPermissions, Blob, Epoch, Timestamp}, - identifiers::{AccountOwner, ApplicationId, BlobId, ChainDescription, ChainId}, + data_types::{Amount, ApplicationPermissions, Blob, ChainDescription, Epoch, Timestamp}, + identifiers::{AccountOwner, ApplicationId, BlobId, ChainId}, ownership::ChainOwnership, }; use linera_views::{ @@ -20,7 +20,7 @@ use linera_views::{ views::{CryptoHashView, View, ViewError}, }; -use super::{MockApplication, RegisterMockApplication}; +use super::{dummy_chain_description, MockApplication, RegisterMockApplication}; use crate::{ committee::Committee, execution::UserAction, ApplicationDescription, ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, ExecutionStateView, OperationContext, @@ -51,15 +51,40 @@ pub struct SystemExecutionState { } impl SystemExecutionState { - pub fn new(epoch: Epoch, description: ChainDescription, admin_id: impl Into) -> Self { + pub fn new(description: ChainDescription) -> Self { + let ownership = description.config().ownership.clone(); + let balance = description.config().balance; + let epoch = description.config().epoch; + let admin_id = Some(description.config().admin_id.unwrap_or(description.id())); + let committees = description + .config() + .committees + .iter() + .map(|(epoch, serialized_committee)| { + ( + *epoch, + bcs::from_bytes::(serialized_committee) + .expect("should correctly deserialize a committee"), + ) + }) + .collect(); SystemExecutionState { epoch: Some(epoch), description: Some(description), - admin_id: Some(admin_id.into()), + admin_id, + ownership, + balance, + committees, ..SystemExecutionState::default() } } + pub fn dummy_chain_state(index: u32) -> (Self, ChainId) { + let description = dummy_chain_description(index); + let chain_id = description.id(); + (Self::new(description), chain_id) + } + pub async fn into_hash(self) -> CryptoHash { let view = self.into_view().await; view.crypto_hash() @@ -70,6 +95,7 @@ impl SystemExecutionState { pub async fn into_view(self) -> ExecutionStateView> { let chain_id = self .description + .as_ref() .expect("Chain description should be set") .into(); self.into_view_with(chain_id, ExecutionRuntimeConfig::default()) @@ -143,7 +169,7 @@ impl SystemExecutionState { impl RegisterMockApplication for SystemExecutionState { fn creator_chain_id(&self) -> ChainId { - self.description.expect( + self.description.as_ref().expect( "Can't register applications on a system state with no associated `ChainDescription`", ).into() } diff --git a/linera-execution/src/transaction_tracker.rs b/linera-execution/src/transaction_tracker.rs index d24ae8bf9b5d..7f238d69a9f2 100644 --- a/linera-execution/src/transaction_tracker.rs +++ b/linera-execution/src/transaction_tracker.rs @@ -30,6 +30,7 @@ pub struct TransactionTracker { transaction_index: u32, next_message_index: u32, next_application_index: u32, + next_chain_index: u32, /// Events recorded by contracts' `emit` calls. events: Vec, /// Blobs created by contracts. @@ -49,6 +50,7 @@ pub struct TransactionOutcome { pub outgoing_messages: Vec, pub next_message_index: u32, pub next_application_index: u32, + pub next_chain_index: u32, /// Events recorded by contracts' `emit` calls. pub events: Vec, /// Blobs created by contracts. @@ -63,6 +65,7 @@ impl TransactionTracker { transaction_index: u32, next_message_index: u32, next_application_index: u32, + next_chain_index: u32, oracle_responses: Option>, ) -> Self { TransactionTracker { @@ -70,6 +73,7 @@ impl TransactionTracker { transaction_index, next_message_index, next_application_index, + next_chain_index, replaying_oracle_responses: oracle_responses.map(Vec::into_iter), ..Self::default() } @@ -102,6 +106,12 @@ impl TransactionTracker { index } + pub fn next_chain_index(&mut self) -> u32 { + let index = self.next_chain_index; + self.next_chain_index += 1; + index + } + pub fn add_outgoing_message( &mut self, message: OutgoingMessage, @@ -251,6 +261,7 @@ impl TransactionTracker { transaction_index: _, next_message_index, next_application_index, + next_chain_index, events, blobs, operation_result, @@ -271,6 +282,7 @@ impl TransactionTracker { oracle_responses, next_message_index, next_application_index, + next_chain_index, events, blobs: blobs.into_values().collect(), operation_result: operation_result.unwrap_or_default(), @@ -283,7 +295,7 @@ impl TransactionTracker { /// Creates a new [`TransactionTracker`] for testing, with default values and the given /// oracle responses. pub fn new_replaying(oracle_responses: Vec) -> Self { - TransactionTracker::new(Timestamp::from(0), 0, 0, 0, Some(oracle_responses)) + TransactionTracker::new(Timestamp::from(0), 0, 0, 0, 0, Some(oracle_responses)) } /// Creates a new [`TransactionTracker`] for testing, with default values and oracle responses diff --git a/linera-execution/src/unit_tests/runtime_tests.rs b/linera-execution/src/unit_tests/runtime_tests.rs index 069a802d6412..db20a0a69a4a 100644 --- a/linera-execution/src/unit_tests/runtime_tests.rs +++ b/linera-execution/src/unit_tests/runtime_tests.rs @@ -11,18 +11,14 @@ use std::{ }; use futures::{channel::mpsc, StreamExt}; -use linera_base::{ - crypto::CryptoHash, - data_types::BlockHeight, - identifiers::{ApplicationId, ChainDescription}, -}; +use linera_base::{crypto::CryptoHash, data_types::BlockHeight, identifiers::ApplicationId}; use linera_views::batch::Batch; -use super::{ApplicationStatus, SyncRuntimeHandle, SyncRuntimeInternal}; +use super::{ApplicationStatus, SyncRuntimeHandle, SyncRuntimeInternal, WithContext}; use crate::{ execution_state_actor::ExecutionRequest, runtime::{LoadedApplication, ResourceController, SyncRuntime}, - test_utils::create_dummy_user_application_description, + test_utils::{create_dummy_user_application_description, dummy_chain_description}, ContractRuntime, TransactionTracker, UserContractInstance, }; @@ -168,11 +164,14 @@ fn create_contract_runtime() -> ( /// /// Returns the [`SyncRuntimeInternal`] instance and the receiver endpoint for the requests the /// runtime sends to the [`ExecutionStateView`] actor. -fn create_runtime() -> ( +fn create_runtime() -> ( SyncRuntimeInternal, mpsc::UnboundedReceiver, -) { - let chain_id = ChainDescription::Root(0).into(); +) +where + Application::UserContext: Default, +{ + let chain_id = dummy_chain_description(0).id(); let (execution_state_sender, execution_state_receiver) = mpsc::unbounded(); let resource_controller = ResourceController::default(); @@ -187,6 +186,7 @@ fn create_runtime() -> ( None, resource_controller, TransactionTracker::new_replaying(Vec::new()), + Default::default(), ); (runtime, execution_state_receiver) diff --git a/linera-execution/src/unit_tests/system_tests.rs b/linera-execution/src/unit_tests/system_tests.rs index c89b1be58811..b617d2d34c4c 100644 --- a/linera-execution/src/unit_tests/system_tests.rs +++ b/linera-execution/src/unit_tests/system_tests.rs @@ -7,7 +7,7 @@ use linera_base::vm::VmRuntime; use linera_views::context::MemoryContext; use super::*; -use crate::{ExecutionStateView, Message, TestExecutionRuntimeContext}; +use crate::{test_utils::dummy_chain_description, ExecutionStateView, TestExecutionRuntimeContext}; /// Returns an execution state view and a matching operation context, for epoch 1, with root /// chain 0 as the admin ID and one empty committee. @@ -15,18 +15,19 @@ async fn new_view_and_context() -> ( ExecutionStateView>, OperationContext, ) { - let description = ChainDescription::Root(5); + let description = dummy_chain_description(5); let context = OperationContext { - chain_id: ChainId::from(description), + chain_id: ChainId::from(&description), authenticated_signer: None, authenticated_caller_id: None, height: BlockHeight::from(7), round: Some(0), + timestamp: Default::default(), }; let state = SystemExecutionState { description: Some(description), epoch: Some(Epoch(1)), - admin_id: Some(ChainId::root(0)), + admin_id: Some(dummy_chain_description(0).id()), committees: BTreeMap::new(), ..SystemExecutionState::default() }; @@ -91,16 +92,10 @@ async fn application_message_index() -> anyhow::Result<()> { #[tokio::test] async fn open_chain_message_index() { let (mut view, context) = new_view_and_context().await; - let epoch = view.system.epoch.get().unwrap(); - let admin_id = view.system.admin_id.get().unwrap(); - let committees = view.system.committees.get().clone(); let owner = linera_base::crypto::AccountPublicKey::test_key(0).into(); let ownership = ChainOwnership::single(owner); let config = OpenChainConfig { ownership, - committees, - epoch, - admin_id, balance: Amount::ZERO, application_permissions: Default::default(), }; @@ -118,9 +113,8 @@ async fn open_chain_message_index() { .unwrap(); assert_eq!(new_application, None); assert_eq!( - txn_tracker.into_outcome().unwrap().outgoing_messages[OPEN_CHAIN_MESSAGE_INDEX as usize] - .message, - Message::System(SystemMessage::OpenChain(Box::new(config))) + txn_tracker.into_outcome().unwrap().blobs[0].id().blob_type, + BlobType::ChainDescription, ); } @@ -131,7 +125,7 @@ async fn empty_accounts_are_removed() -> anyhow::Result<()> { let amount = Amount::from_tokens(99); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), + description: Some(dummy_chain_description(0)), balances: BTreeMap::from([(owner, amount)]), ..SystemExecutionState::default() } diff --git a/linera-execution/src/wasm/runtime_api.rs b/linera-execution/src/wasm/runtime_api.rs index 04c14e843dbb..d4c62e080e52 100644 --- a/linera-execution/src/wasm/runtime_api.rs +++ b/linera-execution/src/wasm/runtime_api.rs @@ -475,7 +475,7 @@ where chain_ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - ) -> Result<(MessageId, ChainId), RuntimeError> { + ) -> Result { caller .user_data_mut() .runtime diff --git a/linera-execution/tests/contract_runtime_apis.rs b/linera-execution/tests/contract_runtime_apis.rs index 7e9fccabc596..daf52af3781e 100644 --- a/linera-execution/tests/contract_runtime_apis.rs +++ b/linera-execution/tests/contract_runtime_apis.rs @@ -16,14 +16,15 @@ use linera_base::{ CompressedBytecode, OracleResponse, }, http, - identifiers::{Account, AccountOwner, ApplicationId, ChainDescription, ChainId, ModuleId}, + identifiers::{Account, AccountOwner, ApplicationId, ModuleId}, ownership::ChainOwnership, vm::VmRuntime, }; use linera_execution::{ test_utils::{ - create_dummy_message_context, create_dummy_operation_context, test_accounts_strategy, - ExpectedCall, RegisterMockApplication, SystemExecutionState, + create_dummy_message_context, create_dummy_operation_context, dummy_chain_description, + dummy_chain_description_with_ownership_and_balance, test_accounts_strategy, ExpectedCall, + RegisterMockApplication, SystemExecutionState, }, BaseRuntime, ContractRuntime, ExecutionError, Message, MessageContext, Operation, OperationContext, ResourceController, SystemExecutionStateView, TestExecutionRuntimeContext, @@ -45,6 +46,8 @@ async fn test_transfer_system_api( ) -> anyhow::Result<()> { let amount = Amount::ONE; + let state = sender.create_system_state(amount); + let chain_id = state.description.unwrap().id(); let mut view = sender.create_system_state(amount).into_view().await; let contract_blob = TransferTestEndpoint::sender_application_contract_blob(); @@ -66,7 +69,7 @@ async fn test_transfer_system_api( sender.sender_account_owner(), Account { owner: recipient.recipient_account_owner(), - chain_id: ChainId::root(0), + chain_id: dummy_chain_description(0).id(), }, amount, )?; @@ -77,7 +80,7 @@ async fn test_transfer_system_api( let context = OperationContext { authenticated_signer: sender.signer(), - ..create_dummy_operation_context() + ..create_dummy_operation_context(chain_id) }; let mut controller = ResourceController::default(); let operation = Operation::User { @@ -104,7 +107,7 @@ async fn test_transfer_system_api( assert!(matches!(outgoing_messages[0].message, Message::System(_))); view.execute_message( - create_dummy_message_context(None), + create_dummy_message_context(chain_id, None), outgoing_messages[0].message.clone(), None, &mut TransactionTracker::new_replaying(Vec::new()), @@ -129,6 +132,8 @@ async fn test_unauthorized_transfer_system_api( ) -> anyhow::Result<()> { let amount = Amount::ONE; + let state = sender.create_system_state(amount); + let chain_id = state.description.unwrap().id(); let mut view = sender.create_system_state(amount).into_view().await; let contract_blob = TransferTestEndpoint::sender_application_contract_blob(); @@ -150,7 +155,7 @@ async fn test_unauthorized_transfer_system_api( sender.unauthorized_sender_account_owner(), Account { owner: recipient.recipient_account_owner(), - chain_id: ChainId::root(0), + chain_id: dummy_chain_description(0).id(), }, amount, )?; @@ -161,7 +166,7 @@ async fn test_unauthorized_transfer_system_api( let context = OperationContext { authenticated_signer: sender.unauthorized_signer(), - ..create_dummy_operation_context() + ..create_dummy_operation_context(chain_id) }; let mut controller = ResourceController::default(); let operation = Operation::User { @@ -198,7 +203,8 @@ async fn test_claim_system_api( ) -> anyhow::Result<()> { let amount = Amount::ONE; - let claimer_chain_description = ChainDescription::Root(1); + let claimer_chain_description = dummy_chain_description(1); + let claimer_chain_id = claimer_chain_description.id(); let source_state = sender.create_system_state(amount); let claimer_state = SystemExecutionState { @@ -206,12 +212,11 @@ async fn test_claim_system_api( ..SystemExecutionState::default() }; - let source_chain_id = ChainId::from( - source_state - .description - .expect("System state created by sender should have a `ChainDescription`"), - ); - let claimer_chain_id = ChainId::from(claimer_chain_description); + let source_chain_id = source_state + .description + .as_ref() + .expect("System state created by sender should have a `ChainDescription`") + .id(); let mut source_view = source_state.into_view().await; let mut claimer_view = claimer_state.into_view().await; @@ -250,7 +255,7 @@ async fn test_claim_system_api( let context = OperationContext { authenticated_signer: sender.signer(), chain_id: claimer_chain_id, - ..create_dummy_operation_context() + ..create_dummy_operation_context(claimer_chain_id) }; let mut controller = ResourceController::default(); let operation = Operation::User { @@ -280,7 +285,7 @@ async fn test_claim_system_api( let mut tracker = TransactionTracker::new_replaying(Vec::new()); source_view .execute_message( - create_dummy_message_context(None), + create_dummy_message_context(source_chain_id, None), outgoing_messages[0].message.clone(), None, &mut tracker, @@ -314,7 +319,7 @@ async fn test_claim_system_api( let mut tracker = TransactionTracker::new_replaying(Vec::new()); let context = MessageContext { chain_id: claimer_chain_id, - ..create_dummy_message_context(None) + ..create_dummy_message_context(claimer_chain_id, None) }; claimer_view .execute_message( @@ -345,15 +350,15 @@ async fn test_unauthorized_claims( ) -> anyhow::Result<()> { let amount = Amount::ONE; - let claimer_chain_description = ChainDescription::Root(1); + let claimer_chain_description = dummy_chain_description(1); + let claimer_chain_id = claimer_chain_description.id(); let claimer_state = SystemExecutionState { description: Some(claimer_chain_description), ..SystemExecutionState::default() }; - let source_chain_id = ChainId::root(0); - let claimer_chain_id = ChainId::from(claimer_chain_description); + let source_chain_id = dummy_chain_description(0).id(); let mut claimer_view = claimer_state.into_view().await; @@ -390,8 +395,7 @@ async fn test_unauthorized_claims( let context = OperationContext { authenticated_signer: sender.unauthorized_signer(), - chain_id: claimer_chain_id, - ..create_dummy_operation_context() + ..create_dummy_operation_context(claimer_chain_id) }; let mut controller = ResourceController::default(); let operation = Operation::User { @@ -415,10 +419,11 @@ async fn test_unauthorized_claims( /// Tests the contract system API to read the chain balance. #[proptest(async = "tokio")] async fn test_read_chain_balance_system_api(chain_balance: Amount) { + let description = dummy_chain_description(0); + let chain_id = description.id(); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balance: chain_balance, - ..SystemExecutionState::default() + ..SystemExecutionState::new(description) } .into_view() .await; @@ -445,7 +450,7 @@ async fn test_read_chain_balance_system_api(chain_balance: Amount) { )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, @@ -471,10 +476,11 @@ async fn test_read_chain_balance_system_api(chain_balance: Amount) { async fn test_read_owner_balance_system_api( #[strategy(test_accounts_strategy())] accounts: BTreeMap, ) { + let description = dummy_chain_description(0); + let chain_id = description.id(); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balances: accounts.clone(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(description) } .into_view() .await; @@ -491,7 +497,7 @@ async fn test_read_owner_balance_system_api( )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, @@ -511,12 +517,9 @@ async fn test_read_owner_balance_system_api( /// Tests if reading the balance of a missing account returns zero. #[proptest(async = "tokio")] async fn test_read_owner_balance_returns_zero_for_missing_accounts(missing_account: AccountOwner) { - let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), - ..SystemExecutionState::default() - } - .into_view() - .await; + let description = dummy_chain_description(0); + let chain_id = description.id(); + let mut view = SystemExecutionState::new(description).into_view().await; let (application_id, application, blobs) = view.register_mock_application(0).await.unwrap(); @@ -531,7 +534,7 @@ async fn test_read_owner_balance_returns_zero_for_missing_accounts(missing_accou )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, @@ -553,10 +556,11 @@ async fn test_read_owner_balance_returns_zero_for_missing_accounts(missing_accou async fn test_read_owner_balances_system_api( #[strategy(test_accounts_strategy())] accounts: BTreeMap, ) { + let description = dummy_chain_description(0); + let chain_id = description.id(); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balances: accounts.clone(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(description) } .into_view() .await; @@ -574,7 +578,7 @@ async fn test_read_owner_balances_system_api( )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, @@ -596,10 +600,11 @@ async fn test_read_owner_balances_system_api( async fn test_read_balance_owners_system_api( #[strategy(test_accounts_strategy())] accounts: BTreeMap, ) { + let description = dummy_chain_description(0); + let chain_id = description.id(); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balances: accounts.clone(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(description) } .into_view() .await; @@ -617,7 +622,7 @@ async fn test_read_balance_owners_system_api( )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, @@ -661,7 +666,7 @@ impl TransferTestEndpoint { ApplicationDescription { module_id: ModuleId::new(contract_id, service_id, vm_runtime), - creator_chain_id: ChainId::root(1000), + creator_chain_id: dummy_chain_description(1000).id(), block_height: BlockHeight(0), application_index: 0, parameters: vec![], @@ -719,8 +724,11 @@ impl TransferTestEndpoint { ..ChainOwnership::default() }; + let chain_description = + dummy_chain_description_with_ownership_and_balance(0, ownership.clone(), balance); + SystemExecutionState { - description: Some(ChainDescription::Root(0)), + description: Some(chain_description), ownership, balance, balances: BTreeMap::from_iter(balances), @@ -804,12 +812,13 @@ impl TransferTestEndpoint { #[test_case(Some(vec![]) => matches Err(ExecutionError::UnauthorizedApplication(_)); "when unauthorized")] #[test_log::test(tokio::test)] async fn test_query_service(authorized_apps: Option>) -> Result<(), ExecutionError> { + let description = dummy_chain_description(0); + let chain_id = description.id(); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), ownership: ChainOwnership::default(), balance: Amount::ONE, balances: BTreeMap::new(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(description) } .into_view() .await; @@ -847,7 +856,7 @@ async fn test_query_service(authorized_apps: Option>) -> Result<(), Exec application.expect_call(ExpectedCall::default_finalize()); application.expect_call(ExpectedCall::handle_query(|_service, _query| Ok(vec![]))); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, @@ -876,12 +885,13 @@ async fn test_query_service(authorized_apps: Option>) -> Result<(), Exec #[test_case(Some(vec![]) => matches Err(ExecutionError::UnauthorizedApplication(_)); "when unauthorized")] #[test_log::test(tokio::test)] async fn test_perform_http_request(authorized_apps: Option>) -> Result<(), ExecutionError> { + let description = dummy_chain_description(0); + let chain_id = description.id(); let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), ownership: ChainOwnership::default(), balance: Amount::ONE, balances: BTreeMap::new(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(description) } .into_view() .await; @@ -918,7 +928,7 @@ async fn test_perform_http_request(authorized_apps: Option>) -> Result<( )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let operation = Operation::User { application_id, diff --git a/linera-execution/tests/fee_consumption.rs b/linera-execution/tests/fee_consumption.rs index b820556e798d..16cf0f7c2aa3 100644 --- a/linera-execution/tests/fee_consumption.rs +++ b/linera-execution/tests/fee_consumption.rs @@ -11,11 +11,12 @@ use linera_base::{ crypto::AccountPublicKey, data_types::{Amount, BlockHeight, OracleResponse}, http, - identifiers::{Account, AccountOwner, ChainDescription, ChainId, MessageId}, + identifiers::{Account, AccountOwner, MessageId}, }; use linera_execution::{ test_utils::{ - blob_oracle_responses, ExpectedCall, RegisterMockApplication, SystemExecutionState, + blob_oracle_responses, dummy_chain_description, ExpectedCall, RegisterMockApplication, + SystemExecutionState, }, ContractRuntime, ExecutionError, Message, MessageContext, ResourceControlPolicy, ResourceController, TransactionTracker, @@ -186,8 +187,10 @@ async fn test_fee_consumption( owner_balance: Option, initial_grant: Option, ) -> anyhow::Result<()> { + let chain_description = dummy_chain_description(0); + let chain_id = chain_description.id(); let mut state = SystemExecutionState { - description: Some(ChainDescription::Root(0)), + description: Some(chain_description.clone()), ..SystemExecutionState::default() }; let (application_id, application, blobs) = state.register_mock_application(0).await?; @@ -265,19 +268,17 @@ async fn test_fee_consumption( application.expect_call(ExpectedCall::default_finalize()); let refund_grant_to = authenticated_signer - .map(|owner| Account { - chain_id: ChainId::root(0), - owner, - }) + .map(|owner| Account { chain_id, owner }) .or(None); let context = MessageContext { - chain_id: ChainId::root(0), + chain_id, is_bouncing: false, authenticated_signer, refund_grant_to, height: BlockHeight(0), round: Some(0), message_id: MessageId::default(), + timestamp: Default::default(), }; let mut grant = initial_grant.unwrap_or_default(); let mut txn_tracker = TransactionTracker::new_replaying(oracle_responses); diff --git a/linera-execution/tests/revm.rs b/linera-execution/tests/revm.rs index eada8d79e453..a631433b6e12 100644 --- a/linera-execution/tests/revm.rs +++ b/linera-execution/tests/revm.rs @@ -8,13 +8,12 @@ use std::sync::Arc; use alloy_sol_types::{sol, SolCall, SolValue}; use linera_base::{ data_types::{Amount, Blob, BlockHeight, Timestamp}, - identifiers::ChainDescription, vm::EvmQuery, }; use linera_execution::{ evm::revm::{EvmContractModule, EvmServiceModule}, test_utils::{ - create_dummy_user_application_description, + create_dummy_user_application_description, dummy_chain_description, solidity::{load_solidity_example, read_evm_u64_entry}, SystemExecutionState, }, @@ -44,7 +43,7 @@ async fn test_fuel_for_counter_revm_application() -> anyhow::Result<()> { let instantiation_argument = Vec::::new(); let instantiation_argument = serde_json::to_string(&instantiation_argument)?.into_bytes(); let state = SystemExecutionState { - description: Some(ChainDescription::Root(0)), + description: Some(dummy_chain_description(0)), ..Default::default() }; let (mut app_desc, contract_blob, service_blob) = create_dummy_user_application_description(1); @@ -88,6 +87,7 @@ async fn test_fuel_for_counter_revm_application() -> anyhow::Result<()> { round: Some(0), authenticated_signer: None, authenticated_caller_id: None, + timestamp: Default::default(), }; let query_context = QueryContext { diff --git a/linera-execution/tests/service_runtime_apis.rs b/linera-execution/tests/service_runtime_apis.rs index 39f49080ad98..8ec2c57eadd3 100644 --- a/linera-execution/tests/service_runtime_apis.rs +++ b/linera-execution/tests/service_runtime_apis.rs @@ -5,14 +5,11 @@ use std::{collections::BTreeMap, vec}; -use linera_base::{ - data_types::Amount, - identifiers::{AccountOwner, ChainDescription}, -}; +use linera_base::{data_types::Amount, identifiers::AccountOwner}; use linera_execution::{ test_utils::{ - create_dummy_query_context, test_accounts_strategy, ExpectedCall, RegisterMockApplication, - SystemExecutionState, + create_dummy_query_context, dummy_chain_description, test_accounts_strategy, ExpectedCall, + RegisterMockApplication, SystemExecutionState, }, BaseRuntime, Query, }; @@ -22,9 +19,8 @@ use test_strategy::proptest; #[proptest(async = "tokio")] async fn test_read_chain_balance_system_api(chain_balance: Amount) { let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balance: chain_balance, - ..SystemExecutionState::default() + ..SystemExecutionState::new(dummy_chain_description(0)) } .into_view() .await; @@ -52,9 +48,8 @@ async fn test_read_owner_balance_system_api( #[strategy(test_accounts_strategy())] accounts: BTreeMap, ) { let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balances: accounts.clone(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(dummy_chain_description(0)) } .into_view() .await; @@ -81,12 +76,9 @@ async fn test_read_owner_balance_system_api( /// Tests if reading the balance of a missing account returns zero. #[proptest(async = "tokio")] async fn test_read_owner_balance_returns_zero_for_missing_accounts(missing_account: AccountOwner) { - let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), - ..SystemExecutionState::default() - } - .into_view() - .await; + let mut view = SystemExecutionState::new(dummy_chain_description(0)) + .into_view() + .await; let (application_id, application, _) = view.register_mock_application(0).await.unwrap(); @@ -114,9 +106,8 @@ async fn test_read_owner_balances_system_api( #[strategy(test_accounts_strategy())] accounts: BTreeMap, ) { let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balances: accounts.clone(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(dummy_chain_description(0)) } .into_view() .await; @@ -147,9 +138,8 @@ async fn test_read_balance_owners_system_api( #[strategy(test_accounts_strategy())] accounts: BTreeMap, ) { let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), balances: accounts.clone(), - ..SystemExecutionState::default() + ..SystemExecutionState::new(dummy_chain_description(0)) } .into_view() .await; diff --git a/linera-execution/tests/test_execution.rs b/linera-execution/tests/test_execution.rs index ec82904f1132..7aa89363ae92 100644 --- a/linera-execution/tests/test_execution.rs +++ b/linera-execution/tests/test_execution.rs @@ -9,18 +9,18 @@ use assert_matches::assert_matches; use linera_base::{ crypto::{AccountPublicKey, ValidatorPublicKey}, data_types::{ - Amount, ApplicationPermissions, Blob, BlockHeight, Epoch, Resources, SendMessageRequest, - Timestamp, + Amount, ApplicationPermissions, Blob, BlockHeight, ChainDescription, ChainOrigin, Epoch, + InitialChainConfig, Resources, SendMessageRequest, Timestamp, }, - identifiers::{Account, AccountOwner, ChainDescription, ChainId, MessageId}, + identifiers::{Account, AccountOwner, BlobType}, ownership::ChainOwnership, }; use linera_execution::{ committee::Committee, - system::SystemMessage, test_utils::{ blob_oracle_responses, create_dummy_message_context, create_dummy_operation_context, - create_dummy_user_application_registrations, ExpectedCall, RegisterMockApplication, + create_dummy_user_application_registrations, dummy_chain_description, + dummy_chain_description_with_ownership_and_balance, ExpectedCall, RegisterMockApplication, SystemExecutionState, }, BaseRuntime, ContractRuntime, ExecutionError, ExecutionRuntimeContext, Message, Operation, @@ -32,8 +32,7 @@ use test_case::test_case; #[tokio::test] async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (app_id, app_desc, contract_blob, service_blob) = @@ -47,7 +46,7 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { .add_blobs([contract_blob.clone(), service_blob.clone(), app_desc_blob]) .await?; - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -75,8 +74,7 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { #[tokio::test] // TODO(#1484): Split this test into multiple more specialized tests. async fn test_simple_user_operation() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -138,7 +136,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { let context = OperationContext { authenticated_signer: Some(owner), - ..create_dummy_operation_context() + ..create_dummy_operation_context(chain_id) }; let mut controller = ResourceController::default(); let mut txn_tracker = @@ -171,7 +169,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { })); let context = QueryContext { - chain_id: ChainId::root(0), + chain_id, next_block_height: BlockHeight(0), local_time: Timestamp::from(0), }; @@ -225,8 +223,7 @@ enum SessionCall { /// Tests a simulated session. #[tokio::test] async fn test_simulated_session() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -283,7 +280,7 @@ async fn test_simulated_session() -> anyhow::Result<()> { })); caller_application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs(caller_blobs.iter().chain(&target_blobs)); @@ -305,8 +302,7 @@ async fn test_simulated_session() -> anyhow::Result<()> { /// Tests if execution fails if a simulated session isn't properly closed. #[tokio::test] async fn test_simulated_session_leak() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -352,7 +348,7 @@ async fn test_simulated_session_leak() -> anyhow::Result<()> { caller_application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -373,8 +369,7 @@ async fn test_simulated_session_leak() -> anyhow::Result<()> { /// Tests if `finalize` can cause execution to fail. #[tokio::test] async fn test_rejecting_block_from_finalize() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (id, application, blobs) = view.register_mock_application(0).await?; @@ -389,7 +384,7 @@ async fn test_rejecting_block_from_finalize() -> anyhow::Result<()> { Err(ExecutionError::UserError(error_message.to_owned())) })); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -410,8 +405,7 @@ async fn test_rejecting_block_from_finalize() -> anyhow::Result<()> { /// Tests if `finalize` from a called application can cause execution to fail. #[tokio::test] async fn test_rejecting_block_from_called_applications_finalize() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (first_id, first_application, first_app_blobs) = view.register_mock_application(0).await?; @@ -452,7 +446,7 @@ async fn test_rejecting_block_from_called_applications_finalize() -> anyhow::Res second_application.expect_call(ExpectedCall::default_finalize()); first_application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -479,8 +473,7 @@ async fn test_rejecting_block_from_called_applications_finalize() -> anyhow::Res /// Tests if `finalize` can send messages. #[tokio::test] async fn test_sending_message_from_finalize() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (first_id, first_application, first_app_blobs) = view.register_mock_application(0).await?; @@ -490,7 +483,7 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { let (fourth_id, fourth_application, fourth_app_blobs) = view.register_mock_application(3).await?; - let destination = ChainId::from(ChainDescription::Root(1)); + let destination = dummy_chain_description(1).id(); let first_message = SendMessageRequest { destination, authenticated: false, @@ -585,7 +578,7 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { Ok(()) })); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs( first_app_blobs @@ -623,8 +616,7 @@ async fn test_sending_message_from_finalize() -> anyhow::Result<()> { /// Tests if an application can't perform cross-application calls during `finalize`. #[tokio::test] async fn test_cross_application_call_from_finalize() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -641,7 +633,7 @@ async fn test_cross_application_call_from_finalize() -> anyhow::Result<()> { } })); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -670,8 +662,7 @@ async fn test_cross_application_call_from_finalize() -> anyhow::Result<()> { /// have already called the same application. #[tokio::test] async fn test_cross_application_call_from_finalize_of_called_application() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -695,7 +686,7 @@ async fn test_cross_application_call_from_finalize_of_called_application() -> an })); caller_application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -723,8 +714,7 @@ async fn test_cross_application_call_from_finalize_of_called_application() -> an /// Tests if a called application can't perform cross-application calls during `finalize`. #[tokio::test] async fn test_calling_application_again_from_finalize() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -748,7 +738,7 @@ async fn test_calling_application_again_from_finalize() -> anyhow::Result<()> { } })); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let result = view .execute_operation( @@ -779,8 +769,7 @@ async fn test_calling_application_again_from_finalize() -> anyhow::Result<()> { /// without panicking. #[tokio::test] async fn test_cross_application_error() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -799,7 +788,7 @@ async fn test_cross_application_error() -> anyhow::Result<()> { Err(ExecutionError::UserError(error_message.to_owned())) })); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); assert_matches!( view.execute_operation( @@ -824,13 +813,12 @@ async fn test_cross_application_error() -> anyhow::Result<()> { /// other chains. #[tokio::test] async fn test_simple_message() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (application_id, application, blobs) = view.register_mock_application(0).await?; - let destination = ChainId::from(ChainDescription::Root(1)); + let destination = dummy_chain_description(1).id(); let dummy_message = SendMessageRequest { destination, authenticated: false, @@ -855,7 +843,7 @@ async fn test_simple_message() -> anyhow::Result<()> { )); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs(blobs); view.execute_operation( @@ -884,8 +872,7 @@ async fn test_simple_message() -> anyhow::Result<()> { /// call. #[tokio::test] async fn test_message_from_cross_application_call() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -898,7 +885,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { }, )); - let destination = ChainId::from(ChainDescription::Root(1)); + let destination = dummy_chain_description(1).id(); let dummy_message = SendMessageRequest { destination, authenticated: false, @@ -923,7 +910,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { target_application.expect_call(ExpectedCall::default_finalize()); caller_application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs(caller_blobs.iter().chain(&target_blobs)); @@ -952,8 +939,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { /// Tests if a message is scheduled to be sent by a deeper cross-application call. #[tokio::test] async fn test_message_from_deeper_call() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; let (caller_id, caller_application, caller_blobs) = view.register_mock_application(0).await?; @@ -974,7 +960,7 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { }, )); - let destination = ChainId::from(ChainDescription::Root(1)); + let destination = dummy_chain_description(1).id(); let dummy_message = SendMessageRequest { destination, authenticated: false, @@ -1002,7 +988,7 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { middle_application.expect_call(ExpectedCall::default_finalize()); caller_application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs( caller_blobs @@ -1039,8 +1025,7 @@ async fn test_message_from_deeper_call() -> anyhow::Result<()> { /// for the applications that will receive messages on them. #[tokio::test] async fn test_multiple_messages_from_different_applications() -> anyhow::Result<()> { - let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let (state, chain_id) = SystemExecutionState::dummy_chain_state(0); let mut view = state.into_view().await; // The entrypoint application, which sends a message and calls other applications @@ -1053,9 +1038,9 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< view.register_mock_application(2).await?; // The first destination chain receives messages from the caller and the sending applications - let first_destination = ChainId::from(ChainDescription::Root(1)); + let first_destination = dummy_chain_description(1).id(); // The second destination chain only receives a message from the sending application - let second_destination = ChainId::from(ChainDescription::Root(2)); + let second_destination = dummy_chain_description(2).id(); // The message sent to the first destination chain by the caller and the sending applications let first_message = SendMessageRequest { @@ -1114,7 +1099,7 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< caller_application.expect_call(ExpectedCall::default_finalize()); // Execute the operation, starting the test scenario - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs( caller_blobs @@ -1174,45 +1159,54 @@ async fn test_open_chain() -> anyhow::Result<()> { ValidatorPublicKey::test_key(0), AccountPublicKey::test_key(0), )]); - let committees = BTreeMap::from([(Epoch::ZERO, committee)]); + let committees = BTreeMap::from([(Epoch::ZERO, bcs::to_bytes(&committee)?)]); let chain_key = AccountPublicKey::test_key(1); let ownership = ChainOwnership::single(chain_key.into()); let child_ownership = ChainOwnership::single(AccountPublicKey::test_key(2).into()); - let state = SystemExecutionState { - committees: committees.clone(), - ownership: ownership.clone(), - balance: Amount::from_tokens(5), - ..SystemExecutionState::new(Epoch::ZERO, ChainDescription::Root(0), ChainId::root(0)) - }; + let balance = Amount::from_tokens(5); + let root_description = + dummy_chain_description_with_ownership_and_balance(0, ownership.clone(), balance); + let state = SystemExecutionState::new(root_description.clone()); let mut view = state.into_view().await; + view.context() + .extra() + .add_blobs([Blob::new_chain_description(&root_description)]) + .await?; let (application_id, application, blobs) = view.register_mock_application(0).await?; let context = OperationContext { height: BlockHeight(1), authenticated_signer: Some(chain_key.into()), - ..create_dummy_operation_context() + ..create_dummy_operation_context(root_description.id()) }; let first_message_index = 5; - // We will send one additional message before calling open_chain. - let index = first_message_index + 1; - let message_id = MessageId { - chain_id: context.chain_id, - height: context.height, - index, + + let child_origin = ChainOrigin::Child { + parent: root_description.id(), + block_height: BlockHeight(1), + chain_index: 0, + }; + let child_application_permissions = ApplicationPermissions::new_single(application_id); + let child_config = InitialChainConfig { + balance: Amount::ONE, + ownership: child_ownership.clone(), + application_permissions: child_application_permissions.clone(), + admin_id: Some(root_description.id()), + ..root_description.config().clone() }; + let child_description = ChainDescription::new(child_origin, child_config, Timestamp::default()); + let child_id = child_description.id(); application.expect_call(ExpectedCall::execute_operation({ let child_ownership = child_ownership.clone(); move |runtime, _operation| { assert_eq!(runtime.chain_ownership()?, ownership); - let destination = Account::chain(ChainId::root(2)); + let destination = Account::chain(dummy_chain_description(2).id()); runtime.transfer(AccountOwner::CHAIN, destination, Amount::ONE)?; - let id = runtime.application_id()?; - let application_permissions = ApplicationPermissions::new_single(id); - let (actual_message_id, chain_id) = + let application_permissions = child_application_permissions.clone(); + let chain_id = runtime.open_chain(child_ownership, application_permissions, Amount::ONE)?; - assert_eq!(message_id, actual_message_id); - assert_eq!(chain_id, ChainId::child(message_id)); + assert_eq!(chain_id, child_id); Ok(vec![]) } })); @@ -1228,6 +1222,7 @@ async fn test_open_chain() -> anyhow::Result<()> { 1, first_message_index, 0, + 0, Some(blob_oracle_responses(blobs.iter())), ); view.execute_operation(context, operation, &mut txn_tracker, &mut controller) @@ -1235,30 +1230,41 @@ async fn test_open_chain() -> anyhow::Result<()> { assert_eq!(*view.system.balance.get(), Amount::from_tokens(3)); let txn_outcome = txn_tracker.into_outcome().unwrap(); - let message = &txn_outcome.outgoing_messages[(index - first_message_index) as usize]; - let OutgoingMessage { - message: Message::System(SystemMessage::OpenChain(config)), - destination: recipient_id, - .. - } = message - else { - panic!("Unexpected message at index {}: {:?}", index, message); - }; - assert_eq!(*recipient_id, ChainId::child(message_id)); - assert_eq!(config.balance, Amount::ONE); - assert_eq!(config.ownership, child_ownership); - assert_eq!(config.committees, committees); - - // Initialize the child chain using the config from the message. + let new_blob = &txn_outcome.blobs[0]; + assert_eq!(new_blob.id().hash, child_id.0); + assert_eq!(new_blob.id().blob_type, BlobType::ChainDescription); + let created_description: ChainDescription = bcs::from_bytes(&new_blob.clone().into_bytes()) + .expect("should deserialize a chain description"); + assert_eq!(created_description.config().balance, Amount::ONE); + assert_eq!(created_description.config().ownership, child_ownership); + assert_eq!(created_description.config().committees, committees); + + // Initialize the child chain using the new blob. let mut child_view = SystemExecutionState::default() - .into_view_with(ChainId::child(message_id), Default::default()) + .into_view_with(child_id, Default::default()) .await; + child_view + .context() + .extra() + .add_blobs([Blob::new_chain_description(&child_description)]) + .await?; child_view .system - .initialize_chain(message_id, Timestamp::from(0), (**config).clone()); + .initialize_chain(child_description.id()) + .await + .expect("should initialize chain correctly"); assert_eq!(*child_view.system.balance.get(), Amount::ONE); assert_eq!(*child_view.system.ownership.get(), child_ownership); - assert_eq!(*child_view.system.committees.get(), committees); + assert_eq!( + *child_view.system.committees.get(), + committees + .into_iter() + .map(|(epoch, serialized_committee)| ( + epoch, + bcs::from_bytes::(&serialized_committee).unwrap() + )) + .collect::>() + ); assert_eq!( *child_view.system.application_permissions.get(), ApplicationPermissions::new_single(application_id) @@ -1270,23 +1276,19 @@ async fn test_open_chain() -> anyhow::Result<()> { /// Tests the system API call `close_chain`. #[tokio::test] async fn test_close_chain() -> anyhow::Result<()> { - let committee = Committee::make_simple(vec![( - ValidatorPublicKey::test_key(0), - AccountPublicKey::test_key(0), - )]); - let committees = BTreeMap::from([(Epoch::ZERO, committee)]); let ownership = ChainOwnership::single(AccountPublicKey::test_key(1).into()); - let state = SystemExecutionState { - committees: committees.clone(), - ownership: ownership.clone(), - balance: Amount::from_tokens(5), - ..SystemExecutionState::new(Epoch::ZERO, ChainDescription::Root(0), ChainId::root(0)) - }; + let description = dummy_chain_description_with_ownership_and_balance( + 0, + ownership.clone(), + Amount::from_tokens(5), + ); + let chain_id = description.id(); + let state = SystemExecutionState::new(description); let mut view = state.into_view().await; let (application_id, application, blobs) = view.register_mock_application(0).await?; // The application is not authorized to close the chain. - let context = create_dummy_operation_context(); + let context = create_dummy_operation_context(chain_id); application.expect_call(ExpectedCall::execute_operation( move |runtime, _operation| { assert_matches!( @@ -1382,22 +1384,22 @@ async fn test_message_receipt_spending_chain_balance( let amount = Amount::ONE; let super_owners = receiving_chain_owner.into_iter().collect(); - let mut view = SystemExecutionState { - description: Some(ChainDescription::Root(0)), - balance: amount, - ownership: ChainOwnership { + let description = dummy_chain_description_with_ownership_and_balance( + 0, + ChainOwnership { super_owners, ..ChainOwnership::default() }, - ..SystemExecutionState::default() - } - .into_view() - .await; + amount, + ); + let chain_id = description.id(); + + let mut view = SystemExecutionState::new(description).into_view().await; let (application_id, application, blobs) = view.register_mock_application(0).await?; let receiver_chain_account = AccountOwner::CHAIN; - let sender_chain_id = ChainId::root(2); + let sender_chain_id = dummy_chain_description(2).id(); let recipient = Account { chain_id: sender_chain_id, owner: AccountOwner::CHAIN, @@ -1409,7 +1411,7 @@ async fn test_message_receipt_spending_chain_balance( })); application.expect_call(ExpectedCall::default_finalize()); - let context = create_dummy_message_context(authenticated_signer); + let context = create_dummy_message_context(chain_id, authenticated_signer); let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying_blobs(blobs); diff --git a/linera-execution/tests/test_system_execution.rs b/linera-execution/tests/test_system_execution.rs index 3eeb55847881..d5921413ea08 100644 --- a/linera-execution/tests/test_system_execution.rs +++ b/linera-execution/tests/test_system_execution.rs @@ -6,26 +6,36 @@ use linera_base::{ crypto::AccountSecretKey, data_types::{Amount, BlockHeight, Timestamp}, - identifiers::{AccountOwner, ChainDescription, ChainId, MessageId}, + identifiers::{AccountOwner, MessageId}, ownership::ChainOwnership, }; use linera_execution::{ - system::Recipient, test_utils::SystemExecutionState, Message, MessageContext, Operation, - OperationContext, Query, QueryContext, QueryOutcome, QueryResponse, ResourceController, - SystemMessage, SystemOperation, SystemQuery, SystemResponse, TransactionTracker, + system::Recipient, + test_utils::{ + dummy_chain_description, dummy_chain_description_with_ownership_and_balance, + SystemExecutionState, + }, + Message, MessageContext, Operation, OperationContext, Query, QueryContext, QueryOutcome, + QueryResponse, ResourceController, SystemMessage, SystemOperation, SystemQuery, SystemResponse, + TransactionTracker, }; #[tokio::test] async fn test_simple_system_operation() -> anyhow::Result<()> { let owner_key_pair = AccountSecretKey::generate(); let owner = AccountOwner::from(owner_key_pair.public()); + let ownership = ChainOwnership { + super_owners: [owner].into_iter().collect(), + ..ChainOwnership::default() + }; + let balance = Amount::from_tokens(4); + let description = + dummy_chain_description_with_ownership_and_balance(0, ownership.clone(), balance); + let chain_id = description.id(); let state = SystemExecutionState { - description: Some(ChainDescription::Root(0)), - balance: Amount::from_tokens(4), - ownership: ChainOwnership { - super_owners: [owner].into_iter().collect(), - ..ChainOwnership::default() - }, + description: Some(description), + balance, + ownership, ..SystemExecutionState::default() }; let mut view = state.into_view().await; @@ -35,11 +45,12 @@ async fn test_simple_system_operation() -> anyhow::Result<()> { recipient: Recipient::Burn, }; let context = OperationContext { - chain_id: ChainId::root(0), + chain_id, height: BlockHeight(0), round: Some(0), authenticated_signer: Some(owner), authenticated_caller_id: None, + timestamp: Default::default(), }; let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying(Vec::new()); @@ -60,7 +71,9 @@ async fn test_simple_system_operation() -> anyhow::Result<()> { #[tokio::test] async fn test_simple_system_message() -> anyhow::Result<()> { let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let description = dummy_chain_description(0); + let chain_id = description.id(); + state.description = Some(description); let mut view = state.into_view().await; let message = SystemMessage::Credit { amount: Amount::from_tokens(4), @@ -68,17 +81,18 @@ async fn test_simple_system_message() -> anyhow::Result<()> { source: AccountOwner::CHAIN, }; let context = MessageContext { - chain_id: ChainId::root(0), + chain_id, is_bouncing: false, height: BlockHeight(0), round: Some(0), message_id: MessageId { - chain_id: ChainId::root(1), + chain_id: dummy_chain_description(1).id(), height: BlockHeight(0), index: 0, }, authenticated_signer: None, refund_grant_to: None, + timestamp: Default::default(), }; let mut controller = ResourceController::default(); let mut txn_tracker = TransactionTracker::new_replaying(Vec::new()); @@ -100,11 +114,13 @@ async fn test_simple_system_message() -> anyhow::Result<()> { #[tokio::test] async fn test_simple_system_query() -> anyhow::Result<()> { let mut state = SystemExecutionState::default(); - state.description = Some(ChainDescription::Root(0)); + let description = dummy_chain_description(0); + let chain_id = description.id(); + state.description = Some(description); state.balance = Amount::from_tokens(4); let mut view = state.into_view().await; let context = QueryContext { - chain_id: ChainId::root(0), + chain_id, next_block_height: BlockHeight(0), local_time: Timestamp::from(0), }; @@ -118,7 +134,7 @@ async fn test_simple_system_query() -> anyhow::Result<()> { assert_eq!( response, QueryResponse::System(SystemResponse { - chain_id: ChainId::root(0), + chain_id, balance: Amount::from_tokens(4) }) ); diff --git a/linera-execution/tests/wasm.rs b/linera-execution/tests/wasm.rs index 2385dc85ba8d..01bf7cb2cc2c 100644 --- a/linera-execution/tests/wasm.rs +++ b/linera-execution/tests/wasm.rs @@ -5,12 +5,11 @@ use std::sync::Arc; -use linera_base::{ - data_types::{Amount, Blob, BlockHeight, Timestamp}, - identifiers::{ChainDescription, ChainId}, -}; +use linera_base::data_types::{Amount, Blob, BlockHeight, Timestamp}; use linera_execution::{ - test_utils::{create_dummy_user_application_description, SystemExecutionState}, + test_utils::{ + create_dummy_user_application_description, dummy_chain_description, SystemExecutionState, + }, ExecutionRuntimeConfig, ExecutionRuntimeContext, Operation, OperationContext, Query, QueryContext, QueryOutcome, QueryResponse, ResourceControlPolicy, ResourceController, ResourceTracker, TransactionTracker, WasmContractModule, WasmRuntime, WasmServiceModule, @@ -30,12 +29,14 @@ async fn test_fuel_for_counter_wasm_application( wasm_runtime: WasmRuntime, expected_fuel: u64, ) -> anyhow::Result<()> { + let chain_description = dummy_chain_description(0); + let chain_id = chain_description.id(); let state = SystemExecutionState { - description: Some(ChainDescription::Root(0)), + description: Some(chain_description), ..Default::default() }; let mut view = state - .into_view_with(ChainId::root(0), ExecutionRuntimeConfig::default()) + .into_view_with(chain_id, ExecutionRuntimeConfig::default()) .await; let (app_desc, contract_blob, service_blob) = create_dummy_user_application_description(1); let app_id = From::from(&app_desc); @@ -67,11 +68,12 @@ async fn test_fuel_for_counter_wasm_application( .await?; let context = OperationContext { - chain_id: ChainId::root(0), + chain_id, height: BlockHeight(0), round: Some(0), authenticated_signer: None, authenticated_caller_id: None, + timestamp: Default::default(), }; let increments = [2_u64, 9, 7, 1000]; let policy = ResourceControlPolicy { @@ -115,7 +117,7 @@ async fn test_fuel_for_counter_wasm_application( ); let context = QueryContext { - chain_id: ChainId::root(0), + chain_id, next_block_height: BlockHeight(0), local_time: Timestamp::from(0), }; diff --git a/linera-explorer/src/lib.rs b/linera-explorer/src/lib.rs index 90170e252a3b..17eccb444752 100644 --- a/linera-explorer/src/lib.rs +++ b/linera-explorer/src/lib.rs @@ -23,11 +23,7 @@ use gql_service::{ }; use graphql_client::Response; use js_utils::{getf, log_str, parse, setf, stringify, SER}; -use linera_base::{ - crypto::CryptoHash, - data_types::BlockHeight, - identifiers::{ChainDescription, ChainId}, -}; +use linera_base::{crypto::CryptoHash, data_types::BlockHeight, identifiers::ChainId}; use linera_indexer_graphql_client::{ indexer::{plugins, Plugins}, operations as gql_operations, @@ -133,7 +129,10 @@ pub fn data() -> JsValue { config: Config::load(), page: Page::Unloaded, chains: Vec::new(), - chain: ChainId::from(ChainDescription::Root(0)), + chain: ChainId::from_str( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), plugins: Vec::new(), }; data.serialize(&SER).unwrap() @@ -284,10 +283,10 @@ async fn chains(app: &JsValue, node: &str) -> Result { .serialize(&SER) .expect("failed to serialize ChainIds"); setf(app, "chains", &chains_js); - Ok(chains.default.unwrap_or_else(|| match chains.list.first() { - None => ChainId::from(ChainDescription::Root(0)), - Some(chain_id) => *chain_id, - })) + chains + .default + .or_else(|| chains.list.first().copied()) + .ok_or_else(|| anyhow::Error::msg("no chains available")) } /// Queries indexer plugins. diff --git a/linera-faucet/client/src/lib.rs b/linera-faucet/client/src/lib.rs index 3d1bea41cbc2..eae74678f145 100644 --- a/linera-faucet/client/src/lib.rs +++ b/linera-faucet/client/src/lib.rs @@ -105,7 +105,7 @@ impl Faucet { ) -> Result { let query = format!( "mutation {{ claim(owner: \"{owner}\") {{ \ - messageId chainId certificateHash \ + chainId certificateHash \ }} }}" ); diff --git a/linera-faucet/server/src/lib.rs b/linera-faucet/server/src/lib.rs index 5ad2599805cc..bbfb0b9eba43 100644 --- a/linera-faucet/server/src/lib.rs +++ b/linera-faucet/server/src/lib.rs @@ -12,7 +12,7 @@ use futures::{lock::Mutex, FutureExt as _}; use linera_base::{ crypto::{CryptoHash, ValidatorPublicKey}, data_types::{Amount, ApplicationPermissions, BlockHeight, Timestamp}, - identifiers::{AccountOwner, ChainId, MessageId}, + identifiers::{AccountOwner, ChainId}, ownership::ChainOwnership, }; use linera_client::{ @@ -60,8 +60,6 @@ pub struct MutationRoot { /// The result of a successful `claim` mutation. #[derive(SimpleObject)] pub struct ClaimOutcome { - /// The ID of the message that created the new chain. - pub message_id: MessageId, /// The ID of the new chain. pub chain_id: ChainId, /// The hash of the parent chain's certificate containing the `OpenChain` operation. @@ -92,7 +90,12 @@ where /// Returns the current committee's validators. async fn current_validators(&self) -> Result, Error> { let chain_id = *self.chain_id.lock().await; - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let committee = client.local_committee().await?; Ok(committee .validators() @@ -122,7 +125,12 @@ where { async fn do_claim(&self, owner: AccountOwner) -> Result { let chain_id = *self.chain_id.lock().await; - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; if self.start_timestamp < self.end_timestamp { let local_time = client.storage_client().clock().current_time(); @@ -153,19 +161,27 @@ where .open_chain(ownership, ApplicationPermissions::default(), self.amount) .await; self.context.lock().await.update_wallet(&client).await?; - let (message_id, certificate) = result?.try_unwrap()?; + let (chain_id, certificate) = match result? { + ClientOutcome::Committed(result) => result, + ClientOutcome::WaitForTimeout(timeout) => { + return Err(Error::new(format!( + "This faucet is using a multi-owner chain and is not the leader right now. \ + Try again at {}", + timeout.timestamp, + ))); + } + }; if client.next_block_height() >= self.end_block_height { let key_pair = client.key_pair().await?; let balance = client.local_balance().await?.try_sub(Amount::ONE)?; let ownership = client.chain_state_view().await?.ownership().clone(); - let (message_id, certificate) = client + let (chain_id, certificate) = client .open_chain(ownership, ApplicationPermissions::default(), balance) .await? .try_unwrap()?; // TODO(#1795): Move the remaining tokens to the new chain. client.close_chain().await?.try_unwrap()?; - let chain_id = ChainId::child(message_id); info!("Switching to a new faucet chain {chain_id:8}; remaining balance: {balance}"); self.context .lock() @@ -179,9 +195,7 @@ where *self.chain_id.lock().await = chain_id; } - let chain_id = ChainId::child(message_id); Ok(ClaimOutcome { - message_id, chain_id, certificate_hash: certificate.hash(), }) @@ -256,7 +270,7 @@ where config: ChainListenerConfig, storage: ::Storage, ) -> anyhow::Result { - let client = context.make_chain_client(chain_id)?; + let client = context.make_chain_client(chain_id).await?; let context = Arc::new(Mutex::new(context)); let start_timestamp = client.storage_client().clock().current_time(); client.process_inbox().await?; diff --git a/linera-faucet/server/src/tests.rs b/linera-faucet/server/src/tests.rs index 98d066bc3478..32768a8191ae 100644 --- a/linera-faucet/server/src/tests.rs +++ b/linera-faucet/server/src/tests.rs @@ -38,7 +38,7 @@ impl chain_listener::ClientContext for ClientContext { self.client.storage_client() } - fn make_chain_client( + async fn make_chain_client( &self, chain_id: ChainId, ) -> Result, linera_client::Error> { diff --git a/linera-faucet/src/lib.rs b/linera-faucet/src/lib.rs index 69c1f34931de..013ec27ae45d 100644 --- a/linera-faucet/src/lib.rs +++ b/linera-faucet/src/lib.rs @@ -5,10 +5,7 @@ Common definitions for the Linera faucet. */ -use linera_base::{ - crypto::CryptoHash, - identifiers::{ChainId, MessageId}, -}; +use linera_base::{crypto::CryptoHash, identifiers::ChainId}; /// The result of a successful `claim` mutation. #[cfg_attr(feature = "async-graphql", derive(async_graphql::SimpleObject))] @@ -16,9 +13,6 @@ use linera_base::{ #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ClaimOutcome { - /// The ID of the message that created the new chain. - #[serde_as(as = "serde_with::DisplayFromStr")] - pub message_id: MessageId, /// The ID of the new chain. #[serde_as(as = "serde_with::DisplayFromStr")] pub chain_id: ChainId, diff --git a/linera-indexer/example/tests/test.rs b/linera-indexer/example/tests/test.rs index ccca39606b86..d4b8f65873a8 100644 --- a/linera-indexer/example/tests/test.rs +++ b/linera-indexer/example/tests/test.rs @@ -120,8 +120,12 @@ async fn test_end_to_end_operations_indexer(config: impl LineraNetConfig) { ); // making a few transfers - let chain0 = ChainId::root(0); - let chain1 = Account::chain(ChainId::root(1)); + let node_chains = { + let wallet = client.load_wallet().unwrap(); + wallet.chain_ids() + }; + let chain0 = node_chains[0]; + let chain1 = Account::chain(node_chains[1]); for _ in 0..10 { transfer(&req_client, chain0, chain1, "0.1").await; linera_base::time::timer::sleep(Duration::from_millis(TRANSFER_DELAY_MILLIS)).await; diff --git a/linera-rpc/src/grpc/conversions.rs b/linera-rpc/src/grpc/conversions.rs index 31e3fd0b8381..717beb4e704c 100644 --- a/linera-rpc/src/grpc/conversions.rs +++ b/linera-rpc/src/grpc/conversions.rs @@ -984,8 +984,12 @@ pub mod tests { impl BcsSignable<'_> for Foo {} + fn dummy_chain_id(index: u32) -> ChainId { + ChainId(CryptoHash::test_hash(format!("chain{}", index))) + } + fn get_block() -> ProposedBlock { - make_first_block(ChainId::root(0)) + make_first_block(dummy_chain_id(0)) } /// A convenience function for testing. It converts a type into its @@ -1037,14 +1041,14 @@ pub mod tests { #[test] pub fn test_chain_id() { - let chain_id = ChainId::root(0); + let chain_id = dummy_chain_id(0); round_trip_check::<_, api::ChainId>(chain_id); } #[test] pub fn test_chain_info_response() { let chain_info = Box::new(ChainInfo { - chain_id: ChainId::root(0), + chain_id: dummy_chain_id(0), epoch: None, description: None, manager: Box::default(), @@ -1081,11 +1085,11 @@ pub mod tests { #[test] pub fn test_chain_info_query() { - let chain_info_query_none = ChainInfoQuery::new(ChainId::root(0)); + let chain_info_query_none = ChainInfoQuery::new(dummy_chain_id(0)); round_trip_check::<_, api::ChainInfoQuery>(chain_info_query_none); let chain_info_query_some = ChainInfoQuery { - chain_id: ChainId::root(0), + chain_id: dummy_chain_id(0), test_next_block_height: Some(BlockHeight::from(10)), request_committees: false, request_owner_balance: AccountOwner::CHAIN, @@ -1106,7 +1110,7 @@ pub mod tests { #[test] pub fn test_pending_blob_request() { - let chain_id = ChainId::root(2); + let chain_id = dummy_chain_id(2); let blob_id = Blob::new(BlobContent::new_data(*b"foo")).id(); let pending_blob_request = (chain_id, blob_id); round_trip_check::<_, api::PendingBlobRequest>(pending_blob_request); @@ -1120,7 +1124,7 @@ pub mod tests { #[test] pub fn test_handle_pending_blob_request() { - let chain_id = ChainId::root(2); + let chain_id = dummy_chain_id(2); let blob_content = BlobContent::new_data(*b"foo"); let pending_blob_request = (chain_id, blob_content); round_trip_check::<_, api::HandlePendingBlobRequest>(pending_blob_request); @@ -1132,7 +1136,7 @@ pub mod tests { let certificate = LiteCertificate { value: LiteValue { value_hash: CryptoHash::new(&Foo("value".into())), - chain_id: ChainId::root(0), + chain_id: dummy_chain_id(0), kind: CertificateKind::Validated, }, round: Round::MultiLeader(2), @@ -1174,16 +1178,16 @@ pub mod tests { #[test] pub fn test_cross_chain_request() { let cross_chain_request_update_recipient = CrossChainRequest::UpdateRecipient { - sender: ChainId::root(0), - recipient: ChainId::root(0), + sender: dummy_chain_id(0), + recipient: dummy_chain_id(0), bundles: vec![], }; round_trip_check::<_, api::CrossChainRequest>(cross_chain_request_update_recipient); let cross_chain_request_confirm_updated_recipient = CrossChainRequest::ConfirmUpdatedRecipient { - sender: ChainId::root(0), - recipient: ChainId::root(0), + sender: dummy_chain_id(0), + recipient: dummy_chain_id(0), latest_height: BlockHeight(1), }; round_trip_check::<_, api::CrossChainRequest>( @@ -1226,7 +1230,7 @@ pub mod tests { #[test] pub fn test_notification() { let notification = Notification { - chain_id: ChainId::root(0), + chain_id: dummy_chain_id(0), reason: linera_core::worker::Reason::NewBlock { height: BlockHeight(0), hash: CryptoHash::new(&Foo("".into())), diff --git a/linera-rpc/tests/format.rs b/linera-rpc/tests/format.rs index 26d7e1c0c73c..ac4a33410c77 100644 --- a/linera-rpc/tests/format.rs +++ b/linera-rpc/tests/format.rs @@ -4,8 +4,8 @@ use linera_base::{ crypto::{AccountPublicKey, AccountSignature, TestString}, - data_types::{BlobContent, OracleResponse, Round}, - identifiers::{AccountOwner, BlobType, ChainDescription, GenericApplicationId}, + data_types::{BlobContent, ChainDescription, ChainOrigin, OracleResponse, Round}, + identifiers::{AccountOwner, BlobType, GenericApplicationId}, ownership::ChainOwnership, vm::VmRuntime, }; @@ -72,6 +72,7 @@ fn get_registry() -> Result { tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; + tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index fb719b70ac1b..de519a6f3a61 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -124,6 +124,8 @@ BlobType: ApplicationDescription: UNIT 5: Committee: UNIT + 6: + ChainDescription: UNIT Block: STRUCT: - header: @@ -258,14 +260,13 @@ ChainAndHeight: - height: TYPENAME: BlockHeight ChainDescription: - ENUM: - 0: - Root: - NEWTYPE: U32 - 1: - Child: - NEWTYPE: - TYPENAME: MessageId + STRUCT: + - origin: + TYPENAME: ChainOrigin + - timestamp: + TYPENAME: Timestamp + - config: + TYPENAME: InitialChainConfig ChainId: NEWTYPESTRUCT: TYPENAME: CryptoHash @@ -375,6 +376,19 @@ ChainManagerInfo: - round_timeout: OPTION: TYPENAME: Timestamp +ChainOrigin: + ENUM: + 0: + Root: + NEWTYPE: U32 + 1: + Child: + STRUCT: + - parent: + TYPENAME: ChainId + - block_height: + TYPENAME: BlockHeight + - chain_index: U32 ChainOwnership: STRUCT: - super_owners: @@ -516,6 +530,25 @@ IncomingBundle: TYPENAME: MessageBundle - action: TYPENAME: MessageAction +InitialChainConfig: + STRUCT: + - ownership: + TYPENAME: ChainOwnership + - admin_id: + OPTION: + TYPENAME: ChainId + - epoch: + TYPENAME: Epoch + - committees: + MAP: + KEY: + TYPENAME: Epoch + VALUE: + SEQ: U8 + - balance: + TYPENAME: Amount + - application_permissions: + TYPENAME: ApplicationPermissions LiteCertificate: STRUCT: - value: @@ -585,13 +618,6 @@ MessageBundle: - messages: SEQ: TYPENAME: PostedMessage -MessageId: - STRUCT: - - chain_id: - TYPENAME: ChainId - - height: - TYPENAME: BlockHeight - - index: U32 MessageKind: ENUM: 0: @@ -709,16 +735,6 @@ OpenChainConfig: STRUCT: - ownership: TYPENAME: ChainOwnership - - admin_id: - TYPENAME: ChainId - - epoch: - TYPENAME: Epoch - - committees: - MAP: - KEY: - TYPENAME: Epoch - VALUE: - TYPENAME: Committee - balance: TYPENAME: Amount - application_permissions: @@ -1062,10 +1078,6 @@ SystemMessage: - recipient: TYPENAME: Recipient 2: - OpenChain: - NEWTYPE: - TYPENAME: OpenChainConfig - 3: ApplicationCreated: UNIT SystemOperation: ENUM: diff --git a/linera-sdk/src/contract/runtime.rs b/linera-sdk/src/contract/runtime.rs index 36eb29e31d0c..853234be63b3 100644 --- a/linera-sdk/src/contract/runtime.rs +++ b/linera-sdk/src/contract/runtime.rs @@ -323,13 +323,13 @@ where chain_ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - ) -> (MessageId, ChainId) { - let (message_id, chain_id) = contract_wit::open_chain( + ) -> ChainId { + let chain_id = contract_wit::open_chain( &chain_ownership.into(), &application_permissions.into(), balance.into(), ); - (message_id.into(), chain_id.into()) + chain_id.into() } /// Closes the current chain. Returns an error if the application doesn't have diff --git a/linera-sdk/src/contract/test_runtime.rs b/linera-sdk/src/contract/test_runtime.rs index a50bb668fd97..346a21a7e0c2 100644 --- a/linera-sdk/src/contract/test_runtime.rs +++ b/linera-sdk/src/contract/test_runtime.rs @@ -62,8 +62,7 @@ where expected_http_requests: VecDeque<(http::Request, http::Response)>, expected_read_data_blob_requests: VecDeque<(DataBlobHash, Vec)>, expected_assert_data_blob_exists_requests: VecDeque<(DataBlobHash, Option<()>)>, - expected_open_chain_calls: - VecDeque<(ChainOwnership, ApplicationPermissions, Amount, MessageId)>, + expected_open_chain_calls: VecDeque<(ChainOwnership, ApplicationPermissions, Amount, ChainId)>, expected_create_application_calls: VecDeque, key_value_store: KeyValueStore, } @@ -651,13 +650,13 @@ where ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - message_id: MessageId, + chain_id: ChainId, ) { self.expected_open_chain_calls.push_back(( ownership, application_permissions, balance, - message_id, + chain_id, )); } @@ -668,16 +667,15 @@ where ownership: ChainOwnership, application_permissions: ApplicationPermissions, balance: Amount, - ) -> (MessageId, ChainId) { - let (expected_ownership, expected_permissions, expected_balance, message_id) = self + ) -> ChainId { + let (expected_ownership, expected_permissions, expected_balance, chain_id) = self .expected_open_chain_calls .pop_front() .expect("Unexpected open_chain call"); assert_eq!(ownership, expected_ownership); assert_eq!(application_permissions, expected_permissions); assert_eq!(balance, expected_balance); - let chain_id = ChainId::child(message_id); - (message_id, chain_id) + chain_id } /// Adds a new expected call to `create_application`. diff --git a/linera-sdk/src/test/chain.rs b/linera-sdk/src/test/chain.rs index bbf88d70a10f..62480b0ab44c 100644 --- a/linera-sdk/src/test/chain.rs +++ b/linera-sdk/src/test/chain.rs @@ -17,9 +17,10 @@ use futures::future; use linera_base::{ crypto::{AccountPublicKey, AccountSecretKey}, data_types::{ - Amount, ApplicationDescription, Blob, BlockHeight, Bytecode, CompressedBytecode, Epoch, + Amount, ApplicationDescription, Blob, BlockHeight, Bytecode, ChainDescription, + CompressedBytecode, Epoch, }, - identifiers::{AccountOwner, ApplicationId, ChainDescription, ChainId, ModuleId}, + identifiers::{AccountOwner, ApplicationId, ChainId, ModuleId}, vm::VmRuntime, }; use linera_chain::{types::ConfirmedBlockCertificate, ChainExecutionContext}; @@ -47,7 +48,7 @@ impl Clone for ActiveChain { fn clone(&self) -> Self { ActiveChain { key_pair: self.key_pair.copy(), - description: self.description, + description: self.description.clone(), tip: self.tip.clone(), validator: self.validator.clone(), } @@ -75,7 +76,7 @@ impl ActiveChain { /// Returns the [`ChainId`] of this microchain. pub fn id(&self) -> ChainId { - self.description.into() + self.description.id() } /// Returns the [`AccountPublicKey`] of the active owner of this microchain. @@ -256,7 +257,7 @@ impl ActiveChain { ) -> Result { let mut tip = self.tip.lock().await; let mut block = BlockBuilder::new( - self.description.into(), + self.description.id(), self.key_pair.public().into(), self.epoch().await, tip.as_ref(), diff --git a/linera-sdk/src/test/validator.rs b/linera-sdk/src/test/validator.rs index f64574ecc34e..f93bae32c1ee 100644 --- a/linera-sdk/src/test/validator.rs +++ b/linera-sdk/src/test/validator.rs @@ -15,14 +15,17 @@ use futures::{ }; use linera_base::{ crypto::{AccountSecretKey, ValidatorKeypair, ValidatorSecretKey}, - data_types::{Amount, ApplicationPermissions, Blob, BlobContent, Epoch, Timestamp}, - identifiers::{AccountOwner, ApplicationId, ChainDescription, ChainId, MessageId, ModuleId}, + data_types::{ + Amount, ApplicationPermissions, Blob, BlobContent, ChainDescription, ChainOrigin, Epoch, + InitialChainConfig, Timestamp, + }, + identifiers::{AccountOwner, ApplicationId, ChainId, ModuleId}, ownership::ChainOwnership, }; use linera_core::worker::WorkerState; use linera_execution::{ committee::Committee, - system::{AdminOperation, OpenChainConfig, SystemOperation, OPEN_CHAIN_MESSAGE_INDEX}, + system::{AdminOperation, OpenChainConfig, SystemOperation}, ResourceControlPolicy, WasmRuntime, }; use linera_storage::{DbStorage, Storage, TestClock}; @@ -52,12 +55,14 @@ pub struct TestValidator { storage: DbStorage, worker: WorkerState>, clock: TestClock, + admin_chain_id: ChainId, chains: Arc>, } impl Clone for TestValidator { fn clone(&self) -> Self { TestValidator { + admin_chain_id: self.admin_chain_id, validator_secret: self.validator_secret.copy(), account_secret: self.account_secret.copy(), committee: self.committee.clone(), @@ -74,13 +79,11 @@ impl TestValidator { pub async fn new() -> Self { let validator_keypair = ValidatorKeypair::generate(); let account_secret = AccountSecretKey::generate(); - let committee = Arc::new(Mutex::new(( - Epoch::ZERO, - Committee::make_simple(vec![( - validator_keypair.public_key, - account_secret.public(), - )]), - ))); + let epoch = Epoch::ZERO; + let committee = Committee::make_simple(vec![( + validator_keypair.public_key, + account_secret.public(), + )]); let wasm_runtime = Some(WasmRuntime::default()); let storage = DbStorage::::make_test_storage(wasm_runtime) .now_or_never() @@ -93,17 +96,48 @@ impl TestValidator { NonZeroUsize::new(40).expect("Chain worker limit should not be zero"), ); + // Create an admin chain. + let key_pair = AccountSecretKey::generate(); + + let new_chain_config = InitialChainConfig { + ownership: ChainOwnership::single(key_pair.public().into()), + committees: [( + epoch, + bcs::to_bytes(&committee).expect("Serializing a committee should not fail!"), + )] + .into_iter() + .collect(), + admin_id: None, + epoch, + balance: Amount::from_tokens(1_000_000), + application_permissions: ApplicationPermissions::default(), + }; + + let origin = ChainOrigin::Root(0); + let description = ChainDescription::new(origin, new_chain_config, Timestamp::from(0)); + let admin_chain_id = description.id(); + + worker + .storage_client() + .create_chain(description.clone()) + .await + .expect("Failed to create root admin chain"); + let validator = TestValidator { validator_secret: validator_keypair.secret_key, account_secret, - committee, + committee: Arc::new(Mutex::new((epoch, committee))), storage, worker, clock, + admin_chain_id, chains: Arc::default(), }; - validator.create_admin_chain().await; + let chain = ActiveChain::new(key_pair, description.clone(), validator.clone()); + + validator.chains.insert(description.id(), chain); + validator } @@ -172,6 +206,11 @@ impl TestValidator { &self.validator_secret } + /// Returns the ID of the admin chain. + pub fn admin_chain_id(&self) -> ChainId { + self.admin_chain_id + } + /// Returns the latest committee that this test validator is part of. /// /// The committee contains only this validator. @@ -182,7 +221,7 @@ impl TestValidator { /// Updates the admin chain, creating a new epoch with an updated /// [`ResourceControlPolicy`]. pub async fn change_resource_control_policy( - &self, + &mut self, adjustment: impl FnOnce(&mut ResourceControlPolicy), ) { let (epoch, committee) = { @@ -197,8 +236,7 @@ impl TestValidator { (*epoch, committee.clone()) }; - let admin_chain_id = ChainId::root(0); - let admin_chain = self.get_chain(&admin_chain_id); + let admin_chain = self.get_chain(&self.admin_chain_id); let committee_blob = Blob::new(BlobContent::new_committee( bcs::to_bytes(&committee).unwrap(), @@ -220,7 +258,7 @@ impl TestValidator { for entry in self.chains.iter() { let chain = entry.value(); - if chain.id() != admin_chain_id { + if chain.id() != self.admin_chain_id { chain .add_block(|block| { block.with_system_operation(SystemOperation::ProcessNewEpoch(epoch)); @@ -236,11 +274,11 @@ impl TestValidator { let description = self .request_new_chain_from_admin_chain(key_pair.public().into()) .await; - let chain = ActiveChain::new(key_pair, description, self.clone()); + let chain = ActiveChain::new(key_pair, description.clone(), self.clone()); chain.handle_received_messages().await; - self.chains.insert(description.into(), chain.clone()); + self.chains.insert(description.id(), chain.clone()); chain } @@ -261,7 +299,7 @@ impl TestValidator { /// /// Returns the [`ChainDescription`] of the new chain. async fn request_new_chain_from_admin_chain(&self, owner: AccountOwner) -> ChainDescription { - let admin_id = ChainId::root(0); + let admin_id = self.admin_chain_id; let admin_chain = self .chains .get(&admin_id) @@ -269,55 +307,40 @@ impl TestValidator { let (epoch, committee) = self.committee.lock().await.clone(); - let new_chain_config = OpenChainConfig { + let open_chain_config = OpenChainConfig { ownership: ChainOwnership::single(owner), - committees: [(epoch, committee)].into_iter().collect(), - admin_id, - epoch, balance: Amount::ZERO, application_permissions: ApplicationPermissions::default(), }; + let new_chain_config = open_chain_config.init_chain_config( + epoch, + Some(admin_id), + [( + epoch, + bcs::to_bytes(&committee).expect("Serializing a committee should not fail!"), + )] + .into_iter() + .collect(), + ); let certificate = admin_chain .add_block(|block| { - block.with_system_operation(SystemOperation::OpenChain(new_chain_config)); + block.with_system_operation(SystemOperation::OpenChain(open_chain_config)); }) .await; let block = certificate.inner().block(); - ChainDescription::Child(MessageId { - chain_id: block.header.chain_id, - height: block.header.height, - index: OPEN_CHAIN_MESSAGE_INDEX, - }) + let origin = ChainOrigin::Child { + parent: block.header.chain_id, + block_height: block.header.height, + chain_index: 0, + }; + + ChainDescription::new(origin, new_chain_config, Timestamp::from(0)) } /// Returns the [`ActiveChain`] reference to the microchain identified by `chain_id`. pub fn get_chain(&self, chain_id: &ChainId) -> ActiveChain { self.chains.get(chain_id).expect("Chain not found").clone() } - - /// Creates the root admin microchain and returns the [`ActiveChain`] map with it. - async fn create_admin_chain(&self) { - let key_pair = AccountSecretKey::generate(); - let description = ChainDescription::Root(0); - let committee = self.committee.lock().await.1.clone(); - - self.worker() - .storage_client() - .create_chain( - committee, - ChainId::root(0), - description, - key_pair.public().into(), - Amount::MAX, - Timestamp::from(0), - ) - .await - .expect("Failed to create root admin chain"); - - let chain = ActiveChain::new(key_pair, description, self.clone()); - - self.chains.insert(description.into(), chain); - } } diff --git a/linera-sdk/wit/contract-runtime-api.wit b/linera-sdk/wit/contract-runtime-api.wit index 526eddd28a03..bff1b7e5256f 100644 --- a/linera-sdk/wit/contract-runtime-api.wit +++ b/linera-sdk/wit/contract-runtime-api.wit @@ -8,7 +8,7 @@ interface contract-runtime-api { send-message: func(message: send-message-request); transfer: func(source: account-owner, destination: account, amount: amount); claim: func(source: account, destination: account, amount: amount); - open-chain: func(chain-ownership: chain-ownership, application-permissions: application-permissions, balance: amount) -> tuple; + open-chain: func(chain-ownership: chain-ownership, application-permissions: application-permissions, balance: amount) -> chain-id; close-chain: func() -> result, close-chain-error>; change-application-permissions: func(application-permissions: application-permissions) -> result, change-application-permissions-error>; create-application: func(module-id: module-id, parameters: list, argument: list, required-application-ids: list) -> application-id; diff --git a/linera-service-graphql-client/gql/service_schema.graphql b/linera-service-graphql-client/gql/service_schema.graphql index 1ec0de78d77b..15f96bf50a62 100644 --- a/linera-service-graphql-client/gql/service_schema.graphql +++ b/linera-service-graphql-client/gql/service_schema.graphql @@ -243,7 +243,7 @@ type ChainAndHeight { } """ -How to create a chain +Initial chain configuration and chain origin. """ scalar ChainDescription diff --git a/linera-service-graphql-client/src/service.rs b/linera-service-graphql-client/src/service.rs index 4a52b99a56a4..f69dff79140d 100644 --- a/linera-service-graphql-client/src/service.rs +++ b/linera-service-graphql-client/src/service.rs @@ -4,10 +4,8 @@ use graphql_client::GraphQLQuery; use linera_base::{ crypto::CryptoHash, - data_types::{Amount, Blob, BlockHeight, OracleResponse, Round, Timestamp}, - identifiers::{ - Account, AccountOwner, BlobId, ChainDescription, ChainId, GenericApplicationId, StreamName, - }, + data_types::{Amount, Blob, BlockHeight, ChainDescription, OracleResponse, Round, Timestamp}, + identifiers::{Account, AccountOwner, BlobId, ChainId, GenericApplicationId, StreamName}, }; use thiserror::Error; diff --git a/linera-service-graphql-client/tests/test.rs b/linera-service-graphql-client/tests/test.rs index 3b0081efad75..9b4d6e2bf3fe 100644 --- a/linera-service-graphql-client/tests/test.rs +++ b/linera-service-graphql-client/tests/test.rs @@ -61,7 +61,7 @@ async fn test_end_to_end_queries(config: impl LineraNetConfig) { let wallet = client.load_wallet().unwrap(); (wallet.default_chain(), wallet.chain_ids()) }; - let chain_id = node_chains.0.unwrap(); + let chain0 = node_chains.0.unwrap(); // publishing an application let (contract, service) = client.build_example("fungible").await.unwrap(); @@ -91,8 +91,7 @@ async fn test_end_to_end_queries(config: impl LineraNetConfig) { let url = &format!("http://localhost:{}/", node_service.port()); // sending a few transfers - let chain0 = ChainId::root(0); - let chain1 = Account::chain(ChainId::root(1)); + let chain1 = Account::chain(node_chains.1[1]); for _ in 0..10 { transfer(req_client, url, chain0, chain1, "0.1").await; } @@ -109,7 +108,7 @@ async fn test_end_to_end_queries(config: impl LineraNetConfig) { req_client, url, blocks::Variables { - chain_id, + chain_id: chain0, from: None, limit: None, }, @@ -124,7 +123,7 @@ async fn test_end_to_end_queries(config: impl LineraNetConfig) { &reqwest_client(), &format!("http://localhost:{}/", node_service.port()), block::Variables { - chain_id, + chain_id: chain0, hash: None, }, ) diff --git a/linera-service/benches/transfers.rs b/linera-service/benches/transfers.rs index 6f5a1e627c36..0a4c5069296e 100644 --- a/linera-service/benches/transfers.rs +++ b/linera-service/benches/transfers.rs @@ -9,7 +9,7 @@ use futures::{ use linera_base::{ crypto::{AccountSecretKey, Ed25519SecretKey, EvmSecretKey, Secp256k1SecretKey}, data_types::Amount, - identifiers::{Account, AccountOwner, ChainId}, + identifiers::{Account, AccountOwner}, time::{Duration, Instant}, }; use linera_execution::system::Recipient; @@ -75,7 +75,7 @@ async fn setup_native_token_balances( .collect::>() .await; - let admin_chain = validator.get_chain(&ChainId::root(0)); + let admin_chain = validator.get_chain(&validator.admin_chain_id()); for chain in &chains { let recipient = Recipient::Account(Account { diff --git a/linera-service/src/cli_wrappers/wallet.rs b/linera-service/src/cli_wrappers/wallet.rs index c32c8ddd098f..322966166bb0 100644 --- a/linera-service/src/cli_wrappers/wallet.rs +++ b/linera-service/src/cli_wrappers/wallet.rs @@ -23,7 +23,7 @@ use linera_base::{ command::{resolve_binary, CommandExt}, crypto::CryptoHash, data_types::{Amount, Bytecode, Epoch}, - identifiers::{Account, AccountOwner, ApplicationId, ChainId, MessageId, ModuleId}, + identifiers::{Account, AccountOwner, ApplicationId, ChainId, ModuleId}, vm::VmRuntime, }; use linera_client::{client_options::ResourceControlPolicyConfig, wallet::Wallet}; @@ -292,11 +292,9 @@ impl ClientWrapper { if matches!(faucet, FaucetOption::NewChain(_)) { let mut lines = stdout.split_whitespace(); let chain_id_str = lines.next().context("missing chain ID")?; - let message_id_str = lines.next().context("missing message ID")?; let certificate_hash_str = lines.next().context("missing certificate hash")?; let outcome = ClaimOutcome { chain_id: chain_id_str.parse().context("invalid chain ID")?, - message_id: message_id_str.parse().context("invalid message ID")?, certificate_hash: certificate_hash_str .parse() .context("invalid certificate hash")?, @@ -326,11 +324,9 @@ impl ClientWrapper { let stdout = command.spawn_and_wait_for_stdout().await?; let mut lines = stdout.split_whitespace(); let chain_id_str = lines.next().context("missing chain ID")?; - let message_id_str = lines.next().context("missing message ID")?; let certificate_hash_str = lines.next().context("missing certificate hash")?; let outcome = ClaimOutcome { chain_id: chain_id_str.parse().context("invalid chain ID")?, - message_id: message_id_str.parse().context("invalid message ID")?, certificate_hash: certificate_hash_str .parse() .context("invalid certificate hash")?, @@ -688,7 +684,7 @@ impl ClientWrapper { from: ChainId, owner: Option, initial_balance: Amount, - ) -> Result<(MessageId, ChainId, AccountOwner)> { + ) -> Result<(ChainId, AccountOwner)> { let mut command = self.command().await?; command .arg("open-chain") @@ -701,13 +697,12 @@ impl ClientWrapper { let stdout = command.spawn_and_wait_for_stdout().await?; let mut split = stdout.split('\n'); - let message_id: MessageId = split.next().context("no message ID in output")?.parse()?; let chain_id = ChainId::from_str(split.next().context("no chain ID in output")?)?; let new_owner = AccountOwner::from_str(split.next().context("no owner in output")?)?; if let Some(owner) = owner { assert_eq!(owner, new_owner); } - Ok((message_id, chain_id, new_owner)) + Ok((chain_id, new_owner)) } /// Runs `linera open-chain` then `linera assign`. @@ -721,10 +716,10 @@ impl ClientWrapper { .default_chain() .context("no default chain found")?; let owner = client.keygen().await?; - let (message_id, new_chain, _) = self + let (new_chain, _) = self .open_chain(our_chain, Some(owner), initial_balance) .await?; - assert_eq!(new_chain, client.assign(owner, message_id).await?); + client.assign(owner, new_chain).await?; Ok(new_chain) } @@ -736,7 +731,7 @@ impl ClientWrapper { multi_leader_rounds: u32, balance: Amount, base_timeout_ms: u64, - ) -> Result<(MessageId, ChainId)> { + ) -> Result { let mut command = self.command().await?; command .arg("open-multi-owner-chain") @@ -755,10 +750,9 @@ impl ClientWrapper { let stdout = command.spawn_and_wait_for_stdout().await?; let mut split = stdout.split('\n'); - let message_id: MessageId = split.next().context("no message ID in output")?.parse()?; let chain_id = ChainId::from_str(split.next().context("no chain ID in output")?)?; - Ok((message_id, chain_id)) + Ok(chain_id) } pub async fn change_ownership( @@ -930,19 +924,16 @@ impl ClientWrapper { } /// Runs `linera assign`. - pub async fn assign(&self, owner: AccountOwner, message_id: MessageId) -> Result { - let stdout = self + pub async fn assign(&self, owner: AccountOwner, chain_id: ChainId) -> Result<()> { + let _stdout = self .command() .await? .arg("assign") .args(["--owner", &owner.to_string()]) - .args(["--message-id", &message_id.to_string()]) + .args(["--chain-id", &chain_id.to_string()]) .spawn_and_wait_for_stdout() .await?; - - let chain_id = ChainId::from_str(stdout.trim())?; - - Ok(chain_id) + Ok(()) } pub async fn build_application( diff --git a/linera-service/src/linera-exporter/exporter_service.rs b/linera-service/src/linera-exporter/exporter_service.rs index b5beb83fc459..878da4646447 100644 --- a/linera-service/src/linera-exporter/exporter_service.rs +++ b/linera-service/src/linera-exporter/exporter_service.rs @@ -295,6 +295,8 @@ mod test { destinations: vec![], }; + let dummy_chain_id = ChainId(CryptoHash::test_hash("root1")); + let block = BlockExecutionOutcome { messages: vec![Vec::new()], previous_message_blocks: BTreeMap::new(), @@ -304,9 +306,7 @@ mod test { blobs: vec![Vec::new()], operation_results: vec![OperationResult::default()], } - .with( - make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(1), Amount::ONE), - ); + .with(make_first_block(dummy_chain_id).with_simple_transfer(dummy_chain_id, Amount::ONE)); let confirmed_block = ConfirmedBlock::new(block); let expected_byte_size = bcs::serialized_size(&confirmed_block).unwrap(); // here just to avoid cloning after the move below. let certificate = ConfirmedBlockCertificate::new(confirmed_block, Round::Fast, vec![]); diff --git a/linera-service/src/linera/command.rs b/linera-service/src/linera/command.rs index 485a79ebf28d..f5e808c1504f 100644 --- a/linera-service/src/linera/command.rs +++ b/linera-service/src/linera/command.rs @@ -7,7 +7,7 @@ use chrono::{DateTime, Utc}; use linera_base::{ crypto::{AccountPublicKey, CryptoHash, ValidatorPublicKey}, data_types::Amount, - identifiers::{Account, AccountOwner, ApplicationId, ChainId, MessageId, ModuleId}, + identifiers::{Account, AccountOwner, ApplicationId, ChainId, ModuleId}, time::Duration, vm::VmRuntime, }; @@ -741,10 +741,9 @@ pub enum ClientCommand { #[arg(long)] owner: AccountOwner, - /// The ID of the message that created the chain. (This uniquely describes the - /// chain and where it was created.) + /// The ID of the chain. #[arg(long)] - message_id: MessageId, + chain_id: ChainId, }, /// Retry a block we unsuccessfully tried to propose earlier. diff --git a/linera-service/src/linera/main.rs b/linera-service/src/linera/main.rs index 959201550483..4c1b127cf751 100644 --- a/linera-service/src/linera/main.rs +++ b/linera-service/src/linera/main.rs @@ -21,9 +21,13 @@ use colored::Colorize; use command::{ClientCommand, DatabaseToolCommand, NetCommand, ProjectCommand, WalletCommand}; use futures::{lock::Mutex, FutureExt as _, StreamExt}; use linera_base::{ + bcs, crypto::{AccountSecretKey, CryptoHash, CryptoRng, Ed25519SecretKey}, - data_types::{ApplicationPermissions, BlockHeight, Timestamp}, - identifiers::{AccountOwner, ChainDescription, ChainId}, + data_types::{ + ApplicationPermissions, BlockHeight, ChainDescription, ChainOrigin, Epoch, + InitialChainConfig, Timestamp, + }, + identifiers::{AccountOwner, ChainId}, listen_for_shutdown_signals, ownership::ChainOwnership, }; @@ -100,7 +104,7 @@ impl Runnable for Job { recipient, amount, } => { - let chain_client = context.make_chain_client(sender.chain_id)?; + let chain_client = context.make_chain_client(sender.chain_id).await?; info!( "Starting transfer of {} native tokens from {} to {}", amount, sender, recipient @@ -128,7 +132,7 @@ impl Runnable for Job { balance, } => { let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; let (new_owner, key_pair) = match owner { Some(owner) => (owner, None), None => { @@ -138,7 +142,7 @@ impl Runnable for Job { }; info!("Opening a new chain from existing chain {}", chain_id); let time_start = Instant::now(); - let (message_id, certificate) = context + let (id, certificate) = context .apply_client_command(&chain_client, |chain_client| { let ownership = ChainOwnership::single(new_owner); let chain_client = chain_client.clone(); @@ -150,7 +154,6 @@ impl Runnable for Job { }) .await .context("Failed to open chain")?; - let id = ChainId::child(message_id); let timestamp = certificate.block().header.timestamp; context .update_wallet_for_new_chain(id, key_pair, timestamp) @@ -161,9 +164,8 @@ impl Runnable for Job { time_total.as_millis() ); debug!("{:?}", certificate); - // Print the new chain ID, message ID, and owner on stdout for scripting purposes. - println!("{}", message_id); - println!("{}", ChainId::child(message_id)); + // Print the new chain ID, and owner on stdout for scripting purposes. + println!("{}", id); println!("{}", new_owner); } @@ -174,7 +176,7 @@ impl Runnable for Job { application_permissions_config, } => { let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!( "Opening a new multi-owner chain from existing chain {}", chain_id @@ -183,7 +185,7 @@ impl Runnable for Job { let ownership = ChainOwnership::try_from(ownership_config)?; let application_permissions = ApplicationPermissions::from(application_permissions_config); - let (message_id, certificate) = context + let (id, certificate) = context .apply_client_command(&chain_client, |chain_client| { let ownership = ownership.clone(); let application_permissions = application_permissions.clone(); @@ -198,7 +200,6 @@ impl Runnable for Job { .context("Failed to open chain")?; // No key pair. This chain can be assigned explicitly using the assign command. let key_pair = None; - let id = ChainId::child(message_id); let timestamp = certificate.block().header.timestamp; context .update_wallet_for_new_chain(id, key_pair, timestamp) @@ -209,9 +210,8 @@ impl Runnable for Job { time_total.as_millis() ); debug!("{:?}", certificate); - // Print the new chain ID and message ID on stdout for scripting purposes. - println!("{}", message_id); - println!("{}", ChainId::child(message_id)); + // Print the new chain ID on stdout for scripting purposes. + println!("{}", id); } ChangeOwnership { @@ -224,7 +224,7 @@ impl Runnable for Job { application_permissions_config, } => { let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!("Changing application permissions for chain {}", chain_id); let time_start = Instant::now(); let application_permissions = @@ -250,7 +250,7 @@ impl Runnable for Job { } CloseChain { chain_id } => { - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!("Closing chain {}", chain_id); let time_start = Instant::now(); let result = context @@ -277,7 +277,7 @@ impl Runnable for Job { LocalBalance { account } => { let account = account.unwrap_or_else(|| context.default_account()); - let chain_client = context.make_chain_client(account.chain_id)?; + let chain_client = context.make_chain_client(account.chain_id).await?; info!("Reading the balance of {} from the local state", account); let time_start = Instant::now(); let balance = chain_client.local_owner_balance(account.owner).await?; @@ -288,7 +288,7 @@ impl Runnable for Job { QueryBalance { account } => { let account = account.unwrap_or_else(|| context.default_account()); - let chain_client = context.make_chain_client(account.chain_id)?; + let chain_client = context.make_chain_client(account.chain_id).await?; info!( "Evaluating the local balance of {account} by staging execution of known \ incoming messages" @@ -302,7 +302,7 @@ impl Runnable for Job { SyncBalance { account } => { let account = account.unwrap_or_else(|| context.default_account()); - let chain_client = context.make_chain_client(account.chain_id)?; + let chain_client = context.make_chain_client(account.chain_id).await?; info!("Synchronizing chain information and querying the local balance"); warn!("This command is deprecated. Use `linera sync && linera query-balance` instead."); let time_start = Instant::now(); @@ -320,7 +320,7 @@ impl Runnable for Job { Sync { chain_id } => { let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!("Synchronizing chain information"); let time_start = Instant::now(); chain_client.synchronize_from_validators().await?; @@ -334,7 +334,7 @@ impl Runnable for Job { ProcessInbox { chain_id } => { let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!("Processing the inbox of chain {}", chain_id); let time_start = Instant::now(); let certificates = context.process_inbox(&chain_client).await?; @@ -418,7 +418,7 @@ impl Runnable for Job { use linera_core::node::ValidatorNode as _; let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!("Querying validators about chain {}", chain_id); let result = chain_client.local_committee().await; context.update_wallet_from_client(&chain_client).await?; @@ -494,7 +494,7 @@ impl Runnable for Job { let validator = context.make_node_provider().make_node(&address)?; for chain_id in chains { - let chain = context.make_chain_client(chain_id)?; + let chain = context.make_chain_client(chain_id).await?; Box::pin(chain.sync_validator(validator.clone())).await?; } @@ -544,8 +544,9 @@ impl Runnable for Job { ), } } - let chain_client = - context.make_chain_client(context.wallet.genesis_admin_chain())?; + let chain_client = context + .make_chain_client(context.wallet.genesis_admin_chain()) + .await?; let n = context .process_inbox(&chain_client) .await @@ -713,8 +714,9 @@ impl Runnable for Job { info!("Starting operations to remove old committees"); let time_start = Instant::now(); - let chain_client = - context.make_chain_client(context.wallet.genesis_admin_chain())?; + let chain_client = context + .make_chain_client(context.wallet.genesis_admin_chain()) + .await?; // Remove the old committee. info!("Finalizing current committee"); @@ -805,7 +807,7 @@ impl Runnable for Job { Watch { chain_id, raw } => { let mut join_set = JoinSet::new(); let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; info!("Watching for notifications for chain {:?}", chain_id); let (listener, _listen_handle, mut notifications) = chain_client.listen().await?; join_set.spawn_task(listener); @@ -874,7 +876,7 @@ impl Runnable for Job { let start_time = Instant::now(); let publisher = publisher.unwrap_or_else(|| context.default_chain()); info!("Publishing module on chain {}", publisher); - let chain_client = context.make_chain_client(publisher)?; + let chain_client = context.make_chain_client(publisher).await?; let module_id = context .publish_module(&chain_client, contract, service, vm_runtime) .await?; @@ -892,7 +894,7 @@ impl Runnable for Job { let start_time = Instant::now(); let publisher = publisher.unwrap_or_else(|| context.default_chain()); info!("Publishing data blob on chain {}", publisher); - let chain_client = context.make_chain_client(publisher)?; + let chain_client = context.make_chain_client(publisher).await?; let hash = context.publish_data_blob(&chain_client, blob_path).await?; println!("{}", hash); info!( @@ -906,7 +908,7 @@ impl Runnable for Job { let start_time = Instant::now(); let reader = reader.unwrap_or_else(|| context.default_chain()); info!("Verifying data blob on chain {}", reader); - let chain_client = context.make_chain_client(reader)?; + let chain_client = context.make_chain_client(reader).await?; context.read_data_blob(&chain_client, hash).await?; info!("Data blob read in {} ms", start_time.elapsed().as_millis()); } @@ -923,7 +925,7 @@ impl Runnable for Job { let start_time = Instant::now(); let creator = creator.unwrap_or_else(|| context.default_chain()); info!("Creating application on chain {}", creator); - let chain_client = context.make_chain_client(creator)?; + let chain_client = context.make_chain_client(creator).await?; let parameters = read_json(json_parameters, json_parameters_path)?; let argument = read_json(json_argument, json_argument_path)?; @@ -972,7 +974,7 @@ impl Runnable for Job { let start_time = Instant::now(); let publisher = publisher.unwrap_or_else(|| context.default_chain()); info!("Publishing and creating application on chain {}", publisher); - let chain_client = context.make_chain_client(publisher)?; + let chain_client = context.make_chain_client(publisher).await?; let parameters = read_json(json_parameters, json_parameters_path)?; let argument = read_json(json_argument, json_argument_path)?; let module_id = context @@ -1006,17 +1008,13 @@ impl Runnable for Job { println!("{}", application_id); } - Assign { owner, message_id } => { + Assign { owner, chain_id } => { let start_time = Instant::now(); - let chain_id = ChainId::child(message_id); info!( "Linking chain {chain_id} to its corresponding key in the wallet, owned by \ {owner}", ); - context - .assign_new_chain_to_key(chain_id, message_id, owner, None) - .await?; - println!("{}", chain_id); + context.assign_new_chain_to_key(chain_id, owner).await?; context.save_wallet().await?; info!( "Chain linked to owner in {} ms", @@ -1039,7 +1037,7 @@ impl Runnable for Job { let start_time = Instant::now(); let publisher = publisher.unwrap_or_else(|| context.default_chain()); info!("Creating application on chain {}", publisher); - let chain_client = context.make_chain_client(publisher)?; + let chain_client = context.make_chain_client(publisher).await?; let parameters = read_json(json_parameters, json_parameters_path)?; let argument = read_json(json_argument, json_argument_path)?; @@ -1085,7 +1083,7 @@ impl Runnable for Job { let start_time = Instant::now(); let chain_id = chain_id.unwrap_or_else(|| context.default_chain()); info!("Committing pending block for chain {}", chain_id); - let chain_client = context.make_chain_client(chain_id)?; + let chain_client = context.make_chain_client(chain_id).await?; match chain_client.process_pending_block().await? { ClientOutcome::Committed(Some(certificate)) => { info!("Pending block committed successfully."); @@ -1122,18 +1120,11 @@ impl Runnable for Job { .await?; let faucet = cli_wrappers::Faucet::new(faucet_url); let outcome = faucet.claim(&owner).await?; - let validators = faucet.current_validators().await?; println!("{}", outcome.chain_id); - println!("{}", outcome.message_id); println!("{}", outcome.certificate_hash); println!("{}", owner); context - .assign_new_chain_to_key( - outcome.chain_id, - outcome.message_id, - owner, - Some(validators), - ) + .assign_new_chain_to_key(outcome.chain_id, owner) .await?; let admin_id = context.wallet().genesis_admin_chain(); let chains = with_other_chains @@ -1167,18 +1158,11 @@ impl Runnable for Job { .await?; let faucet = cli_wrappers::Faucet::new(faucet_url); let outcome = faucet.claim(&owner).await?; - let validators = faucet.current_validators().await?; println!("{}", outcome.chain_id); - println!("{}", outcome.message_id); println!("{}", outcome.certificate_hash); println!("{}", owner); context - .assign_new_chain_to_key( - outcome.chain_id, - outcome.message_id, - owner, - Some(validators), - ) + .assign_new_chain_to_key(outcome.chain_id, owner) .await?; if set_default { context @@ -1716,7 +1700,30 @@ async fn run(options: &ClientOptions) -> Result { Timestamp::from(micros) }) .unwrap_or_else(Timestamp::now); - let admin_id = ChainId::root(*admin_root); + + let mut rng = Box::::from(*testing_prng_seed); + let origin = ChainOrigin::Root(*admin_root); + let admin_key_pair = + AccountSecretKey::Ed25519(Ed25519SecretKey::generate_from(&mut rng)); + let committee = committee_config.clone().into_committee(policy.clone()); + let committees = [( + Epoch::ZERO, + bcs::to_bytes(&committee).expect("serializing a committee should not fail"), + )] + .into_iter() + .collect(); + let admin_config = InitialChainConfig { + admin_id: None, + balance: *initial_funding, + committees, + epoch: Epoch::ZERO, + application_permissions: Default::default(), + ownership: ChainOwnership::single(admin_key_pair.public().into()), + }; + let admin_chain_description = + ChainDescription::new(origin, admin_config.clone(), timestamp); + let admin_id = admin_chain_description.id(); + let network_name = network_name.clone().unwrap_or_else(|| { // Default: e.g. "linera-2023-11-14T23:13:20" format!("linera-{}", Utc::now().naive_utc().format("%FT%T")) @@ -1725,12 +1732,25 @@ async fn run(options: &ClientOptions) -> Result { genesis_config_path, GenesisConfig::new(committee_config, admin_id, timestamp, policy, network_name), )?; - let mut rng = Box::::from(*testing_prng_seed); let mut chains = vec![]; for i in 0..=*num_other_initial_chains { - let description = ChainDescription::Root(i); // Create keys. - let key_pair = AccountSecretKey::Ed25519(Ed25519SecretKey::generate_from(&mut rng)); + let key_pair = if i == 0 { + admin_key_pair.copy() + } else { + AccountSecretKey::Ed25519(Ed25519SecretKey::generate_from(&mut rng)) + }; + let origin = ChainOrigin::Root(i); + let config = if i == 0 { + admin_config.clone() + } else { + InitialChainConfig { + admin_id: Some(admin_id), + ownership: ChainOwnership::single(key_pair.public().into()), + ..admin_config.clone() + } + }; + let description = ChainDescription::new(origin, config, timestamp); let chain = UserChain::make_initial(key_pair, description, timestamp); // Public "genesis" state. let key = chain.key_pair.as_ref().unwrap().public(); diff --git a/linera-service/src/linera/net_up_utils.rs b/linera-service/src/linera/net_up_utils.rs index 0afe98238ee0..e9c2fc746a94 100644 --- a/linera-service/src/linera/net_up_utils.rs +++ b/linera-service/src/linera/net_up_utils.rs @@ -4,9 +4,7 @@ use std::{num::NonZeroU16, str::FromStr}; use colored::Colorize as _; -use linera_base::{ - data_types::Amount, identifiers::ChainId, listen_for_shutdown_signals, time::Duration, -}; +use linera_base::{data_types::Amount, listen_for_shutdown_signals, time::Duration}; use linera_client::client_options::ResourceControlPolicyConfig; use linera_rpc::config::CrossChainConfig; use linera_service::{ @@ -282,17 +280,23 @@ async fn print_messages_and_create_faucet( format!("export LINERA_STORAGE=\"{}\"\n", client.storage_path()).bold() ); + let wallet = client.load_wallet()?; + let chains = wallet.chain_ids(); + // Run the faucet, let faucet_service = if with_faucet { - let faucet_chain = if let Some(faucet_chain) = faucet_chain { - ChainId::root(faucet_chain) - } else { - assert!( - num_other_initial_chains > 1, - "num_other_initial_chains must be greater than 1 if with_faucet is true" - ); - ChainId::root(1) - }; + let faucet_chain_idx = faucet_chain.unwrap_or(0); + assert!( + num_other_initial_chains > faucet_chain_idx, + "num_other_initial_chains must be strictly greater than the faucet chain index if \ + with_faucet is true" + ); + // This picks a lexicographically faucet_chain_idx-th non-admin chain. + let faucet_chain = chains + .into_iter() + .filter(|chain_id| *chain_id != wallet.genesis_admin_chain()) + .nth(faucet_chain_idx as usize) + .unwrap(); // we checked that there are enough chains above, so this should be safe let service = client .run_faucet(Some(faucet_port.into()), faucet_chain, faucet_amount, None) .await?; diff --git a/linera-service/src/node_service.rs b/linera-service/src/node_service.rs index 424d8e210421..ab1c78783199 100644 --- a/linera-service/src/node_service.rs +++ b/linera-service/src/node_service.rs @@ -117,7 +117,12 @@ where &self, chain_id: ChainId, ) -> Result, Error> { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; Ok(client.subscribe().await?) } } @@ -159,7 +164,12 @@ where Fut: Future, Error>, ChainClient)>, { loop { - let client = self.context.lock().await.make_chain_client(*chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(*chain_id) + .await?; let mut stream = client.subscribe().await?; let (result, client) = f(client).await; self.context.lock().await.update_wallet(&client).await?; @@ -182,7 +192,12 @@ where async fn process_inbox(&self, chain_id: ChainId) -> Result, Error> { let mut hashes = Vec::new(); loop { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; client.synchronize_from_validators().await?; let result = client.process_inbox_without_prepare().await; self.context.lock().await.update_wallet(&client).await?; @@ -201,7 +216,12 @@ where /// Retries the pending block that was unsuccessfully proposed earlier. async fn retry_pending_block(&self, chain_id: ChainId) -> Result, Error> { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let outcome = client.process_pending_block().await?; self.context.lock().await.update_wallet(&client).await?; match outcome { @@ -284,7 +304,7 @@ where ) -> Result { let ownership = ChainOwnership::single(owner); let balance = balance.unwrap_or(Amount::ZERO); - let message_id = self + let opened_chain_id = self .apply_client_command(&chain_id, move |client| { let ownership = ownership.clone(); async move { @@ -292,12 +312,12 @@ where .open_chain(ownership, ApplicationPermissions::default(), balance) .await .map_err(Error::from) - .map(|outcome| outcome.map(|(message_id, _)| message_id)); + .map(|outcome| outcome.map(|(chain_id, _)| chain_id)); (result, client) } }) .await?; - Ok(ChainId::child(message_id)) + Ok(opened_chain_id) } /// Creates (or activates) a new chain by installing the given authentication keys. @@ -355,7 +375,7 @@ where }; let ownership = ChainOwnership::multiple(owners, multi_leader_rounds, timeout_config); let balance = balance.unwrap_or(Amount::ZERO); - let message_id = self + let opened_chain_id = self .apply_client_command(&chain_id, move |client| { let ownership = ownership.clone(); let application_permissions = application_permissions.clone().unwrap_or_default(); @@ -364,12 +384,12 @@ where .open_chain(ownership, application_permissions, balance) .await .map_err(Error::from) - .map(|outcome| outcome.map(|(message_id, _)| message_id)); + .map(|outcome| outcome.map(|(chain_id, _)| chain_id)); (result, client) } }) .await?; - Ok(ChainId::child(message_id)) + Ok(opened_chain_id) } /// Closes the chain. Returns `None` if it was already closed. @@ -580,13 +600,23 @@ where ChainStateExtendedView<::StorageContext>, Error, > { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let view = client.chain_state_view().await?; Ok(ChainStateExtendedView::new(view)) } async fn applications(&self, chain_id: ChainId) -> Result, Error> { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let applications = client .chain_state_view() .await? @@ -614,7 +644,12 @@ where hash: Option, chain_id: ChainId, ) -> Result, Error> { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let hash = match hash { Some(hash) => Some(hash), None => { @@ -636,7 +671,12 @@ where chain_id: ChainId, limit: Option, ) -> Result, Error> { - let client = self.context.lock().await.make_chain_client(chain_id)?; + let client = self + .context + .lock() + .await + .make_chain_client(chain_id) + .await?; let limit = limit.unwrap_or(10); let from = match from { Some(from) => Some(from), @@ -879,6 +919,7 @@ where .lock() .await .make_chain_client(chain_id) + .await .map_err(|_| NodeServiceError::UnknownChainId { chain_id: chain_id.to_string(), })?; @@ -915,6 +956,7 @@ where .lock() .await .make_chain_client(chain_id) + .await .map_err(|_| NodeServiceError::UnknownChainId { chain_id: chain_id.to_string(), })?; diff --git a/linera-service/src/proxy/grpc.rs b/linera-service/src/proxy/grpc.rs index fe96be95052f..39ee5f537cfe 100644 --- a/linera-service/src/proxy/grpc.rs +++ b/linera-service/src/proxy/grpc.rs @@ -695,6 +695,7 @@ impl GrpcMessageLimiter { #[cfg(test)] mod proto_message_cap { + use linera_base::crypto::CryptoHash; use linera_chain::{ data_types::BlockExecutionOutcome, types::{Block, Certificate, ConfirmedBlock, ConfirmedBlockCertificate}, @@ -710,7 +711,7 @@ mod proto_message_cap { let validator = keypair.public_key; let signature = ValidatorSignature::new(&TestString::new("Test"), &keypair.secret_key); let block = Block::new( - linera_chain::test::make_first_block(ChainId::root(0)), + linera_chain::test::make_first_block(ChainId(CryptoHash::test_hash("root_chain"))), BlockExecutionOutcome::default(), ); let signatures = vec![(validator, signature)]; diff --git a/linera-service/src/schema_export.rs b/linera-service/src/schema_export.rs index 4c00b8bb6373..6efdb910751d 100644 --- a/linera-service/src/schema_export.rs +++ b/linera-service/src/schema_export.rs @@ -182,7 +182,7 @@ impl Result, Error> { + async fn make_chain_client(&self, _: ChainId) -> Result, Error> { unimplemented!() } @@ -199,7 +199,7 @@ impl Result>, Error> { + async fn clients(&self) -> Result>, Error> { Ok(vec![]) } } diff --git a/linera-service/tests/linera_net_tests.rs b/linera-service/tests/linera_net_tests.rs index 1c8845afeaf9..4beead11a8f0 100644 --- a/linera-service/tests/linera_net_tests.rs +++ b/linera-service/tests/linera_net_tests.rs @@ -2933,12 +2933,12 @@ async fn test_end_to_end_multiple_wallets(config: impl LineraNetConfig) -> Resul let owner2 = client2.keygen().await?; // Open chain on behalf of Client 2. - let (message_id, chain2, _) = client1 + let (chain2, _) = client1 .open_chain(chain1, Some(owner2), Amount::ZERO) .await?; // Assign chain2 to client2_key. - assert_eq!(chain2, client2.assign(owner2, message_id).await?); + client2.assign(owner2, chain2).await?; // Transfer a token to chain 2. Check that this increases the local balance, proving // that client 2 can create blocks on that chain. @@ -2979,7 +2979,7 @@ async fn test_end_to_end_open_multi_owner_chain(config: impl LineraNetConfig) -> let owner2 = client2.keygen().await?; // Open a chain owned by both clients. - let (message_id, chain2) = client1 + let chain2 = client1 .open_multi_owner_chain( chain1, vec![owner1, owner2], @@ -2991,10 +2991,10 @@ async fn test_end_to_end_open_multi_owner_chain(config: impl LineraNetConfig) -> .await?; // Assign chain2 to client1_key. - assert_eq!(chain2, client1.assign(owner1, message_id).await?); + client1.assign(owner1, chain2).await?; // Assign chain2 to client2_key. - assert_eq!(chain2, client2.assign(owner2, message_id).await?); + client2.assign(owner2, chain2).await?; client2.sync(chain2).await?; @@ -3087,14 +3087,14 @@ async fn test_end_to_end_assign_greatgrandchild_chain(config: impl LineraNetConf let owner2 = client2.keygen().await?; // Open a great-grandchild chain on behalf of client 2. - let (_, grandparent, _) = client1 + let (grandparent, _) = client1 .open_chain(chain1, None, Amount::from_tokens(2)) .await?; - let (_, parent, _) = client1.open_chain(grandparent, None, Amount::ONE).await?; - let (message_id, chain2, _) = client1 + let (parent, _) = client1.open_chain(grandparent, None, Amount::ONE).await?; + let (chain2, _) = client1 .open_chain(parent, Some(owner2), Amount::ZERO) .await?; - client2.assign(owner2, message_id).await?; + client2.assign(owner2, chain2).await?; // Transfer a token to chain 2. Check that this increases the local balance, proving // that client 2 can create blocks on that chain. @@ -3171,7 +3171,6 @@ async fn test_end_to_end_faucet(config: impl LineraNetConfig) -> Result<()> { let faucet = faucet_service.instance(); let outcome = faucet.claim(&owner2).await?; let chain2 = outcome.chain_id; - let message_id = outcome.message_id; // Test version info. let info = faucet.version_info().await?; @@ -3208,7 +3207,7 @@ async fn test_end_to_end_faucet(config: impl LineraNetConfig) -> Result<()> { assert!(faucet_balance > balance1 - Amount::from_tokens(9)); // Assign chain2 to client2_key. - assert_eq!(chain2, client2.assign(owner2, message_id).await?); + client2.assign(owner2, chain2).await?; // Clients 2 and 3 should have the tokens, and own the chain. client2.sync(chain2).await?; @@ -3252,7 +3251,7 @@ async fn test_end_to_end_faucet_with_long_chains(config: impl LineraNetConfig) - // Use the faucet directly to initialize many chains for _ in 0..chain_count { - let (_, new_chain_id, _) = faucet_client + let (new_chain_id, _) = faucet_client .open_chain(faucet_chain, None, Amount::ONE) .await?; faucet_client.forget_chain(new_chain_id).await?; @@ -3280,7 +3279,7 @@ async fn test_end_to_end_faucet_with_long_chains(config: impl LineraNetConfig) - // Since the faucet chain exceeds the configured maximum length, the faucet should have // switched after the first new chain. - assert!(other_outcome.message_id.chain_id != outcome.message_id.chain_id); + assert!(other_outcome.chain_id != outcome.chain_id); let chain = outcome.chain_id; assert_eq!(chain, client.load_wallet()?.default_chain().unwrap()); @@ -3378,7 +3377,7 @@ async fn test_end_to_end_listen_for_new_rounds(config: impl LineraNetConfig) -> // Open a chain owned by both clients, with only single-leader rounds. let owner1 = client1.keygen().await?; let owner2 = client2.keygen().await?; - let (message_id, chain2) = client1 + let chain2 = client1 .open_multi_owner_chain( chain1, vec![owner1, owner2], @@ -3388,8 +3387,8 @@ async fn test_end_to_end_listen_for_new_rounds(config: impl LineraNetConfig) -> u64::MAX, ) .await?; - client1.assign(owner1, message_id).await?; - client2.assign(owner2, message_id).await?; + client1.assign(owner1, chain2).await?; + client2.assign(owner2, chain2).await?; client2.sync(chain2).await?; let (tx, mut rx) = mpsc::channel(8); diff --git a/linera-service/tests/local_net_tests.rs b/linera-service/tests/local_net_tests.rs index 8a399a1f8b02..3288e8df292b 100644 --- a/linera-service/tests/local_net_tests.rs +++ b/linera-service/tests/local_net_tests.rs @@ -20,7 +20,7 @@ use linera_base::vm::VmRuntime; use linera_base::{ crypto::Secp256k1SecretKey, data_types::{Amount, BlockHeight, Epoch}, - identifiers::{Account, AccountOwner, ChainId}, + identifiers::{Account, AccountOwner}, }; use linera_core::{data_types::ChainInfoQuery, node::ValidatorNode}; use linera_faucet::ClaimOutcome; @@ -80,7 +80,10 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> { let client_2 = net.make_client().await; client_2.wallet_init(&[], FaucetOption::None).await?; - let chain_1 = ChainId::root(0); + let chain_1 = client + .load_wallet()? + .default_chain() + .expect("should have a default chain"); let chain_2 = client .open_and_assign(&client_2, Amount::from_tokens(3)) @@ -293,12 +296,8 @@ async fn test_end_to_end_receipt_of_old_create_committee_messages( // Create a new chain starting on the new epoch let new_owner = client.keygen().await?; - let ClaimOutcome { - chain_id, - message_id, - .. - } = faucet.claim(&new_owner).await?; - client.assign(new_owner, message_id).await?; + let ClaimOutcome { chain_id, .. } = faucet.claim(&new_owner).await?; + client.assign(new_owner, chain_id).await?; // Attempt to receive the existing epoch change message client.process_inbox(chain_id).await?; @@ -423,12 +422,8 @@ async fn test_end_to_end_receipt_of_old_remove_committee_messages( // Create a new chain starting on the new epoch let new_owner = client.keygen().await?; - let ClaimOutcome { - chain_id, - message_id, - .. - } = faucet.claim(&new_owner).await?; - client.assign(new_owner, message_id).await?; + let ClaimOutcome { chain_id, .. } = faucet.claim(&new_owner).await?; + client.assign(new_owner, chain_id).await?; // Attempt to receive the existing epoch change messages client.process_inbox(chain_id).await?; @@ -449,8 +444,13 @@ async fn test_end_to_end_retry_notification_stream(config: LocalNetConfig) -> Re let (mut net, client1) = config.instantiate().await?; + let (chain, chain1) = { + let wallet = client1.load_wallet()?; + let chains = wallet.chain_ids(); + (chains[0], chains[1]) + }; + let client2 = net.make_client().await; - let chain = ChainId::root(0); let mut height = 0; client2.wallet_init(&[chain], FaucetOption::None).await?; @@ -476,7 +476,7 @@ async fn test_end_to_end_retry_notification_stream(config: LocalNetConfig) -> Re for i in 0..10 { // Add a new block on the chain, triggering a notification. client1 - .transfer(Amount::from_tokens(1), chain, ChainId::root(1)) + .transfer(Amount::from_tokens(1), chain, chain1) .await?; linera_base::time::timer::sleep(Duration::from_secs(i)).await; height += 1; @@ -510,7 +510,11 @@ async fn test_end_to_end_retry_pending_block(config: LocalNetConfig) -> Result<( // Create runner and client. let (mut net, client) = config.instantiate().await?; - let chain_id = client.load_wallet()?.default_chain().unwrap(); + let (chain_id, chain1) = { + let wallet = client.load_wallet()?; + let chains = wallet.chain_ids(); + (chains[0], chains[1]) + }; let account = Account::chain(chain_id); let balance = client.local_balance(account).await?; // Stop validators. @@ -518,7 +522,7 @@ async fn test_end_to_end_retry_pending_block(config: LocalNetConfig) -> Result<( net.remove_validator(i)?; } let result = client - .transfer_with_silent_logs(Amount::from_tokens(2), chain_id, ChainId::root(1)) + .transfer_with_silent_logs(Amount::from_tokens(2), chain_id, chain1) .await; assert!(result.is_err()); // The transfer didn't get confirmed. @@ -789,7 +793,7 @@ async fn test_sync_validator(config: LocalNetConfig) -> Result<()> { // Create some blocks let sender_chain = client.default_chain().expect("Client has no default chain"); - let (_, receiver_chain, _) = client + let (receiver_chain, _) = client .open_chain(sender_chain, None, Amount::from_tokens(1_000)) .await?; diff --git a/linera-storage/src/lib.rs b/linera-storage/src/lib.rs index c611358a5e13..9c8a05c190d5 100644 --- a/linera-storage/src/lib.rs +++ b/linera-storage/src/lib.rs @@ -14,31 +14,30 @@ use dashmap::{mapref::entry::Entry, DashMap}; use linera_base::{ crypto::CryptoHash, data_types::{ - Amount, ApplicationDescription, Blob, BlockHeight, CompressedBytecode, Epoch, TimeDelta, + ApplicationDescription, Blob, ChainDescription, CompressedBytecode, Epoch, TimeDelta, Timestamp, }, - identifiers::{AccountOwner, ApplicationId, BlobId, ChainDescription, ChainId, EventId}, - ownership::ChainOwnership, + identifiers::{ApplicationId, BlobId, ChainId, EventId}, vm::VmRuntime, }; use linera_chain::{ types::{ConfirmedBlock, ConfirmedBlockCertificate}, ChainError, ChainStateView, }; -use linera_execution::{ - committee::Committee, BlobState, ExecutionError, ExecutionRuntimeConfig, - ExecutionRuntimeContext, UserContractCode, UserServiceCode, WasmRuntime, -}; #[cfg(with_revm)] use linera_execution::{ evm::revm::{EvmContractModule, EvmServiceModule}, EvmRuntime, }; +use linera_execution::{ + BlobState, ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, UserContractCode, + UserServiceCode, WasmRuntime, +}; #[cfg(with_wasm_runtime)] use linera_execution::{WasmContractModule, WasmServiceModule}; use linera_views::{ context::Context, - views::{CryptoHashView, RootView, ViewError}, + views::{RootView, ViewError}, }; #[cfg(with_testing)] @@ -181,41 +180,18 @@ pub trait Storage: Sized { /// This method creates a new [`ChainStateView`] instance. If there are multiple instances of /// the same chain active at any given moment, they will race to access persistent storage. /// This can lead to invalid states and data corruption. - async fn create_chain( - &self, - committee: Committee, - admin_id: ChainId, - description: ChainDescription, - owner: AccountOwner, - balance: Amount, - timestamp: Timestamp, - ) -> Result<(), ChainError> + async fn create_chain(&self, description: ChainDescription) -> Result<(), ChainError> where ChainRuntimeContext: ExecutionRuntimeContext, { - let id = description.into(); + let id = description.id(); + // Store the description blob. + self.write_blob(&Blob::new_chain_description(&description)) + .await?; let mut chain = self.load_chain(id).await?; assert!(!chain.is_active(), "Attempting to create a chain twice"); - chain.manager.reset( - ChainOwnership::single(owner), - BlockHeight(0), - self.clock().current_time(), - committee.account_keys_and_weights(), - )?; - let system_state = &mut chain.execution_state.system; - system_state.description.set(Some(description)); - system_state.epoch.set(Some(Epoch::ZERO)); - system_state.admin_id.set(Some(admin_id)); - system_state - .committees - .get_mut() - .insert(Epoch::ZERO, committee); - system_state.ownership.set(ChainOwnership::single(owner)); - system_state.balance.set(balance); - system_state.timestamp.set(timestamp); - - let state_hash = chain.execution_state.crypto_hash().await?; - chain.execution_state_hash.set(Some(state_hash)); + let current_time = self.clock().current_time(); + chain.ensure_is_active(current_time).await?; chain.save().await?; Ok(()) }