Add account owner to Storage Accounts (#4537)

* Add account owner to Storage Accounts

* Fix tests
This commit is contained in:
Sagar Dhawan 2019-06-04 14:52:52 -07:00 committed by GitHub
parent e3365529de
commit de5cad9211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 29 deletions

View File

@ -131,7 +131,7 @@ impl LocalCluster {
config.node_stakes[0], config.node_stakes[0],
); );
let storage_keypair = Keypair::new(); let storage_keypair = Keypair::new();
genesis_block.add_storage_program(&storage_keypair.pubkey()); genesis_block.add_storage_program(&leader_pubkey, &storage_keypair.pubkey());
genesis_block.ticks_per_slot = config.ticks_per_slot; genesis_block.ticks_per_slot = config.ticks_per_slot;
genesis_block.slots_per_epoch = config.slots_per_epoch; genesis_block.slots_per_epoch = config.slots_per_epoch;
genesis_block.stakers_slot_offset = config.stakers_slot_offset; genesis_block.stakers_slot_offset = config.stakers_slot_offset;
@ -479,6 +479,7 @@ impl LocalCluster {
)) ))
} }
/// Sets up the storage account for validators/replicators and assumes the funder is the owner
fn setup_storage_account( fn setup_storage_account(
client: &ThinClient, client: &ThinClient,
storage_keypair: &Keypair, storage_keypair: &Keypair,
@ -488,12 +489,14 @@ impl LocalCluster {
let message = Message::new_with_payer( let message = Message::new_with_payer(
if replicator { if replicator {
storage_instruction::create_replicator_storage_account( storage_instruction::create_replicator_storage_account(
&from_keypair.pubkey(),
&from_keypair.pubkey(), &from_keypair.pubkey(),
&storage_keypair.pubkey(), &storage_keypair.pubkey(),
1, 1,
) )
} else { } else {
storage_instruction::create_validator_storage_account( storage_instruction::create_validator_storage_account(
&from_keypair.pubkey(),
&from_keypair.pubkey(), &from_keypair.pubkey(),
&storage_keypair.pubkey(), &storage_keypair.pubkey(),
1, 1,

View File

@ -444,6 +444,7 @@ impl Replicator {
let (blockhash, _fee_calculator) = client.get_recent_blockhash().expect("blockhash"); let (blockhash, _fee_calculator) = client.get_recent_blockhash().expect("blockhash");
let ix = storage_instruction::create_replicator_storage_account( let ix = storage_instruction::create_replicator_storage_account(
&keypair.pubkey(),
&keypair.pubkey(), &keypair.pubkey(),
&storage_keypair.pubkey(), &storage_keypair.pubkey(),
1, 1,

View File

@ -228,7 +228,10 @@ fn main() -> Result<(), Box<dyn error::Error>> {
solana_exchange_program!(), solana_exchange_program!(),
], ],
); );
genesis_block.add_storage_program(&bootstrap_storage_keypair.pubkey()); genesis_block.add_storage_program(
&bootstrap_leader_keypair.pubkey(),
&bootstrap_storage_keypair.pubkey(),
);
genesis_block.fee_calculator.lamports_per_signature = genesis_block.fee_calculator.lamports_per_signature =
value_t_or_exit!(matches, "lamports_per_signature", u64); value_t_or_exit!(matches, "lamports_per_signature", u64);

View File

@ -116,7 +116,7 @@ setup_validator_accounts() {
# Setup validator storage account # Setup validator storage account
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
create-validator-storage-account "$storage_pubkey" || return $? create-validator-storage-account "$node_pubkey" "$storage_pubkey" || return $?
touch "$node_keypair_path".configured touch "$node_keypair_path".configured
fi fi
@ -140,6 +140,9 @@ setup_replicator_account() {
declare storage_keypair_path=$3 declare storage_keypair_path=$3
declare node_lamports=$4 declare node_lamports=$4
declare node_pubkey
node_pubkey=$($solana_keygen pubkey "$node_keypair_path")
declare storage_pubkey declare storage_pubkey
storage_pubkey=$($solana_keygen pubkey "$storage_keypair_path") storage_pubkey=$($solana_keygen pubkey "$storage_keypair_path")
@ -150,7 +153,7 @@ setup_replicator_account() {
# Setup replicator storage account # Setup replicator storage account
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
create-replicator-storage-account "$storage_pubkey" || return $? create-replicator-storage-account "$node_pubkey" "$storage_pubkey" || return $?
touch "$node_keypair_path".configured touch "$node_keypair_path".configured
fi fi

View File

@ -45,6 +45,7 @@ pub enum StorageContract {
Uninitialized, // Must be first (aka, 0) Uninitialized, // Must be first (aka, 0)
ValidatorStorage { ValidatorStorage {
owner: Pubkey,
// Most recently advertised slot // Most recently advertised slot
slot: u64, slot: u64,
// Most recently advertised blockhash // Most recently advertised blockhash
@ -53,6 +54,7 @@ pub enum StorageContract {
reward_validations: HashMap<usize, HashMap<Hash, ProofStatus>>, reward_validations: HashMap<usize, HashMap<Hash, ProofStatus>>,
}, },
ReplicatorStorage { ReplicatorStorage {
owner: Pubkey,
/// Map of Proofs per segment, in a HashMap based on the sha_state /// Map of Proofs per segment, in a HashMap based on the sha_state
proofs: HashMap<usize, HashMap<Hash, Proof>>, proofs: HashMap<usize, HashMap<Hash, Proof>>,
/// Map of Rewards per segment, in a HashMap based on the sha_state /// Map of Rewards per segment, in a HashMap based on the sha_state
@ -64,11 +66,12 @@ pub enum StorageContract {
} }
// utility function, used by Bank, tests, genesis // utility function, used by Bank, tests, genesis
pub fn create_validator_storage_account(lamports: u64) -> Account { pub fn create_validator_storage_account(owner: Pubkey, lamports: u64) -> Account {
let mut storage_account = Account::new(lamports, STORAGE_ACCOUNT_SPACE as usize, &crate::id()); let mut storage_account = Account::new(lamports, STORAGE_ACCOUNT_SPACE as usize, &crate::id());
storage_account storage_account
.set_state(&StorageContract::ValidatorStorage { .set_state(&StorageContract::ValidatorStorage {
owner,
slot: 0, slot: 0,
hash: Hash::default(), hash: Hash::default(),
lockout_validations: HashMap::new(), lockout_validations: HashMap::new(),
@ -98,10 +101,11 @@ impl<'a> StorageAccount<'a> {
} }
} }
pub fn initialize_replicator_storage(&mut self) -> Result<(), InstructionError> { pub fn initialize_replicator_storage(&mut self, owner: Pubkey) -> Result<(), InstructionError> {
let storage_contract = &mut self.account.state()?; let storage_contract = &mut self.account.state()?;
if let StorageContract::Uninitialized = storage_contract { if let StorageContract::Uninitialized = storage_contract {
*storage_contract = StorageContract::ReplicatorStorage { *storage_contract = StorageContract::ReplicatorStorage {
owner,
proofs: HashMap::new(), proofs: HashMap::new(),
reward_validations: HashMap::new(), reward_validations: HashMap::new(),
}; };
@ -111,10 +115,11 @@ impl<'a> StorageAccount<'a> {
} }
} }
pub fn initialize_validator_storage(&mut self) -> Result<(), InstructionError> { pub fn initialize_validator_storage(&mut self, owner: Pubkey) -> Result<(), InstructionError> {
let storage_contract = &mut self.account.state()?; let storage_contract = &mut self.account.state()?;
if let StorageContract::Uninitialized = storage_contract { if let StorageContract::Uninitialized = storage_contract {
*storage_contract = StorageContract::ValidatorStorage { *storage_contract = StorageContract::ValidatorStorage {
owner,
slot: 0, slot: 0,
hash: Hash::default(), hash: Hash::default(),
lockout_validations: HashMap::new(), lockout_validations: HashMap::new(),
@ -179,6 +184,7 @@ impl<'a> StorageAccount<'a> {
hash: state_hash, hash: state_hash,
reward_validations, reward_validations,
lockout_validations, lockout_validations,
..
} = &mut storage_contract } = &mut storage_contract
{ {
let current_segment = get_segment_from_slot(current_slot); let current_segment = get_segment_from_slot(current_slot);
@ -315,6 +321,7 @@ impl<'a> StorageAccount<'a> {
} else if let StorageContract::ReplicatorStorage { } else if let StorageContract::ReplicatorStorage {
proofs, proofs,
reward_validations, reward_validations,
..
} = &mut storage_contract } = &mut storage_contract
{ {
// if current tick height is a full segment away, allow reward collection // if current tick height is a full segment away, allow reward collection
@ -449,6 +456,7 @@ mod tests {
} }
contract = StorageContract::ValidatorStorage { contract = StorageContract::ValidatorStorage {
owner: Pubkey::default(),
slot: 0, slot: 0,
hash: Hash::default(), hash: Hash::default(),
lockout_validations: HashMap::new(), lockout_validations: HashMap::new(),
@ -459,6 +467,7 @@ mod tests {
panic!("Wrong contract type"); panic!("Wrong contract type");
} }
contract = StorageContract::ReplicatorStorage { contract = StorageContract::ReplicatorStorage {
owner: Pubkey::default(),
proofs: HashMap::new(), proofs: HashMap::new(),
reward_validations: HashMap::new(), reward_validations: HashMap::new(),
}; };
@ -502,6 +511,7 @@ mod tests {
let mut proofs = HashMap::new(); let mut proofs = HashMap::new();
proofs.insert(0, proof_map); proofs.insert(0, proof_map);
*storage_contract = StorageContract::ReplicatorStorage { *storage_contract = StorageContract::ReplicatorStorage {
owner: Pubkey::default(),
proofs, proofs,
reward_validations: HashMap::new(), reward_validations: HashMap::new(),
}; };

View File

@ -16,8 +16,12 @@ pub enum StorageInstruction {
/// Expects 1 Account: /// Expects 1 Account:
/// 0 - Account to be initialized /// 0 - Account to be initialized
InitializeMiningPool, InitializeMiningPool,
InitializeValidatorStorage, InitializeValidatorStorage {
InitializeReplicatorStorage, owner: Pubkey,
},
InitializeReplicatorStorage {
owner: Pubkey,
},
SubmitMiningProof { SubmitMiningProof {
sha_state: Hash, sha_state: Hash,
@ -44,6 +48,7 @@ pub enum StorageInstruction {
pub fn create_validator_storage_account( pub fn create_validator_storage_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
storage_owner: &Pubkey,
storage_pubkey: &Pubkey, storage_pubkey: &Pubkey,
lamports: u64, lamports: u64,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
@ -57,7 +62,9 @@ pub fn create_validator_storage_account(
), ),
Instruction::new( Instruction::new(
id(), id(),
&StorageInstruction::InitializeValidatorStorage, &StorageInstruction::InitializeValidatorStorage {
owner: *storage_owner,
},
vec![AccountMeta::new(*storage_pubkey, false)], vec![AccountMeta::new(*storage_pubkey, false)],
), ),
] ]
@ -65,6 +72,7 @@ pub fn create_validator_storage_account(
pub fn create_replicator_storage_account( pub fn create_replicator_storage_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
storage_owner: &Pubkey,
storage_pubkey: &Pubkey, storage_pubkey: &Pubkey,
lamports: u64, lamports: u64,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
@ -78,7 +86,9 @@ pub fn create_replicator_storage_account(
), ),
Instruction::new( Instruction::new(
id(), id(),
&StorageInstruction::InitializeReplicatorStorage, &StorageInstruction::InitializeReplicatorStorage {
owner: *storage_owner,
},
vec![AccountMeta::new(*storage_pubkey, false)], vec![AccountMeta::new(*storage_pubkey, false)],
), ),
] ]

View File

@ -27,17 +27,17 @@ pub fn process_instruction(
} }
storage_account.initialize_mining_pool() storage_account.initialize_mining_pool()
} }
StorageInstruction::InitializeReplicatorStorage => { StorageInstruction::InitializeReplicatorStorage { owner } => {
if !rest.is_empty() { if !rest.is_empty() {
Err(InstructionError::InvalidArgument)?; Err(InstructionError::InvalidArgument)?;
} }
storage_account.initialize_replicator_storage() storage_account.initialize_replicator_storage(owner)
} }
StorageInstruction::InitializeValidatorStorage => { StorageInstruction::InitializeValidatorStorage { owner } => {
if !rest.is_empty() { if !rest.is_empty() {
Err(InstructionError::InvalidArgument)?; Err(InstructionError::InvalidArgument)?;
} }
storage_account.initialize_validator_storage() storage_account.initialize_validator_storage(owner)
} }
StorageInstruction::SubmitMiningProof { StorageInstruction::SubmitMiningProof {
sha_state, sha_state,
@ -109,6 +109,7 @@ mod tests {
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient; use solana_runtime::bank_client::BankClient;
use solana_sdk::account::{create_keyed_accounts, Account}; use solana_sdk::account::{create_keyed_accounts, Account};
use solana_sdk::account_utils::State;
use solana_sdk::client::SyncClient; use solana_sdk::client::SyncClient;
use solana_sdk::genesis_block::create_genesis_block; use solana_sdk::genesis_block::create_genesis_block;
use solana_sdk::hash::{hash, Hash}; use solana_sdk::hash::{hash, Hash};
@ -140,8 +141,61 @@ mod tests {
ret ret
} }
#[test]
fn test_account_owner() {
let account_owner = Pubkey::new_rand();
let validator_storage_pubkey = Pubkey::new_rand();
let replicator_storage_pubkey = Pubkey::new_rand();
let (genesis_block, mint_keypair) = create_genesis_block(1000);
let mut bank = Bank::new(&genesis_block);
let mint_pubkey = mint_keypair.pubkey();
bank.add_instruction_processor(id(), process_instruction);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let message = Message::new(storage_instruction::create_validator_storage_account(
&mint_pubkey,
&account_owner,
&validator_storage_pubkey,
1,
));
bank_client
.send_message(&[&mint_keypair], message)
.expect("failed to create account");
let account = bank
.get_account(&validator_storage_pubkey)
.expect("account not found");
let storage_contract = account.state().expect("couldn't unpack account data");
if let StorageContract::ValidatorStorage { owner, .. } = storage_contract {
assert_eq!(owner, account_owner);
} else {
assert!(false, "wrong account type found")
}
let message = Message::new(storage_instruction::create_replicator_storage_account(
&mint_pubkey,
&account_owner,
&replicator_storage_pubkey,
1,
));
bank_client
.send_message(&[&mint_keypair], message)
.expect("failed to create account");
let account = bank
.get_account(&replicator_storage_pubkey)
.expect("account not found");
let storage_contract = account.state().expect("couldn't unpack account data");
if let StorageContract::ReplicatorStorage { owner, .. } = storage_contract {
assert_eq!(owner, account_owner);
} else {
assert!(false, "wrong account type found")
}
}
#[test] #[test]
fn test_proof_bounds() { fn test_proof_bounds() {
let account_owner = Pubkey::new_rand();
let pubkey = Pubkey::new_rand(); let pubkey = Pubkey::new_rand();
let mut account = Account { let mut account = Account {
data: vec![0; STORAGE_ACCOUNT_SPACE as usize], data: vec![0; STORAGE_ACCOUNT_SPACE as usize],
@ -149,7 +203,9 @@ mod tests {
}; };
{ {
let mut storage_account = StorageAccount::new(&mut account); let mut storage_account = StorageAccount::new(&mut account);
storage_account.initialize_replicator_storage().unwrap(); storage_account
.initialize_replicator_storage(account_owner)
.unwrap();
} }
let ix = storage_instruction::mining_proof( let ix = storage_instruction::mining_proof(
@ -233,12 +289,15 @@ mod tests {
#[test] #[test]
fn test_submit_mining_ok() { fn test_submit_mining_ok() {
solana_logger::setup(); solana_logger::setup();
let account_owner = Pubkey::new_rand();
let pubkey = Pubkey::new_rand(); let pubkey = Pubkey::new_rand();
let mut account = Account::default(); let mut account = Account::default();
account.data.resize(STORAGE_ACCOUNT_SPACE as usize, 0); account.data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
{ {
let mut storage_account = StorageAccount::new(&mut account); let mut storage_account = StorageAccount::new(&mut account);
storage_account.initialize_replicator_storage().unwrap(); storage_account
.initialize_replicator_storage(account_owner)
.unwrap();
} }
let ix = let ix =
@ -448,6 +507,7 @@ mod tests {
.flat_map(|account| { .flat_map(|account| {
storage_instruction::create_validator_storage_account( storage_instruction::create_validator_storage_account(
&mint.pubkey(), &mint.pubkey(),
&Pubkey::default(),
account, account,
lamports, lamports,
) )
@ -458,6 +518,7 @@ mod tests {
.for_each(|account| { .for_each(|account| {
ixs.append(&mut storage_instruction::create_replicator_storage_account( ixs.append(&mut storage_instruction::create_replicator_storage_account(
&mint.pubkey(), &mint.pubkey(),
&Pubkey::default(),
account, account,
lamports, lamports,
)) ))
@ -559,6 +620,7 @@ mod tests {
let message = Message::new(storage_instruction::create_replicator_storage_account( let message = Message::new(storage_instruction::create_replicator_storage_account(
&mint_pubkey, &mint_pubkey,
&Pubkey::default(),
&replicator_pubkey, &replicator_pubkey,
1, 1,
)); ));
@ -566,6 +628,7 @@ mod tests {
let message = Message::new(storage_instruction::create_validator_storage_account( let message = Message::new(storage_instruction::create_validator_storage_account(
&mint_pubkey, &mint_pubkey,
&Pubkey::default(),
&validator_pubkey, &validator_pubkey,
1, 1,
)); ));

View File

@ -4,14 +4,18 @@ use solana_sdk::pubkey::Pubkey;
use solana_storage_api::storage_contract; use solana_storage_api::storage_contract;
pub trait GenesisBlockUtil { pub trait GenesisBlockUtil {
fn add_storage_program(&mut self, validator_storage_pubkey: &Pubkey); fn add_storage_program(&mut self, validator_pubkey: &Pubkey, validator_storage_pubkey: &Pubkey);
} }
impl GenesisBlockUtil for GenesisBlock { impl GenesisBlockUtil for GenesisBlock {
fn add_storage_program(&mut self, validator_storage_pubkey: &Pubkey) { fn add_storage_program(
&mut self,
validator_pubkey: &Pubkey,
validator_storage_pubkey: &Pubkey,
) {
self.accounts.push(( self.accounts.push((
*validator_storage_pubkey, *validator_storage_pubkey,
storage_contract::create_validator_storage_account(1), storage_contract::create_validator_storage_account(*validator_pubkey, 1),
)); ));
self.native_instruction_processors self.native_instruction_processors
.push(solana_storage_program!()); .push(solana_storage_program!());

View File

@ -55,8 +55,8 @@ pub enum WalletCommand {
RedeemVoteCredits(Pubkey, Pubkey, Pubkey), RedeemVoteCredits(Pubkey, Pubkey, Pubkey),
ShowStakeAccount(Pubkey), ShowStakeAccount(Pubkey),
CreateStorageMiningPoolAccount(Pubkey, u64), CreateStorageMiningPoolAccount(Pubkey, u64),
CreateReplicatorStorageAccount(Pubkey), CreateReplicatorStorageAccount(Pubkey, Pubkey),
CreateValidatorStorageAccount(Pubkey), CreateValidatorStorageAccount(Pubkey, Pubkey),
ClaimStorageReward(Pubkey, Pubkey, u64), ClaimStorageReward(Pubkey, Pubkey, u64),
ShowStorageAccount(Pubkey), ShowStorageAccount(Pubkey),
Deploy(String), Deploy(String),
@ -269,14 +269,18 @@ pub fn parse_command(
)) ))
} }
("create-replicator-storage-account", Some(matches)) => { ("create-replicator-storage-account", Some(matches)) => {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
Ok(WalletCommand::CreateReplicatorStorageAccount( Ok(WalletCommand::CreateReplicatorStorageAccount(
account_owner,
storage_account_pubkey, storage_account_pubkey,
)) ))
} }
("create-validator-storage-account", Some(matches)) => { ("create-validator-storage-account", Some(matches)) => {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
Ok(WalletCommand::CreateValidatorStorageAccount( Ok(WalletCommand::CreateValidatorStorageAccount(
account_owner,
storage_account_pubkey, storage_account_pubkey,
)) ))
} }
@ -670,11 +674,13 @@ fn process_create_storage_mining_pool_account(
fn process_create_replicator_storage_account( fn process_create_replicator_storage_account(
rpc_client: &RpcClient, rpc_client: &RpcClient,
config: &WalletConfig, config: &WalletConfig,
account_owner: &Pubkey,
storage_account_pubkey: &Pubkey, storage_account_pubkey: &Pubkey,
) -> ProcessResult { ) -> ProcessResult {
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = storage_instruction::create_replicator_storage_account( let ixs = storage_instruction::create_replicator_storage_account(
&config.keypair.pubkey(), &config.keypair.pubkey(),
&account_owner,
storage_account_pubkey, storage_account_pubkey,
1, 1,
); );
@ -686,11 +692,13 @@ fn process_create_replicator_storage_account(
fn process_create_validator_storage_account( fn process_create_validator_storage_account(
rpc_client: &RpcClient, rpc_client: &RpcClient,
config: &WalletConfig, config: &WalletConfig,
account_owner: &Pubkey,
storage_account_pubkey: &Pubkey, storage_account_pubkey: &Pubkey,
) -> ProcessResult { ) -> ProcessResult {
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = storage_instruction::create_validator_storage_account( let ixs = storage_instruction::create_validator_storage_account(
&config.keypair.pubkey(), &config.keypair.pubkey(),
account_owner,
storage_account_pubkey, storage_account_pubkey,
1, 1,
); );
@ -1073,12 +1081,23 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
) )
} }
WalletCommand::CreateReplicatorStorageAccount(storage_account_pubkey) => { WalletCommand::CreateReplicatorStorageAccount(
process_create_replicator_storage_account(&rpc_client, config, &storage_account_pubkey) storage_account_owner,
} storage_account_pubkey,
) => process_create_replicator_storage_account(
&rpc_client,
config,
&storage_account_owner,
&storage_account_pubkey,
),
WalletCommand::CreateValidatorStorageAccount(storage_account_pubkey) => { WalletCommand::CreateValidatorStorageAccount(account_owner, storage_account_pubkey) => {
process_create_validator_storage_account(&rpc_client, config, &storage_account_pubkey) process_create_validator_storage_account(
&rpc_client,
config,
&account_owner,
&storage_account_pubkey,
)
} }
WalletCommand::ClaimStorageReward( WalletCommand::ClaimStorageReward(
@ -1500,25 +1519,41 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
SubCommand::with_name("create-replicator-storage-account") SubCommand::with_name("create-replicator-storage-account")
.about("Create a replicator storage account") .about("Create a replicator storage account")
.arg( .arg(
Arg::with_name("storage_account_pubkey") Arg::with_name("storage_account_owner")
.index(1) .index(1)
.value_name("PUBKEY") .value_name("PUBKEY")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.validator(is_pubkey) .validator(is_pubkey)
) )
.arg(
Arg::with_name("storage_account_pubkey")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
)
) )
.subcommand( .subcommand(
SubCommand::with_name("create-validator-storage-account") SubCommand::with_name("create-validator-storage-account")
.about("Create a validator storage account") .about("Create a validator storage account")
.arg( .arg(
Arg::with_name("storage_account_pubkey") Arg::with_name("storage_account_owner")
.index(1) .index(1)
.value_name("PUBKEY") .value_name("PUBKEY")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.validator(is_pubkey) .validator(is_pubkey)
) )
.arg(
Arg::with_name("storage_account_pubkey")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
)
) )
.subcommand( .subcommand(
SubCommand::with_name("claim-storage-reward") SubCommand::with_name("claim-storage-reward")