소스 검색

Cli: move airdrop to rpc requests (#16557)

* Add recent_blockhash to requestAirdrop

* Move tx confirmation to separate method

* Add RpcClient airdrop methods

* Request cli airdrop via RpcClient

* Pass optional faucet_addr into TestValidator and fix tests

* Update client/src/rpc_client.rs

Co-authored-by: Michael Vines <mvines@gmail.com>

Co-authored-by: Michael Vines <mvines@gmail.com>
Tyera Eulberg 4 년 전
부모
커밋
7dfb51c0b4

+ 17 - 125
cli/src/cli.rs

@@ -33,11 +33,6 @@ use solana_client::{
     },
     rpc_response::RpcKeyedAccount,
 };
-#[cfg(not(test))]
-use solana_faucet::faucet::request_airdrop_transaction;
-use solana_faucet::faucet::FaucetError;
-#[cfg(test)]
-use solana_faucet::faucet_mock::request_airdrop_transaction;
 use solana_remote_wallet::remote_wallet::RemoteWalletManager;
 use solana_sdk::{
     clock::{Epoch, Slot},
@@ -59,19 +54,10 @@ use solana_stake_program::{
 use solana_transaction_status::{EncodedTransaction, UiTransactionEncoding};
 use solana_vote_program::vote_state::VoteAuthorize;
 use std::{
-    collections::HashMap,
-    error,
-    fmt::Write as FmtWrite,
-    fs::File,
-    io::Write,
-    net::{IpAddr, SocketAddr},
-    str::FromStr,
-    sync::Arc,
-    thread::sleep,
-    time::Duration,
+    collections::HashMap, error, fmt::Write as FmtWrite, fs::File, io::Write, str::FromStr,
+    sync::Arc, time::Duration,
 };
 use thiserror::Error;
-use url::Url;
 
 pub const DEFAULT_RPC_TIMEOUT_SECONDS: &str = "30";
 
@@ -361,8 +347,6 @@ pub enum CliCommand {
     // Wallet Commands
     Address,
     Airdrop {
-        faucet_host: Option<IpAddr>,
-        faucet_port: u16,
         pubkey: Option<Pubkey>,
         lamports: u64,
     },
@@ -784,20 +768,6 @@ pub fn parse_command(
             signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
         }),
         ("airdrop", Some(matches)) => {
-            let faucet_port = matches
-                .value_of("faucet_port")
-                .ok_or_else(|| CliError::BadParameter("Missing faucet port".to_string()))?
-                .parse()
-                .map_err(|err| CliError::BadParameter(format!("Invalid faucet port: {}", err)))?;
-
-            let faucet_host = matches
-                .value_of("faucet_host")
-                .map(|faucet_host| {
-                    solana_net_utils::parse_host(faucet_host).map_err(|err| {
-                        CliError::BadParameter(format!("Invalid faucet host: {}", err))
-                    })
-                })
-                .transpose()?;
             let pubkey = pubkey_of_signer(matches, "to", wallet_manager)?;
             let signers = if pubkey.is_some() {
                 vec![]
@@ -806,12 +776,7 @@ pub fn parse_command(
             };
             let lamports = lamports_of_sol(matches, "amount").unwrap();
             Ok(CliCommandInfo {
-                command: CliCommand::Airdrop {
-                    faucet_host,
-                    faucet_port,
-                    pubkey,
-                    lamports,
-                },
+                command: CliCommand::Airdrop { pubkey, lamports },
                 signers,
             })
         }
@@ -1008,7 +973,6 @@ fn process_create_address_with_seed(
 fn process_airdrop(
     rpc_client: &RpcClient,
     config: &CliConfig,
-    faucet_addr: &SocketAddr,
     pubkey: &Option<Pubkey>,
     lamports: u64,
 ) -> ProcessResult {
@@ -1018,14 +982,13 @@ fn process_airdrop(
         config.pubkey()?
     };
     println!(
-        "Requesting airdrop of {} from {}",
+        "Requesting airdrop of {}",
         build_balance_message(lamports, false, true),
-        faucet_addr
     );
 
     let pre_balance = rpc_client.get_balance(&pubkey)?;
 
-    let result = request_and_confirm_airdrop(&rpc_client, faucet_addr, &pubkey, lamports);
+    let result = request_and_confirm_airdrop(rpc_client, config, &pubkey, lamports);
     if let Ok(signature) = result {
         let signature_cli_message = log_instruction_custom_error::<SystemError>(result, &config)?;
         println!("{}", signature_cli_message);
@@ -1877,27 +1840,8 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
         // Wallet Commands
 
         // Request an airdrop from Solana Faucet;
-        CliCommand::Airdrop {
-            faucet_host,
-            faucet_port,
-            pubkey,
-            lamports,
-        } => {
-            let faucet_addr = SocketAddr::new(
-                faucet_host.unwrap_or_else(|| {
-                    let faucet_host = Url::parse(&config.json_rpc_url)
-                        .unwrap()
-                        .host()
-                        .unwrap()
-                        .to_string();
-                    solana_net_utils::parse_host(&faucet_host).unwrap_or_else(|err| {
-                        panic!("Unable to resolve {}: {}", faucet_host, err);
-                    })
-                }),
-                *faucet_port,
-            );
-
-            process_airdrop(&rpc_client, config, &faucet_addr, pubkey, *lamports)
+        CliCommand::Airdrop { pubkey, lamports } => {
+            process_airdrop(&rpc_client, config, pubkey, *lamports)
         }
         // Check client balance
         CliCommand::Balance {
@@ -1963,67 +1907,21 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
     }
 }
 
-// Quick and dirty Keypair that assumes the client will do retries but not update the
-// blockhash. If the client updates the blockhash, the signature will be invalid.
-struct FaucetKeypair {
-    transaction: Transaction,
-}
-
-impl FaucetKeypair {
-    fn new_keypair(
-        faucet_addr: &SocketAddr,
-        to_pubkey: &Pubkey,
-        lamports: u64,
-        blockhash: Hash,
-    ) -> Result<Self, FaucetError> {
-        let transaction = request_airdrop_transaction(faucet_addr, to_pubkey, lamports, blockhash)?;
-        Ok(Self { transaction })
-    }
-
-    fn airdrop_transaction(&self) -> Transaction {
-        self.transaction.clone()
-    }
-}
-
-impl Signer for FaucetKeypair {
-    /// Return the public key of the keypair used to sign votes
-    fn pubkey(&self) -> Pubkey {
-        self.transaction.message().account_keys[0]
-    }
-
-    fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
-        Ok(self.pubkey())
-    }
-
-    fn sign_message(&self, _msg: &[u8]) -> Signature {
-        self.transaction.signatures[0]
-    }
-
-    fn try_sign_message(&self, message: &[u8]) -> Result<Signature, SignerError> {
-        Ok(self.sign_message(message))
-    }
-}
-
 pub fn request_and_confirm_airdrop(
     rpc_client: &RpcClient,
-    faucet_addr: &SocketAddr,
+    config: &CliConfig,
     to_pubkey: &Pubkey,
     lamports: u64,
 ) -> ClientResult<Signature> {
-    let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
-    let keypair = {
-        let mut retries = 5;
-        loop {
-            let result = FaucetKeypair::new_keypair(faucet_addr, to_pubkey, lamports, blockhash);
-            if result.is_ok() || retries == 0 {
-                break result;
-            }
-            retries -= 1;
-            sleep(Duration::from_secs(1));
-        }
-    }?;
-    let tx = keypair.airdrop_transaction();
-    rpc_client.send_and_confirm_transaction_with_spinner(&tx)
+    let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
+    let signature =
+        rpc_client.request_airdrop_with_blockhash(to_pubkey, lamports, &recent_blockhash)?;
+    rpc_client.confirm_transaction_with_spinner(
+        &signature,
+        &recent_blockhash,
+        config.commitment,
+    )?;
+    Ok(signature)
 }
 
 pub fn log_instruction_custom_error<E>(
@@ -2493,8 +2391,6 @@ mod tests {
             parse_command(&test_airdrop, &default_signer, &mut None).unwrap(),
             CliCommandInfo {
                 command: CliCommand::Airdrop {
-                    faucet_host: None,
-                    faucet_port: solana_faucet::faucet::FAUCET_PORT,
                     pubkey: Some(pubkey),
                     lamports: 50_000_000_000,
                 },
@@ -2880,8 +2776,6 @@ mod tests {
         let to = solana_sdk::pubkey::new_rand();
         config.signers = vec![&keypair];
         config.command = CliCommand::Airdrop {
-            faucet_host: None,
-            faucet_port: 1234,
             pubkey: Some(to),
             lamports: 50,
         };
@@ -2906,8 +2800,6 @@ mod tests {
         config.rpc_client = Some(RpcClient::new_mock("fails".to_string()));
 
         config.command = CliCommand::Airdrop {
-            faucet_host: None,
-            faucet_port: 1234,
             pubkey: None,
             lamports: 50,
         };

+ 20 - 26
cli/tests/nonce.rs

@@ -22,44 +22,38 @@ use solana_sdk::{
 #[test]
 fn test_nonce() {
     let mint_keypair = Keypair::new();
-    full_battery_tests(
-        TestValidator::with_no_fees(mint_keypair.pubkey()),
-        mint_keypair,
-        None,
-        false,
-    );
+    let mint_pubkey = mint_keypair.pubkey();
+    let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
+
+    full_battery_tests(test_validator, None, false);
 }
 
 #[test]
 fn test_nonce_with_seed() {
     let mint_keypair = Keypair::new();
-    full_battery_tests(
-        TestValidator::with_no_fees(mint_keypair.pubkey()),
-        mint_keypair,
-        Some(String::from("seed")),
-        false,
-    );
+    let mint_pubkey = mint_keypair.pubkey();
+    let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
+
+    full_battery_tests(test_validator, Some(String::from("seed")), false);
 }
 
 #[test]
 fn test_nonce_with_authority() {
     let mint_keypair = Keypair::new();
-    full_battery_tests(
-        TestValidator::with_no_fees(mint_keypair.pubkey()),
-        mint_keypair,
-        None,
-        true,
-    );
+    let mint_pubkey = mint_keypair.pubkey();
+    let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
+
+    full_battery_tests(test_validator, None, true);
 }
 
 fn full_battery_tests(
     test_validator: TestValidator,
-    mint_keypair: Keypair,
     seed: Option<String>,
     use_nonce_authority: bool,
 ) {
-    let faucet_addr = run_local_faucet(mint_keypair, None);
-
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
     let json_rpc_url = test_validator.rpc_url();
@@ -71,7 +65,7 @@ fn full_battery_tests(
 
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &config_payer,
         &config_payer.signers[0].pubkey(),
         2000,
     )
@@ -220,9 +214,9 @@ fn full_battery_tests(
 fn test_create_account_with_seed() {
     solana_logger::setup();
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
-
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
     let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
@@ -233,14 +227,14 @@ fn test_create_account_with_seed() {
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &CliConfig::recent_for_tests(),
         &offline_nonce_authority_signer.pubkey(),
         42,
     )
     .unwrap();
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &CliConfig::recent_for_tests(),
         &online_nonce_creator_signer.pubkey(),
         4242,
     )

+ 16 - 26
cli/tests/program.rs

@@ -28,8 +28,9 @@ fn test_cli_program_deploy_non_upgradeable() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -46,8 +47,6 @@ fn test_cli_program_deploy_non_upgradeable() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 4 * minimum_balance_for_rent_exemption, // min balance for rent exemption for three programs + leftover for tx processing
     };
@@ -104,8 +103,6 @@ fn test_cli_program_deploy_non_upgradeable() {
     let custom_address_keypair = Keypair::new();
     config.signers = vec![&custom_address_keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 2 * minimum_balance_for_rent_exemption, // Anything over minimum_balance_for_rent_exemption should trigger err
     };
@@ -147,8 +144,9 @@ fn test_cli_program_deploy_no_authority() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -171,8 +169,6 @@ fn test_cli_program_deploy_no_authority() {
     let keypair = Keypair::new();
     config.json_rpc_url = test_validator.rpc_url();
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
     };
@@ -231,8 +227,9 @@ fn test_cli_program_deploy_with_authority() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -256,8 +253,6 @@ fn test_cli_program_deploy_with_authority() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
     };
@@ -560,8 +555,9 @@ fn test_cli_program_write_buffer() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -586,8 +582,6 @@ fn test_cli_program_write_buffer() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_buffer,
     };
@@ -843,8 +837,9 @@ fn test_cli_program_set_buffer_authority() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -864,8 +859,6 @@ fn test_cli_program_set_buffer_authority() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_buffer,
     };
@@ -957,8 +950,9 @@ fn test_cli_program_mismatch_buffer_authority() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -978,8 +972,6 @@ fn test_cli_program_mismatch_buffer_authority() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_buffer,
     };
@@ -1047,8 +1039,9 @@ fn test_cli_program_show() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -1071,8 +1064,6 @@ fn test_cli_program_show() {
     // Airdrop
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_buffer,
     };
@@ -1228,8 +1219,9 @@ fn test_cli_program_dump() {
     pathbuf.set_extension("so");
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -1252,8 +1244,6 @@ fn test_cli_program_dump() {
     // Airdrop
     config.signers = vec![&keypair];
     config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 100 * minimum_balance_for_buffer,
     };

+ 2 - 4
cli/tests/request_airdrop.rs

@@ -10,15 +10,13 @@ use solana_sdk::{
 #[test]
 fn test_cli_request_airdrop() {
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
-
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let mut bob_config = CliConfig::recent_for_tests();
     bob_config.json_rpc_url = test_validator.rpc_url();
     bob_config.command = CliCommand::Airdrop {
-        faucet_host: None,
-        faucet_port: faucet_addr.port(),
         pubkey: None,
         lamports: 50,
     };

+ 43 - 63
cli/tests/stake.rs

@@ -26,8 +26,9 @@ use solana_stake_program::{
 #[test]
 fn test_stake_delegation_force() {
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -37,13 +38,8 @@ fn test_stake_delegation_force() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&default_signer];
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        100_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
+        .unwrap();
 
     // Create vote account
     let vote_keypair = Keypair::new();
@@ -119,8 +115,9 @@ fn test_seed_stake_delegation_and_deactivation() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -132,7 +129,7 @@ fn test_seed_stake_delegation_and_deactivation() {
 
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &config_validator,
         &config_validator.signers[0].pubkey(),
         100_000,
     )
@@ -202,8 +199,9 @@ fn test_stake_delegation_and_deactivation() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -217,7 +215,7 @@ fn test_stake_delegation_and_deactivation() {
 
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &config_validator,
         &config_validator.signers[0].pubkey(),
         100_000,
     )
@@ -281,8 +279,9 @@ fn test_offline_stake_delegation_and_deactivation() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -307,7 +306,7 @@ fn test_offline_stake_delegation_and_deactivation() {
 
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &config_validator,
         &config_validator.signers[0].pubkey(),
         100_000,
     )
@@ -316,7 +315,7 @@ fn test_offline_stake_delegation_and_deactivation() {
 
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &config_offline,
         &config_offline.signers[0].pubkey(),
         100_000,
     )
@@ -420,8 +419,9 @@ fn test_nonced_stake_delegation_and_deactivation() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -435,13 +435,8 @@ fn test_nonced_stake_delegation_and_deactivation() {
         .get_minimum_balance_for_rent_exemption(NonceState::size())
         .unwrap();
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        100_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
+        .unwrap();
 
     // Create stake account
     let stake_keypair = Keypair::new();
@@ -539,8 +534,9 @@ fn test_stake_authorize() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -550,13 +546,8 @@ fn test_stake_authorize() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&default_signer];
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        100_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
+        .unwrap();
 
     let offline_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
     let mut config_offline = CliConfig::recent_for_tests();
@@ -569,7 +560,7 @@ fn test_stake_authorize() {
 
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &config_offline,
         &config_offline.signers[0].pubkey(),
         100_000,
     )
@@ -809,8 +800,9 @@ fn test_stake_authorize_with_fee_payer() {
     const SIG_FEE: u64 = 42;
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), SIG_FEE);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, SIG_FEE, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -836,13 +828,13 @@ fn test_stake_authorize_with_fee_payer() {
     config_offline.command = CliCommand::ClusterVersion;
     process_command(&config_offline).unwrap_err();
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 100_000).unwrap();
     check_recent_balance(100_000, &rpc_client, &config.signers[0].pubkey());
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 100_000).unwrap();
     check_recent_balance(100_000, &rpc_client, &payer_pubkey);
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
     check_recent_balance(100_000, &rpc_client, &offline_pubkey);
 
     check_ready(&rpc_client);
@@ -937,8 +929,9 @@ fn test_stake_split() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -957,16 +950,11 @@ fn test_stake_split() {
     config_offline.command = CliCommand::ClusterVersion;
     process_command(&config_offline).unwrap_err();
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        500_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
+        .unwrap();
     check_recent_balance(500_000, &rpc_client, &config.signers[0].pubkey());
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
     check_recent_balance(100_000, &rpc_client, &offline_pubkey);
 
     // Create stake account, identity is authority
@@ -1084,8 +1072,9 @@ fn test_stake_set_lockup() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -1104,16 +1093,11 @@ fn test_stake_set_lockup() {
     config_offline.command = CliCommand::ClusterVersion;
     process_command(&config_offline).unwrap_err();
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        500_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
+        .unwrap();
     check_recent_balance(500_000, &rpc_client, &config.signers[0].pubkey());
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
     check_recent_balance(100_000, &rpc_client, &offline_pubkey);
 
     // Create stake account, identity is authority
@@ -1347,8 +1331,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -1366,16 +1351,11 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
     // Verify that we cannot reach the cluster
     process_command(&config_offline).unwrap_err();
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        200_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 200_000)
+        .unwrap();
     check_recent_balance(200_000, &rpc_client, &config.signers[0].pubkey());
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
     check_recent_balance(100_000, &rpc_client, &offline_pubkey);
 
     // Create nonce account

+ 24 - 14
cli/tests/transfer.rs

@@ -22,8 +22,9 @@ use solana_sdk::{
 fn test_transfer() {
     solana_logger::setup();
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -38,7 +39,7 @@ fn test_transfer() {
     let sender_pubkey = config.signers[0].pubkey();
     let recipient_pubkey = Pubkey::new(&[1u8; 32]);
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
     check_recent_balance(50_000, &rpc_client, &sender_pubkey);
     check_recent_balance(0, &rpc_client, &recipient_pubkey);
 
@@ -94,7 +95,7 @@ fn test_transfer() {
     process_command(&offline).unwrap_err();
 
     let offline_pubkey = offline.signers[0].pubkey();
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, 50).unwrap();
     check_recent_balance(50, &rpc_client, &offline_pubkey);
 
     // Offline transfer
@@ -273,8 +274,9 @@ fn test_transfer() {
 fn test_transfer_multisession_signing() {
     solana_logger::setup();
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let to_pubkey = Pubkey::new(&[1u8; 32]);
     let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
@@ -284,11 +286,16 @@ fn test_transfer_multisession_signing() {
     // Setup accounts
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_from_signer.pubkey(), 43)
-        .unwrap();
     request_and_confirm_airdrop(
         &rpc_client,
-        &faucet_addr,
+        &CliConfig::recent_for_tests(),
+        &offline_from_signer.pubkey(),
+        43,
+    )
+    .unwrap();
+    request_and_confirm_airdrop(
+        &rpc_client,
+        &CliConfig::recent_for_tests(),
         &offline_fee_payer_signer.pubkey(),
         3,
     )
@@ -394,8 +401,9 @@ fn test_transfer_multisession_signing() {
 fn test_transfer_all() {
     solana_logger::setup();
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -409,7 +417,7 @@ fn test_transfer_all() {
     let sender_pubkey = config.signers[0].pubkey();
     let recipient_pubkey = Pubkey::new(&[1u8; 32]);
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
     check_recent_balance(50_000, &rpc_client, &sender_pubkey);
     check_recent_balance(0, &rpc_client, &recipient_pubkey);
 
@@ -441,8 +449,9 @@ fn test_transfer_all() {
 fn test_transfer_unfunded_recipient() {
     solana_logger::setup();
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -456,7 +465,7 @@ fn test_transfer_unfunded_recipient() {
     let sender_pubkey = config.signers[0].pubkey();
     let recipient_pubkey = Pubkey::new(&[1u8; 32]);
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
     check_recent_balance(50_000, &rpc_client, &sender_pubkey);
     check_recent_balance(0, &rpc_client, &recipient_pubkey);
 
@@ -488,8 +497,9 @@ fn test_transfer_unfunded_recipient() {
 fn test_transfer_with_seed() {
     solana_logger::setup();
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -511,8 +521,8 @@ fn test_transfer_with_seed() {
     )
     .unwrap();
 
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 1).unwrap();
-    request_and_confirm_airdrop(&rpc_client, &faucet_addr, &derived_address, 50_000).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 1).unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &derived_address, 50_000).unwrap();
     check_recent_balance(1, &rpc_client, &sender_pubkey);
     check_recent_balance(50_000, &rpc_client, &derived_address);
     check_recent_balance(0, &rpc_client, &recipient_pubkey);

+ 4 - 8
cli/tests/vote.rs

@@ -19,8 +19,9 @@ use solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersion
 #[test]
 fn test_vote_authorize_and_withdraw() {
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let mint_pubkey = mint_keypair.pubkey();
     let faucet_addr = run_local_faucet(mint_keypair, None);
+    let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
 
     let rpc_client =
         RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
@@ -30,13 +31,8 @@ fn test_vote_authorize_and_withdraw() {
     config.json_rpc_url = test_validator.rpc_url();
     config.signers = vec![&default_signer];
 
-    request_and_confirm_airdrop(
-        &rpc_client,
-        &faucet_addr,
-        &config.signers[0].pubkey(),
-        100_000,
-    )
-    .unwrap();
+    request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
+        .unwrap();
 
     // Create vote account
     let vote_account_keypair = Keypair::new();

+ 1 - 0
client/src/mock_sender.rs

@@ -124,6 +124,7 @@ impl RpcSender for MockSender {
             }
             RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
             RpcRequest::GetSlot => Value::Number(Number::from(0)),
+            RpcRequest::RequestAirdrop => Value::String(Signature::new(&[8; 64]).to_string()),
             RpcRequest::SendTransaction => {
                 let signature = if self.url == "malicious" {
                     Signature::new(&[8; 64]).to_string()

+ 80 - 12
client/src/rpc_client.rs

@@ -7,8 +7,8 @@ use {
         rpc_config::{
             RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, RpcEpochConfig,
             RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig,
-            RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcSimulateTransactionConfig,
-            RpcTokenAccountsFilter,
+            RpcProgramAccountsConfig, RpcRequestAirdropConfig, RpcSendTransactionConfig,
+            RpcSimulateTransactionConfig, RpcTokenAccountsFilter,
         },
         rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter},
         rpc_response::*,
@@ -1356,6 +1356,64 @@ impl RpcClient {
         )
     }
 
+    pub fn request_airdrop(&self, pubkey: &Pubkey, lamports: u64) -> ClientResult<Signature> {
+        self.request_airdrop_with_config(
+            pubkey,
+            lamports,
+            RpcRequestAirdropConfig {
+                commitment: Some(self.commitment_config),
+                ..RpcRequestAirdropConfig::default()
+            },
+        )
+    }
+
+    pub fn request_airdrop_with_blockhash(
+        &self,
+        pubkey: &Pubkey,
+        lamports: u64,
+        recent_blockhash: &Hash,
+    ) -> ClientResult<Signature> {
+        self.request_airdrop_with_config(
+            pubkey,
+            lamports,
+            RpcRequestAirdropConfig {
+                commitment: Some(self.commitment_config),
+                recent_blockhash: Some(recent_blockhash.to_string()),
+            },
+        )
+    }
+
+    pub fn request_airdrop_with_config(
+        &self,
+        pubkey: &Pubkey,
+        lamports: u64,
+        config: RpcRequestAirdropConfig,
+    ) -> ClientResult<Signature> {
+        let commitment = config.commitment.unwrap_or_default();
+        let commitment = self.maybe_map_commitment(commitment)?;
+        let config = RpcRequestAirdropConfig {
+            commitment: Some(commitment),
+            ..config
+        };
+        self.send(
+            RpcRequest::RequestAirdrop,
+            json!([pubkey.to_string(), lamports, config]),
+        )
+        .and_then(|signature: String| {
+            Signature::from_str(&signature).map_err(|err| {
+                ClientErrorKind::Custom(format!("signature deserialization failed: {}", err)).into()
+            })
+        })
+        .map_err(|_| {
+            RpcError::ForUser(
+                "airdrop request failed. \
+                This can happen when the rate limit is reached."
+                    .to_string(),
+            )
+            .into()
+        })
+    }
+
     fn poll_balance_with_timeout_and_commitment(
         &self,
         pubkey: &Pubkey,
@@ -1557,6 +1615,24 @@ impl RpcClient {
         commitment: CommitmentConfig,
         config: RpcSendTransactionConfig,
     ) -> ClientResult<Signature> {
+        let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
+            self.get_recent_blockhash_with_commitment(CommitmentConfig::processed())?
+                .value
+                .0
+        } else {
+            transaction.message.recent_blockhash
+        };
+        let signature = self.send_transaction_with_config(transaction, config)?;
+        self.confirm_transaction_with_spinner(&signature, &recent_blockhash, commitment)?;
+        Ok(signature)
+    }
+
+    pub fn confirm_transaction_with_spinner(
+        &self,
+        signature: &Signature,
+        recent_blockhash: &Hash,
+        commitment: CommitmentConfig,
+    ) -> ClientResult<()> {
         let desired_confirmations = if commitment.is_finalized() {
             MAX_LOCKOUT_HISTORY + 1
         } else {
@@ -1568,16 +1644,8 @@ impl RpcClient {
 
         progress_bar.set_message(&format!(
             "[{}/{}] Finalizing transaction {}",
-            confirmations, desired_confirmations, transaction.signatures[0],
+            confirmations, desired_confirmations, signature,
         ));
-        let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
-            self.get_recent_blockhash_with_commitment(CommitmentConfig::processed())?
-                .value
-                .0
-        } else {
-            transaction.message.recent_blockhash
-        };
-        let signature = self.send_transaction_with_config(transaction, config)?;
         let (signature, status) = loop {
             // Get recent commitment in order to count confirmations for successful transactions
             let status = self
@@ -1624,7 +1692,7 @@ impl RpcClient {
             {
                 progress_bar.set_message("Transaction confirmed");
                 progress_bar.finish_and_clear();
-                return Ok(signature);
+                return Ok(());
             }
 
             progress_bar.set_message(&format!(

+ 8 - 0
client/src/rpc_config.rs

@@ -33,6 +33,14 @@ pub struct RpcSimulateTransactionConfig {
     pub encoding: Option<UiTransactionEncoding>,
 }
 
+#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RpcRequestAirdropConfig {
+    pub recent_blockhash: Option<String>, // base-58 encoded blockhash
+    #[serde(flatten)]
+    pub commitment: Option<CommitmentConfig>,
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub enum RpcLargestAccountsFilter {

+ 17 - 11
core/src/rpc.rs

@@ -1754,6 +1754,12 @@ fn verify_pubkey(input: String) -> Result<Pubkey> {
         .map_err(|e| Error::invalid_params(format!("Invalid param: {:?}", e)))
 }
 
+fn verify_hash(input: String) -> Result<Hash> {
+    input
+        .parse()
+        .map_err(|e| Error::invalid_params(format!("Invalid param: {:?}", e)))
+}
+
 fn verify_signature(input: &str) -> Result<Signature> {
     input
         .parse()
@@ -2355,7 +2361,7 @@ pub mod rpc_full {
             meta: Self::Metadata,
             pubkey_str: String,
             lamports: u64,
-            commitment: Option<CommitmentConfig>,
+            config: Option<RpcRequestAirdropConfig>,
         ) -> Result<String>;
 
         #[rpc(meta, name = "sendTransaction")]
@@ -2786,28 +2792,28 @@ pub mod rpc_full {
             meta: Self::Metadata,
             pubkey_str: String,
             lamports: u64,
-            commitment: Option<CommitmentConfig>,
+            config: Option<RpcRequestAirdropConfig>,
         ) -> Result<String> {
             debug!("request_airdrop rpc request received");
             trace!(
-                "request_airdrop id={} lamports={} commitment: {:?}",
+                "request_airdrop id={} lamports={} config: {:?}",
                 pubkey_str,
                 lamports,
-                &commitment
+                &config
             );
 
             let faucet_addr = meta.config.faucet_addr.ok_or_else(Error::invalid_request)?;
             let pubkey = verify_pubkey(pubkey_str)?;
 
-            let (blockhash, last_valid_slot) = {
-                let bank = meta.bank(commitment);
+            let config = config.unwrap_or_default();
+            let bank = meta.bank(config.commitment);
 
-                let blockhash = bank.confirmed_last_blockhash().0;
-                (
-                    blockhash,
-                    bank.get_blockhash_last_valid_slot(&blockhash).unwrap_or(0),
-                )
+            let blockhash = if let Some(blockhash) = config.recent_blockhash {
+                verify_hash(blockhash)?
+            } else {
+                bank.confirmed_last_blockhash().0
             };
+            let last_valid_slot = bank.get_blockhash_last_valid_slot(&blockhash).unwrap_or(0);
 
             let transaction =
                 request_airdrop_transaction(&faucet_addr, &pubkey, lamports, blockhash).map_err(

+ 15 - 2
core/src/test_validator.rs

@@ -95,6 +95,11 @@ impl TestValidatorGenesis {
         self
     }
 
+    pub fn faucet_addr(&mut self, faucet_addr: Option<SocketAddr>) -> &mut Self {
+        self.rpc_config.faucet_addr = faucet_addr;
+        self
+    }
+
     pub fn warp_slot(&mut self, warp_slot: Slot) -> &mut Self {
         self.warp_slot = Some(warp_slot);
         self
@@ -244,9 +249,10 @@ pub struct TestValidator {
 
 impl TestValidator {
     /// Create and start a `TestValidator` with no transaction fees and minimal rent.
+    /// Faucet optional.
     ///
     /// This function panics on initialization failure.
-    pub fn with_no_fees(mint_address: Pubkey) -> Self {
+    pub fn with_no_fees(mint_address: Pubkey, faucet_addr: Option<SocketAddr>) -> Self {
         TestValidatorGenesis::default()
             .fee_rate_governor(FeeRateGovernor::new(0, 0))
             .rent(Rent {
@@ -254,14 +260,20 @@ impl TestValidator {
                 exemption_threshold: 1.0,
                 ..Rent::default()
             })
+            .faucet_addr(faucet_addr)
             .start_with_mint_address(mint_address)
             .expect("validator start failed")
     }
 
     /// Create and start a `TestValidator` with custom transaction fees and minimal rent.
+    /// Faucet optional.
     ///
     /// This function panics on initialization failure.
-    pub fn with_custom_fees(mint_address: Pubkey, target_lamports_per_signature: u64) -> Self {
+    pub fn with_custom_fees(
+        mint_address: Pubkey,
+        target_lamports_per_signature: u64,
+        faucet_addr: Option<SocketAddr>,
+    ) -> Self {
         TestValidatorGenesis::default()
             .fee_rate_governor(FeeRateGovernor::new(target_lamports_per_signature, 0))
             .rent(Rent {
@@ -269,6 +281,7 @@ impl TestValidator {
                 exemption_threshold: 1.0,
                 ..Rent::default()
             })
+            .faucet_addr(faucet_addr)
             .start_with_mint_address(mint_address)
             .expect("validator start failed")
     }

+ 1 - 1
core/tests/client.rs

@@ -34,7 +34,7 @@ fn test_rpc_client() {
     solana_logger::setup();
 
     let alice = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(alice.pubkey());
+    let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
 
     let bob_pubkey = solana_sdk::pubkey::new_rand();
 

+ 4 - 4
core/tests/rpc.rs

@@ -55,7 +55,7 @@ fn test_rpc_send_tx() {
     solana_logger::setup();
 
     let alice = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(alice.pubkey());
+    let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
     let rpc_url = test_validator.rpc_url();
 
     let bob_pubkey = solana_sdk::pubkey::new_rand();
@@ -119,7 +119,7 @@ fn test_rpc_invalid_requests() {
     solana_logger::setup();
 
     let alice = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(alice.pubkey());
+    let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
     let rpc_url = test_validator.rpc_url();
 
     let bob_pubkey = solana_sdk::pubkey::new_rand();
@@ -150,7 +150,7 @@ fn test_rpc_invalid_requests() {
 fn test_rpc_slot_updates() {
     solana_logger::setup();
 
-    let test_validator = TestValidator::with_no_fees(Pubkey::new_unique());
+    let test_validator = TestValidator::with_no_fees(Pubkey::new_unique(), None);
 
     // Create the pub sub runtime
     let rt = Runtime::new().unwrap();
@@ -215,7 +215,7 @@ fn test_rpc_subscriptions() {
     solana_logger::setup();
 
     let alice = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(alice.pubkey());
+    let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
 
     let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
     transactions_socket.connect(test_validator.tpu()).unwrap();

+ 8 - 8
tokens/src/commands.rs

@@ -1046,7 +1046,7 @@ mod tests {
     #[test]
     fn test_process_token_allocations() {
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_no_fees(alice.pubkey());
+        let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
         let url = test_validator.rpc_url();
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
@@ -1056,7 +1056,7 @@ mod tests {
     #[test]
     fn test_process_transfer_amount_allocations() {
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_no_fees(alice.pubkey());
+        let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
         let url = test_validator.rpc_url();
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
@@ -1066,7 +1066,7 @@ mod tests {
     #[test]
     fn test_process_stake_allocations() {
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_no_fees(alice.pubkey());
+        let test_validator = TestValidator::with_no_fees(alice.pubkey(), None);
         let url = test_validator.rpc_url();
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
@@ -1381,7 +1381,7 @@ mod tests {
         let fees_in_sol = lamports_to_sol(fees);
 
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees);
+        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees, None);
         let url = test_validator.rpc_url();
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
@@ -1464,7 +1464,7 @@ mod tests {
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees);
+        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees, None);
         let url = test_validator.rpc_url();
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
@@ -1574,7 +1574,7 @@ mod tests {
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees);
+        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees, None);
         let url = test_validator.rpc_url();
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
 
@@ -1683,7 +1683,7 @@ mod tests {
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         let alice = Keypair::new();
-        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees);
+        let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees, None);
         let url = test_validator.rpc_url();
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
@@ -1998,7 +1998,7 @@ mod tests {
     #[test]
     fn test_distribute_allocations_dump_db() {
         let sender_keypair = Keypair::new();
-        let test_validator = TestValidator::with_no_fees(sender_keypair.pubkey());
+        let test_validator = TestValidator::with_no_fees(sender_keypair.pubkey(), None);
         let url = test_validator.rpc_url();
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
 

+ 1 - 1
tokens/tests/commands.rs

@@ -8,7 +8,7 @@ fn test_process_distribute_with_rpc_client() {
     solana_logger::setup();
 
     let mint_keypair = Keypair::new();
-    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
+    let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey(), None);
 
     let client = RpcClient::new(test_validator.rpc_url());
     test_process_distribute_tokens_with_client(&client, mint_keypair, None);