Add a client for BankForks (#10728)

Also:
* Use BanksClient in solana-tokens
This commit is contained in:
Greg Fitzgerald
2020-08-07 08:45:17 -06:00
committed by GitHub
parent 4f2f9bd26f
commit bad486823c
29 changed files with 1239 additions and 342 deletions

View File

@ -1,18 +1,20 @@
use crate::args::{BalancesArgs, DistributeTokensArgs, StakeArgs, TransactionLogArgs};
use crate::db::{self, TransactionInfo};
use crate::thin_client::{Client, ThinClient};
use console::style;
use csv::{ReaderBuilder, Trim};
use indexmap::IndexMap;
use indicatif::{ProgressBar, ProgressStyle};
use pickledb::PickleDb;
use serde::{Deserialize, Serialize};
use solana_banks_client::{BanksClient, BanksClientExt};
use solana_sdk::{
commitment_config::CommitmentLevel,
message::Message,
native_token::{lamports_to_sol, sol_to_lamports},
signature::{unique_signers, Signature, Signer},
system_instruction,
transport::TransportError,
transaction::Transaction,
transport::{self, TransportError},
};
use solana_stake_program::{
stake_instruction,
@ -21,9 +23,9 @@ use solana_stake_program::{
use std::{
cmp::{self},
io,
thread::sleep,
time::Duration,
};
use tokio::time::delay_for;
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Bid {
@ -92,8 +94,25 @@ fn create_allocation(bid: &Bid, dollars_per_sol: f64) -> Allocation {
}
}
fn distribute_tokens(
client: &ThinClient,
async fn transfer<S: Signer>(
client: &mut BanksClient,
lamports: u64,
sender_keypair: &S,
to_pubkey: &Pubkey,
) -> io::Result<Transaction> {
let create_instruction =
system_instruction::transfer(&sender_keypair.pubkey(), &to_pubkey, lamports);
let message = Message::new(&[create_instruction], Some(&sender_keypair.pubkey()));
let recent_blockhash = client.get_recent_blockhash().await?;
Ok(Transaction::new(
&[sender_keypair],
message,
recent_blockhash,
))
}
async fn distribute_tokens(
client: &mut BanksClient,
db: &mut PickleDb,
allocations: &[Allocation],
args: &DistributeTokensArgs,
@ -159,7 +178,17 @@ fn distribute_tokens(
let fee_payer_pubkey = args.fee_payer.pubkey();
let message = Message::new(&instructions, Some(&fee_payer_pubkey));
match client.send_and_confirm_message(message, &signers) {
let result: transport::Result<(Transaction, u64)> = {
if args.dry_run {
Ok((Transaction::new_unsigned(message), std::u64::MAX))
} else {
let (_fee_calculator, blockhash, last_valid_slot) = client.get_fees().await?;
let transaction = Transaction::new(&signers, message, blockhash);
client.send_transaction(transaction.clone()).await?;
Ok((transaction, last_valid_slot))
}
};
match result {
Ok((transaction, last_valid_slot)) => {
db::set_transaction_info(
db,
@ -206,8 +235,8 @@ fn new_spinner_progress_bar() -> ProgressBar {
progress_bar
}
pub fn process_distribute_tokens(
client: &ThinClient,
pub async fn process_distribute_tokens(
client: &mut BanksClient,
args: &DistributeTokensArgs,
) -> Result<Option<usize>, Error> {
let mut allocations: Vec<Allocation> =
@ -230,7 +259,7 @@ pub fn process_distribute_tokens(
let mut db = db::open_db(&args.transaction_db, args.dry_run)?;
// Start by finalizing any transactions from the previous run.
let confirmations = finalize_transactions(client, &mut db, args.dry_run)?;
let confirmations = finalize_transactions(client, &mut db, args.dry_run).await?;
let transaction_infos = db::read_transaction_infos(&db);
apply_previous_transactions(&mut allocations, &transaction_infos);
@ -284,14 +313,14 @@ pub fn process_distribute_tokens(
);
}
distribute_tokens(client, &mut db, &allocations, args)?;
distribute_tokens(client, &mut db, &allocations, args).await?;
let opt_confirmations = finalize_transactions(client, &mut db, args.dry_run)?;
let opt_confirmations = finalize_transactions(client, &mut db, args.dry_run).await?;
Ok(opt_confirmations)
}
fn finalize_transactions(
client: &ThinClient,
async fn finalize_transactions(
client: &mut BanksClient,
db: &mut PickleDb,
dry_run: bool,
) -> Result<Option<usize>, Error> {
@ -299,7 +328,7 @@ fn finalize_transactions(
return Ok(None);
}
let mut opt_confirmations = update_finalized_transactions(client, db)?;
let mut opt_confirmations = update_finalized_transactions(client, db).await?;
let progress_bar = new_spinner_progress_bar();
@ -312,8 +341,8 @@ fn finalize_transactions(
}
// Sleep for about 1 slot
sleep(Duration::from_millis(500));
let opt_conf = update_finalized_transactions(client, db)?;
delay_for(Duration::from_millis(500)).await;
let opt_conf = update_finalized_transactions(client, db).await?;
opt_confirmations = opt_conf;
}
@ -322,8 +351,8 @@ fn finalize_transactions(
// Update the finalized bit on any transactions that are now rooted
// Return the lowest number of confirmations on the unfinalized transactions or None if all are finalized.
fn update_finalized_transactions(
client: &ThinClient,
async fn update_finalized_transactions(
client: &mut BanksClient,
db: &mut PickleDb,
) -> Result<Option<usize>, Error> {
let transaction_infos = db::read_transaction_infos(db);
@ -342,8 +371,10 @@ fn update_finalized_transactions(
.map(|(tx, _slot)| tx.signatures[0])
.filter(|sig| *sig != Signature::default()) // Filter out dry-run signatures
.collect();
let transaction_statuses = client.get_signature_statuses(&unconfirmed_signatures)?;
let root_slot = client.get_slot()?;
let transaction_statuses = client
.get_transaction_statuses(unconfirmed_signatures)
.await?;
let root_slot = client.get_root_slot().await?;
let mut confirmations = None;
for ((transaction, last_valid_slot), opt_transaction_status) in unconfirmed_transactions
@ -368,7 +399,10 @@ fn update_finalized_transactions(
Ok(confirmations)
}
pub fn process_balances(client: &ThinClient, args: &BalancesArgs) -> Result<(), csv::Error> {
pub async fn process_balances(
client: &mut BanksClient,
args: &BalancesArgs,
) -> Result<(), csv::Error> {
let allocations: Vec<Allocation> =
read_allocations(&args.input_csv, args.from_bids, args.dollars_per_sol);
let allocations = merge_allocations(&allocations);
@ -385,7 +419,7 @@ pub fn process_balances(client: &ThinClient, args: &BalancesArgs) -> Result<(),
for allocation in &allocations {
let address = allocation.recipient.parse().unwrap();
let expected = lamports_to_sol(sol_to_lamports(allocation.amount));
let actual = lamports_to_sol(client.get_balance(&address).unwrap());
let actual = lamports_to_sol(client.get_balance(address).await.unwrap());
println!(
"{:<44} {:>24.9} {:>24.9} {:>24.9}",
allocation.recipient,
@ -406,15 +440,30 @@ pub fn process_transaction_log(args: &TransactionLogArgs) -> Result<(), Error> {
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use tempfile::{tempdir, NamedTempFile};
pub fn test_process_distribute_tokens_with_client<C: Client>(client: C, sender_keypair: Keypair) {
let thin_client = ThinClient::new(client, false);
pub async fn test_process_distribute_tokens_with_client(
client: &mut BanksClient,
sender_keypair: Keypair,
) {
let fee_payer = Keypair::new();
let (transaction, _last_valid_slot) = thin_client
.transfer(sol_to_lamports(1.0), &sender_keypair, &fee_payer.pubkey())
.unwrap();
thin_client
.poll_for_confirmation(&transaction.signatures[0])
let transaction = transfer(
client,
sol_to_lamports(1.0),
&sender_keypair,
&fee_payer.pubkey(),
)
.await
.unwrap();
client
.process_transaction_with_commitment(transaction, CommitmentLevel::Recent)
.await
.unwrap();
assert_eq!(
client
.get_balance_with_commitment(fee_payer.pubkey(), CommitmentLevel::Recent)
.await
.unwrap(),
sol_to_lamports(1.0),
);
let alice_pubkey = Pubkey::new_rand();
let allocation = Allocation {
@ -445,7 +494,7 @@ pub fn test_process_distribute_tokens_with_client<C: Client>(client: C, sender_k
dollars_per_sol: None,
stake_args: None,
};
let confirmations = process_distribute_tokens(&thin_client, &args).unwrap();
let confirmations = process_distribute_tokens(client, &args).await.unwrap();
assert_eq!(confirmations, None);
let transaction_infos =
@ -459,12 +508,12 @@ pub fn test_process_distribute_tokens_with_client<C: Client>(client: C, sender_k
);
assert_eq!(
thin_client.get_balance(&alice_pubkey).unwrap(),
client.get_balance(alice_pubkey).await.unwrap(),
expected_amount,
);
// Now, run it again, and check there's no double-spend.
process_distribute_tokens(&thin_client, &args).unwrap();
process_distribute_tokens(client, &args).await.unwrap();
let transaction_infos =
db::read_transaction_infos(&db::open_db(&transaction_db, true).unwrap());
assert_eq!(transaction_infos.len(), 1);
@ -476,19 +525,27 @@ pub fn test_process_distribute_tokens_with_client<C: Client>(client: C, sender_k
);
assert_eq!(
thin_client.get_balance(&alice_pubkey).unwrap(),
client.get_balance(alice_pubkey).await.unwrap(),
expected_amount,
);
}
pub fn test_process_distribute_stake_with_client<C: Client>(client: C, sender_keypair: Keypair) {
let thin_client = ThinClient::new(client, false);
pub async fn test_process_distribute_stake_with_client(
client: &mut BanksClient,
sender_keypair: Keypair,
) {
let fee_payer = Keypair::new();
let (transaction, _last_valid_slot) = thin_client
.transfer(sol_to_lamports(1.0), &sender_keypair, &fee_payer.pubkey())
.unwrap();
thin_client
.poll_for_confirmation(&transaction.signatures[0])
let transaction = transfer(
client,
sol_to_lamports(1.0),
&sender_keypair,
&fee_payer.pubkey(),
)
.await
.unwrap();
client
.process_transaction_with_commitment(transaction, CommitmentLevel::Recent)
.await
.unwrap();
let stake_account_keypair = Keypair::new();
@ -510,8 +567,11 @@ pub fn test_process_distribute_stake_with_client<C: Client>(client: C, sender_ke
);
let message = Message::new(&instructions, Some(&sender_keypair.pubkey()));
let signers = [&sender_keypair, &stake_account_keypair];
thin_client
.send_and_confirm_message(message, &signers)
let blockhash = client.get_recent_blockhash().await.unwrap();
let transaction = Transaction::new(&signers, message, blockhash);
client
.process_transaction_with_commitment(transaction, CommitmentLevel::Recent)
.await
.unwrap();
let alice_pubkey = Pubkey::new_rand();
@ -549,7 +609,7 @@ pub fn test_process_distribute_stake_with_client<C: Client>(client: C, sender_ke
sender_keypair: Box::new(sender_keypair),
dollars_per_sol: None,
};
let confirmations = process_distribute_tokens(&thin_client, &args).unwrap();
let confirmations = process_distribute_tokens(client, &args).await.unwrap();
assert_eq!(confirmations, None);
let transaction_infos =
@ -563,17 +623,17 @@ pub fn test_process_distribute_stake_with_client<C: Client>(client: C, sender_ke
);
assert_eq!(
thin_client.get_balance(&alice_pubkey).unwrap(),
client.get_balance(alice_pubkey).await.unwrap(),
sol_to_lamports(1.0),
);
let new_stake_account_address = transaction_infos[0].new_stake_account_address.unwrap();
assert_eq!(
thin_client.get_balance(&new_stake_account_address).unwrap(),
client.get_balance(new_stake_account_address).await.unwrap(),
expected_amount - sol_to_lamports(1.0),
);
// Now, run it again, and check there's no double-spend.
process_distribute_tokens(&thin_client, &args).unwrap();
process_distribute_tokens(client, &args).await.unwrap();
let transaction_infos =
db::read_transaction_infos(&db::open_db(&transaction_db, true).unwrap());
assert_eq!(transaction_infos.len(), 1);
@ -585,11 +645,11 @@ pub fn test_process_distribute_stake_with_client<C: Client>(client: C, sender_ke
);
assert_eq!(
thin_client.get_balance(&alice_pubkey).unwrap(),
client.get_balance(alice_pubkey).await.unwrap(),
sol_to_lamports(1.0),
);
assert_eq!(
thin_client.get_balance(&new_stake_account_address).unwrap(),
client.get_balance(new_stake_account_address).await.unwrap(),
expected_amount - sol_to_lamports(1.0),
);
}
@ -597,23 +657,33 @@ pub fn test_process_distribute_stake_with_client<C: Client>(client: C, sender_ke
#[cfg(test)]
mod tests {
use super::*;
use solana_runtime::{bank::Bank, bank_client::BankClient};
use solana_banks_client::start_client;
use solana_banks_server::banks_server::start_local_server;
use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_sdk::genesis_config::create_genesis_config;
use std::sync::{Arc, RwLock};
use tokio::runtime::Runtime;
#[test]
fn test_process_distribute_tokens() {
let (genesis_config, sender_keypair) = create_genesis_config(sol_to_lamports(9_000_000.0));
let bank = Bank::new(&genesis_config);
let bank_client = BankClient::new(bank);
test_process_distribute_tokens_with_client(bank_client, sender_keypair);
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(&genesis_config))));
Runtime::new().unwrap().block_on(async {
let transport = start_local_server(&bank_forks).await;
let mut banks_client = start_client(transport).await.unwrap();
test_process_distribute_tokens_with_client(&mut banks_client, sender_keypair).await;
});
}
#[test]
fn test_process_distribute_stake() {
let (genesis_config, sender_keypair) = create_genesis_config(sol_to_lamports(9_000_000.0));
let bank = Bank::new(&genesis_config);
let bank_client = BankClient::new(bank);
test_process_distribute_stake_with_client(bank_client, sender_keypair);
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(&genesis_config))));
Runtime::new().unwrap().block_on(async {
let transport = start_local_server(&bank_forks).await;
let mut banks_client = start_client(transport).await.unwrap();
test_process_distribute_stake_with_client(&mut banks_client, sender_keypair).await;
});
}
#[test]

View File

@ -1,8 +1,8 @@
use chrono::prelude::*;
use pickledb::{error::Error, PickleDb, PickleDbDumpPolicy};
use serde::{Deserialize, Serialize};
use solana_banks_client::TransactionStatus;
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature, transaction::Transaction};
use solana_transaction_status::TransactionStatus;
use std::{cmp::Ordering, fs, io, path::Path};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
@ -155,7 +155,7 @@ pub fn update_finalized_transaction(
return Ok(Some(confirmations));
}
if let Err(e) = &transaction_status.status {
if let Some(e) = &transaction_status.err {
// The transaction was finalized, but execution failed. Drop it.
eprintln!(
"Error in transaction with signature {}: {}",
@ -273,7 +273,6 @@ mod tests {
let transaction_status = TransactionStatus {
slot: 0,
confirmations: Some(1),
status: Ok(()),
err: None,
};
assert_eq!(
@ -297,12 +296,10 @@ mod tests {
let signature = Signature::default();
let transaction_info = TransactionInfo::default();
db.set(&signature.to_string(), &transaction_info).unwrap();
let status = Err(TransactionError::AccountNotFound);
let transaction_status = TransactionStatus {
slot: 0,
confirmations: None,
status,
err: None,
err: Some(TransactionError::AccountNotFound),
};
assert_eq!(
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
@ -325,7 +322,6 @@ mod tests {
let transaction_status = TransactionStatus {
slot: 0,
confirmations: None,
status: Ok(()),
err: None,
};
assert_eq!(

View File

@ -2,4 +2,3 @@ pub mod arg_parser;
pub mod args;
pub mod commands;
mod db;
pub mod thin_client;

View File

@ -1,11 +1,9 @@
use solana_cli_config::Config;
use solana_cli_config::CONFIG_FILE;
use solana_client::rpc_client::RpcClient;
use solana_tokens::{arg_parser::parse_args, args::Command, commands, thin_client::ThinClient};
use std::env;
use std::error::Error;
use std::path::Path;
use std::process;
use solana_banks_client::start_tcp_client;
use solana_cli_config::{Config, CONFIG_FILE};
use solana_tokens::{arg_parser::parse_args, args::Command, commands};
use std::{env, error::Error, path::Path, process};
use tokio::runtime::Runtime;
use url::Url;
fn main() -> Result<(), Box<dyn Error>> {
let command_args = parse_args(env::args_os())?;
@ -20,16 +18,22 @@ fn main() -> Result<(), Box<dyn Error>> {
Config::default()
};
let json_rpc_url = command_args.url.unwrap_or(config.json_rpc_url);
let client = RpcClient::new(json_rpc_url);
let rpc_banks_url = Config::compute_rpc_banks_url(&json_rpc_url);
let url = Url::parse(&rpc_banks_url)?;
let host_port = (url.host_str().unwrap(), url.port().unwrap());
let mut runtime = Runtime::new().unwrap();
let mut banks_client = runtime.block_on(start_tcp_client(&host_port))?;
match command_args.command {
Command::DistributeTokens(args) => {
let thin_client = ThinClient::new(client, args.dry_run);
commands::process_distribute_tokens(&thin_client, &args)?;
runtime.block_on(commands::process_distribute_tokens(
&mut banks_client,
&args,
))?;
}
Command::Balances(args) => {
let thin_client = ThinClient::new(client, false);
commands::process_balances(&thin_client, &args)?;
runtime.block_on(commands::process_balances(&mut banks_client, &args))?;
}
Command::TransactionLog(args) => {
commands::process_transaction_log(&args)?;

View File

@ -1,193 +0,0 @@
use solana_client::{rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig};
use solana_runtime::bank_client::BankClient;
use solana_sdk::{
account::Account,
client::{AsyncClient, SyncClient},
clock::Slot,
commitment_config::CommitmentConfig,
fee_calculator::FeeCalculator,
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::{Signature, Signer},
signers::Signers,
system_instruction,
transaction::Transaction,
transport::{Result, TransportError},
};
use solana_transaction_status::TransactionStatus;
pub trait Client {
fn send_transaction1(&self, transaction: Transaction) -> Result<Signature>;
fn get_signature_statuses1(
&self,
signatures: &[Signature],
) -> Result<Vec<Option<TransactionStatus>>>;
fn get_balance1(&self, pubkey: &Pubkey) -> Result<u64>;
fn get_fees1(&self) -> Result<(Hash, FeeCalculator, Slot)>;
fn get_slot1(&self) -> Result<Slot>;
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>>;
}
impl Client for RpcClient {
fn send_transaction1(&self, transaction: Transaction) -> Result<Signature> {
self.send_transaction_with_config(
&transaction,
RpcSendTransactionConfig {
skip_preflight: true,
},
)
.map_err(|e| TransportError::Custom(e.to_string()))
}
fn get_signature_statuses1(
&self,
signatures: &[Signature],
) -> Result<Vec<Option<TransactionStatus>>> {
self.get_signature_statuses(signatures)
.map(|response| response.value)
.map_err(|e| TransportError::Custom(e.to_string()))
}
fn get_balance1(&self, pubkey: &Pubkey) -> Result<u64> {
self.get_balance(pubkey)
.map_err(|e| TransportError::Custom(e.to_string()))
}
fn get_fees1(&self) -> Result<(Hash, FeeCalculator, Slot)> {
let result = self
.get_recent_blockhash_with_commitment(CommitmentConfig::default())
.map_err(|e| TransportError::Custom(e.to_string()))?;
Ok(result.value)
}
fn get_slot1(&self) -> Result<Slot> {
self.get_slot()
.map_err(|e| TransportError::Custom(e.to_string()))
}
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.get_account(pubkey)
.map(Some)
.map_err(|e| TransportError::Custom(e.to_string()))
}
}
impl Client for BankClient {
fn send_transaction1(&self, transaction: Transaction) -> Result<Signature> {
self.async_send_transaction(transaction)
}
fn get_signature_statuses1(
&self,
signatures: &[Signature],
) -> Result<Vec<Option<TransactionStatus>>> {
signatures
.iter()
.map(|signature| {
self.get_signature_status(signature).map(|opt| {
opt.map(|status| TransactionStatus {
slot: 0,
confirmations: None,
status,
err: None,
})
})
})
.collect()
}
fn get_balance1(&self, pubkey: &Pubkey) -> Result<u64> {
self.get_balance(pubkey)
}
fn get_fees1(&self) -> Result<(Hash, FeeCalculator, Slot)> {
self.get_recent_blockhash_with_commitment(CommitmentConfig::default())
}
fn get_slot1(&self) -> Result<Slot> {
self.get_slot()
}
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.get_account(pubkey)
}
}
pub struct ThinClient<'a> {
client: Box<dyn Client + 'a>,
dry_run: bool,
}
impl<'a> ThinClient<'a> {
pub fn new<C: Client + 'a>(client: C, dry_run: bool) -> Self {
Self {
client: Box::new(client),
dry_run,
}
}
pub fn send_transaction(&self, transaction: Transaction) -> Result<Signature> {
if self.dry_run {
return Ok(Signature::default());
}
self.client.send_transaction1(transaction)
}
pub fn poll_for_confirmation(&self, signature: &Signature) -> Result<()> {
while self.get_signature_statuses(&[*signature])?[0].is_none() {
std::thread::sleep(std::time::Duration::from_millis(500));
}
Ok(())
}
pub fn get_signature_statuses(
&self,
signatures: &[Signature],
) -> Result<Vec<Option<TransactionStatus>>> {
self.client.get_signature_statuses1(signatures)
}
pub fn send_and_confirm_message<S: Signers>(
&self,
message: Message,
signers: &S,
) -> Result<(Transaction, Slot)> {
if self.dry_run {
return Ok((Transaction::new_unsigned(message), std::u64::MAX));
}
let (blockhash, _fee_caluclator, last_valid_slot) = self.get_fees()?;
let transaction = Transaction::new(signers, message, blockhash);
self.send_transaction(transaction.clone())?;
Ok((transaction, last_valid_slot))
}
pub fn transfer<S: Signer>(
&self,
lamports: u64,
sender_keypair: &S,
to_pubkey: &Pubkey,
) -> Result<(Transaction, u64)> {
let create_instruction =
system_instruction::transfer(&sender_keypair.pubkey(), &to_pubkey, lamports);
let message = Message::new(&[create_instruction], Some(&sender_keypair.pubkey()));
self.send_and_confirm_message(message, &[sender_keypair])
}
pub fn get_fees(&self) -> Result<(Hash, FeeCalculator, Slot)> {
self.client.get_fees1()
}
pub fn get_slot(&self) -> Result<Slot> {
self.client.get_slot1()
}
pub fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
self.client.get_balance1(pubkey)
}
pub fn get_account(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.client.get_account1(pubkey)
}
}