diff --git a/client/src/thin_client.rs b/client/src/thin_client.rs index 8f281b6630..e5b70b3d8e 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -6,6 +6,7 @@ use crate::rpc_client::RpcClient; use bincode::{serialize_into, serialized_size}; use log::*; +use solana_sdk::account::Account; use solana_sdk::client::{AsyncClient, Client, SyncClient}; use solana_sdk::fee_calculator::FeeCalculator; use solana_sdk::hash::Hash; @@ -306,11 +307,31 @@ impl SyncClient for ThinClient { Ok(self.rpc_client().get_account_data(pubkey).ok()) } + fn get_account(&self, pubkey: &Pubkey) -> TransportResult> { + Ok(self.rpc_client().get_account(pubkey).ok()) + } + fn get_balance(&self, pubkey: &Pubkey) -> TransportResult { let balance = self.rpc_client().get_balance(pubkey)?; Ok(balance) } + fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> { + let index = self.optimizer.experiment(); + let now = Instant::now(); + let recent_blockhash = self.rpc_clients[index].get_recent_blockhash(); + match recent_blockhash { + Ok(recent_blockhash) => { + self.optimizer.report(index, duration_as_ms(&now.elapsed())); + Ok(recent_blockhash) + } + Err(e) => { + self.optimizer.report(index, std::u64::MAX); + Err(e)? + } + } + } + fn get_signature_status( &self, signature: &Signature, @@ -337,22 +358,6 @@ impl SyncClient for ThinClient { Ok(slot) } - fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> { - let index = self.optimizer.experiment(); - let now = Instant::now(); - let recent_blockhash = self.rpc_clients[index].get_recent_blockhash(); - match recent_blockhash { - Ok(recent_blockhash) => { - self.optimizer.report(index, duration_as_ms(&now.elapsed())); - Ok(recent_blockhash) - } - Err(e) => { - self.optimizer.report(index, std::u64::MAX); - Err(e)? - } - } - } - fn get_transaction_count(&self) -> TransportResult { let index = self.optimizer.experiment(); let now = Instant::now(); diff --git a/core/src/replicator.rs b/core/src/replicator.rs index fb67f51142..c8c80756d7 100644 --- a/core/src/replicator.rs +++ b/core/src/replicator.rs @@ -17,13 +17,16 @@ use solana_client::rpc_client::RpcClient; use solana_client::rpc_request::RpcRequest; use solana_client::thin_client::ThinClient; use solana_ed25519_dalek as ed25519_dalek; +use solana_sdk::account_utils::State; use solana_sdk::client::{AsyncClient, SyncClient}; use solana_sdk::hash::{Hash, Hasher}; use solana_sdk::message::Message; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; use solana_sdk::timing::timestamp; use solana_sdk::transaction::Transaction; use solana_sdk::transport::TransportError; +use solana_storage_api::storage_contract::StorageContract; use solana_storage_api::{get_segment_from_slot, storage_instruction, SLOTS_PER_SEGMENT}; use std::fs::File; use std::io::{self, BufReader, ErrorKind, Read, Seek, SeekFrom}; @@ -298,7 +301,7 @@ impl Replicator { }) } - pub fn run(&mut self) { + pub fn run(&mut self, mining_pool_pubkey: Option) { info!("waiting for ledger download"); self.thread_handles.pop().unwrap().join().unwrap(); self.encrypt_ledger() @@ -325,6 +328,40 @@ impl Replicator { } }; self.blockhash = storage_blockhash; + if let Some(mining_pool_pubkey) = mining_pool_pubkey { + self.redeem_rewards(&mining_pool_pubkey); + } + } + } + + fn redeem_rewards(&self, mining_pool_pubkey: &Pubkey) { + let nodes = self.cluster_info.read().unwrap().tvu_peers(); + let client = crate::gossip_service::get_client(&nodes); + + if let Ok(Some(account)) = client.get_account(&self.storage_keypair.pubkey()) { + if let Ok(StorageContract::ReplicatorStorage { + reward_validations, .. + }) = account.state() + { + if !reward_validations.is_empty() { + let ix = storage_instruction::claim_reward( + &self.keypair.pubkey(), + &self.storage_keypair.pubkey(), + mining_pool_pubkey, + ); + let message = Message::new_with_payer(vec![ix], Some(&self.keypair.pubkey())); + if let Err(e) = client.send_message(&[&self.keypair], message) { + error!("unable to redeem reward, tx failed: {:?}", e); + } else { + info!( + "collected mining rewards: Account balance {:?}", + client.get_balance(&self.keypair.pubkey()) + ); + } + } + } + } else { + info!("Redeem mining reward: No account data found"); } } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 867ebe3377..9c5442d769 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -151,6 +151,22 @@ fn main() -> Result<(), Box> { .required(true) .help("Path to file containing the bootstrap leader's storage keypair"), ) + .arg( + Arg::with_name("storage_mining_pool_keypair_file") + .long("storage-mining-pool-keypair") + .value_name("KEYPAIR") + .takes_value(true) + .required(true) + .help("Path to file containing the storage mining pool storage keypair"), + ) + .arg( + Arg::with_name("storage_mining_pool_lamports") + .long("storage-mining-pool-lamports") + .value_name("LAMPORTS") + .takes_value(true) + .required(true) + .help("Number of lamports to assign to the storage mining pool"), + ) .arg( Arg::with_name("bootstrap_leader_lamports") .long("bootstrap-leader-lamports") @@ -251,17 +267,22 @@ fn main() -> Result<(), Box> { let bootstrap_stake_keypair_file = matches.value_of("bootstrap_stake_keypair_file").unwrap(); let bootstrap_storage_keypair_file = matches.value_of("bootstrap_storage_keypair_file").unwrap(); + let storage_mining_pool_keypair = matches + .value_of("storage_mining_pool_keypair_file") + .unwrap(); let mint_keypair_file = matches.value_of("mint_keypair_file").unwrap(); let ledger_path = matches.value_of("ledger_path").unwrap(); let lamports = value_t_or_exit!(matches, "lamports", u64); let bootstrap_leader_lamports = value_t_or_exit!(matches, "bootstrap_leader_lamports", u64); let bootstrap_leader_stake_lamports = value_t_or_exit!(matches, "bootstrap_leader_stake_lamports", u64); + let storage_pool_lamports = value_t_or_exit!(matches, "storage_mining_pool_lamports", u64); let bootstrap_leader_keypair = read_keypair(bootstrap_leader_keypair_file)?; let bootstrap_vote_keypair = read_keypair(bootstrap_vote_keypair_file)?; let bootstrap_stake_keypair = read_keypair(bootstrap_stake_keypair_file)?; let bootstrap_storage_keypair = read_keypair(bootstrap_storage_keypair_file)?; + let storage_mining_keypair = read_keypair(storage_mining_pool_keypair)?; let mint_keypair = read_keypair(mint_keypair_file)?; let (vote_account, vote_state) = vote_state::create_bootstrap_leader_account( @@ -301,6 +322,10 @@ fn main() -> Result<(), Box> { 1, ), ), + ( + storage_mining_keypair.pubkey(), + storage_contract::create_mining_pool_account(storage_pool_lamports), + ), ], &[ solana_bpf_loader_program!(), diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index 2d4d7bc318..7f604e3435 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -273,6 +273,8 @@ if [[ $node_type = replicator ]]; then storage_keypair_path="$SOLANA_CONFIG_DIR"/replicator-storage-keypair$label.json ledger_config_dir=$SOLANA_CONFIG_DIR/replicator-ledger$label configured_flag=$SOLANA_CONFIG_DIR/replicator$label.configured + storage_mining_pool_keypair_path="$SOLANA_CONFIG_DIR"/storage-mining-pool-keypair.json + storage_mining_pool_pubkey=$($solana_keygen pubkey "$storage_mining_pool_keypair_path") mkdir -p "$SOLANA_CONFIG_DIR" [[ -r "$identity_keypair_path" ]] || $solana_keygen new -o "$identity_keypair_path" @@ -293,6 +295,7 @@ EOF default_arg --identity "$identity_keypair_path" default_arg --storage-keypair "$storage_keypair_path" default_arg --ledger "$ledger_config_dir" + default_arg --mining-pool "$storage_mining_pool_pubkey" rsync_entrypoint_url=$(rsync_url "$entrypoint") elif [[ $node_type = bootstrap_leader ]]; then diff --git a/multinode-demo/setup.sh b/multinode-demo/setup.sh index b93cbea802..7b57cecbd2 100755 --- a/multinode-demo/setup.sh +++ b/multinode-demo/setup.sh @@ -13,16 +13,19 @@ $solana_keygen new -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-keypair.json $solana_keygen new -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-keypair.json $solana_keygen new -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-stake-keypair.json $solana_keygen new -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-storage-keypair.json +$solana_keygen new -o "$SOLANA_CONFIG_DIR"/storage-mining-pool-keypair.json args=("$@") default_arg --bootstrap-leader-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-keypair.json default_arg --bootstrap-vote-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-keypair.json default_arg --bootstrap-stake-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-stake-keypair.json default_arg --bootstrap-storage-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-storage-keypair.json +default_arg --storage-mining-pool-keypair "$SOLANA_CONFIG_DIR"/storage-mining-pool-keypair.json default_arg --ledger "$SOLANA_RSYNC_CONFIG_DIR"/ledger default_arg --mint "$SOLANA_CONFIG_DIR"/mint-keypair.json default_arg --lamports 100000000000000 default_arg --bootstrap-leader-lamports 424242 +default_arg --storage-mining-pool-lamports 100000000 default_arg --target-lamports-per-signature 42 default_arg --target-signatures-per-slot 42 default_arg --hashes-per-tick auto diff --git a/programs/storage_api/src/storage_contract.rs b/programs/storage_api/src/storage_contract.rs index dd0a9617ce..19147c8f2e 100644 --- a/programs/storage_api/src/storage_contract.rs +++ b/programs/storage_api/src/storage_contract.rs @@ -11,8 +11,8 @@ use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Signature; use std::collections::HashMap; -pub const VALIDATOR_REWARD: u64 = 25; -pub const REPLICATOR_REWARD: u64 = 25; +pub const VALIDATOR_REWARD: u64 = 200; +pub const REPLICATOR_REWARD: u64 = 200; // Todo Tune this for actual use cases when replicators are feature complete pub const STORAGE_ACCOUNT_SPACE: u64 = 1024 * 8; pub const MAX_PROOFS_PER_SEGMENT: usize = 80; @@ -99,6 +99,17 @@ pub fn create_validator_storage_account(owner: Pubkey, lamports: u64) -> Account storage_account } +// utility function, used by genesis +pub fn create_mining_pool_account(lamports: u64) -> Account { + let mut storage_account = Account::new(lamports, STORAGE_ACCOUNT_SPACE as usize, &crate::id()); + + storage_account + .set_state(&StorageContract::MiningPool) + .expect("set_state"); + + storage_account +} + pub struct StorageAccount<'a> { pub(crate) id: Pubkey, account: &'a mut Account, diff --git a/replicator/src/main.rs b/replicator/src/main.rs index a4ef189237..e757862da4 100644 --- a/replicator/src/main.rs +++ b/replicator/src/main.rs @@ -2,11 +2,20 @@ use clap::{crate_description, crate_name, crate_version, App, Arg}; use solana::cluster_info::{Node, FULLNODE_PORT_RANGE}; use solana::contact_info::ContactInfo; use solana::replicator::Replicator; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; use std::net::SocketAddr; use std::process::exit; use std::sync::Arc; +// Return an error if a pubkey cannot be parsed. +fn is_pubkey(string: String) -> Result<(), String> { + match string.parse::() { + Ok(_) => Ok(()), + Err(err) => Err(format!("{:?}", err)), + } +} + fn main() { solana_logger::setup(); @@ -21,6 +30,14 @@ fn main() { .takes_value(true) .help("File containing an identity (keypair)"), ) + .arg( + Arg::with_name("storage_mining_pool_pubkey") + .long("mining-pool") + .value_name("PUBKEY_BASE58_STR") + .takes_value(true) + .validator(is_pubkey) + .help("The public key of the storage mining pool"), + ) .arg( Arg::with_name("entrypoint") .short("n") @@ -69,6 +86,10 @@ fn main() { Keypair::new() }; + let storage_mining_pool_pubkey = matches + .value_of("storage_mining_pool_pubkey") + .map(|value| value.parse::().unwrap()); + let entrypoint_addr = matches .value_of("entrypoint") .map(|entrypoint| { @@ -101,6 +122,6 @@ fn main() { ) .unwrap(); - replicator.run(); + replicator.run(storage_mining_pool_pubkey); replicator.close(); } diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index e24a8d25da..08d0e93b96 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -1,4 +1,5 @@ use crate::bank::Bank; +use solana_sdk::account::Account; use solana_sdk::client::{AsyncClient, Client, SyncClient}; use solana_sdk::fee_calculator::FeeCalculator; use solana_sdk::hash::Hash; @@ -95,6 +96,10 @@ impl SyncClient for BankClient { Ok(self.bank.get_account(pubkey).map(|account| account.data)) } + fn get_account(&self, pubkey: &Pubkey) -> Result> { + Ok(self.bank.get_account(pubkey)) + } + fn get_balance(&self, pubkey: &Pubkey) -> Result { Ok(self.bank.get_balance(pubkey)) } diff --git a/sdk/src/client.rs b/sdk/src/client.rs index f2f34d590e..92e1ad6ad5 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -7,6 +7,7 @@ //! Asynchronous implementations are expected to create transactions, sign them, and send //! them but without waiting to see if the server accepted it. +use crate::account::Account; use crate::fee_calculator::FeeCalculator; use crate::hash::Hash; use crate::instruction::Instruction; @@ -37,6 +38,9 @@ pub trait SyncClient { /// Get an account or None if not found. fn get_account_data(&self, pubkey: &Pubkey) -> Result>>; + /// Get an account or None if not found. + fn get_account(&self, pubkey: &Pubkey) -> Result>; + /// Get account balance or 0 if not found. fn get_balance(&self, pubkey: &Pubkey) -> Result;