2019-03-22 06:47:05 -06:00
|
|
|
//! storage program
|
|
|
|
//! Receive mining proofs from miners, validate the answers
|
|
|
|
//! and give reward for good proofs.
|
2019-11-04 12:31:24 -08:00
|
|
|
use crate::{storage_contract::StorageAccount, storage_instruction::StorageInstruction};
|
|
|
|
use solana_sdk::{
|
|
|
|
account::KeyedAccount,
|
|
|
|
instruction::InstructionError,
|
|
|
|
instruction_processor_utils::limited_deserialize,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
sysvar::{clock::Clock, rewards::Rewards, Sysvar},
|
|
|
|
};
|
2019-04-02 13:08:55 -07:00
|
|
|
|
2019-03-22 06:47:05 -06:00
|
|
|
pub fn process_instruction(
|
|
|
|
_program_id: &Pubkey,
|
|
|
|
keyed_accounts: &mut [KeyedAccount],
|
|
|
|
data: &[u8],
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
solana_logger::setup();
|
|
|
|
|
2019-04-04 12:01:09 -07:00
|
|
|
let (me, rest) = keyed_accounts.split_at_mut(1);
|
2019-05-23 14:50:23 -07:00
|
|
|
let me_unsigned = me[0].signer_key().is_none();
|
2019-06-13 17:53:54 -07:00
|
|
|
let mut storage_account = StorageAccount::new(*me[0].unsigned_key(), &mut me[0].account);
|
2019-04-04 12:01:09 -07:00
|
|
|
|
2019-10-23 19:56:07 -07:00
|
|
|
match limited_deserialize(data)? {
|
2019-10-04 15:18:19 -06:00
|
|
|
StorageInstruction::InitializeStorage {
|
|
|
|
owner,
|
|
|
|
account_type,
|
|
|
|
} => {
|
2019-05-23 14:50:23 -07:00
|
|
|
if !rest.is_empty() {
|
2019-10-02 18:04:18 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
2019-10-04 15:18:19 -06:00
|
|
|
storage_account.initialize_storage(owner, account_type)
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
2019-04-04 12:01:09 -07:00
|
|
|
StorageInstruction::SubmitMiningProof {
|
|
|
|
sha_state,
|
2019-06-11 18:27:47 -07:00
|
|
|
segment_index,
|
2019-04-04 12:01:09 -07:00
|
|
|
signature,
|
2019-06-13 17:53:54 -07:00
|
|
|
blockhash,
|
2019-04-04 12:01:09 -07:00
|
|
|
} => {
|
2019-05-31 16:29:21 -06:00
|
|
|
if me_unsigned || rest.len() != 1 {
|
2019-05-23 14:50:23 -07:00
|
|
|
// This instruction must be signed by `me`
|
2019-10-02 18:04:18 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
2019-04-04 12:01:09 -07:00
|
|
|
}
|
2019-11-04 12:31:24 -08:00
|
|
|
let clock = Clock::from_keyed_account(&rest[0])?;
|
2019-06-13 17:53:54 -07:00
|
|
|
storage_account.submit_mining_proof(
|
|
|
|
sha_state,
|
|
|
|
segment_index,
|
|
|
|
signature,
|
|
|
|
blockhash,
|
2019-07-12 16:38:15 -07:00
|
|
|
clock,
|
2019-06-13 17:53:54 -07:00
|
|
|
)
|
2019-04-04 12:01:09 -07:00
|
|
|
}
|
2019-07-09 16:48:40 -07:00
|
|
|
StorageInstruction::AdvertiseStorageRecentBlockhash { hash, segment } => {
|
2019-05-31 16:29:21 -06:00
|
|
|
if me_unsigned || rest.len() != 1 {
|
2019-05-23 14:50:23 -07:00
|
|
|
// This instruction must be signed by `me`
|
2019-10-02 18:04:18 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
2019-03-22 06:47:05 -06:00
|
|
|
}
|
2019-11-04 12:31:24 -08:00
|
|
|
let clock = Clock::from_keyed_account(&rest[0])?;
|
2019-07-12 16:38:15 -07:00
|
|
|
storage_account.advertise_storage_recent_blockhash(hash, segment, clock)
|
2019-04-04 12:01:09 -07:00
|
|
|
}
|
2019-06-11 18:27:47 -07:00
|
|
|
StorageInstruction::ClaimStorageReward => {
|
2019-06-26 10:40:03 -07:00
|
|
|
if rest.len() != 4 {
|
2019-10-02 18:04:18 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
2019-04-02 13:08:55 -07:00
|
|
|
}
|
2019-07-12 16:38:15 -07:00
|
|
|
let (clock, rest) = rest.split_at_mut(1);
|
2019-06-26 10:40:03 -07:00
|
|
|
let (rewards, rest) = rest.split_at_mut(1);
|
|
|
|
let (rewards_pools, owner) = rest.split_at_mut(1);
|
|
|
|
|
2019-11-04 12:31:24 -08:00
|
|
|
let rewards = Rewards::from_keyed_account(&rewards[0])?;
|
|
|
|
let clock = Clock::from_keyed_account(&clock[0])?;
|
2019-06-13 17:53:54 -07:00
|
|
|
let mut owner = StorageAccount::new(*owner[0].unsigned_key(), &mut owner[0].account);
|
|
|
|
|
2019-07-12 16:38:15 -07:00
|
|
|
storage_account.claim_storage_reward(&mut rewards_pools[0], clock, rewards, &mut owner)
|
2019-04-04 12:01:09 -07:00
|
|
|
}
|
2019-05-24 14:49:10 -07:00
|
|
|
StorageInstruction::ProofValidation { segment, proofs } => {
|
2019-06-26 10:40:03 -07:00
|
|
|
if rest.is_empty() {
|
2019-10-02 18:04:18 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
2019-06-26 10:40:03 -07:00
|
|
|
}
|
|
|
|
|
2019-07-12 16:38:15 -07:00
|
|
|
let (clock, rest) = rest.split_at_mut(1);
|
2019-05-23 14:50:23 -07:00
|
|
|
if me_unsigned || rest.is_empty() {
|
2019-05-24 14:49:10 -07:00
|
|
|
// This instruction must be signed by `me` and `rest` cannot be empty
|
2019-10-02 18:04:18 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
2019-03-22 06:47:05 -06:00
|
|
|
}
|
2019-06-13 17:53:54 -07:00
|
|
|
let me_id = storage_account.id;
|
2019-11-04 12:31:24 -08:00
|
|
|
let clock = Clock::from_keyed_account(&clock[0])?;
|
2019-05-23 14:50:23 -07:00
|
|
|
let mut rest: Vec<_> = rest
|
|
|
|
.iter_mut()
|
2019-06-13 17:53:54 -07:00
|
|
|
.map(|keyed_account| {
|
|
|
|
StorageAccount::new(*keyed_account.unsigned_key(), &mut keyed_account.account)
|
|
|
|
})
|
2019-05-23 14:50:23 -07:00
|
|
|
.collect();
|
2019-07-12 16:38:15 -07:00
|
|
|
storage_account.proof_validation(&me_id, clock, segment, proofs, &mut rest)
|
2019-03-22 06:47:05 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-04 12:41:54 -08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use crate::{
|
|
|
|
id,
|
|
|
|
storage_contract::STORAGE_ACCOUNT_SPACE,
|
|
|
|
storage_instruction::{self, StorageAccountType},
|
|
|
|
};
|
|
|
|
use log::*;
|
|
|
|
|
|
|
|
use assert_matches::assert_matches;
|
|
|
|
use solana_sdk::{
|
|
|
|
account::{create_keyed_accounts, Account, KeyedAccount},
|
|
|
|
clock::DEFAULT_SLOTS_PER_SEGMENT,
|
|
|
|
hash::Hash,
|
|
|
|
instruction::{Instruction, InstructionError},
|
|
|
|
signature::Signature,
|
|
|
|
sysvar::{
|
|
|
|
clock::{self, Clock},
|
|
|
|
Sysvar,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
fn test_instruction(
|
|
|
|
ix: &Instruction,
|
|
|
|
program_accounts: &mut [Account],
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let mut keyed_accounts: Vec<_> = ix
|
|
|
|
.accounts
|
|
|
|
.iter()
|
|
|
|
.zip(program_accounts.iter_mut())
|
|
|
|
.map(|(account_meta, account)| {
|
|
|
|
KeyedAccount::new(&account_meta.pubkey, account_meta.is_signer, account)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let ret = process_instruction(&id(), &mut keyed_accounts, &ix.data);
|
|
|
|
info!("ret: {:?}", ret);
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_proof_bounds() {
|
|
|
|
let account_owner = Pubkey::new_rand();
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let mut account = Account {
|
|
|
|
data: vec![0; STORAGE_ACCOUNT_SPACE as usize],
|
|
|
|
..Account::default()
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let mut storage_account = StorageAccount::new(pubkey, &mut account);
|
|
|
|
storage_account
|
|
|
|
.initialize_storage(account_owner, StorageAccountType::Archiver)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let ix = storage_instruction::mining_proof(
|
|
|
|
&pubkey,
|
|
|
|
Hash::default(),
|
|
|
|
0,
|
|
|
|
Signature::default(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
// the proof is for segment 0, need to move the slot into segment 2
|
|
|
|
let mut clock_account = clock::create_account(1, 0, 0, 0, 0);
|
|
|
|
Clock::to_account(
|
|
|
|
&Clock {
|
|
|
|
slot: DEFAULT_SLOTS_PER_SEGMENT * 2,
|
|
|
|
segment: 2,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&mut clock_account,
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(test_instruction(&ix, &mut [account, clock_account]), Ok(()));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_storage_tx() {
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let mut accounts = [(pubkey, Account::default())];
|
|
|
|
let mut keyed_accounts = create_keyed_accounts(&mut accounts);
|
|
|
|
assert!(process_instruction(&id(), &mut keyed_accounts, &[]).is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_serialize_overflow() {
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let clock_id = clock::id();
|
|
|
|
let mut keyed_accounts = Vec::new();
|
|
|
|
let mut user_account = Account::default();
|
|
|
|
let mut clock_account = clock::create_account(1, 0, 0, 0, 0);
|
|
|
|
keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account));
|
|
|
|
keyed_accounts.push(KeyedAccount::new(&clock_id, false, &mut clock_account));
|
|
|
|
|
|
|
|
let ix = storage_instruction::advertise_recent_blockhash(&pubkey, Hash::default(), 1);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
process_instruction(&id(), &mut keyed_accounts, &ix.data),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invalid_accounts_len() {
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let mut accounts = [Account::default()];
|
|
|
|
|
|
|
|
let ix = storage_instruction::mining_proof(
|
|
|
|
&pubkey,
|
|
|
|
Hash::default(),
|
|
|
|
0,
|
|
|
|
Signature::default(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
// move tick height into segment 1
|
|
|
|
let mut clock_account = clock::create_account(1, 0, 0, 0, 0);
|
|
|
|
Clock::to_account(
|
|
|
|
&Clock {
|
|
|
|
slot: 16,
|
|
|
|
segment: 1,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&mut clock_account,
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(test_instruction(&ix, &mut accounts).is_err());
|
|
|
|
|
|
|
|
let mut accounts = [Account::default(), clock_account, Account::default()];
|
|
|
|
|
|
|
|
assert!(test_instruction(&ix, &mut accounts).is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_submit_mining_invalid_slot() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let mut accounts = [Account::default(), Account::default()];
|
|
|
|
accounts[0].data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
|
|
|
|
accounts[1].data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
|
|
|
|
|
|
|
|
let ix = storage_instruction::mining_proof(
|
|
|
|
&pubkey,
|
|
|
|
Hash::default(),
|
|
|
|
0,
|
|
|
|
Signature::default(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// submitting a proof for a slot in the past, so this should fail
|
|
|
|
assert!(test_instruction(&ix, &mut accounts).is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_submit_mining_ok() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let account_owner = Pubkey::new_rand();
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let mut account = Account::default();
|
|
|
|
account.data.resize(STORAGE_ACCOUNT_SPACE as usize, 0);
|
|
|
|
{
|
|
|
|
let mut storage_account = StorageAccount::new(pubkey, &mut account);
|
|
|
|
storage_account
|
|
|
|
.initialize_storage(account_owner, StorageAccountType::Archiver)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let ix = storage_instruction::mining_proof(
|
|
|
|
&pubkey,
|
|
|
|
Hash::default(),
|
|
|
|
0,
|
|
|
|
Signature::default(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
// move slot into segment 1
|
|
|
|
let mut clock_account = clock::create_account(1, 0, 0, 0, 0);
|
|
|
|
Clock::to_account(
|
|
|
|
&Clock {
|
|
|
|
slot: DEFAULT_SLOTS_PER_SEGMENT,
|
|
|
|
segment: 1,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&mut clock_account,
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_matches!(test_instruction(&ix, &mut [account, clock_account]), Ok(_));
|
|
|
|
}
|
|
|
|
}
|