From b37d2fde3dd74541780fe861cb446b7e71ca9104 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Thu, 23 May 2019 14:50:23 -0700 Subject: [PATCH] Add storage mining pool (#4364) * Add storage mining pool * Set gossip port * Add create-storage-mining-pool-account wallet command * Add claim-storage-reward wallet command * Create storage account upfront * Add storage program to genesis * Use STORAGE_ACCOUNT_SPACE * Fix tests * Add wallet commands to create validator/replicator storage accounts * Add create_validator_storage_account() * Storage stage no longer implicitly creates a storage account --- Cargo.lock | 10 + core/src/cluster_tests.rs | 13 +- core/src/fullnode.rs | 1 + core/src/local_cluster.rs | 61 ++++- core/src/replicator.rs | 31 ++- core/src/storage_stage.rs | 36 ++- genesis/src/main.rs | 4 +- multinode-demo/fullnode.sh | 56 +++- programs/storage_api/Cargo.toml | 1 + programs/storage_api/src/storage_contract.rs | 120 +++++--- .../storage_api/src/storage_instruction.rs | 103 ++++++- programs/storage_api/src/storage_processor.rs | 191 ++++++++----- run.sh | 1 + runtime/Cargo.toml | 2 + runtime/src/genesis_utils.rs | 19 +- runtime/src/lib.rs | 3 + wallet/Cargo.toml | 1 + wallet/src/wallet.rs | 259 +++++++++++++++++- 18 files changed, 735 insertions(+), 177 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b269d0bba5..7f5f1c48ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,11 @@ dependencies = [ "predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "assert_matches" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "atty" version = "0.2.11" @@ -2636,6 +2641,8 @@ dependencies = [ "solana-sdk 0.15.0", "solana-stake-api 0.15.0", "solana-stake-program 0.15.0", + "solana-storage-api 0.15.0", + "solana-storage-program 0.15.0", "solana-vote-api 0.15.0", "solana-vote-program 0.15.0", ] @@ -2692,6 +2699,7 @@ dependencies = [ name = "solana-storage-api" version = "0.15.0" dependencies = [ + "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2804,6 +2812,7 @@ dependencies = [ "solana-netutil 0.15.0", "solana-sdk 0.15.0", "solana-stake-api 0.15.0", + "solana-storage-api 0.15.0", "solana-vote-api 0.15.0", "solana-vote-signer 0.15.0", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3424,6 +3433,7 @@ dependencies = [ "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50" "checksum assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2dc477793bd82ec39799b6f6b3df64938532fdf2ab0d49ef817eac65856a5a1e" +"checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb37ca32a3d9d88f18d08bac8d28368b8ee1f14f8b08eb62999c51720035b55" "checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" diff --git a/core/src/cluster_tests.rs b/core/src/cluster_tests.rs index dd49c0f13e..64b0dc59f4 100644 --- a/core/src/cluster_tests.rs +++ b/core/src/cluster_tests.rs @@ -142,12 +142,19 @@ pub fn kill_entry_and_spend_and_verify_rest( assert!(cluster_nodes.len() >= nodes); let client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE); let first_two_epoch_slots = MINIMUM_SLOT_LENGTH * 3; + + for ingress_node in &cluster_nodes { + client + .poll_get_balance(&ingress_node.id) + .unwrap_or_else(|err| panic!("Node {} has no balance: {}", ingress_node.id, err)); + } + info!("sleeping for 2 leader fortnights"); sleep(Duration::from_millis( slot_millis * first_two_epoch_slots as u64, )); info!("done sleeping for first 2 warmup epochs"); - info!("killing entry point"); + info!("killing entry point: {}", entry_point_info.id); assert!(client.fullnode_exit().unwrap()); info!("sleeping for some time"); sleep(Duration::from_millis( @@ -160,10 +167,10 @@ pub fn kill_entry_and_spend_and_verify_rest( } let client = create_client(ingress_node.client_facing_addr(), FULLNODE_PORT_RANGE); - let bal = client + let balance = client .poll_get_balance(&funding_keypair.pubkey()) .expect("balance in source"); - assert!(bal > 0); + assert_ne!(balance, 0); let mut result = Ok(()); let mut retries = 0; diff --git a/core/src/fullnode.rs b/core/src/fullnode.rs index 669d81aace..5ce7c68618 100644 --- a/core/src/fullnode.rs +++ b/core/src/fullnode.rs @@ -387,6 +387,7 @@ mod tests { #[test] fn validator_exit() { + solana_logger::setup(); let leader_keypair = Keypair::new(); let leader_node = Node::new_localhost_with_pubkey(&leader_keypair.pubkey()); diff --git a/core/src/local_cluster.rs b/core/src/local_cluster.rs index 5b135f36aa..ce8dec5539 100644 --- a/core/src/local_cluster.rs +++ b/core/src/local_cluster.rs @@ -11,6 +11,7 @@ use solana_client::thin_client::create_client; use solana_client::thin_client::ThinClient; use solana_sdk::client::SyncClient; use solana_sdk::genesis_block::GenesisBlock; +use solana_sdk::message::Message; use solana_sdk::poh_config::PohConfig; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; @@ -19,6 +20,7 @@ use solana_sdk::timing::DEFAULT_SLOTS_PER_EPOCH; use solana_sdk::timing::DEFAULT_TICKS_PER_SLOT; use solana_sdk::transaction::Transaction; use solana_stake_api::stake_instruction; +use solana_storage_api::storage_instruction; use solana_vote_api::vote_instruction; use solana_vote_api::vote_state::VoteState; use std::collections::HashMap; @@ -120,6 +122,7 @@ impl LocalCluster { mut genesis_block, mint_keypair, voting_keypair, + storage_keypair, } = create_genesis_block_with_leader( config.cluster_lamports, &leader_pubkey, @@ -135,7 +138,7 @@ impl LocalCluster { let (genesis_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block); let leader_ledger_path = tmp_copy_blocktree!(&genesis_ledger_path); let leader_contact_info = leader_node.info.clone(); - let leader_storage_keypair = Arc::new(Keypair::new()); + let leader_storage_keypair = Arc::new(storage_keypair); let leader_voting_keypair = Arc::new(voting_keypair); let leader_server = Fullnode::new( leader_node, @@ -238,12 +241,12 @@ impl LocalCluster { // setup as a listener info!("listener {} ", validator_pubkey,); } else { - // Send each validator some lamports to vote + // Give the validator some lamports to setup vote and storage accounts let validator_balance = Self::transfer_with_client( &client, &self.funding_keypair, &validator_pubkey, - stake * 2 + 1, + stake * 2 + 2, ); info!( "validator {} balance {}", @@ -257,6 +260,9 @@ impl LocalCluster { stake, ) .unwrap(); + + Self::setup_storage_account(&client, &storage_keypair, &validator_keypair, false) + .unwrap(); } let voting_keypair = Arc::new(voting_keypair); @@ -298,21 +304,24 @@ impl LocalCluster { fn add_replicator(&mut self) { let replicator_keypair = Arc::new(Keypair::new()); - let replicator_id = replicator_keypair.pubkey(); + let replicator_pubkey = replicator_keypair.pubkey(); let storage_keypair = Arc::new(Keypair::new()); - let storage_id = storage_keypair.pubkey(); + let storage_pubkey = storage_keypair.pubkey(); let client = create_client( self.entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE, ); + // Give the replicator some lamports to setup its storage accounts Self::transfer_with_client( &client, &self.funding_keypair, &replicator_keypair.pubkey(), - 1, + 42, ); - let replicator_node = Node::new_localhost_replicator(&replicator_id); + let replicator_node = Node::new_localhost_replicator(&replicator_pubkey); + + Self::setup_storage_account(&client, &storage_keypair, &replicator_keypair, true).unwrap(); let (replicator_ledger_path, _blockhash) = create_new_tmp_ledger!(&self.genesis_block); let replicator = Replicator::new( @@ -322,12 +331,12 @@ impl LocalCluster { replicator_keypair, storage_keypair, ) - .unwrap(); + .unwrap_or_else(|err| panic!("Replicator::new() failed: {:?}", err)); self.replicators.push(replicator); self.replicator_infos.insert( - replicator_id, - ReplicatorInfo::new(storage_id, replicator_ledger_path), + replicator_pubkey, + ReplicatorInfo::new(storage_pubkey, replicator_ledger_path), ); } @@ -464,6 +473,36 @@ impl LocalCluster { "expected successful vote account registration", )) } + + fn setup_storage_account( + client: &ThinClient, + storage_keypair: &Keypair, + from_keypair: &Arc, + replicator: bool, + ) -> Result<()> { + let message = Message::new_with_payer( + if replicator { + storage_instruction::create_replicator_storage_account( + &from_keypair.pubkey(), + &storage_keypair.pubkey(), + 1, + ) + } else { + storage_instruction::create_validator_storage_account( + &from_keypair.pubkey(), + &storage_keypair.pubkey(), + 1, + ) + }, + Some(&from_keypair.pubkey()), + ); + let signer_keys = vec![from_keypair.as_ref()]; + let blockhash = client.get_recent_blockhash().unwrap().0; + let mut transaction = Transaction::new(&signer_keys, message, blockhash); + client + .retry_transfer(&from_keypair, &mut transaction, 5) + .map(|_signature| ()) + } } impl Cluster for LocalCluster { @@ -529,7 +568,7 @@ mod test { let num_replicators = 1; let config = ClusterConfig { fullnode_config, - num_replicators: 1, + num_replicators, node_stakes: vec![3; NUM_NODES], cluster_lamports: 100, ticks_per_slot: 8, diff --git a/core/src/replicator.rs b/core/src/replicator.rs index 0faa577c7d..4fec72be1f 100644 --- a/core/src/replicator.rs +++ b/core/src/replicator.rs @@ -290,8 +290,8 @@ impl Replicator { .expect("ledger encrypt not successful"); loop { self.create_sampling_offsets(); - if self.sample_file_to_create_mining_hash().is_err() { - info!("Error sampling file, exiting..."); + if let Err(err) = self.sample_file_to_create_mining_hash() { + warn!("Error sampling file, exiting: {:?}", err); break; } self.submit_mining_proof(); @@ -365,7 +365,10 @@ impl Replicator { self.num_chacha_blocks = num_encrypted_bytes / CHACHA_BLOCK_SIZE; } - info!("Done encrypting the ledger"); + info!( + "Done encrypting the ledger: {:?}", + self.ledger_data_file_encrypted + ); Ok(()) } @@ -406,29 +409,30 @@ impl Replicator { if client.poll_get_balance(&keypair.pubkey())? == 0 { Err(io::Error::new( io::ErrorKind::Other, - "No account has been setup", + "keypair account has no balance", ))? } - // check if the account exists - let bal = client.poll_get_balance(&storage_keypair.pubkey()); - if bal.is_err() || bal.unwrap() == 0 { + // check if the storage account exists + let balance = client.poll_get_balance(&storage_keypair.pubkey()); + if balance.is_err() || balance.unwrap() == 0 { let (blockhash, _fee_calculator) = client.get_recent_blockhash().expect("blockhash"); - let ix = vec![storage_instruction::create_account( + let ix = storage_instruction::create_replicator_storage_account( &keypair.pubkey(), &storage_keypair.pubkey(), 1, - )]; + ); let tx = Transaction::new_signed_instructions(&[keypair], ix, blockhash); let signature = client.async_send_transaction(tx)?; client .poll_for_signature(&signature) .map_err(|err| match err { TransportError::IoError(e) => e, - TransportError::TransactionError(_) => { - io::Error::new(ErrorKind::Other, "signature not found") - } + TransportError::TransactionError(_) => io::Error::new( + ErrorKind::Other, + "setup_mining_account: signature not found", + ), })?; } Ok(()) @@ -504,10 +508,11 @@ impl Replicator { .expect("rpc request") .as_u64() .unwrap(); - info!("max slot: {}", storage_slot); + info!("storage slot: {}", storage_slot); if get_segment_from_slot(storage_slot) != 0 { return Ok((storage_blockhash, storage_slot)); } + info!("waiting for segment..."); sleep(Duration::from_secs(5)); } Err(Error::new( diff --git a/core/src/storage_stage.rs b/core/src/storage_stage.rs index 52deafb731..70e5d0fa5b 100644 --- a/core/src/storage_stage.rs +++ b/core/src/storage_stage.rs @@ -192,6 +192,15 @@ impl StorageStage { .name("solana-storage-create-accounts".to_string()) .spawn(move || { let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + + { + let working_bank = bank_forks.read().unwrap().working_bank(); + let storage_account = working_bank.get_account(&storage_keypair.pubkey()); + if storage_account.is_none() { + warn!("Storage account not found: {}", storage_keypair.pubkey()); + } + } + loop { match instruction_receiver.recv_timeout(Duration::from_secs(1)) { Ok(instruction) => { @@ -238,22 +247,29 @@ impl StorageStage { ) -> io::Result<()> { let working_bank = bank_forks.read().unwrap().working_bank(); let blockhash = working_bank.confirmed_last_blockhash(); - let mut instructions = vec![]; - let signer_keys = vec![keypair.as_ref(), storage_keypair.as_ref()]; + let keypair_balance = working_bank.get_balance(&keypair.pubkey()); + + if keypair_balance == 0 { + warn!("keypair account balance empty: {}", keypair.pubkey(),); + } else { + debug!( + "keypair account balance: {}: {}", + keypair.pubkey(), + keypair_balance + ); + } if working_bank .get_account(&storage_keypair.pubkey()) .is_none() { - let create_instruction = storage_instruction::create_account( - &keypair.pubkey(), - &storage_keypair.pubkey(), - 1, + warn!( + "storage account does not exist: {}", + storage_keypair.pubkey() ); - instructions.push(create_instruction); - info!("storage account requested"); } - instructions.push(instruction); - let message = Message::new_with_payer(instructions, Some(&signer_keys[0].pubkey())); + + let signer_keys = vec![keypair.as_ref(), storage_keypair.as_ref()]; + let message = Message::new_with_payer(vec![instruction], Some(&signer_keys[0].pubkey())); let transaction = Transaction::new(&signer_keys, message, blockhash); transactions_socket.send_to( diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 3466a264c6..8f7501fe63 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -25,7 +25,7 @@ use solana_sdk::signature::{read_keypair, KeypairUtil}; use solana_sdk::system_program; use solana_sdk::timing; use solana_stake_api::stake_state; -use solana_storage_api::storage_contract::STORAGE_ACCOUNT_SPACE; +use solana_storage_api::storage_contract; use solana_vote_api::vote_state; use std::error; use std::time::{Duration, Instant}; @@ -216,7 +216,7 @@ fn main() -> Result<(), Box> { // storage account ( bootstrap_storage_keypair.pubkey(), - Account::new(1, STORAGE_ACCOUNT_SPACE as usize, &solana_storage_api::id()), + storage_contract::create_validator_storage_account(1), ), ], &[ diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index 3d1a8d47bf..1c17ecb6d5 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -73,12 +73,13 @@ rsync_url() { # adds the 'rsync://` prefix to URLs that need it echo "rsync://$url" } -setup_vote_and_stake_accounts() { +setup_validator_accounts() { declare entrypoint_ip=$1 declare node_keypair_path=$2 declare vote_keypair_path=$3 declare stake_keypair_path=$4 - declare stake=$5 + declare storage_keypair_path=$5 + declare stake=$6 declare node_pubkey node_pubkey=$($solana_keygen pubkey "$node_keypair_path") @@ -89,6 +90,9 @@ setup_vote_and_stake_accounts() { declare stake_pubkey stake_pubkey=$($solana_keygen pubkey "$stake_keypair_path") + declare storage_pubkey + storage_pubkey=$($solana_keygen pubkey "$storage_keypair_path") + if [[ -f "$node_keypair_path".configured ]]; then echo "Vote and stake accounts have already been configured" else @@ -97,27 +101,30 @@ setup_vote_and_stake_accounts() { # Fund the vote account from the node, with the node as the node_pubkey $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ - create-vote-account "$vote_pubkey" "$node_pubkey" "$stake" || return $? + create-vote-account "$vote_pubkey" "$node_pubkey" "$stake" || return $? # Fund the stake account from the node, with the node as the node_pubkey $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ - create-stake-account "$stake_pubkey" "$stake" || return $? + create-stake-account "$stake_pubkey" "$stake" || return $? # Delegate the stake. The transaction fee is paid by the node but the # transaction must be signed by the stake_keypair $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ - delegate-stake "$stake_keypair_path" "$vote_pubkey" || return $? + delegate-stake "$stake_keypair_path" "$vote_pubkey" || return $? + # Setup validator storage account + $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ + create-validator-storage-account "$storage_pubkey" || return $? touch "$node_keypair_path".configured fi $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ - show-vote-account "$vote_pubkey" - + show-vote-account "$vote_pubkey" $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ - show-stake-account "$stake_pubkey" - + show-stake-account "$stake_pubkey" + $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ + show-storage-account "$storage_pubkey" return 0 } @@ -132,14 +139,28 @@ ledger_not_setup() { setup_replicator_account() { declare entrypoint_ip=$1 declare node_keypair_path=$2 - declare stake=$3 + declare storage_keypair_path=$2 + declare stake=$4 + + declare storage_pubkey + storage_pubkey=$($solana_keygen pubkey "$storage_keypair_path") if [[ -f "$node_keypair_path".configured ]]; then echo "Replicator account has already been configured" else $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" airdrop "$stake" || return $? + + # Setup replicator storage account + $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ + create-replicator-storage-account "$storage_keypair_path" || return $? + touch "$node_keypair_path".configured fi + + $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ + show-storage-account "$storage_pubkey" + + return 0 } args=() @@ -228,7 +249,6 @@ if [[ $node_type = bootstrap_leader ]]; then accounts_config_dir="$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts fullnode_storage_keypair_path=$SOLANA_CONFIG_DIR/bootstrap-leader-storage-keypair.json - default_arg --rpc-port 8899 default_arg --rpc-drone-address 127.0.0.1:9900 default_arg --gossip-port 8001 @@ -300,11 +320,13 @@ else fullnode_pubkey=$($solana_keygen pubkey "$fullnode_keypair_path") fullnode_vote_pubkey=$($solana_keygen pubkey "$fullnode_vote_keypair_path") + fullnode_storage_pubkey=$($solana_keygen pubkey "$fullnode_storage_keypair_path") cat </dev/null 2>&1 && wait "$pid"' INT TERM ERR if [[ $node_type = validator ]] && ((stake)); then - setup_vote_and_stake_accounts "${entrypoint_address%:*}" "$fullnode_keypair_path" "$fullnode_vote_keypair_path" "$fullnode_stake_keypair_path" "$stake" + setup_validator_accounts "${entrypoint_address%:*}" \ + "$fullnode_keypair_path" \ + "$fullnode_vote_keypair_path" \ + "$fullnode_stake_keypair_path" \ + "$fullnode_storage_keypair_path" \ + "$stake" elif [[ $node_type = replicator ]] && ((stake)); then - setup_replicator_account "${entrypoint_address%:*}" "$replicator_keypair_path" "$stake" + setup_replicator_account "${entrypoint_address%:*}" \ + "$replicator_keypair_path" \ + "$replicator_storage_keypair_path" \ + "$stake" fi echo "$PS4$program ${args[*]}" diff --git a/programs/storage_api/Cargo.toml b/programs/storage_api/Cargo.toml index 3ee05a09e9..7be4ae1995 100644 --- a/programs/storage_api/Cargo.toml +++ b/programs/storage_api/Cargo.toml @@ -15,6 +15,7 @@ serde = "1.0.91" serde_derive = "1.0.91" solana-logger = { path = "../../logger", version = "0.15.0" } solana-sdk = { path = "../../sdk", version = "0.15.0" } +assert_matches = "1.3.0" [dev-dependencies] solana-runtime = { path = "../../runtime", version = "0.15.0" } diff --git a/programs/storage_api/src/storage_contract.rs b/programs/storage_api/src/storage_contract.rs index 17bced40f7..4f748103ec 100644 --- a/programs/storage_api/src/storage_contract.rs +++ b/programs/storage_api/src/storage_contract.rs @@ -2,6 +2,7 @@ use crate::get_segment_from_slot; use log::*; use serde_derive::{Deserialize, Serialize}; use solana_sdk::account::Account; +use solana_sdk::account::KeyedAccount; use solana_sdk::account_utils::State; use solana_sdk::hash::Hash; use solana_sdk::instruction::InstructionError; @@ -42,8 +43,7 @@ pub struct CheckedProof { #[derive(Debug, Serialize, Deserialize)] pub enum StorageContract { - //don't move this - Default, + Uninitialized, // Must be first (aka, 0) ValidatorStorage { slot: u64, @@ -58,6 +58,24 @@ pub enum StorageContract { /// Multiple validators can validate the same set of proofs so it needs a Vec reward_validations: HashMap>>, }, + + MiningPool, +} + +// utility function, used by Bank, tests, genesis +pub fn create_validator_storage_account(lamports: u64) -> Account { + let mut storage_account = Account::new(lamports, STORAGE_ACCOUNT_SPACE as usize, &crate::id()); + + storage_account + .set_state(&StorageContract::ValidatorStorage { + slot: 0, + hash: Hash::default(), + lockout_validations: HashMap::new(), + reward_validations: HashMap::new(), + }) + .expect("set_state"); + + storage_account } pub struct StorageAccount<'a> { @@ -69,6 +87,44 @@ impl<'a> StorageAccount<'a> { Self { account } } + pub fn initialize_mining_pool(&mut self) -> Result<(), InstructionError> { + let storage_contract = &mut self.account.state()?; + if let StorageContract::Uninitialized = storage_contract { + *storage_contract = StorageContract::MiningPool; + self.account.set_state(storage_contract) + } else { + Err(InstructionError::AccountAlreadyInitialized)? + } + } + + pub fn initialize_replicator_storage(&mut self) -> Result<(), InstructionError> { + let storage_contract = &mut self.account.state()?; + if let StorageContract::Uninitialized = storage_contract { + *storage_contract = StorageContract::ReplicatorStorage { + proofs: HashMap::new(), + reward_validations: HashMap::new(), + }; + self.account.set_state(storage_contract) + } else { + Err(InstructionError::AccountAlreadyInitialized)? + } + } + + pub fn initialize_validator_storage(&mut self) -> Result<(), InstructionError> { + let storage_contract = &mut self.account.state()?; + if let StorageContract::Uninitialized = storage_contract { + *storage_contract = StorageContract::ValidatorStorage { + slot: 0, + hash: Hash::default(), + lockout_validations: HashMap::new(), + reward_validations: HashMap::new(), + }; + self.account.set_state(storage_contract) + } else { + Err(InstructionError::AccountAlreadyInitialized)? + } + } + pub fn submit_mining_proof( &mut self, id: Pubkey, @@ -78,13 +134,6 @@ impl<'a> StorageAccount<'a> { current_slot: u64, ) -> Result<(), InstructionError> { let mut storage_contract = &mut self.account.state()?; - if let StorageContract::Default = storage_contract { - *storage_contract = StorageContract::ReplicatorStorage { - proofs: HashMap::new(), - reward_validations: HashMap::new(), - }; - }; - if let StorageContract::ReplicatorStorage { proofs, .. } = &mut storage_contract { let segment_index = get_segment_from_slot(slot); let current_segment = get_segment_from_slot(current_slot); @@ -120,15 +169,6 @@ impl<'a> StorageAccount<'a> { current_slot: u64, ) -> Result<(), InstructionError> { let mut storage_contract = &mut self.account.state()?; - if let StorageContract::Default = storage_contract { - *storage_contract = StorageContract::ValidatorStorage { - slot: 0, - hash: Hash::default(), - lockout_validations: HashMap::new(), - reward_validations: HashMap::new(), - }; - }; - if let StorageContract::ValidatorStorage { slot: state_slot, hash: state_hash, @@ -165,15 +205,6 @@ impl<'a> StorageAccount<'a> { replicator_accounts: &mut [StorageAccount], ) -> Result<(), InstructionError> { let mut storage_contract = &mut self.account.state()?; - if let StorageContract::Default = storage_contract { - *storage_contract = StorageContract::ValidatorStorage { - slot: 0, - hash: Hash::default(), - lockout_validations: HashMap::new(), - reward_validations: HashMap::new(), - }; - }; - if let StorageContract::ValidatorStorage { slot: state_slot, lockout_validations, @@ -241,13 +272,11 @@ impl<'a> StorageAccount<'a> { pub fn claim_storage_reward( &mut self, + mining_pool: &mut KeyedAccount, slot: u64, current_slot: u64, ) -> Result<(), InstructionError> { let mut storage_contract = &mut self.account.state()?; - if let StorageContract::Default = storage_contract { - Err(InstructionError::InvalidArgument)? - }; if let StorageContract::ValidatorStorage { reward_validations, @@ -266,14 +295,15 @@ impl<'a> StorageAccount<'a> { ); return Err(InstructionError::InvalidArgument); } - let _num_validations = count_valid_proofs( + let num_validations = count_valid_proofs( &reward_validations .remove(&claim_segment) .map(|mut proofs| proofs.drain().map(|(_, proof)| proof).collect::>()) .unwrap_or_default(), ); - // TODO can't just create lamports out of thin air - // self.account.lamports += TOTAL_VALIDATOR_REWARDS * num_validations; + let reward = TOTAL_VALIDATOR_REWARDS * num_validations; + mining_pool.account.lamports -= reward; + self.account.lamports += reward; self.account.set_state(storage_contract) } else if let StorageContract::ReplicatorStorage { proofs, @@ -288,7 +318,7 @@ impl<'a> StorageAccount<'a> { || !reward_validations.contains_key(&claim_segment) || !proofs.contains_key(&claim_segment) { - debug!( + info!( "current {:?}, claim {:?}, have rewards for {:?} segments", claim_index, claim_segment, @@ -317,10 +347,15 @@ impl<'a> StorageAccount<'a> { }) .unwrap_or_default(); let _num_validations = count_valid_proofs(&checked_proofs); - // TODO can't just create lamports out of thin air - // self.account.lamports += num_validations - // * TOTAL_REPLICATOR_REWARDS - // * (num_validations / reward_validations[claim_segment].len() as u64); + + // TODO enable when rewards are working + /* + let reward = num_validations + * TOTAL_REPLICATOR_REWARDS + * (num_validations / reward_validations[&claim_segment].len() as u64); + mining_pool.account.lamports -= reward; + self.account.lamports += reward; + */ self.account.set_state(storage_contract) } else { Err(InstructionError::InvalidArgument)? @@ -399,7 +434,7 @@ mod tests { fn test_account_data() { solana_logger::setup(); let mut account = Account::default(); - account.data.resize(4 * 1024, 0); + account.data.resize(STORAGE_ACCOUNT_SPACE as usize, 0); let storage_account = StorageAccount::new(&mut account); // pretend it's a validator op code let mut contract = storage_account.account.state().unwrap(); @@ -454,9 +489,12 @@ mod tests { // account has no space process_validation(&mut account, segment_index, &proof, &checked_proof).unwrap_err(); - account.account.data.resize(4 * 1024, 0); + account + .account + .data + .resize(STORAGE_ACCOUNT_SPACE as usize, 0); let storage_contract = &mut account.account.state().unwrap(); - if let StorageContract::Default = storage_contract { + if let StorageContract::Uninitialized = storage_contract { let mut proof_map = HashMap::new(); proof_map.insert(proof.sha_state, proof.clone()); let mut proofs = HashMap::new(); diff --git a/programs/storage_api/src/storage_instruction.rs b/programs/storage_api/src/storage_instruction.rs index cfbea48292..40063cefb4 100644 --- a/programs/storage_api/src/storage_instruction.rs +++ b/programs/storage_api/src/storage_instruction.rs @@ -9,6 +9,14 @@ use solana_sdk::system_instruction; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum StorageInstruction { + /// Initialize the account as a mining pool, validator or replicator + /// + /// Expects 1 Account: + /// 0 - Account to be initialized + InitializeMiningPool, + InitializeValidatorStorage, + InitializeReplicatorStorage, + SubmitMiningProof { sha_state: Hash, slot: u64, @@ -18,6 +26,11 @@ pub enum StorageInstruction { hash: Hash, slot: u64, }, + /// Redeem storage reward credits + /// + /// Expects 1 Account: + /// 0 - Storage account with credits to redeem + /// 1 - MiningPool account to redeem credits from ClaimStorageReward { slot: u64, }, @@ -27,12 +40,71 @@ pub enum StorageInstruction { }, } -pub fn create_account(from: &Pubkey, to: &Pubkey, lamports: u64) -> Instruction { - system_instruction::create_account(&from, to, lamports, STORAGE_ACCOUNT_SPACE, &id()) +pub fn create_validator_storage_account( + from_pubkey: &Pubkey, + storage_pubkey: &Pubkey, + lamports: u64, +) -> Vec { + vec![ + system_instruction::create_account( + from_pubkey, + storage_pubkey, + lamports, + STORAGE_ACCOUNT_SPACE, + &id(), + ), + Instruction::new( + id(), + &StorageInstruction::InitializeValidatorStorage, + vec![AccountMeta::new(*storage_pubkey, false)], + ), + ] +} + +pub fn create_replicator_storage_account( + from_pubkey: &Pubkey, + storage_pubkey: &Pubkey, + lamports: u64, +) -> Vec { + vec![ + system_instruction::create_account( + from_pubkey, + storage_pubkey, + lamports, + STORAGE_ACCOUNT_SPACE, + &id(), + ), + Instruction::new( + id(), + &StorageInstruction::InitializeReplicatorStorage, + vec![AccountMeta::new(*storage_pubkey, false)], + ), + ] +} + +pub fn create_mining_pool_account( + from_pubkey: &Pubkey, + storage_pubkey: &Pubkey, + lamports: u64, +) -> Vec { + vec![ + system_instruction::create_account( + from_pubkey, + storage_pubkey, + lamports, + STORAGE_ACCOUNT_SPACE, + &id(), + ), + Instruction::new( + id(), + &StorageInstruction::InitializeMiningPool, + vec![AccountMeta::new(*storage_pubkey, false)], + ), + ] } pub fn mining_proof( - from_pubkey: &Pubkey, + storage_pubkey: &Pubkey, sha_state: Hash, slot: u64, signature: Signature, @@ -42,12 +114,12 @@ pub fn mining_proof( slot, signature, }; - let account_metas = vec![AccountMeta::new(*from_pubkey, true)]; + let account_metas = vec![AccountMeta::new(*storage_pubkey, true)]; Instruction::new(id(), &storage_instruction, account_metas) } pub fn advertise_recent_blockhash( - from_pubkey: &Pubkey, + storage_pubkey: &Pubkey, storage_hash: Hash, slot: u64, ) -> Instruction { @@ -55,12 +127,16 @@ pub fn advertise_recent_blockhash( hash: storage_hash, slot, }; - let account_metas = vec![AccountMeta::new(*from_pubkey, true)]; + let account_metas = vec![AccountMeta::new(*storage_pubkey, true)]; Instruction::new(id(), &storage_instruction, account_metas) } -pub fn proof_validation(from_pubkey: &Pubkey, slot: u64, proofs: Vec) -> Instruction { - let mut account_metas = vec![AccountMeta::new(*from_pubkey, true)]; +pub fn proof_validation( + storage_pubkey: &Pubkey, + slot: u64, + proofs: Vec, +) -> Instruction { + let mut account_metas = vec![AccountMeta::new(*storage_pubkey, true)]; proofs.iter().for_each(|checked_proof| { account_metas.push(AccountMeta::new(checked_proof.proof.id, false)) }); @@ -68,8 +144,15 @@ pub fn proof_validation(from_pubkey: &Pubkey, slot: u64, proofs: Vec Instruction { +pub fn claim_reward( + storage_pubkey: &Pubkey, + mining_pool_pubkey: &Pubkey, + slot: u64, +) -> Instruction { let storage_instruction = StorageInstruction::ClaimStorageReward { slot }; - let account_metas = vec![AccountMeta::new(*from_pubkey, true)]; + let account_metas = vec![ + AccountMeta::new(*storage_pubkey, false), + AccountMeta::new(*mining_pool_pubkey, false), + ]; Instruction::new(id(), &storage_instruction, account_metas) } diff --git a/programs/storage_api/src/storage_processor.rs b/programs/storage_api/src/storage_processor.rs index 5afed69027..2f077d70e8 100644 --- a/programs/storage_api/src/storage_processor.rs +++ b/programs/storage_api/src/storage_processor.rs @@ -1,10 +1,8 @@ //! storage program //! Receive mining proofs from miners, validate the answers //! and give reward for good proofs. - use crate::storage_contract::StorageAccount; use crate::storage_instruction::StorageInstruction; -use log::*; use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; use solana_sdk::pubkey::Pubkey; @@ -18,30 +16,37 @@ pub fn process_instruction( ) -> Result<(), InstructionError> { solana_logger::setup(); - let num_keyed_accounts = keyed_accounts.len(); let (me, rest) = keyed_accounts.split_at_mut(1); - - // accounts_keys[0] must be signed - let storage_account_pubkey = me[0].signer_key(); - if storage_account_pubkey.is_none() { - info!("account[0] is unsigned"); - Err(InstructionError::MissingRequiredSignature)?; - } - let storage_account_pubkey = *storage_account_pubkey.unwrap(); - + let me_unsigned = me[0].signer_key().is_none(); + let storage_account_pubkey = *me[0].unsigned_key(); let mut storage_account = StorageAccount::new(&mut me[0].account); - let mut rest: Vec<_> = rest - .iter_mut() - .map(|keyed_account| StorageAccount::new(&mut keyed_account.account)) - .collect(); match bincode::deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { + StorageInstruction::InitializeMiningPool => { + if !rest.is_empty() { + Err(InstructionError::InvalidArgument)?; + } + storage_account.initialize_mining_pool() + } + StorageInstruction::InitializeReplicatorStorage => { + if !rest.is_empty() { + Err(InstructionError::InvalidArgument)?; + } + storage_account.initialize_replicator_storage() + } + StorageInstruction::InitializeValidatorStorage => { + if !rest.is_empty() { + Err(InstructionError::InvalidArgument)?; + } + storage_account.initialize_validator_storage() + } StorageInstruction::SubmitMiningProof { sha_state, slot, signature, } => { - if num_keyed_accounts != 1 { + if me_unsigned || !rest.is_empty() { + // This instruction must be signed by `me` Err(InstructionError::InvalidArgument)?; } storage_account.submit_mining_proof( @@ -53,9 +58,8 @@ pub fn process_instruction( ) } StorageInstruction::AdvertiseStorageRecentBlockhash { hash, slot } => { - if num_keyed_accounts != 1 { - // keyed_accounts[0] should be the main storage key - // to access its data + if me_unsigned || !rest.is_empty() { + // This instruction must be signed by `me` Err(InstructionError::InvalidArgument)?; } storage_account.advertise_storage_recent_blockhash( @@ -65,18 +69,24 @@ pub fn process_instruction( ) } StorageInstruction::ClaimStorageReward { slot } => { - if num_keyed_accounts != 1 { - // keyed_accounts[0] should be the main storage key - // to access its data + if rest.len() != 1 { Err(InstructionError::InvalidArgument)?; } - storage_account.claim_storage_reward(slot, tick_height / DEFAULT_TICKS_PER_SLOT) + storage_account.claim_storage_reward( + &mut rest[0], + slot, + tick_height / DEFAULT_TICKS_PER_SLOT, + ) } StorageInstruction::ProofValidation { slot, proofs } => { - if num_keyed_accounts == 1 { - // have to have at least 1 replicator to do any verification + if me_unsigned || rest.is_empty() { + // This instruction must be signed by `me` Err(InstructionError::InvalidArgument)?; } + let mut rest: Vec<_> = rest + .iter_mut() + .map(|keyed_account| StorageAccount::new(&mut keyed_account.account)) + .collect(); storage_account.proof_validation(slot, proofs, &mut rest) } } @@ -88,10 +98,13 @@ mod tests { use crate::id; use crate::storage_contract::{ CheckedProof, Proof, ProofStatus, StorageContract, STORAGE_ACCOUNT_SPACE, + TOTAL_VALIDATOR_REWARDS, }; use crate::storage_instruction; use crate::SLOTS_PER_SEGMENT; + use assert_matches::assert_matches; use bincode::deserialize; + use log::*; use solana_runtime::bank::Bank; use solana_runtime::bank_client::BankClient; use solana_sdk::account::{create_keyed_accounts, Account}; @@ -99,6 +112,7 @@ mod tests { use solana_sdk::genesis_block::create_genesis_block; use solana_sdk::hash::{hash, Hash}; use solana_sdk::instruction::Instruction; + use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; use std::sync::Arc; @@ -127,10 +141,14 @@ mod tests { #[test] fn test_proof_bounds() { let pubkey = Pubkey::new_rand(); - let account = Account { + let mut account = Account { data: vec![0; STORAGE_ACCOUNT_SPACE as usize], ..Account::default() }; + { + let mut storage_account = StorageAccount::new(&mut account); + storage_account.initialize_replicator_storage().unwrap(); + } let ix = storage_instruction::mining_proof( &pubkey, @@ -212,13 +230,20 @@ mod tests { let pubkey = Pubkey::new_rand(); let mut accounts = [Account::default(), Account::default()]; accounts[0].data.resize(STORAGE_ACCOUNT_SPACE as usize, 0); + { + let mut storage_account = StorageAccount::new(&mut accounts[0]); + storage_account.initialize_replicator_storage().unwrap(); + } let ix = storage_instruction::mining_proof(&pubkey, Hash::default(), 0, Signature::default()); // move tick height into segment 1 let ticks_till_next_segment = TICKS_IN_SEGMENT + 1; - test_instruction(&ix, &mut accounts, ticks_till_next_segment).unwrap(); + assert_matches!( + test_instruction(&ix, &mut accounts, ticks_till_next_segment), + Ok(_) + ); } #[test] @@ -230,6 +255,8 @@ mod tests { let replicator = replicator_keypair.pubkey(); let validator_keypair = Keypair::new(); let validator = validator_keypair.pubkey(); + let mining_pool_keypair = Keypair::new(); + let mining_pool = mining_pool_keypair.pubkey(); let mut bank = Bank::new(&genesis_block); bank.add_instruction_processor(id(), process_instruction); @@ -237,11 +264,26 @@ mod tests { let slot = 0; let bank_client = BankClient::new_shared(&bank); - let ix = storage_instruction::create_account(&mint_pubkey, &validator, 10); - bank_client.send_instruction(&mint_keypair, ix).unwrap(); + let message = Message::new(storage_instruction::create_validator_storage_account( + &mint_pubkey, + &validator, + 10, + )); + bank_client.send_message(&[&mint_keypair], message).unwrap(); - let ix = storage_instruction::create_account(&mint_pubkey, &replicator, 10); - bank_client.send_instruction(&mint_keypair, ix).unwrap(); + let message = Message::new(storage_instruction::create_replicator_storage_account( + &mint_pubkey, + &replicator, + 10, + )); + bank_client.send_message(&[&mint_keypair], message).unwrap(); + + let message = Message::new(storage_instruction::create_mining_pool_account( + &mint_pubkey, + &mining_pool, + 100, + )); + bank_client.send_message(&[&mint_keypair], message).unwrap(); // tick the bank up until it's moved into storage segment 2 because the next advertise is for segment 1 let next_storage_segment_tick_height = TICKS_IN_SEGMENT * 2; @@ -256,9 +298,7 @@ mod tests { SLOTS_PER_SEGMENT, ); - bank_client - .send_instruction(&validator_keypair, ix) - .unwrap(); + assert_matches!(bank_client.send_instruction(&validator_keypair, ix), Ok(_)); let ix = storage_instruction::mining_proof( &replicator, @@ -266,9 +306,8 @@ mod tests { slot, Signature::default(), ); - bank_client - .send_instruction(&replicator_keypair, ix) - .unwrap(); + + assert_matches!(bank_client.send_instruction(&replicator_keypair, ix), Ok(_)); let ix = storage_instruction::advertise_recent_blockhash( &validator, @@ -281,10 +320,7 @@ mod tests { bank.register_tick(&bank.last_blockhash()); } - bank_client - .send_instruction(&validator_keypair, ix) - .unwrap(); - + assert_matches!(bank_client.send_instruction(&validator_keypair, ix), Ok(_)); let ix = storage_instruction::proof_validation( &validator, slot, @@ -297,9 +333,8 @@ mod tests { status: ProofStatus::Valid, }], ); - bank_client - .send_instruction(&validator_keypair, ix) - .unwrap(); + + assert_matches!(bank_client.send_instruction(&validator_keypair, ix), Ok(_)); let ix = storage_instruction::advertise_recent_blockhash( &validator, @@ -312,30 +347,50 @@ mod tests { bank.register_tick(&bank.last_blockhash()); } - bank_client - .send_instruction(&validator_keypair, ix) - .unwrap(); + assert_matches!(bank_client.send_instruction(&validator_keypair, ix), Ok(_)); - let ix = storage_instruction::reward_claim(&validator, slot); - bank_client - .send_instruction(&validator_keypair, ix) - .unwrap(); + assert_eq!(bank_client.get_balance(&validator).unwrap(), 10,); - // TODO enable when rewards are working - // assert_eq!(bank_client.get_balance(&validator).unwrap(), TOTAL_VALIDATOR_REWARDS); + let message = Message::new_with_payer( + vec![storage_instruction::claim_reward( + &validator, + &mining_pool, + slot, + )], + Some(&validator), + ); + assert_matches!( + bank_client.send_message(&[&validator_keypair], message), + Ok(_) + ); + + assert_eq!( + bank_client.get_balance(&validator).unwrap(), + 10 + TOTAL_VALIDATOR_REWARDS + ); // tick the bank into the next storage epoch so that rewards can be claimed for _ in 0..=TICKS_IN_SEGMENT { bank.register_tick(&bank.last_blockhash()); } - let ix = storage_instruction::reward_claim(&replicator, slot); - bank_client - .send_instruction(&replicator_keypair, ix) - .unwrap(); + assert_eq!(bank_client.get_balance(&replicator).unwrap(), 10); + + let message = Message::new_with_payer( + vec![storage_instruction::claim_reward( + &replicator, + &mining_pool, + slot, + )], + Some(&replicator), + ); + assert_matches!( + bank_client.send_message(&[&replicator_keypair], message), + Ok(_) + ); // TODO enable when rewards are working - // assert_eq!(bank_client.get_balance(&replicator).unwrap(), TOTAL_REPLICATOR_REWARDS); + // assert_eq!(bank_client.get_balance(&replicator).unwrap(), 10 + TOTAL_REPLICATOR_REWARDS); } fn get_storage_slot(client: &C, account: &Pubkey) -> u64 { @@ -399,13 +454,19 @@ mod tests { .transfer(10, &mint_keypair, &replicator_pubkey) .unwrap(); - let ix = storage_instruction::create_account(&mint_pubkey, &replicator_pubkey, 1); + let message = Message::new(storage_instruction::create_replicator_storage_account( + &mint_pubkey, + &replicator_pubkey, + 1, + )); + bank_client.send_message(&[&mint_keypair], message).unwrap(); - bank_client.send_instruction(&mint_keypair, ix).unwrap(); - - let ix = storage_instruction::create_account(&mint_pubkey, &validator_pubkey, 1); - - bank_client.send_instruction(&mint_keypair, ix).unwrap(); + let message = Message::new(storage_instruction::create_validator_storage_account( + &mint_pubkey, + &validator_pubkey, + 1, + )); + bank_client.send_message(&[&mint_keypair], message).unwrap(); let ix = storage_instruction::advertise_recent_blockhash( &validator_pubkey, diff --git a/run.sh b/run.sh index 91f2996323..8d1348783e 100755 --- a/run.sh +++ b/run.sh @@ -73,6 +73,7 @@ args=( --voting-keypair "$dataDir"/config/leader-vote-account-keypair.json --vote-account "$leaderVoteAccountPubkey" --ledger "$dataDir"/ledger/ + --gossip-port 8001 --rpc-port 8899 --rpc-drone-address 127.0.0.1:9900 ) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 48a3f5f07d..2d6e07f7e4 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -27,9 +27,11 @@ solana-logger = { path = "../logger", version = "0.15.0" } solana-metrics = { path = "../metrics", version = "0.15.0" } solana-sdk = { path = "../sdk", version = "0.15.0" } solana-stake-api = { path = "../programs/stake_api", version = "0.15.0" } +solana-storage-api = { path = "../programs/storage_api", version = "0.15.0" } solana-vote-api = { path = "../programs/vote_api", version = "0.15.0" } solana-vote-program = { path = "../programs/vote_program", version = "0.15.0" } solana-stake-program = { path = "../programs/stake_program", version = "0.15.0" } +solana-storage-program = { path = "../programs/storage_program", version = "0.15.0" } solana-noop-program = { path = "../programs/noop_program", version = "0.15.0" } [lib] diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index cb98010d1e..c56cafd641 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -4,6 +4,7 @@ use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_program; use solana_stake_api::stake_state; +use solana_storage_api::storage_contract; use solana_vote_api::vote_state; // The default stake placed with the bootstrap leader @@ -13,6 +14,7 @@ pub struct GenesisBlockInfo { pub genesis_block: GenesisBlock, pub mint_keypair: Keypair, pub voting_keypair: Keypair, + pub storage_keypair: Keypair, } pub fn create_genesis_block_with_leader( @@ -23,6 +25,7 @@ pub fn create_genesis_block_with_leader( let mint_keypair = Keypair::new(); let voting_keypair = Keypair::new(); let staking_keypair = Keypair::new(); + let storage_keypair = Keypair::new(); // TODO: de-duplicate the stake once passive staking // is fully implemented @@ -41,11 +44,11 @@ pub fn create_genesis_block_with_leader( mint_keypair.pubkey(), Account::new(mint_lamports, 0, &system_program::id()), ), - // node needs an account to issue votes from, this will require + // node needs an account to issue votes and storage proofs from, this will require // airdrops at some point to cover fees... ( *bootstrap_leader_id, - Account::new(1, 0, &system_program::id()), + Account::new(42, 0, &system_program::id()), ), // where votes go to (voting_keypair.pubkey(), vote_account), @@ -58,13 +61,23 @@ pub fn create_genesis_block_with_leader( bootstrap_leader_stake_lamports, ), ), + // storage account + ( + storage_keypair.pubkey(), + storage_contract::create_validator_storage_account(1), + ), + ], + &[ + solana_vote_program!(), + solana_stake_program!(), + solana_storage_program!(), // TODO: storage program is only needed by core/, move this line into core/src/genesis_utils.rs ], - &[solana_vote_program!(), solana_stake_program!()], ); GenesisBlockInfo { genesis_block, mint_keypair, voting_keypair, + storage_keypair, } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0e44d53aef..9760effa7f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -25,5 +25,8 @@ extern crate solana_vote_program; #[macro_use] extern crate solana_stake_program; +#[macro_use] +extern crate solana_storage_program; + #[macro_use] extern crate serde_derive; diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 1e892ab277..a1300eae05 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -24,6 +24,7 @@ solana-logger = { path = "../logger", version = "0.15.0" } solana-netutil = { path = "../netutil", version = "0.15.0" } solana-sdk = { path = "../sdk", version = "0.15.0" } solana-stake-api = { path = "../programs/stake_api", version = "0.15.0" } +solana-storage-api = { path = "../programs/storage_api", version = "0.15.0" } solana-vote-api = { path = "../programs/vote_api", version = "0.15.0" } solana-vote-signer = { path = "../vote-signer", version = "0.15.0" } url = "1.7.2" diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index 39fc9e1775..e325be0dd6 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -27,6 +27,7 @@ use solana_sdk::system_instruction::SystemError; use solana_sdk::system_transaction; use solana_sdk::transaction::{Transaction, TransactionError}; use solana_stake_api::stake_instruction; +use solana_storage_api::storage_instruction; use solana_vote_api::vote_instruction; use std::fs::File; use std::io::Read; @@ -53,6 +54,11 @@ pub enum WalletCommand { DelegateStake(Keypair, Pubkey), RedeemVoteCredits(Pubkey, Pubkey, Pubkey), ShowStakeAccount(Pubkey), + CreateStorageMiningPoolAccount(Pubkey, u64), + CreateReplicatorStorageAccount(Pubkey), + CreateValidatorStorageAccount(Pubkey), + ClaimStorageReward(Pubkey, Pubkey, u64), + ShowStorageAccount(Pubkey), Deploy(String), GetTransactionCount, // Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable) @@ -250,6 +256,42 @@ pub fn parse_command( let staking_account_id = pubkey_of(matches, "staking_account_id").unwrap(); Ok(WalletCommand::ShowStakeAccount(staking_account_id)) } + ("create-storage-mining-pool-account", Some(matches)) => { + let storage_mining_pool_account_id = + pubkey_of(matches, "storage_mining_pool_account_id").unwrap(); + let lamports = matches.value_of("lamports").unwrap().parse()?; + Ok(WalletCommand::CreateStorageMiningPoolAccount( + storage_mining_pool_account_id, + lamports, + )) + } + ("create-replicator-storage-account", Some(matches)) => { + let storage_account_id = pubkey_of(matches, "storage_account_id").unwrap(); + Ok(WalletCommand::CreateReplicatorStorageAccount( + storage_account_id, + )) + } + ("create-validator-storage-account", Some(matches)) => { + let storage_account_id = pubkey_of(matches, "storage_account_id").unwrap(); + Ok(WalletCommand::CreateValidatorStorageAccount( + storage_account_id, + )) + } + ("claim-storage-reward", Some(matches)) => { + let storage_mining_pool_account_id = + pubkey_of(matches, "storage_mining_pool_account_id").unwrap(); + let storage_account_id = pubkey_of(matches, "storage_account_id").unwrap(); + let slot = matches.value_of("slot").unwrap().parse()?; + Ok(WalletCommand::ClaimStorageReward( + storage_mining_pool_account_id, + storage_account_id, + slot, + )) + } + ("show-storage-account", Some(matches)) => { + let storage_account_id = pubkey_of(matches, "storage_account_id").unwrap(); + Ok(WalletCommand::ShowStorageAccount(storage_account_id)) + } ("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy( deploy_matches .value_of("program_location") @@ -585,6 +627,91 @@ fn process_show_stake_account( } } +fn process_create_storage_mining_pool_account( + rpc_client: &RpcClient, + config: &WalletConfig, + storage_account_id: &Pubkey, + lamports: u64, +) -> ProcessResult { + let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; + let ixs = storage_instruction::create_mining_pool_account( + &config.keypair.pubkey(), + storage_account_id, + lamports, + ); + let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); + let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; + Ok(signature_str.to_string()) +} + +fn process_create_replicator_storage_account( + rpc_client: &RpcClient, + config: &WalletConfig, + storage_account_id: &Pubkey, +) -> ProcessResult { + let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; + let ixs = storage_instruction::create_replicator_storage_account( + &config.keypair.pubkey(), + storage_account_id, + 1, + ); + let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); + let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; + Ok(signature_str.to_string()) +} + +fn process_create_validator_storage_account( + rpc_client: &RpcClient, + config: &WalletConfig, + storage_account_id: &Pubkey, +) -> ProcessResult { + let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; + let ixs = storage_instruction::create_validator_storage_account( + &config.keypair.pubkey(), + storage_account_id, + 1, + ); + let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); + let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; + Ok(signature_str.to_string()) +} + +fn process_claim_storage_reward( + rpc_client: &RpcClient, + config: &WalletConfig, + storage_mining_pool_account_id: &Pubkey, + storage_account_id: &Pubkey, + slot: u64, +) -> ProcessResult { + let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; + + let instruction = + storage_instruction::claim_reward(storage_account_id, storage_mining_pool_account_id, slot); + let signers = [&config.keypair]; + let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); + + let mut transaction = Transaction::new(&signers, message, recent_blockhash); + let signature_str = rpc_client.send_and_confirm_transaction(&mut transaction, &signers)?; + Ok(signature_str.to_string()) +} + +fn process_show_storage_account( + rpc_client: &RpcClient, + _config: &WalletConfig, + storage_account_id: &Pubkey, +) -> ProcessResult { + use solana_storage_api::storage_contract::StorageContract; + let account = rpc_client.get_account(storage_account_id)?; + let storage_contract: StorageContract = account.state().map_err(|err| { + WalletError::RpcRequestError( + format!("Unable to deserialize storage account: {:?}", err).to_string(), + ) + })?; + println!("{:?}", storage_contract); + println!("account lamports: {}", account.lamports); + Ok("".to_string()) +} + fn process_deploy( rpc_client: &RpcClient, config: &WalletConfig, @@ -904,11 +1031,43 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { &voting_account_id, ), - // Show a vote account WalletCommand::ShowStakeAccount(staking_account_id) => { process_show_stake_account(&rpc_client, config, &staking_account_id) } + WalletCommand::CreateStorageMiningPoolAccount(storage_account_id, lamports) => { + process_create_storage_mining_pool_account( + &rpc_client, + config, + &storage_account_id, + *lamports, + ) + } + + WalletCommand::CreateReplicatorStorageAccount(storage_account_id) => { + process_create_replicator_storage_account(&rpc_client, config, &storage_account_id) + } + + WalletCommand::CreateValidatorStorageAccount(storage_account_id) => { + process_create_validator_storage_account(&rpc_client, config, &storage_account_id) + } + + WalletCommand::ClaimStorageReward( + storage_mining_pool_account_id, + storage_account_id, + slot, + ) => process_claim_storage_reward( + &rpc_client, + config, + &storage_mining_pool_account_id, + &storage_account_id, + *slot, + ), + + WalletCommand::ShowStorageAccount(storage_account_id) => { + process_show_storage_account(&rpc_client, config, &storage_account_id) + } + // Deploy a custom program to the chain WalletCommand::Deploy(ref program_location) => { process_deploy(&rpc_client, config, program_location) @@ -1182,7 +1341,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' ) .subcommand( SubCommand::with_name("create-mining-pool-account") - .about("Create mining pool account") + .about("Create staking mining pool account") .arg( Arg::with_name("mining_pool_account_id") .index(1) @@ -1190,7 +1349,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .takes_value(true) .required(true) .validator(is_pubkey) - .help("Staking account address to fund"), + .help("Staking mining pool account address to fund"), ) .arg( Arg::with_name("lamports") @@ -1249,7 +1408,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .arg( Arg::with_name("mining_pool_account_id") .index(1) - .value_name("PUBKEY") + .value_name("MINING POOL PUBKEY") .takes_value(true) .required(true) .validator(is_pubkey) @@ -1258,7 +1417,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .arg( Arg::with_name("staking_account_id") .index(2) - .value_name("PUBKEY") + .value_name("STAKING ACCOUNT PUBKEY") .takes_value(true) .required(true) .validator(is_pubkey) @@ -1274,7 +1433,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .help("The voting account to which the stake was previously delegated."), ), ) - .subcommand( + .subcommand( SubCommand::with_name("show-stake-account") .about("Show the contents of a stake account") .arg( @@ -1287,6 +1446,94 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .help("Stake account pubkey"), ) ) + .subcommand( + SubCommand::with_name("create-storage-mining-pool-account") + .about("Create mining pool account") + .arg( + Arg::with_name("storage_account_id") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("Storage mining pool account address to fund"), + ) + .arg( + Arg::with_name("lamports") + .index(2) + .value_name("NUM") + .takes_value(true) + .required(true) + .help("The number of lamports to assign to the storage mining pool account"), + ), + ) + .subcommand( + SubCommand::with_name("create-replicator-storage-account") + .about("Create a replicator storage account") + .arg( + Arg::with_name("storage_account_id") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + ) + ) + .subcommand( + SubCommand::with_name("create-validator-storage-account") + .about("Create a validator storage account") + .arg( + Arg::with_name("storage_account_id") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + ) + ) + .subcommand( + SubCommand::with_name("claim-storage-reward") + .about("Redeem storage reward credits") + .arg( + Arg::with_name("storage_mining_pool_account_id") + .index(1) + .value_name("MINING POOL PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("Mining pool account to redeem credits from"), + ) + .arg( + Arg::with_name("storage_account_id") + .index(2) + .value_name("STORAGE ACCOUNT PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("Storage account address to redeem credits for"), + ) + .arg( + Arg::with_name("slot") + .index(3) + .value_name("SLOT") + .takes_value(true) + .required(true) + .help("The slot to claim rewards for"), + ),) + + .subcommand( + SubCommand::with_name("show-storage-account") + .about("Show the contents of a storage account") + .arg( + Arg::with_name("storage_account_id") + .index(1) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("Storage account pubkey"), + ) + ) .subcommand( SubCommand::with_name("deploy") .about("Deploy a program")