diff --git a/Cargo.lock b/Cargo.lock index 1e4fcbff9db..b085c93c6c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4625,6 +4625,52 @@ dependencies = [ "ws_stream_wasm", ] +[[package]] +name = "linera-faucet" +version = "0.14.0" +dependencies = [ + "async-graphql", + "linera-base", +] + +[[package]] +name = "linera-faucet-client" +version = "0.14.0" +dependencies = [ + "anyhow", + "linera-base", + "linera-client", + "linera-execution", + "linera-faucet", + "linera-version", + "reqwest 0.11.27", + "serde_json", +] + +[[package]] +name = "linera-faucet-server" +version = "0.14.0" +dependencies = [ + "anyhow", + "async-graphql", + "async-graphql-axum", + "async-trait", + "axum", + "futures", + "linera-base", + "linera-client", + "linera-core", + "linera-execution", + "linera-storage", + "linera-version", + "linera-views", + "serde", + "serde_json", + "tokio", + "tower-http 0.5.2", + "tracing", +] + [[package]] name = "linera-indexer" version = "0.14.0" @@ -4839,6 +4885,9 @@ dependencies = [ "linera-core", "linera-ethereum", "linera-execution", + "linera-faucet", + "linera-faucet-client", + "linera-faucet-server", "linera-rpc", "linera-sdk", "linera-service", diff --git a/Cargo.toml b/Cargo.toml index e7f208917f6..ca1bb0056e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,9 @@ members = [ "linera-ethereum", "linera-execution", "linera-explorer", + "linera-faucet", + "linera-faucet/client", + "linera-faucet/server", "linera-indexer/example", "linera-indexer/graphql-client", "linera-indexer/lib", @@ -204,6 +207,9 @@ linera-client = { version = "0.14.0", path = "./linera-client" } linera-core = { version = "0.14.0", path = "./linera-core", default-features = false } linera-ethereum = { version = "0.14.0", path = "./linera-ethereum", default-features = false } linera-execution = { version = "0.14.0", path = "./linera-execution", default-features = false } +linera-faucet = { version = "0.14.0", path = "linera-faucet" } +linera-faucet-client = { version = "0.14.0", path = "linera-faucet/client" } +linera-faucet-server = { version = "0.14.0", path = "linera-faucet/server" } linera-indexer = { version = "0.14.0", path = "./linera-indexer/lib" } linera-indexer-graphql-client = { version = "0.14.0", path = "./linera-indexer/graphql-client" } linera-indexer-plugins = { version = "0.14.0", path = "./linera-indexer/plugins" } diff --git a/docker/Dockerfile b/docker/Dockerfile index c46ca94e85e..c50b91a7c87 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -38,6 +38,7 @@ COPY linera-core linera-core COPY linera-ethereum linera-ethereum COPY linera-execution linera-execution COPY linera-explorer linera-explorer +COPY linera-faucet linera-faucet COPY linera-indexer linera-indexer COPY linera-rpc linera-rpc COPY linera-sdk linera-sdk diff --git a/linera-client/Cargo.toml b/linera-client/Cargo.toml index 5f620f850cc..d08c08753de 100644 --- a/linera-client/Cargo.toml +++ b/linera-client/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true version.workspace = true [features] -default = ["fs"] test = ["linera-views/test", "linera-execution/test"] benchmark = ["linera-base/test", "dep:linera-sdk"] wasmer = [ diff --git a/linera-faucet/Cargo.toml b/linera-faucet/Cargo.toml new file mode 100644 index 00000000000..049190f5b81 --- /dev/null +++ b/linera-faucet/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "linera-faucet" +version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +linera-base.workspace = true + +[dependencies.async-graphql] +workspace = true +optional = true diff --git a/linera-faucet/README.md b/linera-faucet/README.md new file mode 100644 index 00000000000..f75b97237cf --- /dev/null +++ b/linera-faucet/README.md @@ -0,0 +1,13 @@ + + +Common definitions for the Linera faucet. + + + +## Contributing + +See the [CONTRIBUTING](../CONTRIBUTING.md) file for how to help out. + +## License + +This project is available under the terms of the [Apache 2.0 license](../LICENSE). diff --git a/linera-faucet/client/Cargo.toml b/linera-faucet/client/Cargo.toml new file mode 100644 index 00000000000..d10ec694888 --- /dev/null +++ b/linera-faucet/client/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "linera-faucet-client" +version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +linera-base.workspace = true +linera-client.workspace = true +linera-execution.workspace = true +linera-faucet = { version = "0.14.0", path = ".." } +linera-version.workspace = true +reqwest.workspace = true +serde_json.workspace = true diff --git a/linera-faucet/client/README.md b/linera-faucet/client/README.md new file mode 100644 index 00000000000..25fb71e7c8d --- /dev/null +++ b/linera-faucet/client/README.md @@ -0,0 +1,13 @@ + + +This module provides the executables needed to operate a Linera service, including a placeholder wallet acting as a GraphQL service for user interfaces. + + + +## Contributing + +See the [CONTRIBUTING](../CONTRIBUTING.md) file for how to help out. + +## License + +This project is available under the terms of the [Apache 2.0 license](../LICENSE). diff --git a/linera-faucet/client/src/lib.rs b/linera-faucet/client/src/lib.rs new file mode 100644 index 00000000000..3f455de15f6 --- /dev/null +++ b/linera-faucet/client/src/lib.rs @@ -0,0 +1,212 @@ +// Copyright (c) Zefchain Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! The client component of the Linera faucet. + +use anyhow::{bail, Context, Result}; +use linera_base::identifiers::Owner; +use linera_client::config::GenesisConfig; +use linera_execution::committee::ValidatorName; +use linera_faucet::ClaimOutcome; +use linera_version::VersionInfo; +use serde_json::{json, Value}; + +fn truncate_query_output(input: &str) -> String { + let max_len = 200; + if input.len() < max_len { + input.to_string() + } else { + format!("{} ...", input.get(..max_len).unwrap()) + } +} + +fn reqwest_client() -> reqwest::Client { + let builder = reqwest::ClientBuilder::new(); + + #[cfg(not(target_arch = "wasm32"))] + let builder = builder.timeout(std::time::Duration::from_secs(30)); + + builder.build().unwrap() +} + +/// A faucet instance that can be queried. +#[derive(Debug, Clone)] +pub struct Faucet { + url: String, +} + +impl Faucet { + pub fn new(url: String) -> Self { + Self { url } + } + + pub fn url(&self) -> &str { + &self.url + } + + pub async fn genesis_config(&self) -> Result { + let query = "query { genesisConfig }"; + let client = reqwest_client(); + let response = client + .post(&self.url) + .json(&json!({ "query": query })) + .send() + .await + .context("genesis_config: failed to post query")?; + anyhow::ensure!( + response.status().is_success(), + "Query \"{}\" failed: {}", + query, + response + .text() + .await + .unwrap_or_else(|error| format!("Could not get response text: {error}")) + ); + let mut value: Value = response.json().await.context("invalid JSON")?; + if let Some(errors) = value.get("errors") { + bail!("Query \"{}\" failed: {}", query, errors); + } + serde_json::from_value(value["data"]["genesisConfig"].take()) + .context("could not parse genesis config") + } + + pub async fn version_info(&self) -> Result { + let query = + "query { version { crateVersion gitCommit gitDirty rpcHash graphqlHash witHash } }"; + let client = reqwest_client(); + let response = client + .post(&self.url) + .json(&json!({ "query": query })) + .send() + .await + .context("version_info: failed to post query")?; + anyhow::ensure!( + response.status().is_success(), + "Query \"{}\" failed: {}", + query, + response + .text() + .await + .unwrap_or_else(|error| format!("Could not get response text: {error}")) + ); + let mut value: Value = response.json().await.context("invalid JSON")?; + if let Some(errors) = value.get("errors") { + bail!("Query \"{}\" failed: {}", query, errors); + } + let crate_version = serde_json::from_value(value["data"]["version"]["crateVersion"].take()) + .context("could not parse crate version")?; + let git_commit = serde_json::from_value(value["data"]["version"]["gitCommit"].take()) + .context("could not parse git commit")?; + let git_dirty = serde_json::from_value(value["data"]["version"]["gitDirty"].take()) + .context("could not parse git dirty")?; + let rpc_hash = serde_json::from_value(value["data"]["version"]["rpcHash"].take()) + .context("could not parse rpc hash")?; + let graphql_hash = serde_json::from_value(value["data"]["version"]["graphqlHash"].take()) + .context("could not parse graphql hash")?; + let wit_hash = serde_json::from_value(value["data"]["version"]["witHash"].take()) + .context("could not parse wit hash")?; + Ok(VersionInfo { + crate_version, + git_commit, + git_dirty, + rpc_hash, + graphql_hash, + wit_hash, + }) + } + + pub async fn claim(&self, owner: &Owner) -> Result { + let query = format!( + "mutation {{ claim(owner: \"{owner}\") {{ \ + messageId chainId certificateHash \ + }} }}" + ); + let client = reqwest_client(); + let response = client + .post(&self.url) + .json(&json!({ "query": &query })) + .send() + .await + .with_context(|| { + format!( + "claim: failed to post query={}", + truncate_query_output(&query) + ) + })?; + anyhow::ensure!( + response.status().is_success(), + "Query \"{}\" failed: {}", + query, + response + .text() + .await + .unwrap_or_else(|error| format!("Could not get response text: {error}")) + ); + let value: Value = response.json().await.context("invalid JSON")?; + if let Some(errors) = value.get("errors") { + bail!("Query \"{}\" failed: {}", query, errors); + } + let data = &value["data"]["claim"]; + let message_id = data["messageId"] + .as_str() + .context("message ID not found")? + .parse() + .context("could not parse message ID")?; + let chain_id = data["chainId"] + .as_str() + .context("chain ID not found")? + .parse() + .context("could not parse chain ID")?; + let certificate_hash = data["certificateHash"] + .as_str() + .context("Certificate hash not found")? + .parse() + .context("could not parse certificate hash")?; + let outcome = ClaimOutcome { + message_id, + chain_id, + certificate_hash, + }; + Ok(outcome) + } + + pub async fn current_validators(&self) -> Result> { + let query = "query { currentValidators { name networkAddress } }"; + let client = reqwest_client(); + let response = client + .post(&self.url) + .json(&json!({ "query": query })) + .send() + .await + .context("current_validators: failed to post query")?; + anyhow::ensure!( + response.status().is_success(), + "Query \"{}\" failed: {}", + query, + response + .text() + .await + .unwrap_or_else(|error| format!("Could not get response text: {error}")) + ); + let mut value: Value = response.json().await.context("invalid JSON")?; + if let Some(errors) = value.get("errors") { + bail!("Query \"{}\" failed: {}", query, errors); + } + let validators = match value["data"]["currentValidators"].take() { + serde_json::Value::Array(validators) => validators, + validators => bail!("{validators} is not an array"), + }; + validators + .into_iter() + .map(|mut validator| { + let name = serde_json::from_value::(validator["name"].take()) + .context("could not parse current validators: invalid name")?; + let addr = validator["networkAddress"] + .as_str() + .context("could not parse current validators: invalid address")? + .to_string(); + Ok((name, addr)) + }) + .collect() + } +} diff --git a/linera-faucet/server/Cargo.toml b/linera-faucet/server/Cargo.toml new file mode 100644 index 00000000000..411ad3eb99b --- /dev/null +++ b/linera-faucet/server/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "linera-faucet-server" +version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +async-graphql.workspace = true +async-graphql-axum.workspace = true +axum.workspace = true +futures.workspace = true +linera-base.workspace = true +linera-client.workspace = true +linera-core.workspace = true +linera-execution.workspace = true +linera-storage.workspace = true +linera-version.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +tower-http = { workspace = true, features = ["cors"] } +tracing.workspace = true + +[dev-dependencies] +async-trait.workspace = true +linera-views.workspace = true + [dev-dependencies.linera-core] + workspace = true + features = ["test"] diff --git a/linera-faucet/server/README.md b/linera-faucet/server/README.md new file mode 100644 index 00000000000..25fb71e7c8d --- /dev/null +++ b/linera-faucet/server/README.md @@ -0,0 +1,13 @@ + + +This module provides the executables needed to operate a Linera service, including a placeholder wallet acting as a GraphQL service for user interfaces. + + + +## Contributing + +See the [CONTRIBUTING](../CONTRIBUTING.md) file for how to help out. + +## License + +This project is available under the terms of the [Apache 2.0 license](../LICENSE). diff --git a/linera-service/src/faucet.rs b/linera-faucet/server/src/lib.rs similarity index 95% rename from linera-service/src/faucet.rs rename to linera-faucet/server/src/lib.rs index b7c9217f80d..cc3eca32351 100644 --- a/linera-service/src/faucet.rs +++ b/linera-faucet/server/src/lib.rs @@ -1,6 +1,8 @@ // Copyright (c) Zefchain Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +//! The server component of the Linera faucet. + use std::{net::SocketAddr, num::NonZeroU16, sync::Arc}; use async_graphql::{EmptySubscription, Error, Schema, SimpleObject}; @@ -24,10 +26,17 @@ use serde::Deserialize; use tower_http::cors::CorsLayer; use tracing::info; -use crate::util; +/// Returns an HTML response constructing the GraphiQL web page for the given URI. +pub(crate) async fn graphiql(uri: axum::http::Uri) -> impl axum::response::IntoResponse { + axum::response::Html( + async_graphql::http::GraphiQLSource::build() + .endpoint(uri.path()) + .subscription_endpoint("/ws") + .finish(), + ) +} #[cfg(test)] -#[path = "unit_tests/faucet.rs"] mod tests; /// The root GraphQL query type. @@ -264,7 +273,7 @@ where /// Runs the faucet. pub async fn run(self) -> anyhow::Result<()> { let port = self.port.get(); - let index_handler = axum::routing::get(util::graphiql).post(Self::index_handler); + let index_handler = axum::routing::get(graphiql).post(Self::index_handler); let app = Router::new() .route("/", index_handler) diff --git a/linera-service/src/unit_tests/faucet.rs b/linera-faucet/server/src/tests.rs similarity index 100% rename from linera-service/src/unit_tests/faucet.rs rename to linera-faucet/server/src/tests.rs diff --git a/linera-faucet/src/lib.rs b/linera-faucet/src/lib.rs new file mode 100644 index 00000000000..7020af1fb91 --- /dev/null +++ b/linera-faucet/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright (c) Zefchain Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/*! +Common definitions for the Linera faucet. +*/ + +use linera_base::{ + crypto::CryptoHash, + identifiers::{ChainId, MessageId}, +}; + +/// The result of a successful `claim` mutation. +#[cfg_attr(feature = "async-graphql", derive(async_graphql::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. + pub certificate_hash: CryptoHash, +} diff --git a/linera-service/Cargo.toml b/linera-service/Cargo.toml index 98f99119f0d..1c706b2a755 100644 --- a/linera-service/Cargo.toml +++ b/linera-service/Cargo.toml @@ -89,6 +89,9 @@ linera-chain.workspace = true linera-client = { workspace = true, features = ["fs"] } linera-core.workspace = true linera-execution = { workspace = true, features = ["fs"] } +linera-faucet = { workspace = true, features = ["async-graphql"] } +linera-faucet-client.workspace = true +linera-faucet-server.workspace = true linera-rpc = { workspace = true, features = ["server", "simple-network"] } linera-sdk = { workspace = true } linera-storage.workspace = true diff --git a/linera-service/src/cli_wrappers/mod.rs b/linera-service/src/cli_wrappers/mod.rs index c6ba950ed4e..916481207a4 100644 --- a/linera-service/src/cli_wrappers/mod.rs +++ b/linera-service/src/cli_wrappers/mod.rs @@ -33,9 +33,9 @@ mod wallet; use anyhow::Result; use async_trait::async_trait; +pub use linera_faucet_client::Faucet; pub use wallet::{ - ApplicationWrapper, ClientWrapper, Faucet, FaucetOption, FaucetService, NodeService, - OnClientDrop, + ApplicationWrapper, ClientWrapper, FaucetOption, FaucetService, NodeService, OnClientDrop, }; /// The information needed to start a Linera net of a particular kind. diff --git a/linera-service/src/cli_wrappers/wallet.rs b/linera-service/src/cli_wrappers/wallet.rs index a6c7023ba89..c4324c61e59 100644 --- a/linera-service/src/cli_wrappers/wallet.rs +++ b/linera-service/src/cli_wrappers/wallet.rs @@ -24,10 +24,11 @@ use linera_base::{ data_types::{Amount, Bytecode}, identifiers::{Account, ApplicationId, BytecodeId, ChainId, MessageId, Owner}, }; -use linera_client::{config::GenesisConfig, wallet::Wallet}; +use linera_client::wallet::Wallet; use linera_core::worker::Notification; -use linera_execution::{committee::ValidatorName, system::SystemChannel, ResourceControlPolicy}; -use linera_version::VersionInfo; +use linera_execution::{system::SystemChannel, ResourceControlPolicy}; +use linera_faucet::ClaimOutcome; +use linera_faucet_client::Faucet; use serde::{de::DeserializeOwned, ser::Serialize}; use serde_json::{json, Value}; use tempfile::TempDir; @@ -39,7 +40,6 @@ use crate::{ local_net::{PathProvider, ProcessInbox}, Network, }, - faucet::ClaimOutcome, util::{self, ChildExt}, }; @@ -1407,188 +1407,6 @@ impl FaucetService { } } -/// A faucet instance that can be queried. -#[derive(Debug, Clone)] -pub struct Faucet { - url: String, -} - -impl Faucet { - pub fn new(url: String) -> Self { - Self { url } - } - - pub fn url(&self) -> &str { - &self.url - } - - pub async fn genesis_config(&self) -> Result { - let query = "query { genesisConfig }"; - let client = reqwest_client(); - let response = client - .post(&self.url) - .json(&json!({ "query": query })) - .send() - .await - .context("genesis_config: failed to post query")?; - anyhow::ensure!( - response.status().is_success(), - "Query \"{}\" failed: {}", - query, - response - .text() - .await - .unwrap_or_else(|error| format!("Could not get response text: {error}")) - ); - let mut value: Value = response.json().await.context("invalid JSON")?; - if let Some(errors) = value.get("errors") { - bail!("Query \"{}\" failed: {}", query, errors); - } - serde_json::from_value(value["data"]["genesisConfig"].take()) - .context("could not parse genesis config") - } - - pub async fn version_info(&self) -> Result { - let query = - "query { version { crateVersion gitCommit gitDirty rpcHash graphqlHash witHash } }"; - let client = reqwest_client(); - let response = client - .post(&self.url) - .json(&json!({ "query": query })) - .send() - .await - .context("version_info: failed to post query")?; - anyhow::ensure!( - response.status().is_success(), - "Query \"{}\" failed: {}", - query, - response - .text() - .await - .unwrap_or_else(|error| format!("Could not get response text: {error}")) - ); - let mut value: Value = response.json().await.context("invalid JSON")?; - if let Some(errors) = value.get("errors") { - bail!("Query \"{}\" failed: {}", query, errors); - } - let crate_version = serde_json::from_value(value["data"]["version"]["crateVersion"].take()) - .context("could not parse crate version")?; - let git_commit = serde_json::from_value(value["data"]["version"]["gitCommit"].take()) - .context("could not parse git commit")?; - let git_dirty = serde_json::from_value(value["data"]["version"]["gitDirty"].take()) - .context("could not parse git dirty")?; - let rpc_hash = serde_json::from_value(value["data"]["version"]["rpcHash"].take()) - .context("could not parse rpc hash")?; - let graphql_hash = serde_json::from_value(value["data"]["version"]["graphqlHash"].take()) - .context("could not parse graphql hash")?; - let wit_hash = serde_json::from_value(value["data"]["version"]["witHash"].take()) - .context("could not parse wit hash")?; - Ok(VersionInfo { - crate_version, - git_commit, - git_dirty, - rpc_hash, - graphql_hash, - wit_hash, - }) - } - - pub async fn claim(&self, owner: &Owner) -> Result { - let query = format!( - "mutation {{ claim(owner: \"{owner}\") {{ \ - messageId chainId certificateHash \ - }} }}" - ); - let client = reqwest_client(); - let response = client - .post(&self.url) - .json(&json!({ "query": &query })) - .send() - .await - .with_context(|| { - format!( - "claim: failed to post query={}", - truncate_query_output(&query) - ) - })?; - anyhow::ensure!( - response.status().is_success(), - "Query \"{}\" failed: {}", - query, - response - .text() - .await - .unwrap_or_else(|error| format!("Could not get response text: {error}")) - ); - let value: Value = response.json().await.context("invalid JSON")?; - if let Some(errors) = value.get("errors") { - bail!("Query \"{}\" failed: {}", query, errors); - } - let data = &value["data"]["claim"]; - let message_id = data["messageId"] - .as_str() - .context("message ID not found")? - .parse() - .context("could not parse message ID")?; - let chain_id = data["chainId"] - .as_str() - .context("chain ID not found")? - .parse() - .context("could not parse chain ID")?; - let certificate_hash = data["certificateHash"] - .as_str() - .context("Certificate hash not found")? - .parse() - .context("could not parse certificate hash")?; - let outcome = ClaimOutcome { - message_id, - chain_id, - certificate_hash, - }; - Ok(outcome) - } - - pub async fn current_validators(&self) -> Result> { - let query = "query { currentValidators { name networkAddress } }"; - let client = reqwest_client(); - let response = client - .post(&self.url) - .json(&json!({ "query": query })) - .send() - .await - .context("current_validators: failed to post query")?; - anyhow::ensure!( - response.status().is_success(), - "Query \"{}\" failed: {}", - query, - response - .text() - .await - .unwrap_or_else(|error| format!("Could not get response text: {error}")) - ); - let mut value: Value = response.json().await.context("invalid JSON")?; - if let Some(errors) = value.get("errors") { - bail!("Query \"{}\" failed: {}", query, errors); - } - let validators = match value["data"]["currentValidators"].take() { - serde_json::Value::Array(validators) => validators, - validators => bail!("{validators} is not an array"), - }; - validators - .into_iter() - .map(|mut validator| { - let name = serde_json::from_value::(validator["name"].take()) - .context("could not parse current validators: invalid name")?; - let addr = validator["networkAddress"] - .as_str() - .context("could not parse current validators: invalid address")? - .to_string(); - Ok((name, addr)) - }) - .collect() - } -} - /// A running `Application` to be queried in GraphQL. pub struct ApplicationWrapper { uri: String, diff --git a/linera-service/src/lib.rs b/linera-service/src/lib.rs index 99adfcf9792..d80d43d5c27 100644 --- a/linera-service/src/lib.rs +++ b/linera-service/src/lib.rs @@ -7,7 +7,6 @@ #![deny(clippy::large_futures)] pub mod cli_wrappers; -pub mod faucet; pub mod node_service; pub mod project; #[cfg(with_metrics)] diff --git a/linera-service/src/linera/main.rs b/linera-service/src/linera/main.rs index a202e83e284..f9c84aa8b54 100644 --- a/linera-service/src/linera/main.rs +++ b/linera-service/src/linera/main.rs @@ -43,9 +43,9 @@ use linera_execution::{ committee::{Committee, ValidatorName, ValidatorState}, Message, ResourceControlPolicy, SystemMessage, }; +use linera_faucet_server::FaucetService; use linera_service::{ cli_wrappers, - faucet::FaucetService, node_service::NodeService, project::{self, Project}, util, wallet, diff --git a/linera-service/tests/local_net_tests.rs b/linera-service/tests/local_net_tests.rs index ff680f4367d..c2bc0a64f10 100644 --- a/linera-service/tests/local_net_tests.rs +++ b/linera-service/tests/local_net_tests.rs @@ -19,6 +19,7 @@ use linera_base::{ identifiers::{Account, AccountOwner, ChainId}, }; use linera_core::{data_types::ChainInfoQuery, node::ValidatorNode}; +use linera_faucet::ClaimOutcome; use linera_service::{ cli_wrappers::{ local_net::{ @@ -26,7 +27,6 @@ use linera_service::{ }, ClientWrapper, FaucetOption, LineraNet, LineraNetConfig, Network, OnClientDrop, }, - faucet::ClaimOutcome, test_name, }; use test_case::test_case; diff --git a/packages.txt b/packages.txt index 3b834f4a46e..15cc2f627e1 100644 --- a/packages.txt +++ b/packages.txt @@ -17,5 +17,6 @@ linera-sdk-derive linera-sdk linera-rpc linera-client +linera-faucet linera-service linera-service-graphql-client