Add solana-tokens (#10011)

* Initial commit

* Execute transfers

* Refactor for testing

* Cleanup readme

* Rewrite

* Cleanup

* Cleanup

* Cleanup client

* Use a Null Client to move prints closer to where messages are sent

* Upgrade Solana

* Move core functionality into its own module

* Handle transaction errors

* Merge allocations

* Fixes

* Cleanup readme

* Fix markdown

* Add example input

* Add integration test - currently fails

* Add integration test

* Add metrics

* Use RpcClient in dry-run, just don't send messages

* More metrics

* Fix dry run with no keys

* Only require one approval if fee-payer is the sender keypair

* Fix bugs

* Don't create the transaction log if nothing to put into it;
  otherwise the next innvocation won't add the header

* Apply previous transactions to allocations with matching recipients

* Bail out of any account already has a balance

* Polish

* Add new 'balances' command

* 9 decimal places

* Add missing file

* Better dry-run; keypair options now optional

* Change field name from 'bid' to 'accepted'

Also, tolerate precision change from 2 decimal places to 4

* Write to transaction log immediately

* Rename allocations_csv to bids_csv

So that we can bypass bids_csv with an allocations CSV file

* Upgrade Solana

* Remove faucet from integration test

* Cleaner integration test

Won't work until this lands and is released:

https://github.com/solana-labs/solana/pull/9717

* Update README

* Add TravicCI script to build and test (#1)

* Add distribute-stake command (#2)

* Distribute -> DistributeTokens (#3)

* Cache cargo deps (#4)

* Add docs (#5)

* Switch to latest Solana 1.1 release (#7)

* distribute -> distribute-tokens (#9)

* Switch from CSV to a pickledb database (#8)

* Switch from CSV to a pickledb database

* Allow PickleDb errors to bubble up

* Dedup

* Hoist db

* Add finalized field to TransactionInfo

* Don't allow RPC client to resign transactions

* Remove dead code

* Use transport::Result

* Record unconfirmed transaction

* Fix: separate stake account per allocation

* Catch transport errors

* Panic if we attempt to replay a transaction that hasn't been finalized

* Attempt to fix CI

PickleDb isn't calling flush() or close() after writing to files.
No issue on MacOS, but looks racy in CI.

* Revert "Attempt to fix CI"

This reverts commit 1632394f636c54402b3578120e8817dd1660e19b.

* Poll for signature before returning

* Add --sol-for-fees option for stake distributions

* Add --allocations-csv option (#14)

* Add allocations-csv option

* Add tests or GTFO

* Apply review feedback

* apply feedback

* Add read_allocations function

* Update arg_parser.rs

* Fix balances command (#17)

* Fix balances command

* Fix readme

* Add --force to transfer to non-empty accounts (#18)

* Add --no-wait (#16)

* Add ThinClient methods to implement --no-wait

* Plumb --no-wait through

No tests yet

* Check transaction status on startup

* Easier to test

* Wait until transaction is finalized before checking if it failed with an error

It's possible that a minority fork thinks it failed.

* Add unit tests

* Remove dead code and rustfmt

* Don't flush database to file if doing a dry-run

* Continue when transactions not yet finalized (#20)

If those transactions are dropped, the next run will execute them.

* Return the number of confirmations (#21)

* Add read_allocations() unit-test (#22)

Delete the copy-pasted top-level test.

Fixes #19

* Add a CSV printer (#23)

* Remove all the copypasta (#24)

* Move resolve_distribute_stake_args into its own function

* Add stake args to token args

* Unify option names

* Move Command::DistributeStake into DistributeTokens

* Remove process_distribute_stake

* Only unique signers

* Use sender keypair to fund new fee-payer accounts

* Unify distribute_tokens and distribute_stake

* Rename print-database command to transaction-log (#25)

* Send all transactions as quickly as possible, then wait (#26)

* Send all transactions as quickly as possible, then wait

* Exit when finalized or blockhashes have expired

* Don't need blockhash in the CSV output

* Better types

CSV library was choking on Pubkey as a type. PickleDb doesn't have that problem.

* Resend if blockhash has not expired

* Attempt to fix CI

* Move log to stderr

* Add constructor, tuck away client (#30)

* Add constructor, tuck away client

* Fix unwrap() caught by CI

* Fix optional option flagged as required

* Bunch of cleanup (#31)

* Remove untested --no-wait feature

* Make --transactions-db an option, not an arg

So that in the future, we can make it optional

* Remove more untested features

Too many false positives in that santity check.  Use --dry-run
instead.

* Add dry-run mode to ThinClient

* Cleaner dry-run

* Make key parameters required

Just don't use them in --dry-run

* Add option to write the transaction log

--dry-run doesn't write to the database. Use this option if you
want a copy of the transaction log before the final run.

* Revert --transaction-log addition

Implement #27 first

* Fix CI

* Update readme

* Fix CI in copypasta

* Sort transaction log by finalized date (#33)

* Make --transaction-db option implicit (#34)

* Move db functionality into its own module (#35)

* Move db functionality into its own module

* Rename tokens module to commands

* Version bump

* Upgrade Solana

* Add solana-tokens to build

* Remove Cargo.lock

* Remove vscode file

* Remove TravisCI build script

* Install solana-tokens

Co-authored-by: Dan Albert <dan@solana.com>
This commit is contained in:
Greg Fitzgerald
2020-05-13 08:36:30 -06:00
committed by GitHub
parent 1eb40c3fe0
commit e09f517094
14 changed files with 1902 additions and 0 deletions

174
tokens/src/thin_client.rs Normal file
View File

@@ -0,0 +1,174 @@
use solana_client::rpc_client::RpcClient;
use solana_runtime::bank_client::BankClient;
use solana_sdk::{
account::Account,
client::{AsyncClient, SyncClient},
fee_calculator::FeeCalculator,
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::{Signature, Signer},
signers::Signers,
system_instruction,
sysvar::{
recent_blockhashes::{self, RecentBlockhashes},
Sysvar,
},
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_recent_blockhash1(&self) -> Result<(Hash, FeeCalculator)>;
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>>;
}
impl Client for RpcClient {
fn send_transaction1(&self, transaction: Transaction) -> Result<Signature> {
self.send_transaction(&transaction)
.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_recent_blockhash1(&self) -> Result<(Hash, FeeCalculator)> {
self.get_recent_blockhash()
.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_recent_blockhash1(&self) -> Result<(Hash, FeeCalculator)> {
self.get_recent_blockhash()
}
fn get_account1(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
self.get_account(pubkey)
}
}
pub struct ThinClient<C: Client> {
client: C,
dry_run: bool,
}
impl<C: Client> ThinClient<C> {
pub fn new(client: C, dry_run: bool) -> Self {
Self { 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_message<S: Signers>(&self, message: Message, signers: &S) -> Result<Transaction> {
if self.dry_run {
return Ok(Transaction::new_unsigned(message));
}
let (blockhash, _fee_caluclator) = self.get_recent_blockhash()?;
let transaction = Transaction::new(signers, message, blockhash);
self.send_transaction(transaction.clone())?;
Ok(transaction)
}
pub fn transfer<S: Signer>(
&self,
lamports: u64,
sender_keypair: &S,
to_pubkey: &Pubkey,
) -> Result<Transaction> {
let create_instruction =
system_instruction::transfer(&sender_keypair.pubkey(), &to_pubkey, lamports);
let message = Message::new(&[create_instruction]);
self.send_message(message, &[sender_keypair])
}
pub fn get_recent_blockhash(&self) -> Result<(Hash, FeeCalculator)> {
self.client.get_recent_blockhash1()
}
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)
}
pub fn get_recent_blockhashes(&self) -> Result<Vec<Hash>> {
let opt_blockhashes_account = self.get_account(&recent_blockhashes::id())?;
let blockhashes_account = opt_blockhashes_account.unwrap();
let recent_blockhashes = RecentBlockhashes::from_account(&blockhashes_account).unwrap();
let hashes = recent_blockhashes.iter().map(|x| x.blockhash).collect();
Ok(hashes)
}
}