Reduce remaining program crates to boilerplate crates
This commit is contained in:
		
							
								
								
									
										19
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -2445,8 +2445,11 @@ name = "solana-storage-api" | ||||
| version = "0.13.0" | ||||
| dependencies = [ | ||||
|  "bincode 1.1.2 (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.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "solana-logger 0.13.0", | ||||
|  "solana-runtime 0.13.0", | ||||
|  "solana-sdk 0.13.0", | ||||
| ] | ||||
|  | ||||
| @@ -2454,12 +2457,8 @@ dependencies = [ | ||||
| name = "solana-storage-program" | ||||
| version = "0.13.0" | ||||
| dependencies = [ | ||||
|  "bincode 1.1.2 (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.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "solana-logger 0.13.0", | ||||
|  "solana-runtime 0.13.0", | ||||
|  "solana-sdk 0.13.0", | ||||
|  "solana-storage-api 0.13.0", | ||||
| ] | ||||
| @@ -2469,8 +2468,10 @@ name = "solana-token-api" | ||||
| version = "0.13.0" | ||||
| dependencies = [ | ||||
|  "bincode 1.1.2 (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.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "solana-logger 0.13.0", | ||||
|  "solana-sdk 0.13.0", | ||||
| ] | ||||
|  | ||||
| @@ -2478,12 +2479,10 @@ dependencies = [ | ||||
| name = "solana-token-program" | ||||
| version = "0.13.0" | ||||
| dependencies = [ | ||||
|  "bincode 1.1.2 (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.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "solana-logger 0.13.0", | ||||
|  "solana-sdk 0.13.0", | ||||
|  "solana-token-api 0.13.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -2502,6 +2501,9 @@ dependencies = [ | ||||
|  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "solana-logger 0.13.0", | ||||
|  "solana-metrics 0.13.0", | ||||
|  "solana-runtime 0.13.0", | ||||
|  "solana-sdk 0.13.0", | ||||
| ] | ||||
|  | ||||
| @@ -2509,11 +2511,8 @@ dependencies = [ | ||||
| name = "solana-vote-program" | ||||
| version = "0.13.0" | ||||
| dependencies = [ | ||||
|  "bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "solana-logger 0.13.0", | ||||
|  "solana-metrics 0.13.0", | ||||
|  "solana-runtime 0.13.0", | ||||
|  "solana-sdk 0.13.0", | ||||
|  "solana-vote-api 0.13.0", | ||||
| ] | ||||
|   | ||||
| @@ -141,7 +141,7 @@ pub fn process_instruction( | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use crate::budget_instruction::BudgetInstruction; | ||||
|     use crate::budget_script::BudgetScript; | ||||
|   | ||||
| @@ -10,10 +10,15 @@ edition = "2018" | ||||
|  | ||||
| [dependencies] | ||||
| bincode = "1.1.2" | ||||
| log = "0.4.2" | ||||
| serde = "1.0.89" | ||||
| serde_derive = "1.0.89" | ||||
| solana-logger = { path = "../../logger", version = "0.13.0" } | ||||
| solana-sdk = { path = "../../sdk", version = "0.13.0" } | ||||
|  | ||||
| [dev-dependencies] | ||||
| solana-runtime = { path = "../../runtime", version = "0.13.0" } | ||||
|  | ||||
| [lib] | ||||
| name = "solana_storage_api" | ||||
| crate-type = ["lib"] | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| pub mod storage_processor; | ||||
|  | ||||
| use serde_derive::{Deserialize, Serialize}; | ||||
| use solana_sdk::hash::Hash; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
|   | ||||
							
								
								
									
										460
									
								
								programs/storage_api/src/storage_processor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								programs/storage_api/src/storage_processor.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,460 @@ | ||||
| //! storage program | ||||
| //!  Receive mining proofs from miners, validate the answers | ||||
| //!  and give reward for good proofs. | ||||
|  | ||||
| use crate::*; | ||||
| use log::*; | ||||
| use solana_sdk::account::KeyedAccount; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::transaction::InstructionError; | ||||
|  | ||||
| pub const TOTAL_VALIDATOR_REWARDS: u64 = 1000; | ||||
| pub const TOTAL_REPLICATOR_REWARDS: u64 = 1000; | ||||
|  | ||||
| fn count_valid_proofs(proofs: &[ProofStatus]) -> u64 { | ||||
|     let mut num = 0; | ||||
|     for proof in proofs { | ||||
|         if let ProofStatus::Valid = proof { | ||||
|             num += 1; | ||||
|         } | ||||
|     } | ||||
|     num | ||||
| } | ||||
|  | ||||
| pub fn process_instruction( | ||||
|     _program_id: &Pubkey, | ||||
|     keyed_accounts: &mut [KeyedAccount], | ||||
|     data: &[u8], | ||||
|     _tick_height: u64, | ||||
| ) -> Result<(), InstructionError> { | ||||
|     solana_logger::setup(); | ||||
|  | ||||
|     if keyed_accounts.len() != 1 { | ||||
|         // keyed_accounts[1] should be the main storage key | ||||
|         // to access its data | ||||
|         Err(InstructionError::InvalidArgument)?; | ||||
|     } | ||||
|  | ||||
|     // accounts_keys[0] must be signed | ||||
|     if keyed_accounts[0].signer_key().is_none() { | ||||
|         info!("account[0] is unsigned"); | ||||
|         Err(InstructionError::GenericError)?; | ||||
|     } | ||||
|  | ||||
|     if let Ok(syscall) = bincode::deserialize(data) { | ||||
|         let mut storage_account_state = if let Ok(storage_account_state) = | ||||
|             bincode::deserialize(&keyed_accounts[0].account.data) | ||||
|         { | ||||
|             storage_account_state | ||||
|         } else { | ||||
|             StorageProgramState::default() | ||||
|         }; | ||||
|  | ||||
|         debug!( | ||||
|             "deserialized state height: {}", | ||||
|             storage_account_state.entry_height | ||||
|         ); | ||||
|         match syscall { | ||||
|             StorageProgram::SubmitMiningProof { | ||||
|                 sha_state, | ||||
|                 entry_height, | ||||
|                 signature, | ||||
|             } => { | ||||
|                 let segment_index = get_segment_from_entry(entry_height); | ||||
|                 let current_segment_index = | ||||
|                     get_segment_from_entry(storage_account_state.entry_height); | ||||
|                 if segment_index >= current_segment_index { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 debug!( | ||||
|                     "Mining proof submitted with state {:?} entry_height: {}", | ||||
|                     sha_state, entry_height | ||||
|                 ); | ||||
|  | ||||
|                 let proof_info = ProofInfo { | ||||
|                     id: *keyed_accounts[0].signer_key().unwrap(), | ||||
|                     sha_state, | ||||
|                     signature, | ||||
|                 }; | ||||
|                 storage_account_state.proofs[segment_index].push(proof_info); | ||||
|             } | ||||
|             StorageProgram::AdvertiseStorageRecentBlockhash { hash, entry_height } => { | ||||
|                 let original_segments = storage_account_state.entry_height / ENTRIES_PER_SEGMENT; | ||||
|                 let segments = entry_height / ENTRIES_PER_SEGMENT; | ||||
|                 debug!( | ||||
|                     "advertise new last id segments: {} orig: {}", | ||||
|                     segments, original_segments | ||||
|                 ); | ||||
|                 if segments <= original_segments { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 storage_account_state.entry_height = entry_height; | ||||
|                 storage_account_state.hash = hash; | ||||
|  | ||||
|                 // move the proofs to previous_proofs | ||||
|                 storage_account_state.previous_proofs = storage_account_state.proofs.clone(); | ||||
|                 storage_account_state.proofs.clear(); | ||||
|                 storage_account_state | ||||
|                     .proofs | ||||
|                     .resize(segments as usize, Vec::new()); | ||||
|  | ||||
|                 // move lockout_validations to reward_validations | ||||
|                 storage_account_state.reward_validations = | ||||
|                     storage_account_state.lockout_validations.clone(); | ||||
|                 storage_account_state.lockout_validations.clear(); | ||||
|                 storage_account_state | ||||
|                     .lockout_validations | ||||
|                     .resize(segments as usize, Vec::new()); | ||||
|             } | ||||
|             StorageProgram::ProofValidation { | ||||
|                 entry_height, | ||||
|                 proof_mask, | ||||
|             } => { | ||||
|                 if entry_height >= storage_account_state.entry_height { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 let segment_index = get_segment_from_entry(entry_height); | ||||
|                 if storage_account_state.previous_proofs[segment_index].len() != proof_mask.len() { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 // TODO: Check that each proof mask matches the signature | ||||
|                 /*for (i, entry) in proof_mask.iter().enumerate() { | ||||
|                     if storage_account_state.previous_proofs[segment_index][i] != signature.as_ref[0] { | ||||
|                         return Err(InstructionError::InvalidArgument); | ||||
|                     } | ||||
|                 }*/ | ||||
|  | ||||
|                 let info = ValidationInfo { | ||||
|                     id: *keyed_accounts[0].signer_key().unwrap(), | ||||
|                     proof_mask, | ||||
|                 }; | ||||
|                 storage_account_state.lockout_validations[segment_index].push(info); | ||||
|             } | ||||
|             StorageProgram::ClaimStorageReward { entry_height } => { | ||||
|                 let claims_index = get_segment_from_entry(entry_height); | ||||
|                 let account_key = keyed_accounts[0].signer_key().unwrap(); | ||||
|                 let mut num_validations = 0; | ||||
|                 let mut total_validations = 0; | ||||
|                 for validation in &storage_account_state.reward_validations[claims_index] { | ||||
|                     if *account_key == validation.id { | ||||
|                         num_validations += count_valid_proofs(&validation.proof_mask); | ||||
|                     } else { | ||||
|                         total_validations += count_valid_proofs(&validation.proof_mask); | ||||
|                     } | ||||
|                 } | ||||
|                 total_validations += num_validations; | ||||
|                 if total_validations > 0 { | ||||
|                     keyed_accounts[0].account.lamports += | ||||
|                         (TOTAL_VALIDATOR_REWARDS * num_validations) / total_validations; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if bincode::serialize_into( | ||||
|             &mut keyed_accounts[0].account.data[..], | ||||
|             &storage_account_state, | ||||
|         ) | ||||
|         .is_err() | ||||
|         { | ||||
|             return Err(InstructionError::AccountDataTooSmall); | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } else { | ||||
|         info!("Invalid instruction data: {:?}", data); | ||||
|         Err(InstructionError::InvalidInstructionData) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use crate::{ProofStatus, StorageTransaction, ENTRIES_PER_SEGMENT}; | ||||
|     use bincode::deserialize; | ||||
|     use solana_runtime::bank::Bank; | ||||
|     use solana_sdk::account::{create_keyed_accounts, Account}; | ||||
|     use solana_sdk::genesis_block::GenesisBlock; | ||||
|     use solana_sdk::hash::{hash, Hash}; | ||||
|     use solana_sdk::pubkey::Pubkey; | ||||
|     use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; | ||||
|     use solana_sdk::system_transaction::SystemTransaction; | ||||
|     use solana_sdk::transaction::{CompiledInstruction, Transaction}; | ||||
|  | ||||
|     fn test_transaction( | ||||
|         tx: &Transaction, | ||||
|         program_accounts: &mut [Account], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         assert_eq!(tx.instructions.len(), 1); | ||||
|         let CompiledInstruction { | ||||
|             ref accounts, | ||||
|             ref data, | ||||
|             .. | ||||
|         } = tx.instructions[0]; | ||||
|  | ||||
|         info!("accounts: {:?}", accounts); | ||||
|  | ||||
|         let mut keyed_accounts: Vec<_> = accounts | ||||
|             .iter() | ||||
|             .map(|&index| { | ||||
|                 let index = index as usize; | ||||
|                 let key = &tx.account_keys[index]; | ||||
|                 (key, index < tx.signatures.len()) | ||||
|             }) | ||||
|             .zip(program_accounts.iter_mut()) | ||||
|             .map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account)) | ||||
|             .collect(); | ||||
|  | ||||
|         let ret = process_instruction(&id(), &mut keyed_accounts, &data, 42); | ||||
|         info!("ret: {:?}", ret); | ||||
|         ret | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_storage_tx() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [(keypair.pubkey(), Account::default())]; | ||||
|         let mut keyed_accounts = create_keyed_accounts(&mut accounts); | ||||
|         assert!(process_instruction(&id(), &mut keyed_accounts, &[], 42).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_serialize_overflow() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut keyed_accounts = Vec::new(); | ||||
|         let mut user_account = Account::default(); | ||||
|         let pubkey = keypair.pubkey(); | ||||
|         keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account)); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         assert_eq!( | ||||
|             process_instruction(&id(), &mut keyed_accounts, &tx.instructions[0].data, 42), | ||||
|             Err(InstructionError::AccountDataTooSmall) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_invalid_accounts_len() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default()]; | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             0, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|         assert!(test_transaction(&tx, &mut accounts).is_err()); | ||||
|  | ||||
|         let mut accounts = [Account::default(), Account::default(), Account::default()]; | ||||
|  | ||||
|         assert!(test_transaction(&tx, &mut accounts).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_submit_mining_invalid_entry_height() { | ||||
|         solana_logger::setup(); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default(), Account::default()]; | ||||
|         accounts[1].data.resize(16 * 1024, 0); | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             0, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|  | ||||
|         // Haven't seen a transaction to roll over the epoch, so this should fail | ||||
|         assert!(test_transaction(&tx, &mut accounts).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_submit_mining_ok() { | ||||
|         solana_logger::setup(); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default(), Account::default()]; | ||||
|         accounts[0].data.resize(16 * 1024, 0); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             0, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|  | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_validate_mining() { | ||||
|         solana_logger::setup(); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default(), Account::default()]; | ||||
|         accounts[0].data.resize(16 * 1024, 0); | ||||
|  | ||||
|         let entry_height = 0; | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             entry_height, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT * 2, | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_proof_validation( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             entry_height, | ||||
|             vec![ProofStatus::Valid], | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT * 3, | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_reward_claim(&keypair, Hash::default(), entry_height); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         assert!(accounts[0].lamports == TOTAL_VALIDATOR_REWARDS); | ||||
|     } | ||||
|  | ||||
|     fn get_storage_entry_height(bank: &Bank, account: &Pubkey) -> u64 { | ||||
|         match bank.get_account(&account) { | ||||
|             Some(storage_system_account) => { | ||||
|                 let state = deserialize(&storage_system_account.data); | ||||
|                 if let Ok(state) = state { | ||||
|                     let state: StorageProgramState = state; | ||||
|                     return state.entry_height; | ||||
|                 } | ||||
|             } | ||||
|             None => { | ||||
|                 info!("error in reading entry_height"); | ||||
|             } | ||||
|         } | ||||
|         0 | ||||
|     } | ||||
|  | ||||
|     fn get_storage_blockhash(bank: &Bank, account: &Pubkey) -> Hash { | ||||
|         if let Some(storage_system_account) = bank.get_account(&account) { | ||||
|             let state = deserialize(&storage_system_account.data); | ||||
|             if let Ok(state) = state { | ||||
|                 let state: StorageProgramState = state; | ||||
|                 return state.hash; | ||||
|             } | ||||
|         } | ||||
|         Hash::default() | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_bank_storage() { | ||||
|         let (mut genesis_block, alice) = GenesisBlock::new(1000); | ||||
|         genesis_block | ||||
|             .native_programs | ||||
|             .push(("solana_storage_program".to_string(), id())); | ||||
|         let bank = Bank::new(&genesis_block); | ||||
|  | ||||
|         let bob = Keypair::new(); | ||||
|         let jack = Keypair::new(); | ||||
|         let jill = Keypair::new(); | ||||
|  | ||||
|         let x = 42; | ||||
|         let blockhash = genesis_block.hash(); | ||||
|         let x2 = x * 2; | ||||
|         let storage_blockhash = hash(&[x2]); | ||||
|  | ||||
|         bank.register_tick(&blockhash); | ||||
|  | ||||
|         bank.transfer(10, &alice, &jill.pubkey(), blockhash) | ||||
|             .unwrap(); | ||||
|  | ||||
|         bank.transfer(10, &alice, &bob.pubkey(), blockhash).unwrap(); | ||||
|         bank.transfer(10, &alice, &jack.pubkey(), blockhash) | ||||
|             .unwrap(); | ||||
|  | ||||
|         let tx = SystemTransaction::new_program_account( | ||||
|             &alice, | ||||
|             &bob.pubkey(), | ||||
|             blockhash, | ||||
|             1, | ||||
|             4 * 1024, | ||||
|             &id(), | ||||
|             0, | ||||
|         ); | ||||
|  | ||||
|         bank.process_transaction(&tx).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &bob, | ||||
|             storage_blockhash, | ||||
|             blockhash, | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         bank.process_transaction(&tx).unwrap(); | ||||
|  | ||||
|         let entry_height = 0; | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &bob, | ||||
|             Hash::default(), | ||||
|             blockhash, | ||||
|             entry_height, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|         let _result = bank.process_transaction(&tx).unwrap(); | ||||
|  | ||||
|         assert_eq!( | ||||
|             get_storage_entry_height(&bank, &bob.pubkey()), | ||||
|             ENTRIES_PER_SEGMENT | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             get_storage_blockhash(&bank, &bob.pubkey()), | ||||
|             storage_blockhash | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -9,17 +9,11 @@ homepage = "https://solana.com/" | ||||
| edition = "2018" | ||||
|  | ||||
| [dependencies] | ||||
| bincode = "1.1.2" | ||||
| log = "0.4.2" | ||||
| serde = "1.0.89" | ||||
| serde_derive = "1.0.89" | ||||
| solana-logger = { path = "../../logger", version = "0.13.0" } | ||||
| solana-sdk = { path = "../../sdk", version = "0.13.0" } | ||||
| solana-storage-api = { path = "../storage_api", version = "0.13.0" } | ||||
|  | ||||
| [dev-dependencies] | ||||
| solana-runtime = { path = "../../runtime", version = "0.13.0" } | ||||
|  | ||||
| [lib] | ||||
| name = "solana_storage_program" | ||||
| crate-type = ["cdylib"] | ||||
|   | ||||
| @@ -1,365 +1,3 @@ | ||||
| //! storage program | ||||
| //!  Receive mining proofs from miners, validate the answers | ||||
| //!  and give reward for good proofs. | ||||
| use solana_storage_api::storage_processor::process_instruction; | ||||
|  | ||||
| use log::*; | ||||
| extern crate solana_sdk; | ||||
|  | ||||
| use solana_sdk::account::KeyedAccount; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::solana_entrypoint; | ||||
| use solana_sdk::transaction::InstructionError; | ||||
| use solana_storage_api::*; | ||||
|  | ||||
| pub const TOTAL_VALIDATOR_REWARDS: u64 = 1000; | ||||
| pub const TOTAL_REPLICATOR_REWARDS: u64 = 1000; | ||||
|  | ||||
| fn count_valid_proofs(proofs: &[ProofStatus]) -> u64 { | ||||
|     let mut num = 0; | ||||
|     for proof in proofs { | ||||
|         if let ProofStatus::Valid = proof { | ||||
|             num += 1; | ||||
|         } | ||||
|     } | ||||
|     num | ||||
| } | ||||
|  | ||||
| solana_entrypoint!(entrypoint); | ||||
| fn entrypoint( | ||||
|     _program_id: &Pubkey, | ||||
|     keyed_accounts: &mut [KeyedAccount], | ||||
|     data: &[u8], | ||||
|     _tick_height: u64, | ||||
| ) -> Result<(), InstructionError> { | ||||
|     solana_logger::setup(); | ||||
|  | ||||
|     if keyed_accounts.len() != 1 { | ||||
|         // keyed_accounts[1] should be the main storage key | ||||
|         // to access its data | ||||
|         Err(InstructionError::InvalidArgument)?; | ||||
|     } | ||||
|  | ||||
|     // accounts_keys[0] must be signed | ||||
|     if keyed_accounts[0].signer_key().is_none() { | ||||
|         info!("account[0] is unsigned"); | ||||
|         Err(InstructionError::GenericError)?; | ||||
|     } | ||||
|  | ||||
|     if let Ok(syscall) = bincode::deserialize(data) { | ||||
|         let mut storage_account_state = if let Ok(storage_account_state) = | ||||
|             bincode::deserialize(&keyed_accounts[0].account.data) | ||||
|         { | ||||
|             storage_account_state | ||||
|         } else { | ||||
|             StorageProgramState::default() | ||||
|         }; | ||||
|  | ||||
|         debug!( | ||||
|             "deserialized state height: {}", | ||||
|             storage_account_state.entry_height | ||||
|         ); | ||||
|         match syscall { | ||||
|             StorageProgram::SubmitMiningProof { | ||||
|                 sha_state, | ||||
|                 entry_height, | ||||
|                 signature, | ||||
|             } => { | ||||
|                 let segment_index = get_segment_from_entry(entry_height); | ||||
|                 let current_segment_index = | ||||
|                     get_segment_from_entry(storage_account_state.entry_height); | ||||
|                 if segment_index >= current_segment_index { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 debug!( | ||||
|                     "Mining proof submitted with state {:?} entry_height: {}", | ||||
|                     sha_state, entry_height | ||||
|                 ); | ||||
|  | ||||
|                 let proof_info = ProofInfo { | ||||
|                     id: *keyed_accounts[0].signer_key().unwrap(), | ||||
|                     sha_state, | ||||
|                     signature, | ||||
|                 }; | ||||
|                 storage_account_state.proofs[segment_index].push(proof_info); | ||||
|             } | ||||
|             StorageProgram::AdvertiseStorageRecentBlockhash { hash, entry_height } => { | ||||
|                 let original_segments = storage_account_state.entry_height / ENTRIES_PER_SEGMENT; | ||||
|                 let segments = entry_height / ENTRIES_PER_SEGMENT; | ||||
|                 debug!( | ||||
|                     "advertise new last id segments: {} orig: {}", | ||||
|                     segments, original_segments | ||||
|                 ); | ||||
|                 if segments <= original_segments { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 storage_account_state.entry_height = entry_height; | ||||
|                 storage_account_state.hash = hash; | ||||
|  | ||||
|                 // move the proofs to previous_proofs | ||||
|                 storage_account_state.previous_proofs = storage_account_state.proofs.clone(); | ||||
|                 storage_account_state.proofs.clear(); | ||||
|                 storage_account_state | ||||
|                     .proofs | ||||
|                     .resize(segments as usize, Vec::new()); | ||||
|  | ||||
|                 // move lockout_validations to reward_validations | ||||
|                 storage_account_state.reward_validations = | ||||
|                     storage_account_state.lockout_validations.clone(); | ||||
|                 storage_account_state.lockout_validations.clear(); | ||||
|                 storage_account_state | ||||
|                     .lockout_validations | ||||
|                     .resize(segments as usize, Vec::new()); | ||||
|             } | ||||
|             StorageProgram::ProofValidation { | ||||
|                 entry_height, | ||||
|                 proof_mask, | ||||
|             } => { | ||||
|                 if entry_height >= storage_account_state.entry_height { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 let segment_index = get_segment_from_entry(entry_height); | ||||
|                 if storage_account_state.previous_proofs[segment_index].len() != proof_mask.len() { | ||||
|                     return Err(InstructionError::InvalidArgument); | ||||
|                 } | ||||
|  | ||||
|                 // TODO: Check that each proof mask matches the signature | ||||
|                 /*for (i, entry) in proof_mask.iter().enumerate() { | ||||
|                     if storage_account_state.previous_proofs[segment_index][i] != signature.as_ref[0] { | ||||
|                         return Err(InstructionError::InvalidArgument); | ||||
|                     } | ||||
|                 }*/ | ||||
|  | ||||
|                 let info = ValidationInfo { | ||||
|                     id: *keyed_accounts[0].signer_key().unwrap(), | ||||
|                     proof_mask, | ||||
|                 }; | ||||
|                 storage_account_state.lockout_validations[segment_index].push(info); | ||||
|             } | ||||
|             StorageProgram::ClaimStorageReward { entry_height } => { | ||||
|                 let claims_index = get_segment_from_entry(entry_height); | ||||
|                 let account_key = keyed_accounts[0].signer_key().unwrap(); | ||||
|                 let mut num_validations = 0; | ||||
|                 let mut total_validations = 0; | ||||
|                 for validation in &storage_account_state.reward_validations[claims_index] { | ||||
|                     if *account_key == validation.id { | ||||
|                         num_validations += count_valid_proofs(&validation.proof_mask); | ||||
|                     } else { | ||||
|                         total_validations += count_valid_proofs(&validation.proof_mask); | ||||
|                     } | ||||
|                 } | ||||
|                 total_validations += num_validations; | ||||
|                 if total_validations > 0 { | ||||
|                     keyed_accounts[0].account.lamports += | ||||
|                         (TOTAL_VALIDATOR_REWARDS * num_validations) / total_validations; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if bincode::serialize_into( | ||||
|             &mut keyed_accounts[0].account.data[..], | ||||
|             &storage_account_state, | ||||
|         ) | ||||
|         .is_err() | ||||
|         { | ||||
|             return Err(InstructionError::AccountDataTooSmall); | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } else { | ||||
|         info!("Invalid instruction data: {:?}", data); | ||||
|         Err(InstructionError::InvalidInstructionData) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use super::*; | ||||
|     use solana_sdk::account::{create_keyed_accounts, Account}; | ||||
|     use solana_sdk::hash::Hash; | ||||
|     use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; | ||||
|     use solana_sdk::transaction::{CompiledInstruction, Transaction}; | ||||
|     use solana_storage_api::{ProofStatus, StorageTransaction}; | ||||
|  | ||||
|     fn test_transaction( | ||||
|         tx: &Transaction, | ||||
|         program_accounts: &mut [Account], | ||||
|     ) -> Result<(), InstructionError> { | ||||
|         assert_eq!(tx.instructions.len(), 1); | ||||
|         let CompiledInstruction { | ||||
|             ref accounts, | ||||
|             ref data, | ||||
|             .. | ||||
|         } = tx.instructions[0]; | ||||
|  | ||||
|         info!("accounts: {:?}", accounts); | ||||
|  | ||||
|         let mut keyed_accounts: Vec<_> = accounts | ||||
|             .iter() | ||||
|             .map(|&index| { | ||||
|                 let index = index as usize; | ||||
|                 let key = &tx.account_keys[index]; | ||||
|                 (key, index < tx.signatures.len()) | ||||
|             }) | ||||
|             .zip(program_accounts.iter_mut()) | ||||
|             .map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account)) | ||||
|             .collect(); | ||||
|  | ||||
|         let ret = entrypoint(&id(), &mut keyed_accounts, &data, 42); | ||||
|         info!("ret: {:?}", ret); | ||||
|         ret | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_storage_tx() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [(keypair.pubkey(), Account::default())]; | ||||
|         let mut keyed_accounts = create_keyed_accounts(&mut accounts); | ||||
|         assert!(entrypoint(&id(), &mut keyed_accounts, &[], 42).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_serialize_overflow() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut keyed_accounts = Vec::new(); | ||||
|         let mut user_account = Account::default(); | ||||
|         let pubkey = keypair.pubkey(); | ||||
|         keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account)); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         assert_eq!( | ||||
|             entrypoint(&id(), &mut keyed_accounts, &tx.instructions[0].data, 42), | ||||
|             Err(InstructionError::AccountDataTooSmall) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_invalid_accounts_len() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default()]; | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             0, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|         assert!(test_transaction(&tx, &mut accounts).is_err()); | ||||
|  | ||||
|         let mut accounts = [Account::default(), Account::default(), Account::default()]; | ||||
|  | ||||
|         assert!(test_transaction(&tx, &mut accounts).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_submit_mining_invalid_entry_height() { | ||||
|         solana_logger::setup(); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default(), Account::default()]; | ||||
|         accounts[1].data.resize(16 * 1024, 0); | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             0, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|  | ||||
|         // Haven't seen a transaction to roll over the epoch, so this should fail | ||||
|         assert!(test_transaction(&tx, &mut accounts).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_submit_mining_ok() { | ||||
|         solana_logger::setup(); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default(), Account::default()]; | ||||
|         accounts[0].data.resize(16 * 1024, 0); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             0, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|  | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_validate_mining() { | ||||
|         solana_logger::setup(); | ||||
|         let keypair = Keypair::new(); | ||||
|         let mut accounts = [Account::default(), Account::default()]; | ||||
|         accounts[0].data.resize(16 * 1024, 0); | ||||
|  | ||||
|         let entry_height = 0; | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT, | ||||
|         ); | ||||
|  | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_mining_proof( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             entry_height, | ||||
|             Signature::default(), | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT * 2, | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_proof_validation( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             entry_height, | ||||
|             vec![ProofStatus::Valid], | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|             &keypair, | ||||
|             Hash::default(), | ||||
|             Hash::default(), | ||||
|             ENTRIES_PER_SEGMENT * 3, | ||||
|         ); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         let tx = StorageTransaction::new_reward_claim(&keypair, Hash::default(), entry_height); | ||||
|         test_transaction(&tx, &mut accounts).unwrap(); | ||||
|  | ||||
|         assert!(accounts[0].lamports == TOTAL_VALIDATOR_REWARDS); | ||||
|     } | ||||
| } | ||||
| solana_sdk::process_instruction_entrypoint!(process_instruction); | ||||
|   | ||||
| @@ -1,104 +0,0 @@ | ||||
| use bincode::deserialize; | ||||
| use log::*; | ||||
| use solana_runtime::bank::Bank; | ||||
| use solana_sdk::genesis_block::GenesisBlock; | ||||
| use solana_sdk::hash::{hash, Hash}; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; | ||||
| use solana_sdk::system_transaction::SystemTransaction; | ||||
| use solana_storage_api::{StorageTransaction, ENTRIES_PER_SEGMENT}; | ||||
|  | ||||
| fn get_storage_entry_height(bank: &Bank, account: &Pubkey) -> u64 { | ||||
|     match bank.get_account(&account) { | ||||
|         Some(storage_system_account) => { | ||||
|             let state = deserialize(&storage_system_account.data); | ||||
|             if let Ok(state) = state { | ||||
|                 let state: solana_storage_api::StorageProgramState = state; | ||||
|                 return state.entry_height; | ||||
|             } | ||||
|         } | ||||
|         None => { | ||||
|             info!("error in reading entry_height"); | ||||
|         } | ||||
|     } | ||||
|     0 | ||||
| } | ||||
|  | ||||
| fn get_storage_blockhash(bank: &Bank, account: &Pubkey) -> Hash { | ||||
|     if let Some(storage_system_account) = bank.get_account(&account) { | ||||
|         let state = deserialize(&storage_system_account.data); | ||||
|         if let Ok(state) = state { | ||||
|             let state: solana_storage_api::StorageProgramState = state; | ||||
|             return state.hash; | ||||
|         } | ||||
|     } | ||||
|     Hash::default() | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn test_bank_storage() { | ||||
|     let (mut genesis_block, alice) = GenesisBlock::new(1000); | ||||
|     genesis_block.native_programs.push(( | ||||
|         "solana_storage_program".to_string(), | ||||
|         solana_storage_api::id(), | ||||
|     )); | ||||
|     let bank = Bank::new(&genesis_block); | ||||
|  | ||||
|     let bob = Keypair::new(); | ||||
|     let jack = Keypair::new(); | ||||
|     let jill = Keypair::new(); | ||||
|  | ||||
|     let x = 42; | ||||
|     let blockhash = genesis_block.hash(); | ||||
|     let x2 = x * 2; | ||||
|     let storage_blockhash = hash(&[x2]); | ||||
|  | ||||
|     bank.register_tick(&blockhash); | ||||
|  | ||||
|     bank.transfer(10, &alice, &jill.pubkey(), blockhash) | ||||
|         .unwrap(); | ||||
|  | ||||
|     bank.transfer(10, &alice, &bob.pubkey(), blockhash).unwrap(); | ||||
|     bank.transfer(10, &alice, &jack.pubkey(), blockhash) | ||||
|         .unwrap(); | ||||
|  | ||||
|     let tx = SystemTransaction::new_program_account( | ||||
|         &alice, | ||||
|         &bob.pubkey(), | ||||
|         blockhash, | ||||
|         1, | ||||
|         4 * 1024, | ||||
|         &solana_storage_api::id(), | ||||
|         0, | ||||
|     ); | ||||
|  | ||||
|     bank.process_transaction(&tx).unwrap(); | ||||
|  | ||||
|     let tx = StorageTransaction::new_advertise_recent_blockhash( | ||||
|         &bob, | ||||
|         storage_blockhash, | ||||
|         blockhash, | ||||
|         ENTRIES_PER_SEGMENT, | ||||
|     ); | ||||
|  | ||||
|     bank.process_transaction(&tx).unwrap(); | ||||
|  | ||||
|     let entry_height = 0; | ||||
|     let tx = StorageTransaction::new_mining_proof( | ||||
|         &bob, | ||||
|         Hash::default(), | ||||
|         blockhash, | ||||
|         entry_height, | ||||
|         Signature::default(), | ||||
|     ); | ||||
|     let _result = bank.process_transaction(&tx).unwrap(); | ||||
|  | ||||
|     assert_eq!( | ||||
|         get_storage_entry_height(&bank, &bob.pubkey()), | ||||
|         ENTRIES_PER_SEGMENT | ||||
|     ); | ||||
|     assert_eq!( | ||||
|         get_storage_blockhash(&bank, &bob.pubkey()), | ||||
|         storage_blockhash | ||||
|     ); | ||||
| } | ||||
| @@ -10,8 +10,10 @@ edition = "2018" | ||||
|  | ||||
| [dependencies] | ||||
| bincode = "1.1.2" | ||||
| log = "0.4.2" | ||||
| serde = "1.0.89" | ||||
| serde_derive = "1.0.89" | ||||
| solana-logger = { path = "../../logger", version = "0.13.0" } | ||||
| solana-sdk = { path = "../../sdk", version = "0.13.0" } | ||||
|  | ||||
| [lib] | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| pub mod token_processor; | ||||
| mod token_state; | ||||
|  | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
|  | ||||
| const TOKEN_PROGRAM_ID: [u8; 32] = [ | ||||
|   | ||||
							
								
								
									
										20
									
								
								programs/token_api/src/token_processor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								programs/token_api/src/token_processor.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| use crate::token_state::TokenState; | ||||
| use bincode::serialize; | ||||
| use log::*; | ||||
| use solana_sdk::account::KeyedAccount; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::transaction::InstructionError; | ||||
|  | ||||
| pub fn process_instruction( | ||||
|     program_id: &Pubkey, | ||||
|     info: &mut [KeyedAccount], | ||||
|     input: &[u8], | ||||
|     _tick_height: u64, | ||||
| ) -> Result<(), InstructionError> { | ||||
|     solana_logger::setup(); | ||||
|  | ||||
|     TokenState::process(program_id, info, input).map_err(|e| { | ||||
|         error!("error: {:?}", e); | ||||
|         InstructionError::CustomError(serialize(&e).unwrap()) | ||||
|     }) | ||||
| } | ||||
| @@ -92,54 +92,54 @@ enum Command { | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] | ||||
| pub enum TokenProgram { | ||||
| pub enum TokenState { | ||||
|     Unallocated, | ||||
|     Token(TokenInfo), | ||||
|     Account(TokenAccountInfo), | ||||
|     Invalid, | ||||
| } | ||||
| impl Default for TokenProgram { | ||||
|     fn default() -> TokenProgram { | ||||
|         TokenProgram::Unallocated | ||||
| impl Default for TokenState { | ||||
|     fn default() -> TokenState { | ||||
|         TokenState::Unallocated | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TokenProgram { | ||||
| impl TokenState { | ||||
|     #[allow(clippy::needless_pass_by_value)] | ||||
|     fn map_to_invalid_args(err: std::boxed::Box<bincode::ErrorKind>) -> Error { | ||||
|         warn!("invalid argument: {:?}", err); | ||||
|         Error::InvalidArgument | ||||
|     } | ||||
| 
 | ||||
|     pub fn deserialize(input: &[u8]) -> Result<TokenProgram> { | ||||
|     pub fn deserialize(input: &[u8]) -> Result<TokenState> { | ||||
|         if input.is_empty() { | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
|         match input[0] { | ||||
|             0 => Ok(TokenProgram::Unallocated), | ||||
|             1 => Ok(TokenProgram::Token( | ||||
|             0 => Ok(TokenState::Unallocated), | ||||
|             1 => Ok(TokenState::Token( | ||||
|                 bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?, | ||||
|             )), | ||||
|             2 => Ok(TokenProgram::Account( | ||||
|             2 => Ok(TokenState::Account( | ||||
|                 bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?, | ||||
|             )), | ||||
|             _ => Err(Error::InvalidArgument), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn serialize(self: &TokenProgram, output: &mut [u8]) -> Result<()> { | ||||
|     fn serialize(self: &TokenState, output: &mut [u8]) -> Result<()> { | ||||
|         if output.is_empty() { | ||||
|             warn!("serialize fail: ouput.len is 0"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
|         match self { | ||||
|             TokenProgram::Unallocated | TokenProgram::Invalid => Err(Error::InvalidArgument), | ||||
|             TokenProgram::Token(token_info) => { | ||||
|             TokenState::Unallocated | TokenState::Invalid => Err(Error::InvalidArgument), | ||||
|             TokenState::Token(token_info) => { | ||||
|                 output[0] = 1; | ||||
|                 let writer = std::io::BufWriter::new(&mut output[1..]); | ||||
|                 bincode::serialize_into(writer, &token_info).map_err(Self::map_to_invalid_args) | ||||
|             } | ||||
|             TokenProgram::Account(account_info) => { | ||||
|             TokenState::Account(account_info) => { | ||||
|                 output[0] = 2; | ||||
|                 let writer = std::io::BufWriter::new(&mut output[1..]); | ||||
|                 bincode::serialize_into(writer, &account_info).map_err(Self::map_to_invalid_args) | ||||
| @@ -149,7 +149,7 @@ impl TokenProgram { | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn amount(&self) -> Result<u64> { | ||||
|         if let TokenProgram::Account(account_info) = self { | ||||
|         if let TokenState::Account(account_info) = self { | ||||
|             Ok(account_info.amount) | ||||
|         } else { | ||||
|             Err(Error::InvalidArgument) | ||||
| @@ -159,28 +159,28 @@ impl TokenProgram { | ||||
|     #[allow(dead_code)] | ||||
|     pub fn only_owner(&self, key: &Pubkey) -> Result<()> { | ||||
|         if *key != Pubkey::default() { | ||||
|             if let TokenProgram::Account(account_info) = self { | ||||
|             if let TokenState::Account(account_info) = self { | ||||
|                 if account_info.owner == *key { | ||||
|                     return Ok(()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         warn!("TokenProgram: non-owner rejected"); | ||||
|         warn!("TokenState: non-owner rejected"); | ||||
|         Err(Error::NotOwner) | ||||
|     } | ||||
| 
 | ||||
|     pub fn process_command_newtoken( | ||||
|         info: &mut [KeyedAccount], | ||||
|         token_info: TokenInfo, | ||||
|         input_program_accounts: &[TokenProgram], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenProgram)>, | ||||
|         input_program_accounts: &[TokenState], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenState)>, | ||||
|     ) -> Result<()> { | ||||
|         if input_program_accounts.len() != 2 { | ||||
|             error!("Expected 2 accounts"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| 
 | ||||
|         if let TokenProgram::Account(dest_account) = &input_program_accounts[1] { | ||||
|         if let TokenState::Account(dest_account) = &input_program_accounts[1] { | ||||
|             if info[0].signer_key().unwrap() != &dest_account.token { | ||||
|                 error!("account 1 token mismatch"); | ||||
|                 Err(Error::InvalidArgument)?; | ||||
| @@ -193,24 +193,24 @@ impl TokenProgram { | ||||
| 
 | ||||
|             let mut output_dest_account = dest_account.clone(); | ||||
|             output_dest_account.amount = token_info.supply; | ||||
|             output_program_accounts.push((1, TokenProgram::Account(output_dest_account))); | ||||
|             output_program_accounts.push((1, TokenState::Account(output_dest_account))); | ||||
|         } else { | ||||
|             error!("account 1 invalid"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| 
 | ||||
|         if input_program_accounts[0] != TokenProgram::Unallocated { | ||||
|         if input_program_accounts[0] != TokenState::Unallocated { | ||||
|             error!("account 0 not available"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
|         output_program_accounts.push((0, TokenProgram::Token(token_info))); | ||||
|         output_program_accounts.push((0, TokenState::Token(token_info))); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn process_command_newaccount( | ||||
|         info: &mut [KeyedAccount], | ||||
|         input_program_accounts: &[TokenProgram], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenProgram)>, | ||||
|         input_program_accounts: &[TokenState], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenState)>, | ||||
|     ) -> Result<()> { | ||||
|         // key 0 - Destination new token account
 | ||||
|         // key 1 - Owner of the account
 | ||||
| @@ -220,7 +220,7 @@ impl TokenProgram { | ||||
|             error!("Expected 3 accounts"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
|         if input_program_accounts[0] != TokenProgram::Unallocated { | ||||
|         if input_program_accounts[0] != TokenState::Unallocated { | ||||
|             error!("account 0 is already allocated"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| @@ -236,22 +236,22 @@ impl TokenProgram { | ||||
|                 original_amount: 0, | ||||
|             }); | ||||
|         } | ||||
|         output_program_accounts.push((0, TokenProgram::Account(token_account_info))); | ||||
|         output_program_accounts.push((0, TokenState::Account(token_account_info))); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn process_command_transfer( | ||||
|         info: &mut [KeyedAccount], | ||||
|         amount: u64, | ||||
|         input_program_accounts: &[TokenProgram], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenProgram)>, | ||||
|         input_program_accounts: &[TokenState], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenState)>, | ||||
|     ) -> Result<()> { | ||||
|         if input_program_accounts.len() < 3 { | ||||
|             error!("Expected 3 accounts"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| 
 | ||||
|         if let (TokenProgram::Account(source_account), TokenProgram::Account(dest_account)) = | ||||
|         if let (TokenState::Account(source_account), TokenState::Account(dest_account)) = | ||||
|             (&input_program_accounts[1], &input_program_accounts[2]) | ||||
|         { | ||||
|             if source_account.token != dest_account.token { | ||||
| @@ -275,7 +275,7 @@ impl TokenProgram { | ||||
| 
 | ||||
|             let mut output_source_account = source_account.clone(); | ||||
|             output_source_account.amount -= amount; | ||||
|             output_program_accounts.push((1, TokenProgram::Account(output_source_account))); | ||||
|             output_program_accounts.push((1, TokenState::Account(output_source_account))); | ||||
| 
 | ||||
|             if let Some(ref delegate_info) = source_account.delegate { | ||||
|                 if input_program_accounts.len() != 4 { | ||||
| @@ -284,7 +284,7 @@ impl TokenProgram { | ||||
|                 } | ||||
| 
 | ||||
|                 let delegate_account = source_account; | ||||
|                 if let TokenProgram::Account(source_account) = &input_program_accounts[3] { | ||||
|                 if let TokenState::Account(source_account) = &input_program_accounts[3] { | ||||
|                     if source_account.token != delegate_account.token { | ||||
|                         error!("account 1/3 token mismatch"); | ||||
|                         Err(Error::InvalidArgument)?; | ||||
| @@ -300,7 +300,7 @@ impl TokenProgram { | ||||
| 
 | ||||
|                     let mut output_source_account = source_account.clone(); | ||||
|                     output_source_account.amount -= amount; | ||||
|                     output_program_accounts.push((3, TokenProgram::Account(output_source_account))); | ||||
|                     output_program_accounts.push((3, TokenState::Account(output_source_account))); | ||||
|                 } else { | ||||
|                     error!("account 3 is an invalid account"); | ||||
|                     Err(Error::InvalidArgument)?; | ||||
| @@ -309,7 +309,7 @@ impl TokenProgram { | ||||
| 
 | ||||
|             let mut output_dest_account = dest_account.clone(); | ||||
|             output_dest_account.amount += amount; | ||||
|             output_program_accounts.push((2, TokenProgram::Account(output_dest_account))); | ||||
|             output_program_accounts.push((2, TokenState::Account(output_dest_account))); | ||||
|         } else { | ||||
|             error!("account 1 and/or 2 are invalid accounts"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
| @@ -320,15 +320,15 @@ impl TokenProgram { | ||||
|     pub fn process_command_approve( | ||||
|         info: &mut [KeyedAccount], | ||||
|         amount: u64, | ||||
|         input_program_accounts: &[TokenProgram], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenProgram)>, | ||||
|         input_program_accounts: &[TokenState], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenState)>, | ||||
|     ) -> Result<()> { | ||||
|         if input_program_accounts.len() != 3 { | ||||
|             error!("Expected 3 accounts"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| 
 | ||||
|         if let (TokenProgram::Account(source_account), TokenProgram::Account(delegate_account)) = | ||||
|         if let (TokenState::Account(source_account), TokenState::Account(delegate_account)) = | ||||
|             (&input_program_accounts[1], &input_program_accounts[2]) | ||||
|         { | ||||
|             if source_account.token != delegate_account.token { | ||||
| @@ -363,8 +363,7 @@ impl TokenProgram { | ||||
|                         source: delegate_info.source, | ||||
|                         original_amount: amount, | ||||
|                     }); | ||||
|                     output_program_accounts | ||||
|                         .push((2, TokenProgram::Account(output_delegate_account))); | ||||
|                     output_program_accounts.push((2, TokenState::Account(output_delegate_account))); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
| @@ -376,15 +375,15 @@ impl TokenProgram { | ||||
| 
 | ||||
|     pub fn process_command_setowner( | ||||
|         info: &mut [KeyedAccount], | ||||
|         input_program_accounts: &[TokenProgram], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenProgram)>, | ||||
|         input_program_accounts: &[TokenState], | ||||
|         output_program_accounts: &mut Vec<(usize, TokenState)>, | ||||
|     ) -> Result<()> { | ||||
|         if input_program_accounts.len() < 3 { | ||||
|             error!("Expected 3 accounts"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| 
 | ||||
|         if let TokenProgram::Account(source_account) = &input_program_accounts[1] { | ||||
|         if let TokenState::Account(source_account) = &input_program_accounts[1] { | ||||
|             if info[0].signer_key().unwrap() != &source_account.owner { | ||||
|                 info!("owner of account 1 not present"); | ||||
|                 Err(Error::InvalidArgument)?; | ||||
| @@ -392,7 +391,7 @@ impl TokenProgram { | ||||
| 
 | ||||
|             let mut output_source_account = source_account.clone(); | ||||
|             output_source_account.owner = *info[2].unsigned_key(); | ||||
|             output_program_accounts.push((1, TokenProgram::Account(output_source_account))); | ||||
|             output_program_accounts.push((1, TokenState::Account(output_source_account))); | ||||
|         } else { | ||||
|             info!("account 1 is invalid"); | ||||
|             Err(Error::InvalidArgument)?; | ||||
| @@ -408,7 +407,7 @@ impl TokenProgram { | ||||
|             Err(Error::InvalidArgument)?; | ||||
|         } | ||||
| 
 | ||||
|         let input_program_accounts: Vec<TokenProgram> = info | ||||
|         let input_program_accounts: Vec<TokenState> = info | ||||
|             .iter() | ||||
|             .map(|keyed_account| { | ||||
|                 let account = &keyed_account.account; | ||||
| @@ -417,11 +416,11 @@ impl TokenProgram { | ||||
|                         Ok(token_program) => token_program, | ||||
|                         Err(err) => { | ||||
|                             error!("deserialize failed: {:?}", err); | ||||
|                             TokenProgram::Invalid | ||||
|                             TokenState::Invalid | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     TokenProgram::Invalid | ||||
|                     TokenState::Invalid | ||||
|                 } | ||||
|             }) | ||||
|             .collect(); | ||||
| @@ -482,47 +481,47 @@ mod test { | ||||
|     use super::*; | ||||
|     #[test] | ||||
|     pub fn serde() { | ||||
|         assert_eq!(TokenProgram::deserialize(&[0]), Ok(TokenProgram::default())); | ||||
|         assert_eq!(TokenState::deserialize(&[0]), Ok(TokenState::default())); | ||||
| 
 | ||||
|         let mut data = vec![0; 256]; | ||||
| 
 | ||||
|         let account = TokenProgram::Account(TokenAccountInfo { | ||||
|         let account = TokenState::Account(TokenAccountInfo { | ||||
|             token: Pubkey::new(&[1; 32]), | ||||
|             owner: Pubkey::new(&[2; 32]), | ||||
|             amount: 123, | ||||
|             delegate: None, | ||||
|         }); | ||||
|         account.serialize(&mut data).unwrap(); | ||||
|         assert_eq!(TokenProgram::deserialize(&data), Ok(account)); | ||||
|         assert_eq!(TokenState::deserialize(&data), Ok(account)); | ||||
| 
 | ||||
|         let account = TokenProgram::Token(TokenInfo { | ||||
|         let account = TokenState::Token(TokenInfo { | ||||
|             supply: 12345, | ||||
|             decimals: 2, | ||||
|             name: "A test token".to_string(), | ||||
|             symbol: "TEST".to_string(), | ||||
|         }); | ||||
|         account.serialize(&mut data).unwrap(); | ||||
|         assert_eq!(TokenProgram::deserialize(&data), Ok(account)); | ||||
|         assert_eq!(TokenState::deserialize(&data), Ok(account)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     pub fn serde_expect_fail() { | ||||
|         let mut data = vec![0; 256]; | ||||
| 
 | ||||
|         // Certain TokenProgram's may not be serialized
 | ||||
|         let account = TokenProgram::default(); | ||||
|         assert_eq!(account, TokenProgram::Unallocated); | ||||
|         // Certain TokenState's may not be serialized
 | ||||
|         let account = TokenState::default(); | ||||
|         assert_eq!(account, TokenState::Unallocated); | ||||
|         assert!(account.serialize(&mut data).is_err()); | ||||
|         assert!(account.serialize(&mut data).is_err()); | ||||
|         let account = TokenProgram::Invalid; | ||||
|         let account = TokenState::Invalid; | ||||
|         assert!(account.serialize(&mut data).is_err()); | ||||
| 
 | ||||
|         // Bad deserialize data
 | ||||
|         assert!(TokenProgram::deserialize(&[]).is_err()); | ||||
|         assert!(TokenProgram::deserialize(&[1]).is_err()); | ||||
|         assert!(TokenProgram::deserialize(&[1, 2]).is_err()); | ||||
|         assert!(TokenProgram::deserialize(&[2, 2]).is_err()); | ||||
|         assert!(TokenProgram::deserialize(&[3]).is_err()); | ||||
|         assert!(TokenState::deserialize(&[]).is_err()); | ||||
|         assert!(TokenState::deserialize(&[1]).is_err()); | ||||
|         assert!(TokenState::deserialize(&[1, 2]).is_err()); | ||||
|         assert!(TokenState::deserialize(&[2, 2]).is_err()); | ||||
|         assert!(TokenState::deserialize(&[3]).is_err()); | ||||
|     } | ||||
| 
 | ||||
|     // Note: business logic tests are located in the @solana/web3.js test suite
 | ||||
| @@ -9,12 +9,10 @@ homepage = "https://solana.com/" | ||||
| edition = "2018" | ||||
|  | ||||
| [dependencies] | ||||
| bincode = "1.1.2" | ||||
| log = "0.4.2" | ||||
| serde = "1.0.89" | ||||
| serde_derive = "1.0.89" | ||||
| solana-logger = { path = "../../logger", version = "0.13.0" } | ||||
| solana-sdk = { path = "../../sdk", version = "0.13.0" } | ||||
| solana-token-api = { path = "../token_api", version = "0.13.0" } | ||||
|  | ||||
| [lib] | ||||
| name = "solana_token_program" | ||||
|   | ||||
| @@ -1,23 +1,3 @@ | ||||
| use bincode::serialize; | ||||
| use log::*; | ||||
| use solana_sdk::account::KeyedAccount; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::solana_entrypoint; | ||||
| use solana_sdk::transaction::InstructionError; | ||||
| use solana_token_api::token_processor::process_instruction; | ||||
|  | ||||
| mod token_program; | ||||
|  | ||||
| solana_entrypoint!(entrypoint); | ||||
| fn entrypoint( | ||||
|     program_id: &Pubkey, | ||||
|     info: &mut [KeyedAccount], | ||||
|     input: &[u8], | ||||
|     _tick_height: u64, | ||||
| ) -> Result<(), InstructionError> { | ||||
|     solana_logger::setup(); | ||||
|  | ||||
|     token_program::TokenProgram::process(program_id, info, input).map_err(|e| { | ||||
|         error!("error: {:?}", e); | ||||
|         InstructionError::CustomError(serialize(&e).unwrap()) | ||||
|     }) | ||||
| } | ||||
| solana_sdk::process_instruction_entrypoint!(process_instruction); | ||||
|   | ||||
| @@ -13,8 +13,13 @@ bincode = "1.1.2" | ||||
| log = "0.4.2" | ||||
| serde = "1.0.89" | ||||
| serde_derive = "1.0.89" | ||||
| solana-logger = { path = "../../logger", version = "0.13.0" } | ||||
| solana-metrics = { path = "../../metrics", version = "0.13.0" } | ||||
| solana-sdk = { path = "../../sdk", version = "0.13.0" } | ||||
|  | ||||
| [dev-dependencies] | ||||
| solana-runtime = { path = "../../runtime", version = "0.13.0" } | ||||
|  | ||||
| [lib] | ||||
| name = "solana_vote_api" | ||||
| crate-type = ["lib"] | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| pub mod vote_instruction; | ||||
| pub mod vote_processor; | ||||
| pub mod vote_state; | ||||
| pub mod vote_transaction; | ||||
|  | ||||
|   | ||||
							
								
								
									
										192
									
								
								programs/vote_api/src/vote_processor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								programs/vote_api/src/vote_processor.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| //! Vote program | ||||
| //! Receive and processes votes from validators | ||||
|  | ||||
| use crate::vote_instruction::VoteInstruction; | ||||
| use crate::vote_state; | ||||
| use bincode::deserialize; | ||||
| use log::*; | ||||
| use solana_sdk::account::KeyedAccount; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::transaction::InstructionError; | ||||
|  | ||||
| pub fn process_instruction( | ||||
|     _program_id: &Pubkey, | ||||
|     keyed_accounts: &mut [KeyedAccount], | ||||
|     data: &[u8], | ||||
|     _tick_height: u64, | ||||
| ) -> Result<(), InstructionError> { | ||||
|     solana_logger::setup(); | ||||
|  | ||||
|     trace!("process_instruction: {:?}", data); | ||||
|     trace!("keyed_accounts: {:?}", keyed_accounts); | ||||
|  | ||||
|     match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { | ||||
|         VoteInstruction::InitializeAccount => vote_state::initialize_account(keyed_accounts), | ||||
|         VoteInstruction::DelegateStake(delegate_id) => { | ||||
|             vote_state::delegate_stake(keyed_accounts, &delegate_id) | ||||
|         } | ||||
|         VoteInstruction::AuthorizeVoter(voter_id) => { | ||||
|             vote_state::authorize_voter(keyed_accounts, &voter_id) | ||||
|         } | ||||
|         VoteInstruction::Vote(vote) => { | ||||
|             debug!("{:?} by {}", vote, keyed_accounts[0].signer_key().unwrap()); | ||||
|             solana_metrics::submit( | ||||
|                 solana_metrics::influxdb::Point::new("vote-native") | ||||
|                     .add_field("count", solana_metrics::influxdb::Value::Integer(1)) | ||||
|                     .to_owned(), | ||||
|             ); | ||||
|             vote_state::process_vote(keyed_accounts, vote) | ||||
|         } | ||||
|         VoteInstruction::ClearCredits => vote_state::clear_credits(keyed_accounts), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use crate::id; | ||||
|     use crate::vote_instruction::{Vote, VoteInstruction}; | ||||
|     use crate::vote_state::VoteState; | ||||
|     use crate::vote_transaction::VoteTransaction; | ||||
|     use solana_runtime::bank::{Bank, Result}; | ||||
|     use solana_sdk::genesis_block::GenesisBlock; | ||||
|     use solana_sdk::hash::hash; | ||||
|     use solana_sdk::pubkey::Pubkey; | ||||
|     use solana_sdk::signature::{Keypair, KeypairUtil}; | ||||
|     use solana_sdk::system_instruction::SystemInstruction; | ||||
|     use solana_sdk::transaction::{ | ||||
|         AccountMeta, Instruction, InstructionError, Transaction, TransactionError, | ||||
|     }; | ||||
|  | ||||
|     fn create_bank(lamports: u64) -> (Bank, Keypair) { | ||||
|         let (genesis_block, mint_keypair) = GenesisBlock::new(lamports); | ||||
|         let mut bank = Bank::new(&genesis_block); | ||||
|         bank.add_instruction_processor(id(), process_instruction); | ||||
|         (bank, mint_keypair) | ||||
|     } | ||||
|  | ||||
|     struct VoteBank<'a> { | ||||
|         bank: &'a Bank, | ||||
|     } | ||||
|  | ||||
|     impl<'a> VoteBank<'a> { | ||||
|         fn new(bank: &'a Bank) -> Self { | ||||
|             Self { bank } | ||||
|         } | ||||
|  | ||||
|         fn create_vote_account( | ||||
|             &self, | ||||
|             from_keypair: &Keypair, | ||||
|             vote_id: &Pubkey, | ||||
|             lamports: u64, | ||||
|         ) -> Result<()> { | ||||
|             let blockhash = self.bank.last_blockhash(); | ||||
|             let tx = VoteTransaction::new_account(from_keypair, vote_id, blockhash, lamports, 0); | ||||
|             self.bank.process_transaction(&tx) | ||||
|         } | ||||
|  | ||||
|         fn create_vote_account_with_delegate( | ||||
|             &self, | ||||
|             from_keypair: &Keypair, | ||||
|             vote_keypair: &Keypair, | ||||
|             delegate_id: &Pubkey, | ||||
|             lamports: u64, | ||||
|         ) -> Result<()> { | ||||
|             let blockhash = self.bank.last_blockhash(); | ||||
|             let tx = VoteTransaction::new_account_with_delegate( | ||||
|                 from_keypair, | ||||
|                 vote_keypair, | ||||
|                 delegate_id, | ||||
|                 blockhash, | ||||
|                 lamports, | ||||
|                 0, | ||||
|             ); | ||||
|             self.bank.process_transaction(&tx) | ||||
|         } | ||||
|  | ||||
|         fn submit_vote( | ||||
|             &self, | ||||
|             staking_account: &Pubkey, | ||||
|             vote_keypair: &Keypair, | ||||
|             tick_height: u64, | ||||
|         ) -> Result<VoteState> { | ||||
|             let blockhash = self.bank.last_blockhash(); | ||||
|             let tx = | ||||
|                 VoteTransaction::new_vote(staking_account, vote_keypair, tick_height, blockhash, 0); | ||||
|             self.bank.process_transaction(&tx)?; | ||||
|             self.bank.register_tick(&hash(blockhash.as_ref())); | ||||
|  | ||||
|             let vote_account = self.bank.get_account(&vote_keypair.pubkey()).unwrap(); | ||||
|             Ok(VoteState::deserialize(&vote_account.data).unwrap()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_vote_bank_basic() { | ||||
|         let (bank, from_keypair) = create_bank(10_000); | ||||
|         let vote_bank = VoteBank::new(&bank); | ||||
|  | ||||
|         let vote_keypair = Keypair::new(); | ||||
|         let vote_id = vote_keypair.pubkey(); | ||||
|         vote_bank | ||||
|             .create_vote_account(&from_keypair, &vote_id, 100) | ||||
|             .unwrap(); | ||||
|  | ||||
|         let vote_state = vote_bank.submit_vote(&vote_id, &vote_keypair, 0).unwrap(); | ||||
|         assert_eq!(vote_state.votes.len(), 1); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_vote_bank_delegate() { | ||||
|         let (bank, from_keypair) = create_bank(10_000); | ||||
|         let vote_bank = VoteBank::new(&bank); | ||||
|         let vote_keypair = Keypair::new(); | ||||
|         let delegate_keypair = Keypair::new(); | ||||
|         let delegate_id = delegate_keypair.pubkey(); | ||||
|         vote_bank | ||||
|             .create_vote_account_with_delegate(&from_keypair, &vote_keypair, &delegate_id, 100) | ||||
|             .unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_vote_via_bank_with_no_signature() { | ||||
|         let (bank, mallory_keypair) = create_bank(10_000); | ||||
|         let vote_bank = VoteBank::new(&bank); | ||||
|  | ||||
|         let vote_keypair = Keypair::new(); | ||||
|         let vote_id = vote_keypair.pubkey(); | ||||
|         vote_bank | ||||
|             .create_vote_account(&mallory_keypair, &vote_id, 100) | ||||
|             .unwrap(); | ||||
|  | ||||
|         let mallory_id = mallory_keypair.pubkey(); | ||||
|         let blockhash = bank.last_blockhash(); | ||||
|         let vote_ix = Instruction::new( | ||||
|             id(), | ||||
|             &VoteInstruction::Vote(Vote::new(0)), | ||||
|             vec![AccountMeta::new(vote_id, false)], // <--- attack!! No signer required. | ||||
|         ); | ||||
|  | ||||
|         // Sneak in an instruction so that the transaction is signed but | ||||
|         // the 0th account in the second instruction is not! The program | ||||
|         // needs to check that it's signed. | ||||
|         let move_ix = SystemInstruction::new_move(&mallory_id, &vote_id, 1); | ||||
|         let mut tx = Transaction::new(vec![move_ix, vote_ix]); | ||||
|         tx.sign(&[&mallory_keypair], blockhash); | ||||
|  | ||||
|         let result = bank.process_transaction(&tx); | ||||
|  | ||||
|         // And ensure there's no vote. | ||||
|         let vote_account = bank.get_account(&vote_id).unwrap(); | ||||
|         let vote_state = VoteState::deserialize(&vote_account.data).unwrap(); | ||||
|         assert_eq!(vote_state.votes.len(), 0); | ||||
|  | ||||
|         assert_eq!( | ||||
|             result, | ||||
|             Err(TransactionError::InstructionError( | ||||
|                 1, | ||||
|                 InstructionError::InvalidArgument | ||||
|             )) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -9,16 +9,11 @@ homepage = "https://solana.com/" | ||||
| edition = "2018" | ||||
|  | ||||
| [dependencies] | ||||
| bincode = "1.1.2" | ||||
| log = "0.4.2" | ||||
| solana-logger = { path = "../../logger", version = "0.13.0" } | ||||
| solana-metrics = { path = "../../metrics", version = "0.13.0" } | ||||
| solana-sdk = { path = "../../sdk", version = "0.13.0" } | ||||
| solana-vote-api = { path = "../vote_api", version = "0.13.0" } | ||||
|  | ||||
| [dev-dependencies] | ||||
| solana-runtime = { path = "../../runtime", version = "0.13.0" } | ||||
|  | ||||
| [lib] | ||||
| name = "solana_vote_program" | ||||
| crate-type = ["cdylib"] | ||||
|   | ||||
| @@ -1,44 +1,3 @@ | ||||
| //! Vote program | ||||
| //! Receive and processes votes from validators | ||||
| use solana_vote_api::vote_processor::process_instruction; | ||||
|  | ||||
| use bincode::deserialize; | ||||
| use log::*; | ||||
| use solana_sdk::account::KeyedAccount; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::solana_entrypoint; | ||||
| use solana_sdk::transaction::InstructionError; | ||||
| use solana_vote_api::vote_instruction::VoteInstruction; | ||||
| use solana_vote_api::vote_state; | ||||
|  | ||||
| solana_entrypoint!(entrypoint); | ||||
| fn entrypoint( | ||||
|     _program_id: &Pubkey, | ||||
|     keyed_accounts: &mut [KeyedAccount], | ||||
|     data: &[u8], | ||||
|     _tick_height: u64, | ||||
| ) -> Result<(), InstructionError> { | ||||
|     solana_logger::setup(); | ||||
|  | ||||
|     trace!("process_instruction: {:?}", data); | ||||
|     trace!("keyed_accounts: {:?}", keyed_accounts); | ||||
|  | ||||
|     match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { | ||||
|         VoteInstruction::InitializeAccount => vote_state::initialize_account(keyed_accounts), | ||||
|         VoteInstruction::DelegateStake(delegate_id) => { | ||||
|             vote_state::delegate_stake(keyed_accounts, &delegate_id) | ||||
|         } | ||||
|         VoteInstruction::AuthorizeVoter(voter_id) => { | ||||
|             vote_state::authorize_voter(keyed_accounts, &voter_id) | ||||
|         } | ||||
|         VoteInstruction::Vote(vote) => { | ||||
|             debug!("{:?} by {}", vote, keyed_accounts[0].signer_key().unwrap()); | ||||
|             solana_metrics::submit( | ||||
|                 solana_metrics::influxdb::Point::new("vote-native") | ||||
|                     .add_field("count", solana_metrics::influxdb::Value::Integer(1)) | ||||
|                     .to_owned(), | ||||
|             ); | ||||
|             vote_state::process_vote(keyed_accounts, vote) | ||||
|         } | ||||
|         VoteInstruction::ClearCredits => vote_state::clear_credits(keyed_accounts), | ||||
|     } | ||||
| } | ||||
| solana_sdk::process_instruction_entrypoint!(process_instruction); | ||||
|   | ||||
| @@ -1,141 +0,0 @@ | ||||
| use solana_runtime::bank::{Bank, Result}; | ||||
| use solana_sdk::genesis_block::GenesisBlock; | ||||
| use solana_sdk::hash::hash; | ||||
| use solana_sdk::pubkey::Pubkey; | ||||
| use solana_sdk::signature::{Keypair, KeypairUtil}; | ||||
| use solana_sdk::system_instruction::SystemInstruction; | ||||
| use solana_sdk::transaction::{ | ||||
|     AccountMeta, Instruction, InstructionError, Transaction, TransactionError, | ||||
| }; | ||||
| use solana_vote_api::vote_instruction::{Vote, VoteInstruction}; | ||||
| use solana_vote_api::vote_state::VoteState; | ||||
| use solana_vote_api::vote_transaction::VoteTransaction; | ||||
|  | ||||
| struct VoteBank<'a> { | ||||
|     bank: &'a Bank, | ||||
| } | ||||
|  | ||||
| impl<'a> VoteBank<'a> { | ||||
|     fn new(bank: &'a Bank) -> Self { | ||||
|         bank.add_native_program("solana_vote_program", &solana_vote_api::id()); | ||||
|         Self { bank } | ||||
|     } | ||||
|  | ||||
|     fn create_vote_account( | ||||
|         &self, | ||||
|         from_keypair: &Keypair, | ||||
|         vote_id: &Pubkey, | ||||
|         lamports: u64, | ||||
|     ) -> Result<()> { | ||||
|         let blockhash = self.bank.last_blockhash(); | ||||
|         let tx = VoteTransaction::new_account(from_keypair, vote_id, blockhash, lamports, 0); | ||||
|         self.bank.process_transaction(&tx) | ||||
|     } | ||||
|  | ||||
|     fn create_vote_account_with_delegate( | ||||
|         &self, | ||||
|         from_keypair: &Keypair, | ||||
|         vote_keypair: &Keypair, | ||||
|         delegate_id: &Pubkey, | ||||
|         lamports: u64, | ||||
|     ) -> Result<()> { | ||||
|         let blockhash = self.bank.last_blockhash(); | ||||
|         let tx = VoteTransaction::new_account_with_delegate( | ||||
|             from_keypair, | ||||
|             vote_keypair, | ||||
|             delegate_id, | ||||
|             blockhash, | ||||
|             lamports, | ||||
|             0, | ||||
|         ); | ||||
|         self.bank.process_transaction(&tx) | ||||
|     } | ||||
|  | ||||
|     fn submit_vote( | ||||
|         &self, | ||||
|         staking_account: &Pubkey, | ||||
|         vote_keypair: &Keypair, | ||||
|         tick_height: u64, | ||||
|     ) -> Result<VoteState> { | ||||
|         let blockhash = self.bank.last_blockhash(); | ||||
|         let tx = | ||||
|             VoteTransaction::new_vote(staking_account, vote_keypair, tick_height, blockhash, 0); | ||||
|         self.bank.process_transaction(&tx)?; | ||||
|         self.bank.register_tick(&hash(blockhash.as_ref())); | ||||
|  | ||||
|         let vote_account = self.bank.get_account(&vote_keypair.pubkey()).unwrap(); | ||||
|         Ok(VoteState::deserialize(&vote_account.data).unwrap()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn test_vote_bank_basic() { | ||||
|     let (genesis_block, from_keypair) = GenesisBlock::new(10_000); | ||||
|     let bank = Bank::new(&genesis_block); | ||||
|     let vote_bank = VoteBank::new(&bank); | ||||
|  | ||||
|     let vote_keypair = Keypair::new(); | ||||
|     let vote_id = vote_keypair.pubkey(); | ||||
|     vote_bank | ||||
|         .create_vote_account(&from_keypair, &vote_id, 100) | ||||
|         .unwrap(); | ||||
|  | ||||
|     let vote_state = vote_bank.submit_vote(&vote_id, &vote_keypair, 0).unwrap(); | ||||
|     assert_eq!(vote_state.votes.len(), 1); | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn test_vote_bank_delegate() { | ||||
|     let (genesis_block, from_keypair) = GenesisBlock::new(10_000); | ||||
|     let bank = Bank::new(&genesis_block); | ||||
|     let vote_bank = VoteBank::new(&bank); | ||||
|     let vote_keypair = Keypair::new(); | ||||
|     let delegate_keypair = Keypair::new(); | ||||
|     let delegate_id = delegate_keypair.pubkey(); | ||||
|     vote_bank | ||||
|         .create_vote_account_with_delegate(&from_keypair, &vote_keypair, &delegate_id, 100) | ||||
|         .unwrap(); | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn test_vote_via_bank_with_no_signature() { | ||||
|     let (genesis_block, mallory_keypair) = GenesisBlock::new(10_000); | ||||
|     let bank = Bank::new(&genesis_block); | ||||
|     let vote_bank = VoteBank::new(&bank); | ||||
|  | ||||
|     let vote_keypair = Keypair::new(); | ||||
|     let vote_id = vote_keypair.pubkey(); | ||||
|     vote_bank | ||||
|         .create_vote_account(&mallory_keypair, &vote_id, 100) | ||||
|         .unwrap(); | ||||
|  | ||||
|     let mallory_id = mallory_keypair.pubkey(); | ||||
|     let blockhash = bank.last_blockhash(); | ||||
|     let vote_ix = Instruction::new( | ||||
|         solana_vote_api::id(), | ||||
|         &VoteInstruction::Vote(Vote::new(0)), | ||||
|         vec![AccountMeta::new(vote_id, false)], // <--- attack!! No signer required. | ||||
|     ); | ||||
|  | ||||
|     // Sneak in an instruction so that the transaction is signed but | ||||
|     // the 0th account in the second instruction is not! The program | ||||
|     // needs to check that it's signed. | ||||
|     let move_ix = SystemInstruction::new_move(&mallory_id, &vote_id, 1); | ||||
|     let mut tx = Transaction::new(vec![move_ix, vote_ix]); | ||||
|     tx.sign(&[&mallory_keypair], blockhash); | ||||
|  | ||||
|     let result = bank.process_transaction(&tx); | ||||
|  | ||||
|     // And ensure there's no vote. | ||||
|     let vote_account = bank.get_account(&vote_id).unwrap(); | ||||
|     let vote_state = VoteState::deserialize(&vote_account.data).unwrap(); | ||||
|     assert_eq!(vote_state.votes.len(), 0); | ||||
|  | ||||
|     assert_eq!( | ||||
|         result, | ||||
|         Err(TransactionError::InstructionError( | ||||
|             1, | ||||
|             InstructionError::InvalidArgument | ||||
|         )) | ||||
|     ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user