这是indexloc提供的服务,不要输入任何密码
Skip to content
44 changes: 35 additions & 9 deletions crates/aptos-faucet/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

use anyhow::{Context, Result};
use aptos_faucet_core::funder::{
ApiConnectionConfig, FunderTrait, MintFunder, TransactionSubmissionConfig,
ApiConnectionConfig, AssetConfig, FunderTrait, MintAssetConfig, MintFunder,
TransactionSubmissionConfig, DEFAULT_ASSET_NAME,
};
use aptos_sdk::{
crypto::ed25519::Ed25519PublicKey,
Expand All @@ -13,7 +14,11 @@ use aptos_sdk::{
},
};
use clap::Parser;
use std::{collections::HashSet, str::FromStr};
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
str::FromStr,
};

#[tokio::main]
async fn main() -> Result<()> {
Expand Down Expand Up @@ -45,18 +50,24 @@ pub struct FaucetCliArgs {
#[clap(long)]
pub mint_account_address: Option<AccountAddress>,

/// Path to the private key file for minting coins.
/// To manually generate a keypair, use generate-key:
/// `cargo run -p generate-keypair -- -o <output_file_path>`
#[clap(long, default_value = "/opt/aptos/etc/mint.key")]
pub key_file_path: PathBuf,

/// The maximum amount of gas in OCTA to spend on a single transaction.
#[clap(long, default_value_t = 500_000)]
pub max_gas_amount: u64,
}

impl FaucetCliArgs {
async fn run(&self) -> Result<()> {
// Get network root key based on the connection config.
let key = self
.api_connection_args
.get_key()
.context("Failed to build root key")?;
// Create an AssetConfig to get the key
let asset_config = AssetConfig::new(None, self.key_file_path.clone());

// Get network root key from the asset config.
let key = asset_config.get_key().context("Failed to build root key")?;

// Build the account that the MintFunder will use.
let faucet_account = LocalAccount::new(
Expand All @@ -79,14 +90,29 @@ impl FaucetCliArgs {
true, // wait_for_transactions
);

// Create asset configuration for the default asset
let base_asset_config = AssetConfig::new(None, self.key_file_path.clone());
let mint_asset_config = MintAssetConfig::new(
base_asset_config,
self.mint_account_address,
false, // do_not_delegate is set to false - CLI uses delegation
);

// Build assets map with the default asset
let mut assets = HashMap::new();
assets.insert(DEFAULT_ASSET_NAME.to_string(), mint_asset_config);

// Build the MintFunder service.
let mut mint_funder = MintFunder::new(
let mint_funder = MintFunder::new(
self.api_connection_args.node_url.clone(),
self.api_connection_args.api_key.clone(),
self.api_connection_args.additional_headers.clone(),
self.api_connection_args.chain_id,
transaction_submission_config,
faucet_account,
assets,
DEFAULT_ASSET_NAME.to_string(),
self.amount,
);

// Create an account that we'll delegate mint functionality to, then use it.
Expand All @@ -105,7 +131,7 @@ impl FaucetCliArgs {
// Mint coins to each of the accounts.
for account in accounts {
let response = mint_funder
.fund(Some(self.amount), account, false, false)
.fund(Some(self.amount), account, None, false, false)
.await;
match response {
Ok(response) => println!(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
server_config:
api_path_base: ""
metrics_server_config:
listen_port: 9105
bypasser_configs: []
checker_configs: []
funder_config:
type: "MintFunder"
node_url: "http://127.0.0.1:8080"
chain_id: 4
amount_to_fund: 100000000
assets:
apt:
do_not_delegate: false
key_file_path: ".aptos/testnet/mint.key"
mint_account_address: "0xA550C18"
handler_config:
use_helpful_errors: true
return_rejections_early: false
29 changes: 23 additions & 6 deletions crates/aptos-faucet/core/src/endpoints/fund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ impl FundApi {
async fn fund(
&self,
fund_request: Json<FundRequest>,
asset: poem_openapi::param::Query<Option<String>>,
// This automagically uses FromRequest to get this data from the request.
// It takes into things like X-Forwarded-IP and X-Real-IP.
source_ip: RealIp,
Expand All @@ -110,7 +111,7 @@ impl FundApi {
) -> poem::Result<Json<FundResponse>, AptosTapErrorResponse> {
let txns = self
.components
.fund_inner(fund_request.0, source_ip, header_map, false)
.fund_inner(fund_request.0, source_ip, header_map, false, asset.0)
.await?;
Ok(Json(FundResponse {
txn_hashes: get_hashes(&txns),
Expand All @@ -132,6 +133,7 @@ impl FundApi {
async fn is_eligible(
&self,
fund_request: Json<FundRequest>,
asset: poem_openapi::param::Query<Option<String>>,
// This automagically uses FromRequest to get this data from the request.
// It takes into things like X-Forwarded-IP and X-Real-IP.
source_ip: RealIp,
Expand All @@ -149,10 +151,16 @@ impl FundApi {

// Call Funder.fund with `check_only` set, meaning it only does the
// initial set of checks without actually submitting any transactions
// to fund the account.
// to fund the account. Pass asset directly, funder will use its configured default if None.
self.components
.funder
.fund(fund_request.amount, checker_data.receiver, true, bypass)
.fund(
fund_request.amount,
checker_data.receiver,
asset.0,
true,
bypass,
)
.await?;

Ok(())
Expand Down Expand Up @@ -281,15 +289,23 @@ impl FundApiComponents {
// Same thing, this uses FromRequest.
header_map: &HeaderMap,
dry_run: bool,
asset: Option<String>,
) -> poem::Result<Vec<SignedTransaction>, AptosTapError> {
let (checker_data, bypass, _semaphore_permit) = self
.preprocess_request(&fund_request, source_ip, header_map, dry_run)
.await?;

// Fund the account.
// Fund the account - pass asset directly, funder will use its configured default if None
let asset_for_logging = asset.clone();
let fund_result = self
.funder
.fund(fund_request.amount, checker_data.receiver, false, bypass)
.fund(
fund_request.amount,
checker_data.receiver,
asset,
false,
bypass,
)
.await;

// This might be empty if there is an error and we never got to the
Expand All @@ -305,6 +321,7 @@ impl FundApiComponents {
jwt_sub = jwt_sub(checker_data.headers.clone()).ok(),
address = checker_data.receiver,
requested_amount = fund_request.amount,
asset = asset_for_logging.as_deref().unwrap_or("default"),
txn_hashes = txn_hashes,
success = fund_result.is_ok(),
);
Expand Down Expand Up @@ -387,7 +404,7 @@ pub async fn mint(
};
let txns = fund_api_components
.0
.fund_inner(fund_request, source_ip, header_map, false)
.fund_inner(fund_request, source_ip, header_map, false, None)
.await
.map_err(|e| {
poem::Error::from((e.status_and_retry_after().0, anyhow::anyhow!(e.message)))
Expand Down
104 changes: 56 additions & 48 deletions crates/aptos-faucet/core/src/funder/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const MAX_NUM_OUTSTANDING_TRANSACTIONS: u64 = 15;

const DEFAULT_KEY_FILE_PATH: &str = "/opt/aptos/etc/mint.key";

/// Default asset name used when no asset is specified in requests.
pub const DEFAULT_ASSET_NAME: &str = "apt";

/// This defines configuration for any Funder that needs to interact with a real
/// blockchain API. This includes the MintFunder and the TransferFunder currently.
///
Expand All @@ -62,20 +65,6 @@ pub struct ApiConnectionConfig {
#[clap(skip)]
pub additional_headers: Option<HashMap<String, String>>,

/// Path to the private key for creating test account and minting coins in
/// the MintFunder case, or for transferring coins in the TransferFunder case.
/// To keep Testnet simple, we used one private key for aptos root account
/// To manually generate a keypair, use generate-key:
/// `cargo run -p generate-keypair -- -o <output_file_path>`
#[serde(default = "ApiConnectionConfig::default_mint_key_file_path")]
#[clap(long, default_value = DEFAULT_KEY_FILE_PATH, value_parser)]
key_file_path: PathBuf,

/// Hex string of an Ed25519PrivateKey for minting / transferring coins.
#[serde(skip_serializing_if = "Option::is_none")]
#[clap(long, value_parser = ConfigKey::<Ed25519PrivateKey>::from_encoded_string)]
key: Option<ConfigKey<Ed25519PrivateKey>>,

/// Chain ID of the network this client is connecting to. For example, for mainnet:
/// "MAINNET" or 1, testnet: "TESTNET" or 2. If there is no predefined string
/// alias (e.g. "MAINNET"), just use the number. Note: Chain ID of 0 is not allowed.
Expand All @@ -88,49 +77,15 @@ impl ApiConnectionConfig {
node_url: Url,
api_key: Option<String>,
additional_headers: Option<HashMap<String, String>>,
key_file_path: PathBuf,
key: Option<ConfigKey<Ed25519PrivateKey>>,
chain_id: ChainId,
) -> Self {
Self {
node_url,
api_key,
additional_headers,
key_file_path,
key,
chain_id,
}
}

fn default_mint_key_file_path() -> PathBuf {
PathBuf::from_str(DEFAULT_KEY_FILE_PATH).unwrap()
}

pub fn get_key(&self) -> Result<Ed25519PrivateKey> {
if let Some(ref key) = self.key {
return Ok(key.private_key());
}
let key_bytes = std::fs::read(self.key_file_path.as_path()).with_context(|| {
format!(
"Failed to read key file: {}",
self.key_file_path.to_string_lossy()
)
})?;
// decode as bcs first, fall back to a file of hex
let result = aptos_sdk::bcs::from_bytes(&key_bytes); //.with_context(|| "bad bcs");
if let Ok(x) = result {
return Ok(x);
}
let keystr = String::from_utf8(key_bytes).map_err(|e| anyhow!(e))?;
Ok(ConfigKey::from_encoded_string(keystr.as_str())
.with_context(|| {
format!(
"{}: key file failed as both bcs and hex",
self.key_file_path.to_string_lossy()
)
})?
.private_key())
}
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -467,3 +422,56 @@ impl GasUnitPriceManager {
.gas_estimate)
}
}

#[derive(Clone, Debug, Deserialize, Serialize, Parser)]
pub struct AssetConfig {
/// Path to the private key for creating test account and minting coins in
/// the MintFunder case, or for transferring coins in the TransferFunder case.
/// To keep Testnet simple, we used one private key for aptos root account
/// To manually generate a keypair, use generate-key:
/// `cargo run -p generate-keypair -- -o <output_file_path>`
#[serde(default = "AssetConfig::default_key_file_path")]
#[clap(long, default_value = DEFAULT_KEY_FILE_PATH, value_parser)]
pub key_file_path: PathBuf,

/// Hex string of an Ed25519PrivateKey for minting / transferring coins.
#[serde(skip_serializing_if = "Option::is_none")]
#[clap(long, value_parser = ConfigKey::<Ed25519PrivateKey>::from_encoded_string)]
pub key: Option<ConfigKey<Ed25519PrivateKey>>,
}

impl AssetConfig {
pub fn new(key: Option<ConfigKey<Ed25519PrivateKey>>, key_file_path: PathBuf) -> Self {
Self { key, key_file_path }
}

fn default_key_file_path() -> PathBuf {
PathBuf::from_str(DEFAULT_KEY_FILE_PATH).unwrap()
}

pub fn get_key(&self) -> Result<Ed25519PrivateKey> {
if let Some(ref key) = self.key {
return Ok(key.private_key());
}
let key_bytes = std::fs::read(self.key_file_path.as_path()).with_context(|| {
format!(
"Failed to read key file: {}",
self.key_file_path.to_string_lossy()
)
})?;
// decode as bcs first, fall back to a file of hex
let result = aptos_sdk::bcs::from_bytes(&key_bytes); //.with_context(|| "bad bcs");
if let Ok(x) = result {
return Ok(x);
}
let keystr = String::from_utf8(key_bytes).map_err(|e| anyhow!(e))?;
Ok(ConfigKey::from_encoded_string(keystr.as_str())
.with_context(|| {
format!(
"{}: key file failed as both bcs and hex",
self.key_file_path.to_string_lossy()
)
})?
.private_key())
}
}
1 change: 1 addition & 0 deletions crates/aptos-faucet/core/src/funder/fake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl FunderTrait for FakeFunder {
&self,
_amount: Option<u64>,
_receiver_address: AccountAddress,
_asset: Option<String>,
_check_only: bool,
_did_bypass_checkers: bool,
) -> Result<Vec<SignedTransaction>, AptosTapError> {
Expand Down
Loading
Loading