Skip to content

Commit

Permalink
[aptos-cli] Use named networks
Browse files Browse the repository at this point in the history
There's confusion between the faucet and network endpoints.  This
just adds prenamed combinations that can be used, while leaving
the ability for custom combinations.  Additionally, stops making
the init fail if the faucet fails.
  • Loading branch information
gregnazario committed Oct 16, 2022
1 parent 9d91824 commit efb429a
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 63 deletions.
258 changes: 196 additions & 62 deletions crates/aptos/src/common/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ use crate::common::{
utils::{fund_account, prompt_yes_with_override, read_line},
};
use aptos_crypto::{ed25519::Ed25519PrivateKey, PrivateKey, ValidCryptoMaterialStringExt};
use aptos_rest_client::aptos_api_types::{AptosError, AptosErrorCode};
use aptos_rest_client::error::{AptosErrorResponse, RestError};
use async_trait::async_trait;
use clap::Parser;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::str::FromStr;

pub const DEFAULT_REST_URL: &str = "https://fullnode.devnet.aptoslabs.com/v1";
pub const DEFAULT_REST_URL: &str = "https://fullnode.devnet.aptoslabs.com";
pub const DEFAULT_FAUCET_URL: &str = "https://faucet.devnet.aptoslabs.com";
const NUM_DEFAULT_COINS: u64 = 10000;

Expand All @@ -25,6 +29,12 @@ const NUM_DEFAULT_COINS: u64 = 10000;
/// Configuration will be pushed into .aptos/config.yaml
#[derive(Debug, Parser)]
pub struct InitTool {
/// Network to use for default settings
///
/// If custom `rest_url` and `faucet_url` are wanted, use `custom`
#[clap(long)]
pub network: Option<Network>,

/// URL to a fullnode on the network
#[clap(long)]
pub rest_url: Option<Url>,
Expand Down Expand Up @@ -77,66 +87,37 @@ impl CliCommand<()> for InitTool {

eprintln!("Configuring for profile {}", profile_name);

// Rest Endpoint
let rest_url = if let Some(rest_url) = self.rest_url {
eprintln!("Using command line argument for rest URL {}", rest_url);
rest_url
// Choose a network
// TODO: Change custom to a specific network int he future
eprintln!("Choose network from [testnet, devnet, local, custom | defaults to custom]");
let input = read_line("network")?;
let input = input.trim();
let network = if input.is_empty() {
eprintln!("No network given, using devnet...");
Network::Custom
} else {
eprintln!(
"Enter your rest endpoint [Current: {} | No input: {}]",
profile_config
.rest_url
.unwrap_or_else(|| "None".to_string()),
DEFAULT_REST_URL
);
let input = read_line("Rest endpoint")?;
let input = input.trim();
if input.is_empty() {
eprintln!("No rest url given, using {}...", DEFAULT_REST_URL);
reqwest::Url::parse(DEFAULT_REST_URL).map_err(|err| {
CliError::UnexpectedError(format!("Failed to parse default rest URL {}", err))
})?
} else {
reqwest::Url::parse(input)
.map_err(|err| CliError::UnableToParse("Rest Endpoint", err.to_string()))?
}
Network::from_str(input)?
};
profile_config.rest_url = Some(rest_url.to_string());

// Faucet Endpoint
let faucet_url = if self.skip_faucet {
eprintln!("Not configuring a faucet because --skip-faucet was provided");
None
} else if let Some(faucet_url) = self.faucet_url {
eprintln!("Using command line argument for faucet URL {}", faucet_url);
Some(faucet_url)
} else {
eprintln!(
"Enter your faucet endpoint [Current: {} | No input: {} | 'skip' to not use a faucet]",
profile_config
.faucet_url
.unwrap_or_else(|| "None".to_string()),
DEFAULT_FAUCET_URL
);
let input = read_line("Faucet endpoint")?;
let input = input.trim();
if input.is_empty() {
eprintln!("No faucet url given, using {}...", DEFAULT_FAUCET_URL);
Some(reqwest::Url::parse(DEFAULT_FAUCET_URL).map_err(|err| {
CliError::UnexpectedError(format!("Failed to parse default faucet URL {}", err))
})?)
} else if input.to_lowercase() == "skip" {
eprintln!("Skipping faucet");
None
} else {
Some(
reqwest::Url::parse(input).map_err(|err| {
CliError::UnableToParse("Faucet Endpoint", err.to_string())
})?,
)
let mut is_community_faucet = false;

match network {
Network::Testnet => {
profile_config.rest_url =
Some("https://fullnode.testnet.aptoslabs.com".to_string());
profile_config.faucet_url = None;
is_community_faucet = true;
}
};
profile_config.faucet_url = faucet_url.as_ref().map(|inner| inner.to_string());
Network::Devnet => {
profile_config.rest_url = Some("https://fullnode.devnet.aptoslabs.com".to_string());
profile_config.faucet_url = Some("https://faucet.devnet.aptoslabs.com".to_string());
}
Network::Local => {
profile_config.rest_url = Some("http://localhost:8080".to_string());
profile_config.faucet_url = Some("http://localhost:8081".to_string());
}
Network::Custom => self.custom_network(&mut profile_config)?,
}

// Private key
let private_key = if let Some(private_key) = self
Expand Down Expand Up @@ -171,15 +152,67 @@ impl CliCommand<()> for InitTool {
profile_config.account = Some(address);

// Create account if it doesn't exist (and there's a faucet)
let client = aptos_rest_client::Client::new(rest_url);
if let Some(faucet_url) = faucet_url {
if client.get_account(address).await.is_err() {
let client = aptos_rest_client::Client::new(
Url::parse(profile_config.rest_url.as_ref().unwrap())
.map_err(|err| CliError::UnableToParse("rest_url", err.to_string()))?,
);

// Check if account exists
let account_exists = match client.get_account(address).await {
Ok(_) => true,
Err(err) => {
if let RestError::Api(AptosErrorResponse {
error:
AptosError {
error_code: AptosErrorCode::ResourceNotFound,
..
},
..
})
| RestError::Api(AptosErrorResponse {
error:
AptosError {
error_code: AptosErrorCode::AccountNotFound,
..
},
..
}) = err
{
false
} else {
return Err(CliError::UnexpectedError(format!(
"Failed to check if account exists: {:?}",
err
)));
}
}
};
if let Some(ref faucet_url) = profile_config.faucet_url {
if account_exists {
eprintln!("Account {} has been already found onchain", address);
} else {
eprintln!(
"Account {} doesn't exist, creating it and funding it with {} Octas",
address, NUM_DEFAULT_COINS
);
fund_account(faucet_url, NUM_DEFAULT_COINS, address).await?;
match fund_account(
Url::parse(faucet_url)
.map_err(|err| CliError::UnableToParse("rest_url", err.to_string()))?,
NUM_DEFAULT_COINS,
address,
)
.await
{
Ok(_) => eprintln!("Account {} funded successfully", address),
Err(err) => eprintln!("Account {} failed to be funded: {:?}", address, err),
};
}
} else if account_exists {
eprintln!("Account {} has been already found onchain", address);
} else if is_community_faucet {
eprintln!("Account {} does not exist, you may need to fund the account through a community faucet e.g. https://aptoslabs.com/testnet-faucet", address);
} else {
eprintln!("Account {} has been initialized locally, but must have coins transferred to it to create the account onchain", address);
}

// Ensure the loaded config has profiles setup for a possible empty file
Expand All @@ -192,7 +225,108 @@ impl CliCommand<()> for InitTool {
.unwrap()
.insert(profile_name.to_string(), profile_config);
config.save()?;
eprintln!("Aptos is now set up for account {}! Run `aptos help` for more information about commands", address);
eprintln!("\n---\nAptos CLI is now set up for account {} as profile {}! Run `aptos --help` for more information about commands", address, self.profile_options.profile_name().unwrap_or(DEFAULT_PROFILE));
Ok(())
}
}

impl InitTool {
fn custom_network(&self, profile_config: &mut ProfileConfig) -> CliTypedResult<()> {
// Rest Endpoint
let rest_url = if let Some(ref rest_url) = self.rest_url {
eprintln!("Using command line argument for rest URL {}", rest_url);
rest_url.clone()
} else {
eprintln!(
"Enter your rest endpoint [Current: {} | No input: {}]",
profile_config.rest_url.as_deref().unwrap_or("None"),
DEFAULT_REST_URL
);
let input = read_line("Rest endpoint")?;
let input = input.trim();
if input.is_empty() {
eprintln!("No rest url given, using {}...", DEFAULT_REST_URL);
reqwest::Url::parse(DEFAULT_REST_URL).map_err(|err| {
CliError::UnexpectedError(format!("Failed to parse default rest URL {}", err))
})?
} else {
reqwest::Url::parse(input)
.map_err(|err| CliError::UnableToParse("Rest Endpoint", err.to_string()))?
}
};
profile_config.rest_url = Some(rest_url.to_string());

// Faucet Endpoint
let faucet_url = if self.skip_faucet {
eprintln!("Not configuring a faucet because --skip-faucet was provided");
None
} else if let Some(ref faucet_url) = self.faucet_url {
eprintln!("Using command line argument for faucet URL {}", faucet_url);
Some(faucet_url.clone())
} else {
eprintln!(
"Enter your faucet endpoint [Current: {} | No input: {} | 'skip' to not use a faucet]",
profile_config
.faucet_url.as_deref()
.unwrap_or("None"),
DEFAULT_FAUCET_URL
);
let input = read_line("Faucet endpoint")?;
let input = input.trim();
if input.is_empty() {
eprintln!("No faucet url given, using {}...", DEFAULT_FAUCET_URL);
Some(reqwest::Url::parse(DEFAULT_FAUCET_URL).map_err(|err| {
CliError::UnexpectedError(format!("Failed to parse default faucet URL {}", err))
})?)
} else if input.to_lowercase() == "skip" {
eprintln!("Skipping faucet");
None
} else {
Some(
reqwest::Url::parse(input).map_err(|err| {
CliError::UnableToParse("Faucet Endpoint", err.to_string())
})?,
)
}
};
profile_config.faucet_url = faucet_url.as_ref().map(|inner| inner.to_string());
Ok(())
}
}

/// A simplified list of all networks supported by the CLI
///
/// Any command using this, will be simpler to setup as profiles
#[derive(Debug, Serialize, Deserialize)]
pub enum Network {
Testnet,
Devnet,
Local,
Custom,
}

impl FromStr for Network {
type Err = CliError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_lowercase().trim() {
"testnet" => Self::Testnet,
"devnet" => Self::Devnet,
"local" => Self::Local,
"custom" => Self::Custom,
str => {
return Err(CliError::CommandArgumentError(format!(
"Invalid network {}. Must be one of [testnet, devnet, local, custom]",
str
)))
}
})
}
}

impl Default for Network {
fn default() -> Self {
// This unfortunately has to be custom if people play with their configs
Self::Custom
}
}
3 changes: 3 additions & 0 deletions crates/aptos/src/common/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::init::Network;
use crate::common::utils::prompt_yes_with_override;
use crate::{
common::{
Expand Down Expand Up @@ -186,6 +187,8 @@ pub const CONFIG_FOLDER: &str = ".aptos";
/// An individual profile
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ProfileConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<Network>,
/// Private key for commands.
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key: Option<Ed25519PrivateKey>,
Expand Down
3 changes: 2 additions & 1 deletion crates/aptos/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::account::{
list::{ListAccount, ListQuery},
transfer::{TransferCoins, TransferSummary},
};
use crate::common::init::InitTool;
use crate::common::init::{InitTool, Network};
use crate::common::types::{
account_address_from_public_key, AccountAddressWrapper, CliError, CliTypedResult,
EncodingOptions, FaucetOptions, GasOptions, KeyType, MoveManifestAccountWrapper,
Expand Down Expand Up @@ -505,6 +505,7 @@ impl CliTestFramework {

pub async fn init(&self, private_key: &Ed25519PrivateKey) -> CliTypedResult<()> {
InitTool {
network: Some(Network::Custom),
rest_url: Some(self.endpoint.clone()),
faucet_url: Some(self.faucet_endpoint.clone()),
rng_args: RngArgs::from_seed([0; 32]),
Expand Down

0 comments on commit efb429a

Please sign in to comment.