From e301a4eb51e837d05518e6ebba61a2659baafc17 Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Thu, 20 Feb 2025 21:11:15 -0500 Subject: [PATCH 1/8] Create commands request-chain and follow-chain --- CLI.md | 33 ++++++++++++++- linera-client/src/client_options.rs | 20 +++++++++- linera-service/src/linera/main.rs | 62 +++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 3 deletions(-) diff --git a/CLI.md b/CLI.md index 6925e5e77179..d6e6f14272df 100644 --- a/CLI.md +++ b/CLI.md @@ -40,6 +40,8 @@ This document contains the help content for the `linera` command-line program. * [`linera wallet show`↴](#linera-wallet-show) * [`linera wallet set-default`↴](#linera-wallet-set-default) * [`linera wallet init`↴](#linera-wallet-init) +* [`linera wallet request-chain`↴](#linera-wallet-request-chain) +* [`linera wallet follow-chain`↴](#linera-wallet-follow-chain) * [`linera wallet forget-keys`↴](#linera-wallet-forget-keys) * [`linera wallet forget-chain`↴](#linera-wallet-forget-chain) * [`linera project`↴](#linera-project) @@ -744,7 +746,9 @@ Show the contents of the wallet * `show` — Show the contents of the wallet * `set-default` — Change the wallet default chain * `init` — Initialize a wallet from the genesis configuration -* `forget-keys` — Forgets the specified chain's keys +* `request-chain` — Request a chain from a faucet and add it to the wallet +* `follow-chain` — Add a new followed chain (a chain without keypair) to the follow +* `forget-keys` — Forgets the specified chain's keys. The chain will still be followed by the wallet * `forget-chain` — Forgets the specified chain, including the associated key pair @@ -794,9 +798,34 @@ Initialize a wallet from the genesis configuration +## `linera wallet request-chain` + +Request a chain from a faucet and add it to the wallet + +**Usage:** `linera wallet request-chain [OPTIONS] --faucet ` + +###### **Options:** + +* `--faucet ` — The address of a faucet +* `--set-default` — Whether this chain should become the default chain + + + +## `linera wallet follow-chain` + +Add a new followed chain (a chain without keypair) to the follow + +**Usage:** `linera wallet follow-chain ` + +###### **Arguments:** + +* `` — The chain ID + + + ## `linera wallet forget-keys` -Forgets the specified chain's keys +Forgets the specified chain's keys. The chain will still be followed by the wallet **Usage:** `linera wallet forget-keys ` diff --git a/linera-client/src/client_options.rs b/linera-client/src/client_options.rs index 7bf5f65c8c7b..5fc89dd9d8a5 100644 --- a/linera-client/src/client_options.rs +++ b/linera-client/src/client_options.rs @@ -1199,7 +1199,25 @@ pub enum WalletCommand { testing_prng_seed: Option, }, - /// Forgets the specified chain's keys. + /// Request a chain from a faucet and add it to the wallet. + RequestChain { + /// The address of a faucet. + #[arg(long)] + faucet: String, + + /// Whether this chain should become the default chain. + #[arg(long)] + set_default: bool, + }, + + /// Add a new followed chain (a chain without keypair) to the follow. + FollowChain { + /// The chain ID. + chain_id: ChainId, + }, + + /// Forgets the specified chain's keys. The chain will still be followed by the + /// wallet. ForgetKeys { chain_id: ChainId }, /// Forgets the specified chain, including the associated key pair. diff --git a/linera-service/src/linera/main.rs b/linera-service/src/linera/main.rs index 1184d04f6500..baf6f96b2e6d 100644 --- a/linera-service/src/linera/main.rs +++ b/linera-service/src/linera/main.rs @@ -1196,6 +1196,47 @@ impl Runnable for Job { ); } + Wallet(WalletCommand::RequestChain { + faucet: faucet_url, + set_default, + }) => { + let start_time = Instant::now(); + let key_pair = context.wallet.generate_key_pair(); + let owner = key_pair.public().into(); + info!( + "Requesting a new chain for owner {owner} using the faucet at address \ + {faucet_url}", + ); + context + .wallet_mut() + .mutate(|w| w.add_unassigned_key_pair(key_pair)) + .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); + Self::assign_new_chain_to_key( + outcome.chain_id, + outcome.message_id, + owner, + Some(validators), + &mut context, + ) + .await?; + if set_default { + context + .wallet_mut() + .mutate(|w| w.set_default_chain(outcome.chain_id)) + .await??; + } + info!( + "New chain requested and added in {} ms", + start_time.elapsed().as_millis() + ); + } + CreateGenesisConfig { .. } | Keygen | Net(_) @@ -1829,6 +1870,22 @@ async fn run(options: &ClientOptions) -> Result { Ok(0) } + WalletCommand::FollowChain { chain_id } => { + let start_time = Instant::now(); + options + .wallet() + .await? + .mutate(|wallet| { + wallet.extend([UserChain::make_other(*chain_id, Timestamp::now())]) + }) + .await?; + info!( + "Chain followed and added in {} ms", + start_time.elapsed().as_millis() + ); + Ok(0) + } + WalletCommand::ForgetChain { chain_id } => { let start_time = Instant::now(); options @@ -1840,6 +1897,11 @@ async fn run(options: &ClientOptions) -> Result { Ok(0) } + WalletCommand::RequestChain { .. } => { + options.run_with_storage(Job(options.clone())).await??; + Ok(0) + } + WalletCommand::Init { genesis_config_path, faucet, From e9fbdd672f3b7c8774b2ae9e319179e8aa8ad231 Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 00:55:02 -0500 Subject: [PATCH 2/8] add cli wrappers --- linera-service/src/cli_wrappers/wallet.rs | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/linera-service/src/cli_wrappers/wallet.rs b/linera-service/src/cli_wrappers/wallet.rs index d309661c4a39..ccafe2761189 100644 --- a/linera-service/src/cli_wrappers/wallet.rs +++ b/linera-service/src/cli_wrappers/wallet.rs @@ -356,6 +356,28 @@ impl ClientWrapper { } } + /// Runs `linera wallet request-chain`. + pub async fn request_chain(&self, faucet: &Faucet, set_default: bool) -> Result { + let mut command = self.command().await?; + command.args(["wallet", "request_chain", "--faucet", faucet.url()]); + if set_default { + command.arg("--set-default"); + } + 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")?, + }; + Ok(outcome) + } + /// Runs `linera wallet publish-and-create`. pub async fn publish_and_create< A: ContractAbi, @@ -801,6 +823,16 @@ impl ClientWrapper { Ok(()) } + /// Runs `linera wallet follow-chain CHAIN_ID`. + pub async fn follow_chain(&self, chain_id: ChainId) -> Result<()> { + let mut command = self.command().await?; + command + .args(["wallet", "follow-chain"]) + .arg(chain_id.to_string()); + command.spawn_and_wait_for_stdout().await?; + Ok(()) + } + /// Runs `linera wallet forget-chain CHAIN_ID`. pub async fn forget_chain(&self, chain_id: ChainId) -> Result<()> { let mut command = self.command().await?; From 80314a2e3cb21624b07239949bbe53cc5134b390 Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 00:36:40 -0500 Subject: [PATCH 3/8] Make the main README compatible with a testnet --- README.md | 36 ++++++++++++++++++++++++------------ scripts/linera_net_helper.sh | 25 ++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b261818d0d82..ebf74288fdf2 100644 --- a/README.md +++ b/README.md @@ -67,32 +67,44 @@ microchains owned by a single wallet. ```bash # Make sure to compile the Linera binaries and add them in the $PATH. -# cargo build -p linera-storage-service -p linera-service --bins --features storage-service +# cargo build -p linera-storage-service -p linera-service --bins export PATH="$PWD/target/debug:$PATH" -# Import the optional helper function `linera_spawn_and_read_wallet_variables`. +# Import the optional helper function `linera_spawn`. source /dev/stdin <<<"$(linera net helper 2>/dev/null)" # Run a local test network with the default parameters and a number of microchains -# owned by the default wallet. (The helper function `linera_spawn_and_read_wallet_variables` -# is used to set the two environment variables LINERA_{WALLET,STORAGE}.) -linera_spawn_and_read_wallet_variables \ -linera net up +# owned by the default wallet. This also defines `LINERA_TMP_DIR`. +linera_spawn \ +linera net up --with-faucet --faucet-port 8080 -# Print the set of validators. -linera query-validators +# Remember the URL of the faucet. +FAUCET_URL=http://localhost:8080 + +# If you're using a testnet, start here and run this instead: +# LINERA_TMP_DIR=$(mktemp -d) +# FAUCET_URL=https://faucet.testnet-XXX.linera.net # for some value XXX + +# Set the path of the future wallet. +export LINERA_WALLET="$LINERA_TMP_DIR/wallet.json" +export LINERA_STORAGE="rocksdb:$LINERA_TMP_DIR/client.db" + +# Initialize a new user wallet. +linera wallet init --faucet $FAUCET_URL + +# Request chains. +CHAIN1=$(linera wallet request-chain --faucet $FAUCET_URL | head -n 1) +CHAIN2=$(linera wallet request-chain --faucet $FAUCET_URL | head -n 1) # Query the chain balance of some of the chains. -CHAIN1="aee928d4bf3880353b4a3cd9b6f88e6cc6e5ed050860abae439e7782e9b2dfe8" -CHAIN2="a3edc33d8e951a1139333be8a4b56646b5598a8f51216e86592d881808972b07" linera query-balance "$CHAIN1" linera query-balance "$CHAIN2" -# Transfer 10 units then 5 back +# Transfer 10 units then 5 back. linera transfer 10 --from "$CHAIN1" --to "$CHAIN2" linera transfer 5 --from "$CHAIN2" --to "$CHAIN1" -# Query balances again +# Query balances again. linera query-balance "$CHAIN1" linera query-balance "$CHAIN2" ``` diff --git a/scripts/linera_net_helper.sh b/scripts/linera_net_helper.sh index f8408c9bcec1..c9b47f4f40d3 100644 --- a/scripts/linera_net_helper.sh +++ b/scripts/linera_net_helper.sh @@ -1,10 +1,11 @@ # Runs a command such as `linera net up` in the background. +# - Export a temporary directory `$LINERA_TMP_DIR`. # - Records stdout # - Waits for the background process to print READY! on stderr # - Then executes the bash command recorded from stdout # - Returns without killing the process function linera_spawn_and_read_wallet_variables() { - LINERA_TMP_DIR=$(mktemp -d) || exit 1 + export LINERA_TMP_DIR=$(mktemp -d) || exit 1 # When the shell exits, we will clean up the top-level jobs (if any), the temporary # directory, and the main process. Handling future top-level jobs here is useful @@ -29,3 +30,25 @@ function linera_spawn_and_read_wallet_variables() { # Continue to output the command's stderr onto stdout. cat "$LINERA_TMP_ERR" & } + +# Runs a command such as `linera net up` in the background. +# - Export a temporary directory `$LINERA_TMP_DIR`. +# - Waits for the background process to print READY! on stderr +# - Returns without killing the process +function linera_spawn() { + export LINERA_TMP_DIR=$(mktemp -d) || exit 1 + + trap 'jobs -p | xargs -r kill; rm -rf "$LINERA_TMP_DIR"' EXIT + + LINERA_TMP_OUT="$LINERA_TMP_DIR/out" + LINERA_TMP_ERR="$LINERA_TMP_DIR/err" + mkfifo "$LINERA_TMP_ERR" || exit 1 + + "$@" 2>"$LINERA_TMP_ERR" & + + # Read from LINERA_TMP_ERR until the string "READY!" is found. + sed '/^READY!/q' <"$LINERA_TMP_ERR" || exit 1 + + # Continue to output the command's stderr onto stdout. + cat "$LINERA_TMP_ERR" & +} From 1dbed485d07fd720ec1985295b44ffc7e45093f1 Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 01:00:51 -0500 Subject: [PATCH 4/8] nit --- scripts/linera_net_helper.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/linera_net_helper.sh b/scripts/linera_net_helper.sh index c9b47f4f40d3..b55a50a7ca28 100644 --- a/scripts/linera_net_helper.sh +++ b/scripts/linera_net_helper.sh @@ -5,7 +5,7 @@ # - Then executes the bash command recorded from stdout # - Returns without killing the process function linera_spawn_and_read_wallet_variables() { - export LINERA_TMP_DIR=$(mktemp -d) || exit 1 + LINERA_TMP_DIR=$(mktemp -d) || exit 1 # When the shell exits, we will clean up the top-level jobs (if any), the temporary # directory, and the main process. Handling future top-level jobs here is useful @@ -36,7 +36,7 @@ function linera_spawn_and_read_wallet_variables() { # - Waits for the background process to print READY! on stderr # - Returns without killing the process function linera_spawn() { - export LINERA_TMP_DIR=$(mktemp -d) || exit 1 + LINERA_TMP_DIR=$(mktemp -d) || exit 1 trap 'jobs -p | xargs -r kill; rm -rf "$LINERA_TMP_DIR"' EXIT From 886376da088b914e2ed62684758efb14bf3fa58a Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:10:40 -0500 Subject: [PATCH 5/8] address reviewers' comments --- CLI.md | 8 ++++---- linera-client/src/client_options.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CLI.md b/CLI.md index d6e6f14272df..387aaeec54fa 100644 --- a/CLI.md +++ b/CLI.md @@ -746,8 +746,8 @@ Show the contents of the wallet * `show` — Show the contents of the wallet * `set-default` — Change the wallet default chain * `init` — Initialize a wallet from the genesis configuration -* `request-chain` — Request a chain from a faucet and add it to the wallet -* `follow-chain` — Add a new followed chain (a chain without keypair) to the follow +* `request-chain` — Request a new chain from a faucet and add it to the wallet +* `follow-chain` — Add a new followed chain (i.e. a chain without keypair) to the wallet * `forget-keys` — Forgets the specified chain's keys. The chain will still be followed by the wallet * `forget-chain` — Forgets the specified chain, including the associated key pair @@ -800,7 +800,7 @@ Initialize a wallet from the genesis configuration ## `linera wallet request-chain` -Request a chain from a faucet and add it to the wallet +Request a new chain from a faucet and add it to the wallet **Usage:** `linera wallet request-chain [OPTIONS] --faucet ` @@ -813,7 +813,7 @@ Request a chain from a faucet and add it to the wallet ## `linera wallet follow-chain` -Add a new followed chain (a chain without keypair) to the follow +Add a new followed chain (i.e. a chain without keypair) to the wallet **Usage:** `linera wallet follow-chain ` diff --git a/linera-client/src/client_options.rs b/linera-client/src/client_options.rs index 5fc89dd9d8a5..1e6a8987572d 100644 --- a/linera-client/src/client_options.rs +++ b/linera-client/src/client_options.rs @@ -1199,7 +1199,7 @@ pub enum WalletCommand { testing_prng_seed: Option, }, - /// Request a chain from a faucet and add it to the wallet. + /// Request a new chain from a faucet and add it to the wallet. RequestChain { /// The address of a faucet. #[arg(long)] @@ -1210,7 +1210,7 @@ pub enum WalletCommand { set_default: bool, }, - /// Add a new followed chain (a chain without keypair) to the follow. + /// Add a new followed chain (i.e. a chain without keypair) to the wallet. FollowChain { /// The chain ID. chain_id: ChainId, From b043cd9d0d3f864337cf97d6b3e39e5e89ccaf2d Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:19:17 -0500 Subject: [PATCH 6/8] add basic test coverage for new cli wrappers --- linera-service/tests/linera_net_tests.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/linera-service/tests/linera_net_tests.rs b/linera-service/tests/linera_net_tests.rs index d7288e6beb7d..904ff900534e 100644 --- a/linera-service/tests/linera_net_tests.rs +++ b/linera-service/tests/linera_net_tests.rs @@ -2814,11 +2814,24 @@ async fn test_end_to_end_faucet(config: impl LineraNetConfig) -> Result<()> { // Use the faucet directly to initialize client 3. let client3 = net.make_client().await; - let outcome = client3 - .wallet_init(&[], FaucetOption::NewChain(&faucet)) - .await?; - let chain3 = outcome.unwrap().chain_id; - assert_eq!(chain3, client3.load_wallet()?.default_chain().unwrap()); + client3.wallet_init(&[], FaucetOption::None).await?; + let outcome = client3.request_chain(&faucet, false).await?; + assert_eq!( + outcome.chain_id, + client3.load_wallet()?.default_chain().unwrap() + ); + + let outcome = client3.request_chain(&faucet, false).await?; + assert!(outcome.chain_id != client3.load_wallet()?.default_chain().unwrap()); + client3.forget_chain(outcome.chain_id).await?; + client3.follow_chain(outcome.chain_id).await?; + + let outcome = client3.request_chain(&faucet, true).await?; + assert_eq!( + outcome.chain_id, + client3.load_wallet()?.default_chain().unwrap() + ); + let chain3 = outcome.chain_id; faucet_service.ensure_is_running()?; faucet_service.terminate().await?; From dcb4482e4c1913837ba4d1673a6339ad8445360e Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 17:35:04 -0500 Subject: [PATCH 7/8] fix typo --- linera-service/src/cli_wrappers/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linera-service/src/cli_wrappers/wallet.rs b/linera-service/src/cli_wrappers/wallet.rs index ccafe2761189..dff4462653e3 100644 --- a/linera-service/src/cli_wrappers/wallet.rs +++ b/linera-service/src/cli_wrappers/wallet.rs @@ -359,7 +359,7 @@ impl ClientWrapper { /// Runs `linera wallet request-chain`. pub async fn request_chain(&self, faucet: &Faucet, set_default: bool) -> Result { let mut command = self.command().await?; - command.args(["wallet", "request_chain", "--faucet", faucet.url()]); + command.args(["wallet", "request-chain", "--faucet", faucet.url()]); if set_default { command.arg("--set-default"); } From f8820366c9a3f788738338dcdea304ddaacff5c7 Mon Sep 17 00:00:00 2001 From: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Date: Fri, 21 Feb 2025 17:54:51 -0500 Subject: [PATCH 8/8] fix test --- linera-service/tests/linera_net_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linera-service/tests/linera_net_tests.rs b/linera-service/tests/linera_net_tests.rs index 904ff900534e..3b9b653ad63d 100644 --- a/linera-service/tests/linera_net_tests.rs +++ b/linera-service/tests/linera_net_tests.rs @@ -2839,8 +2839,8 @@ async fn test_end_to_end_faucet(config: impl LineraNetConfig) -> Result<()> { // Chain 1 should have transferred four tokens, two to each child. client1.sync(chain1).await?; let faucet_balance = client1.query_balance(Account::chain(chain1)).await?; - assert!(faucet_balance <= balance1 - Amount::from_tokens(4)); - assert!(faucet_balance > balance1 - Amount::from_tokens(5)); + assert!(faucet_balance <= balance1 - Amount::from_tokens(8)); + assert!(faucet_balance > balance1 - Amount::from_tokens(9)); // Assign chain2 to client2_key. assert_eq!(chain2, client2.assign(owner2, message_id).await?);