Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create command request-chain to allow using testnets in bash #3385

Merged
merged 8 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 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


Expand Down Expand Up @@ -794,9 +798,34 @@ Initialize a wallet from the genesis configuration



## `linera wallet request-chain`

Request a new chain from a faucet and add it to the wallet

**Usage:** `linera wallet request-chain [OPTIONS] --faucet <FAUCET>`

###### **Options:**

* `--faucet <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 (i.e. a chain without keypair) to the wallet

**Usage:** `linera wallet follow-chain <CHAIN_ID>`

###### **Arguments:**

* `<CHAIN_ID>` — 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 <CHAIN_ID>`

Expand Down
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
```
Expand Down
20 changes: 19 additions & 1 deletion linera-client/src/client_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,25 @@ pub enum WalletCommand {
testing_prng_seed: Option<u64>,
},

/// Forgets the specified chain's keys.
/// Request a new 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 (i.e. a chain without keypair) to the wallet.
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.
Expand Down
32 changes: 32 additions & 0 deletions linera-service/src/cli_wrappers/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,28 @@ impl ClientWrapper {
}
}

/// Runs `linera wallet request-chain`.
pub async fn request_chain(&self, faucet: &Faucet, set_default: bool) -> Result<ClaimOutcome> {
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,
Expand Down Expand Up @@ -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?;
Expand Down
62 changes: 62 additions & 0 deletions linera-service/src/linera/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_)
Expand Down Expand Up @@ -1829,6 +1870,22 @@ async fn run(options: &ClientOptions) -> Result<i32, anyhow::Error> {
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
Expand All @@ -1840,6 +1897,11 @@ async fn run(options: &ClientOptions) -> Result<i32, anyhow::Error> {
Ok(0)
}

WalletCommand::RequestChain { .. } => {
options.run_with_storage(Job(options.clone())).await??;
Ok(0)
}

WalletCommand::Init {
genesis_config_path,
faucet,
Expand Down
27 changes: 20 additions & 7 deletions linera-service/tests/linera_net_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2814,20 +2814,33 @@ 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?;

// 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?);
Expand Down
23 changes: 23 additions & 0 deletions scripts/linera_net_helper.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# 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
Expand Down Expand Up @@ -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() {
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" &
}