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
This commit is contained in:
Michael Vines 2019-05-23 14:50:23 -07:00 committed by GitHub
parent 6b35e16676
commit b37d2fde3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 735 additions and 177 deletions

10
Cargo.lock generated
View File

@ -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"

View File

@ -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;

View File

@ -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());

View File

@ -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<Keypair>,
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,

View File

@ -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(

View File

@ -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(

View File

@ -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<dyn error::Error>> {
// 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),
),
],
&[

View File

@ -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 <<EOF
======================[ Fullnode configuration ]======================
node pubkey: $fullnode_pubkey
vote pubkey: $fullnode_vote_pubkey
storage pubkey: $fullnode_storage_pubkey
ledger: $ledger_config_dir
accounts: $accounts_config_dir
======================================================================
@ -362,9 +384,17 @@ while true; do
trap '[[ -n $pid ]] && kill "$pid" >/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[*]}"

View File

@ -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" }

View File

@ -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<usize, HashMap<Hash, Vec<CheckedProof>>>,
},
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::<Vec<_>>())
.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();

View File

@ -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<Instruction> {
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<Instruction> {
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<Instruction> {
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<CheckedProof>) -> Instruction {
let mut account_metas = vec![AccountMeta::new(*from_pubkey, true)];
pub fn proof_validation(
storage_pubkey: &Pubkey,
slot: u64,
proofs: Vec<CheckedProof>,
) -> 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<CheckedProo
Instruction::new(id(), &storage_instruction, account_metas)
}
pub fn reward_claim(from_pubkey: &Pubkey, slot: u64) -> 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)
}

View File

@ -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<C: SyncClient>(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,

1
run.sh
View File

@ -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
)

View File

@ -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]

View File

@ -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,
}
}

View File

@ -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;

View File

@ -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"

View File

@ -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")