Skip to content

Commit 29c7584

Browse files
authored
Allow running a faucet from linera net up (#3115)
## Motivation First step in addressing #2890 ## Proposal * Make a function of `find_owned_chains` out of the code in `ClientWrapper::drop()` * Add an option `--with-faucet-chain N` to `linera net up` as well as `--faucet-port` and `--faucet-amount` * Add a simple test The only caveat is that `N` cannot be a chain-id (since we wouldn't know what to write) so we use the index amongst root chains (typically 1 for the first non-admin initial chain). ## Test Plan CI
1 parent 3a61111 commit 29c7584

File tree

6 files changed

+153
-45
lines changed

6 files changed

+153
-45
lines changed

CLI.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,13 @@ Start a Local Linera Network
903903
* `--external-protocol <EXTERNAL_PROTOCOL>` — External protocol used, either grpc or grpcs
904904

905905
Default value: `grpc`
906+
* `--with-faucet-chain <WITH_FAUCET_CHAIN>` — If present, a faucet is started using the given chain root number (0 for the admin chain, 1 for the first non-admin initial chain, etc)
907+
* `--faucet-port <FAUCET_PORT>` — The port on which to run the faucet server
908+
909+
Default value: `8080`
910+
* `--faucet-amount <FAUCET_AMOUNT>` — The number of tokens to send to each new chain created by the faucet
911+
912+
Default value: `1000`
906913

907914

908915

linera-client/src/client_options.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ pub enum ClientCommand {
712712
config: ChainListenerConfig,
713713

714714
/// The port on which to run the server
715-
#[arg(long = "port", default_value = "8080")]
715+
#[arg(long, default_value = "8080")]
716716
port: NonZeroU16,
717717
},
718718

@@ -723,11 +723,11 @@ pub enum ClientCommand {
723723
chain_id: Option<ChainId>,
724724

725725
/// The port on which to run the server
726-
#[arg(long = "port", default_value = "8080")]
726+
#[arg(long, default_value = "8080")]
727727
port: NonZeroU16,
728728

729729
/// The number of tokens to send to each new chain.
730-
#[arg(long = "amount")]
730+
#[arg(long)]
731731
amount: Amount,
732732

733733
/// The end timestamp: The faucet will rate-limit the token supply so it runs out of money
@@ -980,6 +980,7 @@ impl DatabaseToolCommand {
980980
}
981981
}
982982

983+
#[allow(clippy::large_enum_variant)]
983984
#[derive(Clone, clap::Parser)]
984985
pub enum NetCommand {
985986
/// Start a Local Linera Network
@@ -1052,6 +1053,19 @@ pub enum NetCommand {
10521053
/// External protocol used, either grpc or grpcs.
10531054
#[arg(long, default_value = "grpc")]
10541055
external_protocol: String,
1056+
1057+
/// If present, a faucet is started using the given chain root number (0 for the
1058+
/// admin chain, 1 for the first non-admin initial chain, etc).
1059+
#[arg(long)]
1060+
with_faucet_chain: Option<u32>,
1061+
1062+
/// The port on which to run the faucet server
1063+
#[arg(long, default_value = "8080")]
1064+
faucet_port: NonZeroU16,
1065+
1066+
/// The number of tokens to send to each new chain created by the faucet.
1067+
#[arg(long, default_value = "1000")]
1068+
faucet_amount: Amount,
10551069
},
10561070

10571071
/// Print a bash helper script to make `linera net up` easier to use. The script is

linera-service/src/cli_wrappers/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ use anyhow::Result;
3535
use async_trait::async_trait;
3636
use linera_execution::ResourceControlPolicy;
3737
pub use wallet::{
38-
ApplicationWrapper, ClientWrapper, Faucet, FaucetOption, NodeService, OnClientDrop,
38+
ApplicationWrapper, ClientWrapper, Faucet, FaucetOption, FaucetService, NodeService,
39+
OnClientDrop,
3940
};
4041

4142
/// The information needed to start a Linera net of a particular kind.

linera-service/src/linera/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,9 @@ async fn run(options: &ClientOptions) -> Result<i32, anyhow::Error> {
16381638
path: _,
16391639
storage: _,
16401640
external_protocol: _,
1641+
with_faucet_chain,
1642+
faucet_port,
1643+
faucet_amount,
16411644
} => {
16421645
net_up_utils::handle_net_up_kubernetes(
16431646
*extra_wallets,
@@ -1650,6 +1653,9 @@ async fn run(options: &ClientOptions) -> Result<i32, anyhow::Error> {
16501653
*no_build,
16511654
docker_image_name.clone(),
16521655
policy_config.into_policy(),
1656+
*with_faucet_chain,
1657+
*faucet_port,
1658+
*faucet_amount,
16531659
)
16541660
.boxed()
16551661
.await?;
@@ -1667,6 +1673,9 @@ async fn run(options: &ClientOptions) -> Result<i32, anyhow::Error> {
16671673
path,
16681674
storage,
16691675
external_protocol,
1676+
with_faucet_chain,
1677+
faucet_port,
1678+
faucet_amount,
16701679
..
16711680
} => {
16721681
net_up_utils::handle_net_up_service(
@@ -1680,6 +1689,9 @@ async fn run(options: &ClientOptions) -> Result<i32, anyhow::Error> {
16801689
path,
16811690
storage,
16821691
external_protocol.clone(),
1692+
*with_faucet_chain,
1693+
*faucet_port,
1694+
*faucet_amount,
16831695
)
16841696
.boxed()
16851697
.await?;

linera-service/src/linera/net_up_utils.rs

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
// Copyright (c) Zefchain Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::str::FromStr;
4+
use std::{num::NonZeroU16, str::FromStr};
55

66
use colored::Colorize as _;
7-
use linera_base::{data_types::Amount, time::Duration};
7+
use linera_base::{data_types::Amount, identifiers::ChainId, time::Duration};
88
use linera_client::storage::{StorageConfig, StorageConfigNamespace};
99
use linera_execution::ResourceControlPolicy;
1010
use linera_service::{
1111
cli_wrappers::{
1212
local_net::{Database, LocalNetConfig, PathProvider, StorageConfigBuilder},
13-
ClientWrapper, FaucetOption, LineraNet, LineraNetConfig, Network, NetworkConfig,
13+
ClientWrapper, FaucetOption, FaucetService, LineraNet, LineraNetConfig, Network,
14+
NetworkConfig,
1415
},
1516
util::listen_for_shutdown_signals,
1617
};
@@ -113,6 +114,9 @@ pub async fn handle_net_up_kubernetes(
113114
no_build: bool,
114115
docker_image_name: String,
115116
policy: ResourceControlPolicy,
117+
with_faucet_chain: Option<u32>,
118+
faucet_port: NonZeroU16,
119+
faucet_amount: Amount,
116120
) -> anyhow::Result<()> {
117121
if num_initial_validators < 1 {
118122
panic!("The local test network must have at least one validator.");
@@ -136,9 +140,17 @@ pub async fn handle_net_up_kubernetes(
136140
docker_image_name,
137141
policy,
138142
};
139-
let (mut net, client1) = config.instantiate().await?;
140-
net_up(extra_wallets, &mut net, client1).await?;
141-
wait_for_shutdown(shutdown_notifier, &mut net).await
143+
let (mut net, client) = config.instantiate().await?;
144+
let faucet_service = create_wallets_and_faucets(
145+
extra_wallets,
146+
&mut net,
147+
client,
148+
with_faucet_chain,
149+
faucet_port,
150+
faucet_amount,
151+
)
152+
.await?;
153+
wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
142154
}
143155

144156
#[expect(clippy::too_many_arguments)]
@@ -153,6 +165,9 @@ pub async fn handle_net_up_service(
153165
path: &Option<String>,
154166
storage: &Option<String>,
155167
external_protocol: String,
168+
with_faucet_chain: Option<u32>,
169+
faucet_port: NonZeroU16,
170+
faucet_amount: Amount,
156171
) -> anyhow::Result<()> {
157172
if num_initial_validators < 1 {
158173
panic!("The local test network must have at least one validator.");
@@ -190,29 +205,46 @@ pub async fn handle_net_up_service(
190205
storage_config_builder,
191206
path_provider,
192207
};
193-
let (mut net, client1) = config.instantiate().await?;
194-
net_up(extra_wallets, &mut net, client1).await?;
195-
wait_for_shutdown(shutdown_notifier, &mut net).await
208+
let (mut net, client) = config.instantiate().await?;
209+
let faucet_service = create_wallets_and_faucets(
210+
extra_wallets,
211+
&mut net,
212+
client,
213+
with_faucet_chain,
214+
faucet_port,
215+
faucet_amount,
216+
)
217+
.await?;
218+
wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
196219
}
197220

198221
async fn wait_for_shutdown(
199222
shutdown_notifier: CancellationToken,
200223
net: &mut impl LineraNet,
224+
faucet_service: Option<FaucetService>,
201225
) -> anyhow::Result<()> {
202226
shutdown_notifier.cancelled().await;
203-
eprintln!("\nTerminating the local test network");
227+
eprintln!();
228+
if let Some(service) = faucet_service {
229+
eprintln!("Terminating the faucet service");
230+
service.terminate().await?;
231+
}
232+
eprintln!("Terminating the local test network");
204233
net.terminate().await?;
205-
eprintln!("\nDone.");
234+
eprintln!("Done.");
206235

207236
Ok(())
208237
}
209238

210-
async fn net_up(
239+
async fn create_wallets_and_faucets(
211240
extra_wallets: Option<usize>,
212241
net: &mut impl LineraNet,
213-
client1: ClientWrapper,
214-
) -> Result<(), anyhow::Error> {
215-
let default_chain = client1
242+
client: ClientWrapper,
243+
with_faucet_chain: Option<u32>,
244+
faucet_port: NonZeroU16,
245+
faucet_amount: Amount,
246+
) -> Result<Option<FaucetService>, anyhow::Error> {
247+
let default_chain = client
216248
.default_chain()
217249
.expect("Initialized clients should always have a default chain");
218250

@@ -241,15 +273,15 @@ async fn net_up(
241273
"{}",
242274
format!(
243275
"export LINERA_WALLET{suffix}=\"{}\"",
244-
client1.wallet_path().display()
276+
client.wallet_path().display()
245277
)
246278
.bold()
247279
);
248280
println!(
249281
"{}",
250282
format!(
251283
"export LINERA_STORAGE{suffix}=\"{}\"\n",
252-
client1.storage_path()
284+
client.storage_path()
253285
)
254286
.bold()
255287
);
@@ -260,7 +292,7 @@ async fn net_up(
260292
let extra_wallet = net.make_client().await;
261293
extra_wallet.wallet_init(&[], FaucetOption::None).await?;
262294
let unassigned_key = extra_wallet.keygen().await?;
263-
let new_chain_msg_id = client1
295+
let new_chain_msg_id = client
264296
.open_chain(default_chain, Some(unassigned_key), Amount::ZERO)
265297
.await?
266298
.0;
@@ -286,9 +318,23 @@ async fn net_up(
286318
}
287319
}
288320

321+
// Run the faucet,
322+
let faucet_service = if let Some(faucet_chain) = with_faucet_chain {
323+
let service = client
324+
.run_faucet(
325+
Some(faucet_port.into()),
326+
ChainId::root(faucet_chain),
327+
faucet_amount,
328+
)
329+
.await?;
330+
Some(service)
331+
} else {
332+
None
333+
};
334+
289335
eprintln!(
290336
"\nREADY!\nPress ^C to terminate the local test network and clean the temporary directory."
291337
);
292338

293-
Ok(())
339+
Ok(faucet_service)
294340
}

linera-service/tests/local_net_tests.rs

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use linera_service::{
2929
test_name,
3030
};
3131
use test_case::test_case;
32+
#[cfg(feature = "storage-service")]
33+
use {linera_base::port::get_free_port, linera_service::cli_wrappers::Faucet};
3234

3335
#[cfg(feature = "benchmark")]
3436
fn get_fungible_account_owner(client: &ClientWrapper) -> AccountOwner {
@@ -732,41 +734,67 @@ async fn test_storage_service_linera_net_up_simple() -> Result<()> {
732734
let _guard = INTEGRATION_TEST_GUARD.lock().await;
733735
tracing::info!("Starting test {}", test_name!());
734736

737+
let port = get_free_port().await?;
738+
735739
let mut command = Command::new(env!("CARGO_BIN_EXE_linera"));
736-
command.args(["net", "up"]);
740+
command.args([
741+
"net",
742+
"up",
743+
"--with-faucet-chain",
744+
"1",
745+
"--faucet-port",
746+
&port.to_string(),
747+
]);
737748
let mut child = command
738749
.stdout(Stdio::piped())
739750
.stderr(Stdio::piped())
740751
.spawn()?;
741752

742753
let stdout = BufReader::new(child.stdout.take().unwrap());
743754
let stderr = BufReader::new(child.stderr.take().unwrap());
755+
let mut lines = stderr.lines();
744756

745-
for line in stderr.lines() {
757+
let mut is_ready = false;
758+
for line in &mut lines {
746759
let line = line?;
747760
if line.starts_with("READY!") {
748-
let mut exports = stdout.lines();
749-
assert!(exports
750-
.next()
751-
.unwrap()?
752-
.starts_with("export LINERA_WALLET="));
753-
assert!(exports
754-
.next()
755-
.unwrap()?
756-
.starts_with("export LINERA_STORAGE="));
757-
assert_eq!(exports.next().unwrap()?, "");
758-
759-
// Send SIGINT to the child process.
760-
Command::new("kill")
761-
.args(["-s", "INT", &child.id().to_string()])
762-
.output()?;
763-
764-
assert!(exports.next().is_none());
765-
assert!(child.wait()?.success());
766-
return Ok(());
761+
is_ready = true;
762+
break;
767763
}
768764
}
769-
panic!("Unexpected EOF for stderr");
765+
assert!(is_ready, "Unexpected EOF for stderr");
766+
767+
// Echo faucet stderr for debugging and to empty the buffer.
768+
std::thread::spawn(move || {
769+
for line in lines {
770+
let line = line.unwrap();
771+
eprintln!("{}", line);
772+
}
773+
});
774+
775+
let mut exports = stdout.lines();
776+
assert!(exports
777+
.next()
778+
.unwrap()?
779+
.starts_with("export LINERA_WALLET="));
780+
assert!(exports
781+
.next()
782+
.unwrap()?
783+
.starts_with("export LINERA_STORAGE="));
784+
assert_eq!(exports.next().unwrap()?, "");
785+
786+
// Test faucet.
787+
let faucet = Faucet::new(format!("http://localhost:{}/", port));
788+
faucet.version_info().await.unwrap();
789+
790+
// Send SIGINT to the child process.
791+
Command::new("kill")
792+
.args(["-s", "INT", &child.id().to_string()])
793+
.output()?;
794+
795+
assert!(exports.next().is_none());
796+
assert!(child.wait()?.success());
797+
return Ok(());
770798
}
771799

772800
#[cfg(feature = "benchmark")]

0 commit comments

Comments
 (0)