Transactions with multiple programs. (#1381)
Transactions contain a vector of instructions that are executed atomically. Bench shows a 2.3x speed up when using 5 instructions per tx.
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							a5f07638ec
						
					
				
				
					commit
					e7de7c32db
				
			
							
								
								
									
										273
									
								
								src/bank.rs
									
									
									
									
									
								
							
							
						
						
									
										273
									
								
								src/bank.rs
									
									
									
									
									
								
							| @@ -6,7 +6,6 @@ | ||||
| use bincode::deserialize; | ||||
| use bincode::serialize; | ||||
| use budget_program::BudgetState; | ||||
| use budget_transaction::BudgetTransaction; | ||||
| use counter::Counter; | ||||
| use dynamic_program::DynamicProgram; | ||||
| use entry::Entry; | ||||
| @@ -68,23 +67,23 @@ pub enum BankError { | ||||
|     /// Proof of History verification failed. | ||||
|     LedgerVerificationFailed, | ||||
|     /// Contract's transaction token balance does not equal the balance after the transaction | ||||
|     UnbalancedTransaction, | ||||
|     UnbalancedTransaction(u8), | ||||
|     /// Contract's transactions resulted in an account with a negative balance | ||||
|     /// The difference from InsufficientFundsForFee is that the transaction was executed by the | ||||
|     /// contract | ||||
|     ResultWithNegativeTokens, | ||||
|     ResultWithNegativeTokens(u8), | ||||
|  | ||||
|     /// Contract id is unknown | ||||
|     UnknownContractId, | ||||
|     UnknownContractId(u8), | ||||
|  | ||||
|     /// Contract modified an accounts contract id | ||||
|     ModifiedContractId, | ||||
|     ModifiedContractId(u8), | ||||
|  | ||||
|     /// Contract spent the tokens of an account that doesn't belong to it | ||||
|     ExternalAccountTokenSpend, | ||||
|     ExternalAccountTokenSpend(u8), | ||||
|  | ||||
|     /// The program returned an error | ||||
|     ProgramRuntimeError, | ||||
|     ProgramRuntimeError(u8), | ||||
| } | ||||
|  | ||||
| pub type Result<T> = result::Result<T, BankError>; | ||||
| @@ -94,7 +93,6 @@ type SignatureStatusMap = HashMap<Signature, Result<()>>; | ||||
| struct ErrorCounters { | ||||
|     account_not_found_validator: usize, | ||||
|     account_not_found_leader: usize, | ||||
|     account_not_found_vote: usize, | ||||
| } | ||||
|  | ||||
| /// The state of all accounts and contracts after processing its entries. | ||||
| @@ -294,24 +292,18 @@ impl Bank { | ||||
|         error_counters: &mut ErrorCounters, | ||||
|     ) -> Result<Vec<Account>> { | ||||
|         // Copy all the accounts | ||||
|         if accounts.get(&tx.keys[0]).is_none() { | ||||
|         if accounts.get(&tx.account_keys[0]).is_none() { | ||||
|             if !self.is_leader { | ||||
|                 error_counters.account_not_found_validator += 1; | ||||
|             } else { | ||||
|                 error_counters.account_not_found_leader += 1; | ||||
|             } | ||||
|             if BudgetState::check_id(&tx.program_id) { | ||||
|                 use budget_instruction::Instruction; | ||||
|                 if let Some(Instruction::NewVote(_vote)) = tx.instruction() { | ||||
|                     error_counters.account_not_found_vote += 1; | ||||
|                 } | ||||
|             } | ||||
|             Err(BankError::AccountNotFound) | ||||
|         } else if accounts.get(&tx.keys[0]).unwrap().tokens < tx.fee { | ||||
|         } else if accounts.get(&tx.account_keys[0]).unwrap().tokens < tx.fee { | ||||
|             Err(BankError::InsufficientFundsForFee) | ||||
|         } else { | ||||
|             let mut called_accounts: Vec<Account> = tx | ||||
|                 .keys | ||||
|                 .account_keys | ||||
|                 .iter() | ||||
|                 .map(|key| accounts.get(key).cloned().unwrap_or_default()) | ||||
|                 .collect(); | ||||
| @@ -335,7 +327,8 @@ impl Bank { | ||||
|     } | ||||
|  | ||||
|     pub fn verify_transaction( | ||||
|         tx: &Transaction, | ||||
|         program_index: usize, | ||||
|         tx_program_id: &Pubkey, | ||||
|         pre_program_id: &Pubkey, | ||||
|         pre_tokens: i64, | ||||
|         account: &Account, | ||||
| @@ -343,84 +336,138 @@ impl Bank { | ||||
|         // Verify the transaction | ||||
|         // make sure that program_id is still the same or this was just assigned by the system call contract | ||||
|         if !((*pre_program_id == account.program_id) | ||||
|             || (SystemProgram::check_id(&tx.program_id) | ||||
|             || (SystemProgram::check_id(&tx_program_id) | ||||
|                 && SystemProgram::check_id(&pre_program_id))) | ||||
|         { | ||||
|             //TODO, this maybe redundant bpf should be able to guarantee this property | ||||
|             return Err(BankError::ModifiedContractId); | ||||
|             return Err(BankError::ModifiedContractId(program_index as u8)); | ||||
|         } | ||||
|         // For accounts unassigned to the contract, the individual balance of each accounts cannot decrease. | ||||
|         if tx.program_id != account.program_id && pre_tokens > account.tokens { | ||||
|             return Err(BankError::ExternalAccountTokenSpend); | ||||
|         if *tx_program_id != account.program_id && pre_tokens > account.tokens { | ||||
|             return Err(BankError::ExternalAccountTokenSpend(program_index as u8)); | ||||
|         } | ||||
|         if account.tokens < 0 { | ||||
|             return Err(BankError::ResultWithNegativeTokens); | ||||
|             return Err(BankError::ResultWithNegativeTokens(program_index as u8)); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn loaded_contract(&self, tx: &Transaction, accounts: &mut [Account]) -> bool { | ||||
|     fn loaded_contract( | ||||
|         &self, | ||||
|         tx_program_id: &Pubkey, | ||||
|         tx: &Transaction, | ||||
|         program_index: usize, | ||||
|         accounts: &mut [&mut Account], | ||||
|     ) -> bool { | ||||
|         let loaded_contracts = self.loaded_contracts.write().unwrap(); | ||||
|         match loaded_contracts.get(&tx.program_id) { | ||||
|         match loaded_contracts.get(&tx_program_id) { | ||||
|             Some(dc) => { | ||||
|                 let mut infos: Vec<_> = (&tx.keys) | ||||
|                 let mut infos: Vec<_> = (&tx.account_keys) | ||||
|                     .into_iter() | ||||
|                     .zip(accounts) | ||||
|                     .map(|(key, account)| KeyedAccount { key, account }) | ||||
|                     .collect(); | ||||
|  | ||||
|                 dc.call(&mut infos, &tx.userdata); | ||||
|                 dc.call(&mut infos, tx.userdata(program_index)); | ||||
|                 true | ||||
|             } | ||||
|             None => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Execute a transaction. | ||||
|     /// This method calls the contract's process_transaction method and verifies that the result of | ||||
|     /// the contract does not violate the bank's accounting rules. | ||||
|     /// Execute a function with a subset of accounts as writable references. | ||||
|     /// Since the subset can point to the same references, in any order there is no way | ||||
|     /// for the borrow checker to track them with regards to the original set. | ||||
|     fn with_subset<F, A>(accounts: &mut [Account], ixes: &[u8], func: F) -> A | ||||
|     where | ||||
|         F: Fn(&mut [&mut Account]) -> A, | ||||
|     { | ||||
|         let mut subset: Vec<&mut Account> = ixes | ||||
|             .iter() | ||||
|             .map(|ix| { | ||||
|                 let ptr = &mut accounts[*ix as usize] as *mut Account; | ||||
|                 // lifetime of this unsafe is only within the scope of the closure | ||||
|                 // there is no way to reorder them without breaking borrow checker rules | ||||
|                 unsafe { &mut *ptr } | ||||
|             }).collect(); | ||||
|         func(&mut subset) | ||||
|     } | ||||
|     /// Execute an instruction | ||||
|     /// This method calls the instruction's program entry pont method and verifies that the result of | ||||
|     /// the call does not violate the bank's accounting rules. | ||||
|     /// The accounts are committed back to the bank only if this function returns Ok(_). | ||||
|     fn execute_transaction(&self, tx: &Transaction, accounts: &mut [Account]) -> Result<()> { | ||||
|         let pre_total: i64 = accounts.iter().map(|a| a.tokens).sum(); | ||||
|         let pre_data: Vec<_> = accounts | ||||
|     fn execute_instruction( | ||||
|         &self, | ||||
|         tx: &Transaction, | ||||
|         program_index: usize, | ||||
|         program_accounts: &mut [&mut Account], | ||||
|     ) -> Result<()> { | ||||
|         let tx_program_id = tx.program_id(program_index); | ||||
|         // TODO: the runtime should be checking read/write access to memory | ||||
|         // we are trusting the hard coded contracts not to clobber or allocate | ||||
|         let pre_total: i64 = program_accounts.iter().map(|a| a.tokens).sum(); | ||||
|         let pre_data: Vec<_> = program_accounts | ||||
|             .iter_mut() | ||||
|             .map(|a| (a.program_id, a.tokens)) | ||||
|             .collect(); | ||||
|  | ||||
|         // Call the contract method | ||||
|         // It's up to the contract to implement its own rules on moving funds | ||||
|         if SystemProgram::check_id(&tx.program_id) { | ||||
|             SystemProgram::process_transaction(&tx, accounts, &self.loaded_contracts) | ||||
|         } else if BudgetState::check_id(&tx.program_id) { | ||||
|             // TODO: the runtime should be checking read/write access to memory | ||||
|             // we are trusting the hard coded contracts not to clobber or allocate | ||||
|             if BudgetState::process_transaction(&tx, accounts).is_err() { | ||||
|                 return Err(BankError::ProgramRuntimeError); | ||||
|         if SystemProgram::check_id(&tx_program_id) { | ||||
|             SystemProgram::process_transaction( | ||||
|                 &tx, | ||||
|                 program_index, | ||||
|                 program_accounts, | ||||
|                 &self.loaded_contracts, | ||||
|             ) | ||||
|         } else if BudgetState::check_id(&tx_program_id) { | ||||
|             if BudgetState::process_transaction(&tx, program_index, program_accounts).is_err() { | ||||
|                 return Err(BankError::ProgramRuntimeError(program_index as u8)); | ||||
|             } | ||||
|         } else if StorageProgram::check_id(&tx.program_id) { | ||||
|             if StorageProgram::process_transaction(&tx, accounts).is_err() { | ||||
|                 return Err(BankError::ProgramRuntimeError); | ||||
|         } else if StorageProgram::check_id(&tx_program_id) { | ||||
|             if StorageProgram::process_transaction(&tx, program_index, program_accounts).is_err() { | ||||
|                 return Err(BankError::ProgramRuntimeError(program_index as u8)); | ||||
|             } | ||||
|         } else if TicTacToeProgram::check_id(&tx.program_id) { | ||||
|             if TicTacToeProgram::process_transaction(&tx, accounts).is_err() { | ||||
|                 return Err(BankError::ProgramRuntimeError); | ||||
|         } else if TicTacToeProgram::check_id(&tx_program_id) { | ||||
|             if TicTacToeProgram::process_transaction(&tx, program_index, program_accounts).is_err() | ||||
|             { | ||||
|                 return Err(BankError::ProgramRuntimeError(program_index as u8)); | ||||
|             } | ||||
|         } else if self.loaded_contract(&tx, accounts) { | ||||
|         } else if self.loaded_contract(tx_program_id, tx, program_index, program_accounts) { | ||||
|         } else { | ||||
|             return Err(BankError::UnknownContractId); | ||||
|             return Err(BankError::UnknownContractId(program_index as u8)); | ||||
|         } | ||||
|         // Verify the transaction | ||||
|         for ((pre_program_id, pre_tokens), post_account) in pre_data.iter().zip(accounts.iter()) { | ||||
|             Self::verify_transaction(&tx, pre_program_id, *pre_tokens, post_account)?; | ||||
|         for ((pre_program_id, pre_tokens), post_account) in | ||||
|             pre_data.iter().zip(program_accounts.iter()) | ||||
|         { | ||||
|             Self::verify_transaction( | ||||
|                 program_index, | ||||
|                 &tx_program_id, | ||||
|                 pre_program_id, | ||||
|                 *pre_tokens, | ||||
|                 post_account, | ||||
|             )?; | ||||
|         } | ||||
|         // The total sum of all the tokens in all the pages cannot change. | ||||
|         let post_total: i64 = accounts.iter().map(|a| a.tokens).sum(); | ||||
|         let post_total: i64 = program_accounts.iter().map(|a| a.tokens).sum(); | ||||
|         if pre_total != post_total { | ||||
|             Err(BankError::UnbalancedTransaction) | ||||
|             Err(BankError::UnbalancedTransaction(program_index as u8)) | ||||
|         } else { | ||||
|             Ok(()) | ||||
|         } | ||||
|     } | ||||
|     /// Execute a transaction. | ||||
|     /// This method calls each instruction in the transaction over the set of loaded Accounts | ||||
|     /// The accounts are committed back to the bank only if every instruction succeeds | ||||
|     fn execute_transaction(&self, tx: &Transaction, tx_accounts: &mut [Account]) -> Result<()> { | ||||
|         for (program_index, prog) in tx.instructions.iter().enumerate() { | ||||
|             Self::with_subset(tx_accounts, &prog.accounts, |program_accounts| { | ||||
|                 self.execute_instruction(tx, program_index, program_accounts) | ||||
|             })?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn store_accounts( | ||||
|         txs: &[Transaction], | ||||
| @@ -435,7 +482,7 @@ impl Bank { | ||||
|  | ||||
|             let tx = &txs[i]; | ||||
|             let acc = racc.as_ref().unwrap(); | ||||
|             for (key, account) in tx.keys.iter().zip(acc.iter()) { | ||||
|             for (key, account) in tx.account_keys.iter().zip(acc.iter()) { | ||||
|                 //purge if 0 | ||||
|                 if account.tokens == 0 { | ||||
|                     accounts.remove(&key); | ||||
| @@ -507,10 +554,6 @@ impl Bank { | ||||
|                     "bank-appy_debits-account_not_found-leader", | ||||
|                     error_counters.account_not_found_leader | ||||
|                 ); | ||||
|                 inc_new_counter_info!( | ||||
|                     "bank-appy_debits-vote_account_not_found", | ||||
|                     error_counters.account_not_found_vote | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|         let cur_tx_count = self.transaction_count.load(Ordering::Relaxed); | ||||
| @@ -611,8 +654,8 @@ impl Bank { | ||||
|             .expect("invalid ledger: need at least 2 entries"); | ||||
|         { | ||||
|             let tx = &entry1.transactions[0]; | ||||
|             assert!(SystemProgram::check_id(&tx.program_id), "Invalid ledger"); | ||||
|             let instruction: SystemProgram = deserialize(&tx.userdata).unwrap(); | ||||
|             assert!(SystemProgram::check_id(tx.program_id(0)), "Invalid ledger"); | ||||
|             let instruction: SystemProgram = deserialize(tx.userdata(0)).unwrap(); | ||||
|             let deposit = if let SystemProgram::Move { tokens } = instruction { | ||||
|                 Some(tokens) | ||||
|             } else { | ||||
| @@ -620,7 +663,9 @@ impl Bank { | ||||
|             }.expect("invalid ledger, needs to start with a contract"); | ||||
|             { | ||||
|                 let mut accounts = self.accounts.write().unwrap(); | ||||
|                 let account = accounts.entry(tx.keys[0]).or_insert_with(Account::default); | ||||
|                 let account = accounts | ||||
|                     .entry(tx.account_keys[0]) | ||||
|                     .or_insert_with(Account::default); | ||||
|                 account.tokens += deposit; | ||||
|                 trace!("applied genesis payment {:?} => {:?}", deposit, account); | ||||
|             } | ||||
| @@ -700,6 +745,14 @@ impl Bank { | ||||
|         self.get_signature_status(signature) != Err(BankError::SignatureNotFound) | ||||
|     } | ||||
|  | ||||
|     pub fn get_signature(&self, last_id: &Hash, signature: &Signature) -> Option<Result<()>> { | ||||
|         self.last_ids_sigs | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .get(last_id) | ||||
|             .and_then(|sigs| sigs.0.get(signature).cloned()) | ||||
|     } | ||||
|  | ||||
|     /// Hash the `accounts` HashMap. This represents a validator's interpretation | ||||
|     ///  of the ledger up to the `last_id`, to be sent back to the leader when voting. | ||||
|     pub fn hash_internal_state(&self) -> Hash { | ||||
| @@ -732,6 +785,7 @@ mod tests { | ||||
|     use signature::{GenKeys, KeypairUtil}; | ||||
|     use std; | ||||
|     use std::io::{BufReader, Cursor, Seek, SeekFrom}; | ||||
|     use transaction::Instruction; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_bank_new() { | ||||
| @@ -757,6 +811,84 @@ mod tests { | ||||
|         assert_eq!(bank.transaction_count(), 2); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_one_tx_two_out_atomic_fail() { | ||||
|         let mint = Mint::new(1); | ||||
|         let key1 = Keypair::new().pubkey(); | ||||
|         let key2 = Keypair::new().pubkey(); | ||||
|         let bank = Bank::new(&mint); | ||||
|  | ||||
|         let spend = SystemProgram::Move { tokens: 1 }; | ||||
|         let instructions = vec![ | ||||
|             Instruction { | ||||
|                 program_id: 0, | ||||
|                 userdata: serialize(&spend).unwrap(), | ||||
|                 accounts: vec![0, 1], | ||||
|             }, | ||||
|             Instruction { | ||||
|                 program_id: 0, | ||||
|                 userdata: serialize(&spend).unwrap(), | ||||
|                 accounts: vec![0, 2], | ||||
|             }, | ||||
|         ]; | ||||
|  | ||||
|         let t1 = Transaction::new_with_instructions( | ||||
|             &mint.keypair(), | ||||
|             &[key1, key2], | ||||
|             mint.last_id(), | ||||
|             0, | ||||
|             vec![SystemProgram::id()], | ||||
|             instructions, | ||||
|         ); | ||||
|         let res = bank.process_transactions(&vec![t1.clone()]); | ||||
|         assert_eq!(res.len(), 1); | ||||
|         assert_eq!(res[0], Err(BankError::ResultWithNegativeTokens(1))); | ||||
|         assert_eq!(bank.get_balance(&mint.pubkey()), 1); | ||||
|         assert_eq!(bank.get_balance(&key1), 0); | ||||
|         assert_eq!(bank.get_balance(&key2), 0); | ||||
|         assert_eq!( | ||||
|             bank.get_signature(&t1.last_id, &t1.signature), | ||||
|             Some(Err(BankError::ResultWithNegativeTokens(1))) | ||||
|         ); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_one_tx_two_out_atomic_pass() { | ||||
|         let mint = Mint::new(2); | ||||
|         let key1 = Keypair::new().pubkey(); | ||||
|         let key2 = Keypair::new().pubkey(); | ||||
|         let bank = Bank::new(&mint); | ||||
|  | ||||
|         let spend = SystemProgram::Move { tokens: 1 }; | ||||
|         let instructions = vec![ | ||||
|             Instruction { | ||||
|                 program_id: 0, | ||||
|                 userdata: serialize(&spend).unwrap(), | ||||
|                 accounts: vec![0, 1], | ||||
|             }, | ||||
|             Instruction { | ||||
|                 program_id: 0, | ||||
|                 userdata: serialize(&spend).unwrap(), | ||||
|                 accounts: vec![0, 2], | ||||
|             }, | ||||
|         ]; | ||||
|  | ||||
|         let t1 = Transaction::new_with_instructions( | ||||
|             &mint.keypair(), | ||||
|             &[key1, key2], | ||||
|             mint.last_id(), | ||||
|             0, | ||||
|             vec![SystemProgram::id()], | ||||
|             instructions, | ||||
|         ); | ||||
|         let res = bank.process_transactions(&vec![t1.clone()]); | ||||
|         assert_eq!(res.len(), 1); | ||||
|         assert!(res[0].is_ok()); | ||||
|         assert_eq!(bank.get_balance(&mint.pubkey()), 0); | ||||
|         assert_eq!(bank.get_balance(&key1), 1); | ||||
|         assert_eq!(bank.get_balance(&key2), 1); | ||||
|         assert_eq!(bank.get_signature(&t1.last_id, &t1.signature), Some(Ok(()))); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_negative_tokens() { | ||||
|         logger::setup(); | ||||
| @@ -765,7 +897,7 @@ mod tests { | ||||
|         let bank = Bank::new(&mint); | ||||
|         let res = bank.transfer(-1, &mint.keypair(), pubkey, mint.last_id()); | ||||
|         println!("{:?}", bank.get_account(&pubkey)); | ||||
|         assert_matches!(res, Err(BankError::ResultWithNegativeTokens)); | ||||
|         assert_matches!(res, Err(BankError::ResultWithNegativeTokens(0))); | ||||
|         assert_eq!(bank.transaction_count(), 0); | ||||
|     } | ||||
|  | ||||
| @@ -796,7 +928,7 @@ mod tests { | ||||
|         assert!(bank.has_signature(&signature)); | ||||
|         assert_matches!( | ||||
|             bank.get_signature_status(&signature), | ||||
|             Err(BankError::ResultWithNegativeTokens) | ||||
|             Err(BankError::ResultWithNegativeTokens(0)) | ||||
|         ); | ||||
|  | ||||
|         // The tokens didn't move, but the from address paid the transaction fee. | ||||
| @@ -829,7 +961,7 @@ mod tests { | ||||
|         assert_eq!(bank.get_balance(&pubkey), 1_000); | ||||
|         assert_matches!( | ||||
|             bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()), | ||||
|             Err(BankError::ResultWithNegativeTokens) | ||||
|             Err(BankError::ResultWithNegativeTokens(0)) | ||||
|         ); | ||||
|         assert_eq!(bank.transaction_count(), 1); | ||||
|  | ||||
| @@ -1126,19 +1258,4 @@ mod tests { | ||||
|         def_bank.set_finality(90); | ||||
|         assert_eq!(def_bank.finality(), 90); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_storage_tx() { | ||||
|         let mint = Mint::new(1); | ||||
|         let bank = Bank::new(&mint); | ||||
|         let tx = Transaction::new( | ||||
|             &mint.keypair(), | ||||
|             &[], | ||||
|             StorageProgram::id(), | ||||
|             vec![], // <--- attack! Panic on bad userdata? | ||||
|             mint.last_id(), | ||||
|             0, | ||||
|         ); | ||||
|         assert!(bank.process_transaction(&tx).is_err()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -224,7 +224,7 @@ impl BankingStage { | ||||
|                 .zip(vers) | ||||
|                 .filter_map(|(tx, ver)| match tx { | ||||
|                     None => None, | ||||
|                     Some((tx, _addr)) => if tx.verify_plan() && ver != 0 { | ||||
|                     Some((tx, _addr)) => if tx.verify_refs() && tx.verify_plan() && ver != 0 { | ||||
|                         Some(tx) | ||||
|                     } else { | ||||
|                         None | ||||
|   | ||||
| @@ -11,13 +11,13 @@ use transaction::Transaction; | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | ||||
| pub enum BudgetError { | ||||
|     InsufficientFunds(Pubkey), | ||||
|     ContractAlreadyExists(Pubkey), | ||||
|     ContractNotPending(Pubkey), | ||||
|     SourceIsPendingContract(Pubkey), | ||||
|     UninitializedContract(Pubkey), | ||||
|     InsufficientFunds, | ||||
|     ContractAlreadyExists, | ||||
|     ContractNotPending, | ||||
|     SourceIsPendingContract, | ||||
|     UninitializedContract, | ||||
|     NegativeTokens, | ||||
|     DestinationMissing(Pubkey), | ||||
|     DestinationMissing, | ||||
|     FailedWitness, | ||||
|     UserdataTooSmall, | ||||
|     UserdataDeserializeFailure, | ||||
| @@ -47,19 +47,21 @@ impl BudgetState { | ||||
|     /// will progress one step. | ||||
|     fn apply_signature( | ||||
|         &mut self, | ||||
|         keys: &[Pubkey], | ||||
|         account: &mut [Account], | ||||
|         tx: &Transaction, | ||||
|         program_index: usize, | ||||
|         account: &mut [&mut Account], | ||||
|     ) -> Result<(), BudgetError> { | ||||
|         let mut final_payment = None; | ||||
|         if let Some(ref mut budget) = self.pending_budget { | ||||
|             budget.apply_witness(&Witness::Signature, &keys[0]); | ||||
|             let key = tx.key(program_index, 0).unwrap(); | ||||
|             budget.apply_witness(&Witness::Signature, key); | ||||
|             final_payment = budget.final_payment(); | ||||
|         } | ||||
|  | ||||
|         if let Some(payment) = final_payment { | ||||
|             if keys.len() < 2 || payment.to != keys[2] { | ||||
|             if Some(&payment.to) != tx.key(program_index, 2) { | ||||
|                 trace!("destination missing"); | ||||
|                 return Err(BudgetError::DestinationMissing(payment.to)); | ||||
|                 return Err(BudgetError::DestinationMissing); | ||||
|             } | ||||
|             self.pending_budget = None; | ||||
|             account[1].tokens -= payment.tokens; | ||||
| @@ -72,22 +74,24 @@ impl BudgetState { | ||||
|     /// will progress one step. | ||||
|     fn apply_timestamp( | ||||
|         &mut self, | ||||
|         keys: &[Pubkey], | ||||
|         accounts: &mut [Account], | ||||
|         tx: &Transaction, | ||||
|         program_index: usize, | ||||
|         accounts: &mut [&mut Account], | ||||
|         dt: DateTime<Utc>, | ||||
|     ) -> Result<(), BudgetError> { | ||||
|         // Check to see if any timelocked transactions can be completed. | ||||
|         let mut final_payment = None; | ||||
|  | ||||
|         if let Some(ref mut budget) = self.pending_budget { | ||||
|             budget.apply_witness(&Witness::Timestamp(dt), &keys[0]); | ||||
|             let key = tx.key(program_index, 0).unwrap(); | ||||
|             budget.apply_witness(&Witness::Timestamp(dt), key); | ||||
|             final_payment = budget.final_payment(); | ||||
|         } | ||||
|  | ||||
|         if let Some(payment) = final_payment { | ||||
|             if keys.len() < 2 || payment.to != keys[2] { | ||||
|             if Some(&payment.to) != tx.key(program_index, 2) { | ||||
|                 trace!("destination missing"); | ||||
|                 return Err(BudgetError::DestinationMissing(payment.to)); | ||||
|                 return Err(BudgetError::DestinationMissing); | ||||
|             } | ||||
|             self.pending_budget = None; | ||||
|             accounts[1].tokens -= payment.tokens; | ||||
| @@ -99,15 +103,14 @@ impl BudgetState { | ||||
|     /// Deduct tokens from the source account if it has sufficient funds and the contract isn't | ||||
|     /// pending | ||||
|     fn apply_debits_to_budget_state( | ||||
|         tx: &Transaction, | ||||
|         accounts: &mut [Account], | ||||
|         accounts: &mut [&mut Account], | ||||
|         instruction: &Instruction, | ||||
|     ) -> Result<(), BudgetError> { | ||||
|         { | ||||
|             // if the source account userdata is not empty, this is a pending contract | ||||
|             if !accounts[0].userdata.is_empty() { | ||||
|                 trace!("source is pending"); | ||||
|                 return Err(BudgetError::SourceIsPendingContract(tx.keys[0])); | ||||
|                 return Err(BudgetError::SourceIsPendingContract); | ||||
|             } | ||||
|             if let Instruction::NewContract(contract) = &instruction { | ||||
|                 if contract.tokens < 0 { | ||||
| @@ -117,7 +120,7 @@ impl BudgetState { | ||||
|  | ||||
|                 if accounts[0].tokens < contract.tokens { | ||||
|                     trace!("insufficient funds"); | ||||
|                     return Err(BudgetError::InsufficientFunds(tx.keys[0])); | ||||
|                     return Err(BudgetError::InsufficientFunds); | ||||
|                 } else { | ||||
|                     accounts[0].tokens -= contract.tokens; | ||||
|                 } | ||||
| @@ -130,7 +133,8 @@ impl BudgetState { | ||||
|     /// Note: It is safe to apply credits from multiple transactions in parallel. | ||||
|     fn apply_credits_to_budget_state( | ||||
|         tx: &Transaction, | ||||
|         accounts: &mut [Account], | ||||
|         program_index: usize, | ||||
|         accounts: &mut [&mut Account], | ||||
|         instruction: &Instruction, | ||||
|     ) -> Result<(), BudgetError> { | ||||
|         match instruction { | ||||
| @@ -143,7 +147,7 @@ impl BudgetState { | ||||
|                     let existing = Self::deserialize(&accounts[1].userdata).ok(); | ||||
|                     if Some(true) == existing.map(|x| x.initialized) { | ||||
|                         trace!("contract already exists"); | ||||
|                         Err(BudgetError::ContractAlreadyExists(tx.keys[1])) | ||||
|                         Err(BudgetError::ContractAlreadyExists) | ||||
|                     } else { | ||||
|                         let mut state = BudgetState::default(); | ||||
|                         state.pending_budget = Some(budget); | ||||
| @@ -156,35 +160,35 @@ impl BudgetState { | ||||
|             Instruction::ApplyTimestamp(dt) => { | ||||
|                 if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) { | ||||
|                     if !state.is_pending() { | ||||
|                         Err(BudgetError::ContractNotPending(tx.keys[1])) | ||||
|                         Err(BudgetError::ContractNotPending) | ||||
|                     } else if !state.initialized { | ||||
|                         trace!("contract is uninitialized"); | ||||
|                         Err(BudgetError::UninitializedContract(tx.keys[1])) | ||||
|                         Err(BudgetError::UninitializedContract) | ||||
|                     } else { | ||||
|                         trace!("apply timestamp"); | ||||
|                         state.apply_timestamp(&tx.keys, accounts, *dt)?; | ||||
|                         state.apply_timestamp(tx, program_index, accounts, *dt)?; | ||||
|                         trace!("apply timestamp committed"); | ||||
|                         state.serialize(&mut accounts[1].userdata) | ||||
|                     } | ||||
|                 } else { | ||||
|                     Err(BudgetError::UninitializedContract(tx.keys[1])) | ||||
|                     Err(BudgetError::UninitializedContract) | ||||
|                 } | ||||
|             } | ||||
|             Instruction::ApplySignature => { | ||||
|                 if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) { | ||||
|                     if !state.is_pending() { | ||||
|                         Err(BudgetError::ContractNotPending(tx.keys[1])) | ||||
|                         Err(BudgetError::ContractNotPending) | ||||
|                     } else if !state.initialized { | ||||
|                         trace!("contract is uninitialized"); | ||||
|                         Err(BudgetError::UninitializedContract(tx.keys[1])) | ||||
|                         Err(BudgetError::UninitializedContract) | ||||
|                     } else { | ||||
|                         trace!("apply signature"); | ||||
|                         state.apply_signature(&tx.keys, accounts)?; | ||||
|                         state.apply_signature(tx, program_index, accounts)?; | ||||
|                         trace!("apply signature committed"); | ||||
|                         state.serialize(&mut accounts[1].userdata) | ||||
|                     } | ||||
|                 } else { | ||||
|                     Err(BudgetError::UninitializedContract(tx.keys[1])) | ||||
|                     Err(BudgetError::UninitializedContract) | ||||
|                 } | ||||
|             } | ||||
|             Instruction::NewVote(_vote) => { | ||||
| @@ -237,14 +241,19 @@ impl BudgetState { | ||||
|     /// be spent from this account . | ||||
|     pub fn process_transaction( | ||||
|         tx: &Transaction, | ||||
|         accounts: &mut [Account], | ||||
|         program_index: usize, | ||||
|         accounts: &mut [&mut Account], | ||||
|     ) -> Result<(), BudgetError> { | ||||
|         if let Ok(instruction) = deserialize(&tx.userdata) { | ||||
|         if let Ok(instruction) = deserialize(tx.userdata(program_index)) { | ||||
|             trace!("process_transaction: {:?}", instruction); | ||||
|             Self::apply_debits_to_budget_state(tx, accounts, &instruction) | ||||
|                 .and_then(|_| Self::apply_credits_to_budget_state(tx, accounts, &instruction)) | ||||
|             Self::apply_debits_to_budget_state(accounts, &instruction).and_then(|_| { | ||||
|                 Self::apply_credits_to_budget_state(tx, program_index, accounts, &instruction) | ||||
|             }) | ||||
|         } else { | ||||
|             info!("Invalid transaction userdata: {:?}", tx.userdata); | ||||
|             info!( | ||||
|                 "Invalid transaction userdata: {:?}", | ||||
|                 tx.userdata(program_index) | ||||
|             ); | ||||
|             Err(BudgetError::UserdataDeserializeFailure) | ||||
|         } | ||||
|     } | ||||
| @@ -275,6 +284,10 @@ mod test { | ||||
|     use solana_program_interface::pubkey::Pubkey; | ||||
|     use transaction::Transaction; | ||||
|  | ||||
|     fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<(), BudgetError> { | ||||
|         let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); | ||||
|         BudgetState::process_transaction(&tx, 0, &mut refs[..]) | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_serializer() { | ||||
|         let mut a = Account::new(0, 512, BudgetState::id()); | ||||
| @@ -303,16 +316,16 @@ mod test { | ||||
|         ]; | ||||
|         let from = Keypair::new(); | ||||
|         let contract = Keypair::new(); | ||||
|  | ||||
|         let userdata = vec![1, 2, 3]; | ||||
|         let tx = Transaction::new( | ||||
|             &from, | ||||
|             &[contract.pubkey()], | ||||
|             BudgetState::id(), | ||||
|             vec![1, 2, 3], // <== garbage instruction | ||||
|             userdata, | ||||
|             Hash::default(), | ||||
|             0, | ||||
|         ); | ||||
|         assert!(BudgetState::process_transaction(&tx, &mut accounts).is_err()); | ||||
|         assert!(process_transaction(&tx, &mut accounts).is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @@ -340,7 +353,7 @@ mod test { | ||||
|             1, | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         BudgetState::process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 1); | ||||
|         let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); | ||||
| @@ -355,8 +368,8 @@ mod test { | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             BudgetState::process_transaction(&tx, &mut accounts), | ||||
|             Err(BudgetError::DestinationMissing(to.pubkey())) | ||||
|             process_transaction(&tx, &mut accounts), | ||||
|             Err(BudgetError::DestinationMissing) | ||||
|         ); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 1); | ||||
| @@ -374,7 +387,7 @@ mod test { | ||||
|             dt, | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         BudgetState::process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 0); | ||||
|         assert_eq!(accounts[to_account].tokens, 1); | ||||
| @@ -384,8 +397,8 @@ mod test { | ||||
|  | ||||
|         // try to replay the timestamp contract | ||||
|         assert_eq!( | ||||
|             BudgetState::process_transaction(&tx, &mut accounts), | ||||
|             Err(BudgetError::ContractNotPending(contract.pubkey())) | ||||
|             process_transaction(&tx, &mut accounts), | ||||
|             Err(BudgetError::ContractNotPending) | ||||
|         ); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 0); | ||||
| @@ -415,7 +428,7 @@ mod test { | ||||
|             1, | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         BudgetState::process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 1); | ||||
|         let state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap(); | ||||
| @@ -426,7 +439,7 @@ mod test { | ||||
|             Transaction::budget_new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default()); | ||||
|         // unit test hack, the `from account` is passed instead of the `to` account to avoid | ||||
|         // creating more account vectors | ||||
|         BudgetState::process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         // nothing should be changed because apply witness didn't finalize a payment | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 1); | ||||
| @@ -440,7 +453,7 @@ mod test { | ||||
|             from.pubkey(), | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         BudgetState::process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         process_transaction(&tx, &mut accounts).unwrap(); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 0); | ||||
|         assert_eq!(accounts[pay_account].tokens, 1); | ||||
| @@ -453,8 +466,8 @@ mod test { | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             BudgetState::process_transaction(&tx, &mut accounts), | ||||
|             Err(BudgetError::ContractNotPending(contract.pubkey())) | ||||
|             process_transaction(&tx, &mut accounts), | ||||
|             Err(BudgetError::ContractNotPending) | ||||
|         ); | ||||
|         assert_eq!(accounts[from_account].tokens, 0); | ||||
|         assert_eq!(accounts[contract_account].tokens, 0); | ||||
| @@ -482,7 +495,7 @@ mod test { | ||||
|             Hash::default(), | ||||
|         ); | ||||
|  | ||||
|         assert!(BudgetState::process_transaction(&tx, &mut accounts).is_err()); | ||||
|         assert!(process_transaction(&tx, &mut accounts).is_err()); | ||||
|         assert!(BudgetState::deserialize(&accounts[1].userdata).is_err()); | ||||
|  | ||||
|         let tx = Transaction::budget_new_timestamp( | ||||
| @@ -492,7 +505,7 @@ mod test { | ||||
|             Utc::now(), | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         assert!(BudgetState::process_transaction(&tx, &mut accounts).is_err()); | ||||
|         assert!(process_transaction(&tx, &mut accounts).is_err()); | ||||
|         assert!(BudgetState::deserialize(&accounts[1].userdata).is_err()); | ||||
|  | ||||
|         // Success if there was no panic... | ||||
| @@ -517,7 +530,7 @@ mod test { | ||||
|  | ||||
|         let tx = Transaction::budget_new(&keypair, to, 192, Hash::default()); | ||||
|         assert_eq!( | ||||
|             tx.userdata, | ||||
|             tx.userdata(0).to_vec(), | ||||
|             vec![ | ||||
|                 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 1, 1, | ||||
|                 1, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 1, | ||||
| @@ -536,7 +549,7 @@ mod test { | ||||
|             Hash::default(), | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             tx.userdata, | ||||
|             tx.userdata(0).to_vec(), | ||||
|             vec![ | ||||
|                 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, | ||||
|                 0, 50, 48, 49, 54, 45, 48, 55, 45, 48, 56, 84, 48, 57, 58, 49, 48, 58, 49, 49, 90, | ||||
| @@ -561,10 +574,10 @@ mod test { | ||||
|         ); | ||||
|         let mut expected_userdata = vec![1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0]; | ||||
|         expected_userdata.extend(date_iso8601.as_bytes()); | ||||
|         assert_eq!(tx.userdata, expected_userdata); | ||||
|         assert_eq!(tx.userdata(0).to_vec(), expected_userdata); | ||||
|  | ||||
|         // ApplySignature | ||||
|         let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default()); | ||||
|         assert_eq!(tx.userdata, vec![2, 0, 0, 0]); | ||||
|         assert_eq!(tx.userdata(0).to_vec(), vec![2, 0, 0, 0]); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -62,7 +62,7 @@ pub trait BudgetTransaction { | ||||
|  | ||||
|     fn vote(&self) -> Option<(Pubkey, Vote, Hash)>; | ||||
|  | ||||
|     fn instruction(&self) -> Option<Instruction>; | ||||
|     fn instruction(&self, program_index: usize) -> Option<Instruction>; | ||||
|  | ||||
|     fn verify_plan(&self) -> bool; | ||||
| } | ||||
| @@ -204,26 +204,33 @@ impl BudgetTransaction for Transaction { | ||||
|     } | ||||
|  | ||||
|     fn vote(&self) -> Option<(Pubkey, Vote, Hash)> { | ||||
|         if let Some(Instruction::NewVote(vote)) = self.instruction() { | ||||
|             Some((*self.from(), vote, self.last_id)) | ||||
|         if self.instructions.len() > 1 { | ||||
|             error!("expecting only 1 Instruction per vote"); | ||||
|             None | ||||
|         } else if let Some(Instruction::NewVote(vote)) = self.instruction(0) { | ||||
|             Some((self.account_keys[0], vote, self.last_id)) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn instruction(&self) -> Option<Instruction> { | ||||
|         deserialize(&self.userdata).ok() | ||||
|     fn instruction(&self, program_index: usize) -> Option<Instruction> { | ||||
|         deserialize(&self.userdata(program_index)).ok() | ||||
|     } | ||||
|  | ||||
|     /// Verify only the payment plan. | ||||
|     fn verify_plan(&self) -> bool { | ||||
|         if let Some(Instruction::NewContract(contract)) = self.instruction() { | ||||
|             self.fee >= 0 | ||||
|                 && self.fee <= contract.tokens | ||||
|                 && contract.budget.verify(contract.tokens - self.fee) | ||||
|         } else { | ||||
|             true | ||||
|         for pix in 0..self.instructions.len() { | ||||
|             if let Some(Instruction::NewContract(contract)) = self.instruction(pix) { | ||||
|                 if !(self.fee >= 0 | ||||
|                     && self.fee <= contract.tokens | ||||
|                     && contract.budget.verify(contract.tokens - self.fee)) | ||||
|                 { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         true | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -232,6 +239,7 @@ mod tests { | ||||
|     use super::*; | ||||
|     use bincode::{deserialize, serialize}; | ||||
|     use signature::KeypairUtil; | ||||
|     use transaction; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_claim() { | ||||
| @@ -269,13 +277,18 @@ mod tests { | ||||
|         }); | ||||
|         let instruction = Instruction::NewContract(Contract { budget, tokens: 0 }); | ||||
|         let userdata = serialize(&instruction).unwrap(); | ||||
|         let instructions = vec![transaction::Instruction { | ||||
|             program_id: 0, | ||||
|             userdata, | ||||
|             accounts: vec![], | ||||
|         }]; | ||||
|         let claim0 = Transaction { | ||||
|             keys: vec![], | ||||
|             account_keys: vec![], | ||||
|             last_id: Default::default(), | ||||
|             signature: Default::default(), | ||||
|             program_id: Default::default(), | ||||
|             program_keys: vec![], | ||||
|             instructions, | ||||
|             fee: 0, | ||||
|             userdata, | ||||
|         }; | ||||
|         let buf = serialize(&claim0).unwrap(); | ||||
|         let claim1: Transaction = deserialize(&buf).unwrap(); | ||||
| @@ -288,14 +301,14 @@ mod tests { | ||||
|         let keypair = Keypair::new(); | ||||
|         let pubkey = keypair.pubkey(); | ||||
|         let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero); | ||||
|         let mut instruction = tx.instruction().unwrap(); | ||||
|         let mut instruction = tx.instruction(0).unwrap(); | ||||
|         if let Instruction::NewContract(ref mut contract) = instruction { | ||||
|             contract.tokens = 1_000_000; // <-- attack, part 1! | ||||
|             if let Budget::Pay(ref mut payment) = contract.budget { | ||||
|                 payment.tokens = contract.tokens; // <-- attack, part 2! | ||||
|             } | ||||
|         } | ||||
|         tx.userdata = serialize(&instruction).unwrap(); | ||||
|         tx.instructions[0].userdata = serialize(&instruction).unwrap(); | ||||
|         assert!(tx.verify_plan()); | ||||
|         assert!(!tx.verify_signature()); | ||||
|     } | ||||
| @@ -308,13 +321,13 @@ mod tests { | ||||
|         let pubkey1 = keypair1.pubkey(); | ||||
|         let zero = Hash::default(); | ||||
|         let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero); | ||||
|         let mut instruction = tx.instruction(); | ||||
|         let mut instruction = tx.instruction(0); | ||||
|         if let Some(Instruction::NewContract(ref mut contract)) = instruction { | ||||
|             if let Budget::Pay(ref mut payment) = contract.budget { | ||||
|                 payment.to = thief_keypair.pubkey(); // <-- attack! | ||||
|             } | ||||
|         } | ||||
|         tx.userdata = serialize(&instruction).unwrap(); | ||||
|         tx.instructions[0].userdata = serialize(&instruction).unwrap(); | ||||
|         assert!(tx.verify_plan()); | ||||
|         assert!(!tx.verify_signature()); | ||||
|     } | ||||
| @@ -325,23 +338,23 @@ mod tests { | ||||
|         let keypair1 = Keypair::new(); | ||||
|         let zero = Hash::default(); | ||||
|         let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero); | ||||
|         let mut instruction = tx.instruction().unwrap(); | ||||
|         let mut instruction = tx.instruction(0).unwrap(); | ||||
|         if let Instruction::NewContract(ref mut contract) = instruction { | ||||
|             if let Budget::Pay(ref mut payment) = contract.budget { | ||||
|                 payment.tokens = 2; // <-- attack! | ||||
|             } | ||||
|         } | ||||
|         tx.userdata = serialize(&instruction).unwrap(); | ||||
|         tx.instructions[0].userdata = serialize(&instruction).unwrap(); | ||||
|         assert!(!tx.verify_plan()); | ||||
|  | ||||
|         // Also, ensure all branchs of the plan spend all tokens | ||||
|         let mut instruction = tx.instruction().unwrap(); | ||||
|         let mut instruction = tx.instruction(0).unwrap(); | ||||
|         if let Instruction::NewContract(ref mut contract) = instruction { | ||||
|             if let Budget::Pay(ref mut payment) = contract.budget { | ||||
|                 payment.tokens = 0; // <-- whoops! | ||||
|             } | ||||
|         } | ||||
|         tx.userdata = serialize(&instruction).unwrap(); | ||||
|         tx.instructions[0].userdata = serialize(&instruction).unwrap(); | ||||
|         assert!(!tx.verify_plan()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -76,8 +76,9 @@ mod tests { | ||||
|     fn test_create_transactions() { | ||||
|         let mut transactions = Mint::new(100).create_transactions().into_iter(); | ||||
|         let tx = transactions.next().unwrap(); | ||||
|         assert!(SystemProgram::check_id(&tx.program_id)); | ||||
|         let instruction: SystemProgram = deserialize(&tx.userdata).unwrap(); | ||||
|         assert_eq!(tx.instructions.len(), 1); | ||||
|         assert!(SystemProgram::check_id(tx.program_id(0))); | ||||
|         let instruction: SystemProgram = deserialize(tx.userdata(0)).unwrap(); | ||||
|         if let SystemProgram::Move { tokens } = instruction { | ||||
|             assert_eq!(tokens, 100); | ||||
|         } | ||||
|   | ||||
| @@ -173,7 +173,7 @@ impl RpcSol for RpcSolImpl { | ||||
|         Ok( | ||||
|             match meta.request_processor.get_signature_status(signature) { | ||||
|                 Ok(_) => RpcSignatureStatus::Confirmed, | ||||
|                 Err(BankError::ProgramRuntimeError) => RpcSignatureStatus::ProgramRuntimeError, | ||||
|                 Err(BankError::ProgramRuntimeError(_)) => RpcSignatureStatus::ProgramRuntimeError, | ||||
|                 Err(BankError::SignatureNotFound) => RpcSignatureStatus::SignatureNotFound, | ||||
|                 Err(err) => { | ||||
|                     trace!("mapping {:?} to GenericFailure", err); | ||||
|   | ||||
| @@ -33,9 +33,10 @@ impl StorageProgram { | ||||
|  | ||||
|     pub fn process_transaction( | ||||
|         tx: &Transaction, | ||||
|         _accounts: &mut [Account], | ||||
|         pix: usize, | ||||
|         _accounts: &mut [&mut Account], | ||||
|     ) -> Result<(), StorageError> { | ||||
|         if let Ok(syscall) = deserialize(&tx.userdata) { | ||||
|         if let Ok(syscall) = deserialize(tx.userdata(pix)) { | ||||
|             match syscall { | ||||
|                 StorageProgram::SubmitMiningProof { sha_state } => { | ||||
|                     info!("Mining proof submitted with state {}", sha_state[0]); | ||||
| @@ -49,4 +50,22 @@ impl StorageProgram { | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test {} | ||||
| mod test { | ||||
|     use super::*; | ||||
|     use signature::Keypair; | ||||
|     use signature::KeypairUtil; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_storage_tx() { | ||||
|         let keypair = Keypair::new(); | ||||
|         let tx = Transaction::new( | ||||
|             &keypair, | ||||
|             &[], | ||||
|             StorageProgram::id(), | ||||
|             vec![], | ||||
|             Default::default(), | ||||
|             0, | ||||
|         ); | ||||
|         assert!(StorageProgram::process_transaction(&tx, 0, &mut []).is_err()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -49,10 +49,11 @@ impl SystemProgram { | ||||
|     } | ||||
|     pub fn process_transaction( | ||||
|         tx: &Transaction, | ||||
|         accounts: &mut [Account], | ||||
|         pix: usize, | ||||
|         accounts: &mut [&mut Account], | ||||
|         loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>, | ||||
|     ) { | ||||
|         if let Ok(syscall) = deserialize(&tx.userdata) { | ||||
|         if let Ok(syscall) = deserialize(tx.userdata(pix)) { | ||||
|             trace!("process_transaction: {:?}", syscall); | ||||
|             match syscall { | ||||
|                 SystemProgram::CreateAccount { | ||||
| @@ -91,12 +92,13 @@ impl SystemProgram { | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             info!("Invalid transaction userdata: {:?}", tx.userdata); | ||||
|             info!("Invalid transaction userdata: {:?}", tx.userdata(pix)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use super::*; | ||||
|     use hash::Hash; | ||||
|     use signature::{Keypair, KeypairUtil}; | ||||
|     use solana_program_interface::account::Account; | ||||
| @@ -107,6 +109,14 @@ mod test { | ||||
|     use system_transaction::SystemTransaction; | ||||
|     use transaction::Transaction; | ||||
|  | ||||
|     fn process_transaction( | ||||
|         tx: &Transaction, | ||||
|         accounts: &mut [Account], | ||||
|         loaded_programs: &RwLock<HashMap<Pubkey, DynamicProgram>>, | ||||
|     ) { | ||||
|         let mut refs: Vec<&mut Account> = accounts.iter_mut().collect(); | ||||
|         SystemProgram::process_transaction(&tx, 0, &mut refs[..], loaded_programs) | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_create_noop() { | ||||
|         let from = Keypair::new(); | ||||
| @@ -114,7 +124,7 @@ mod test { | ||||
|         let mut accounts = vec![Account::default(), Account::default()]; | ||||
|         let tx = Transaction::system_new(&from, to.pubkey(), 0, Hash::default()); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert_eq!(accounts[0].tokens, 0); | ||||
|         assert_eq!(accounts[1].tokens, 0); | ||||
|     } | ||||
| @@ -126,7 +136,7 @@ mod test { | ||||
|         accounts[0].tokens = 1; | ||||
|         let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert_eq!(accounts[0].tokens, 0); | ||||
|         assert_eq!(accounts[1].tokens, 1); | ||||
|     } | ||||
| @@ -139,7 +149,7 @@ mod test { | ||||
|         accounts[0].program_id = from.pubkey(); | ||||
|         let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert_eq!(accounts[0].tokens, 1); | ||||
|         assert_eq!(accounts[1].tokens, 0); | ||||
|     } | ||||
| @@ -151,7 +161,7 @@ mod test { | ||||
|         let tx = | ||||
|             Transaction::system_create(&from, to.pubkey(), Hash::default(), 0, 1, to.pubkey(), 0); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert!(accounts[0].userdata.is_empty()); | ||||
|         assert_eq!(accounts[1].userdata.len(), 1); | ||||
|         assert_eq!(accounts[1].program_id, to.pubkey()); | ||||
| @@ -172,7 +182,7 @@ mod test { | ||||
|             0, | ||||
|         ); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert!(accounts[1].userdata.is_empty()); | ||||
|     } | ||||
|     #[test] | ||||
| @@ -191,7 +201,7 @@ mod test { | ||||
|             0, | ||||
|         ); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert!(accounts[1].userdata.is_empty()); | ||||
|     } | ||||
|     #[test] | ||||
| @@ -210,7 +220,7 @@ mod test { | ||||
|             0, | ||||
|         ); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert_eq!(accounts[1].userdata.len(), 3); | ||||
|     } | ||||
|     #[test] | ||||
| @@ -220,7 +230,7 @@ mod test { | ||||
|         let mut accounts = vec![Account::default()]; | ||||
|         let tx = Transaction::system_assign(&from, Hash::default(), program.pubkey(), 0); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert_eq!(accounts[0].program_id, program.pubkey()); | ||||
|     } | ||||
|     #[test] | ||||
| @@ -231,7 +241,7 @@ mod test { | ||||
|         accounts[0].tokens = 1; | ||||
|         let tx = Transaction::system_new(&from, to.pubkey(), 1, Hash::default()); | ||||
|         let hash = RwLock::new(HashMap::new()); | ||||
|         SystemProgram::process_transaction(&tx, &mut accounts, &hash); | ||||
|         process_transaction(&tx, &mut accounts, &hash); | ||||
|         assert_eq!(accounts[0].tokens, 0); | ||||
|         assert_eq!(accounts[1].tokens, 1); | ||||
|     } | ||||
| @@ -254,7 +264,7 @@ mod test { | ||||
|         ); | ||||
|  | ||||
|         assert_eq!( | ||||
|             tx.userdata, | ||||
|             tx.userdata(0).to_vec(), | ||||
|             vec![ | ||||
|                 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | ||||
| @@ -273,7 +283,7 @@ mod test { | ||||
|         ); | ||||
|  | ||||
|         assert_eq!( | ||||
|             tx.userdata, | ||||
|             tx.userdata(0).to_vec(), | ||||
|             vec![ | ||||
|                 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | ||||
| @@ -288,7 +298,7 @@ mod test { | ||||
|             0, | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             tx.userdata, | ||||
|             tx.userdata(0).to_vec(), | ||||
|             vec![ | ||||
|                 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 0, 0, 0, 0, 0 | ||||
| @@ -297,6 +307,9 @@ mod test { | ||||
|  | ||||
|         // Move | ||||
|         let tx = Transaction::system_move(&keypair, keypair.pubkey(), 123, Hash::default(), 0); | ||||
|         assert_eq!(tx.userdata, vec![2, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0]); | ||||
|         assert_eq!( | ||||
|             tx.userdata(0).to_vec(), | ||||
|             vec![2, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0] | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -55,23 +55,25 @@ impl SystemTransaction for Transaction { | ||||
|             space, | ||||
|             program_id, | ||||
|         }; | ||||
|         let userdata = serialize(&create).unwrap(); | ||||
|         Transaction::new( | ||||
|             from_keypair, | ||||
|             &[to], | ||||
|             SystemProgram::id(), | ||||
|             serialize(&create).unwrap(), | ||||
|             userdata, | ||||
|             last_id, | ||||
|             fee, | ||||
|         ) | ||||
|     } | ||||
|     /// Create and sign new SystemProgram::CreateAccount transaction | ||||
|     fn system_assign(from_keypair: &Keypair, last_id: Hash, program_id: Pubkey, fee: i64) -> Self { | ||||
|         let create = SystemProgram::Assign { program_id }; | ||||
|         let assign = SystemProgram::Assign { program_id }; | ||||
|         let userdata = serialize(&assign).unwrap(); | ||||
|         Transaction::new( | ||||
|             from_keypair, | ||||
|             &[], | ||||
|             SystemProgram::id(), | ||||
|             serialize(&create).unwrap(), | ||||
|             userdata, | ||||
|             last_id, | ||||
|             fee, | ||||
|         ) | ||||
| @@ -88,12 +90,13 @@ impl SystemTransaction for Transaction { | ||||
|         last_id: Hash, | ||||
|         fee: i64, | ||||
|     ) -> Self { | ||||
|         let create = SystemProgram::Move { tokens }; | ||||
|         let move_tokens = SystemProgram::Move { tokens }; | ||||
|         let userdata = serialize(&move_tokens).unwrap(); | ||||
|         Transaction::new( | ||||
|             from_keypair, | ||||
|             &[to], | ||||
|             SystemProgram::id(), | ||||
|             serialize(&create).unwrap(), | ||||
|             userdata, | ||||
|             last_id, | ||||
|             fee, | ||||
|         ) | ||||
| @@ -107,11 +110,12 @@ impl SystemTransaction for Transaction { | ||||
|         name: String, | ||||
|     ) -> Self { | ||||
|         let load = SystemProgram::Load { program_id, name }; | ||||
|         let userdata = serialize(&load).unwrap(); | ||||
|         Transaction::new( | ||||
|             from_keypair, | ||||
|             &[], | ||||
|             SystemProgram::id(), | ||||
|             serialize(&load).unwrap(), | ||||
|             userdata, | ||||
|             last_id, | ||||
|             fee, | ||||
|         ) | ||||
| @@ -152,7 +156,7 @@ mod tests { | ||||
|         assert_eq!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET)); | ||||
|         assert_eq!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET)); | ||||
|         assert_eq!( | ||||
|             memfind(&tx_bytes, &tx.from().as_ref()), | ||||
|             memfind(&tx_bytes, &tx.account_keys[0].as_ref()), | ||||
|             Some(PUB_KEY_OFFSET) | ||||
|         ); | ||||
|         assert!(tx.verify_signature()); | ||||
| @@ -161,7 +165,7 @@ mod tests { | ||||
|     #[test] | ||||
|     fn test_userdata_layout() { | ||||
|         let mut tx0 = test_tx(); | ||||
|         tx0.userdata = vec![1, 2, 3]; | ||||
|         tx0.instructions[0].userdata = vec![1, 2, 3]; | ||||
|         let sign_data0a = tx0.get_sign_data(); | ||||
|         let tx_bytes = serialize(&tx0).unwrap(); | ||||
|         assert!(tx_bytes.len() < PACKET_DATA_SIZE); | ||||
| @@ -171,14 +175,14 @@ mod tests { | ||||
|             Some(SIG_OFFSET) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             memfind(&tx_bytes, &tx0.from().as_ref()), | ||||
|             memfind(&tx_bytes, &tx0.account_keys[0].as_ref()), | ||||
|             Some(PUB_KEY_OFFSET) | ||||
|         ); | ||||
|         let tx1 = deserialize(&tx_bytes).unwrap(); | ||||
|         assert_eq!(tx0, tx1); | ||||
|         assert_eq!(tx1.userdata, vec![1, 2, 3]); | ||||
|         assert_eq!(tx1.instructions[0].userdata, vec![1, 2, 3]); | ||||
|  | ||||
|         tx0.userdata = vec![1, 2, 4]; | ||||
|         tx0.instructions[0].userdata = vec![1, 2, 4]; | ||||
|         let sign_data0b = tx0.get_sign_data(); | ||||
|         assert_ne!(sign_data0a, sign_data0b); | ||||
|     } | ||||
|   | ||||
| @@ -540,11 +540,11 @@ mod tests { | ||||
|         let last_id = client.get_last_id(); | ||||
|  | ||||
|         let mut tr2 = Transaction::system_new(&alice.keypair(), bob_pubkey, 501, last_id); | ||||
|         let mut instruction2 = deserialize(&tr2.userdata).unwrap(); | ||||
|         let mut instruction2 = deserialize(tr2.userdata(0)).unwrap(); | ||||
|         if let SystemProgram::Move { ref mut tokens } = instruction2 { | ||||
|             *tokens = 502; | ||||
|         } | ||||
|         tr2.userdata = serialize(&instruction2).unwrap(); | ||||
|         tr2.instructions[0].userdata = serialize(&instruction2).unwrap(); | ||||
|         let signature = client.transfer_signed(&tr2).unwrap(); | ||||
|         client.poll_for_signature(&signature).unwrap(); | ||||
|  | ||||
|   | ||||
| @@ -263,7 +263,11 @@ impl TicTacToeProgram { | ||||
|         Pubkey::new(&TICTACTOE_PROGRAM_ID) | ||||
|     } | ||||
|  | ||||
|     pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> { | ||||
|     pub fn process_transaction( | ||||
|         tx: &Transaction, | ||||
|         pix: usize, | ||||
|         accounts: &mut [&mut Account], | ||||
|     ) -> Result<()> { | ||||
|         // accounts[1] must always be the Tic-tac-toe game state account | ||||
|         if accounts.len() < 2 || !Self::check_id(&accounts[1].program_id) { | ||||
|             error!("accounts[1] is not assigned to the TICTACTOE_PROGRAM_ID"); | ||||
| @@ -276,7 +280,7 @@ impl TicTacToeProgram { | ||||
|  | ||||
|         let mut program_state = Self::deserialize(&accounts[1].userdata)?; | ||||
|  | ||||
|         let command = serde_cbor::from_slice::<Command>(&tx.userdata).map_err(|err| { | ||||
|         let command = serde_cbor::from_slice::<Command>(tx.userdata(pix)).map_err(|err| { | ||||
|             error!("{:?}", err); | ||||
|             Error::InvalidUserdata | ||||
|         })?; | ||||
| @@ -288,14 +292,13 @@ impl TicTacToeProgram { | ||||
|                 error!("accounts[0] is not assigned to the TICTACTOE_PROGRAM_ID"); | ||||
|                 return Err(Error::InvalidArguments); | ||||
|             } | ||||
|  | ||||
|             // player X public key is in keys[2] | ||||
|             if accounts.len() < 3 { | ||||
|             if tx.key(pix, 2).is_none() { | ||||
|                 Err(Error::InvalidArguments)?; | ||||
|             } | ||||
|             program_state.dispatch_command(&command, &tx.keys[2])?; | ||||
|             program_state.dispatch_command(&command, tx.key(pix, 2).unwrap())?; | ||||
|         } else { | ||||
|             program_state.dispatch_command(&command, &tx.keys[0])?; | ||||
|             program_state.dispatch_command(&command, tx.key(pix, 0).unwrap())?; | ||||
|         } | ||||
|         program_state.serialize(&mut accounts[1].userdata)?; | ||||
|         Ok(()) | ||||
|   | ||||
| @@ -10,7 +10,20 @@ pub const SIGNED_DATA_OFFSET: usize = size_of::<Signature>(); | ||||
| pub const SIG_OFFSET: usize = 0; | ||||
| pub const PUB_KEY_OFFSET: usize = size_of::<Signature>() + size_of::<u64>(); | ||||
|  | ||||
| /// An instruction signed by a client with `Pubkey`. | ||||
| /// An instruction to execute a program under `program_id` with the | ||||
| /// specified accounts and userdata | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] | ||||
| pub struct Instruction { | ||||
|     /// The program code that executes this transaction is identified by the program_id. | ||||
|     /// this is an offset into the Transaction::program_keys field | ||||
|     pub program_id: u8, | ||||
|     /// Indices into the keys array of which accounts to load | ||||
|     pub accounts: Vec<u8>, | ||||
|     /// Userdata to be stored in the account | ||||
|     pub userdata: Vec<u8>, | ||||
| } | ||||
|  | ||||
| /// An atomic transaction | ||||
| #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] | ||||
| pub struct Transaction { | ||||
|     /// A digital signature of `keys`, `program_id`, `last_id`, `fee` and `userdata`, signed by `Pubkey`. | ||||
| @@ -18,13 +31,10 @@ pub struct Transaction { | ||||
|  | ||||
|     /// The `Pubkeys` that are executing this transaction userdata.  The meaning of each key is | ||||
|     /// program-specific. | ||||
|     /// * keys[0] - Typically this is the `caller` public key.  `signature` is verified with keys[0]. | ||||
|     /// * account_keys[0] - Typically this is the `caller` public key.  `signature` is verified with account_keys[0]. | ||||
|     /// In the future which key pays the fee and which keys have signatures would be configurable. | ||||
|     /// * keys[1] - Typically this is the program context or the recipient of the tokens | ||||
|     pub keys: Vec<Pubkey>, | ||||
|  | ||||
|     /// The program code that executes this transaction is identified by the program_id. | ||||
|     pub program_id: Pubkey, | ||||
|     /// * account_keys[1] - Typically this is the program context or the recipient of the tokens | ||||
|     pub account_keys: Vec<Pubkey>, | ||||
|  | ||||
|     /// The ID of a recent ledger entry. | ||||
|     pub last_id: Hash, | ||||
| @@ -32,18 +42,14 @@ pub struct Transaction { | ||||
|     /// The number of tokens paid for processing and storage of this transaction. | ||||
|     pub fee: i64, | ||||
|  | ||||
|     /// Userdata to be stored in the account | ||||
|     pub userdata: Vec<u8>, | ||||
|     /// Keys indentifying programs in the instructions vector. | ||||
|     pub program_keys: Vec<Pubkey>, | ||||
|     /// Programs that will be executed in sequence and commited in one atomic transaction if all | ||||
|     /// succeed. | ||||
|     pub instructions: Vec<Instruction>, | ||||
| } | ||||
|  | ||||
| impl Transaction { | ||||
|     /// Create a signed transaction from the given `Instruction`. | ||||
|     /// * `from_keypair` - The key used to sign the transaction.  This key is stored as keys[0] | ||||
|     /// * `transaction_keys` - The keys for the transaction.  These are the program state | ||||
|     ///    instances or token recipient keys. | ||||
|     /// * `userdata` - The input data that the program will execute with | ||||
|     /// * `last_id` - The PoH hash. | ||||
|     /// * `fee` - The transaction fee. | ||||
|     pub fn new( | ||||
|         from_keypair: &Keypair, | ||||
|         transaction_keys: &[Pubkey], | ||||
| @@ -52,36 +58,80 @@ impl Transaction { | ||||
|         last_id: Hash, | ||||
|         fee: i64, | ||||
|     ) -> Self { | ||||
|         let from = from_keypair.pubkey(); | ||||
|         let mut keys = vec![from]; | ||||
|         keys.extend_from_slice(transaction_keys); | ||||
|         let mut tx = Transaction { | ||||
|             signature: Signature::default(), | ||||
|             keys, | ||||
|             program_id, | ||||
|         let program_keys = vec![program_id]; | ||||
|         let instructions = vec![Instruction { | ||||
|             program_id: 0, | ||||
|             userdata, | ||||
|             accounts: (0..(transaction_keys.len() as u8 + 1)) | ||||
|                 .into_iter() | ||||
|                 .collect(), | ||||
|         }]; | ||||
|         Self::new_with_instructions( | ||||
|             from_keypair, | ||||
|             transaction_keys, | ||||
|             last_id, | ||||
|             fee, | ||||
|             userdata, | ||||
|             program_keys, | ||||
|             instructions, | ||||
|         ) | ||||
|     } | ||||
|     /// Create a signed transaction | ||||
|     /// * `from_keypair` - The key used to sign the transaction.  This key is stored as keys[0] | ||||
|     /// * `account_keys` - The keys for the transaction.  These are the program state | ||||
|     ///    instances or token recipient keys. | ||||
|     /// * `last_id` - The PoH hash. | ||||
|     /// * `fee` - The transaction fee. | ||||
|     /// * `program_keys` - The keys that identify programs used in the `instruction` vector. | ||||
|     /// * `instructions` - The programs and their arguments that the transaction will execute atomically | ||||
|     pub fn new_with_instructions( | ||||
|         from_keypair: &Keypair, | ||||
|         keys: &[Pubkey], | ||||
|         last_id: Hash, | ||||
|         fee: i64, | ||||
|         program_keys: Vec<Pubkey>, | ||||
|         instructions: Vec<Instruction>, | ||||
|     ) -> Self { | ||||
|         let from = from_keypair.pubkey(); | ||||
|         let mut account_keys = vec![from]; | ||||
|         account_keys.extend_from_slice(keys); | ||||
|         let mut tx = Transaction { | ||||
|             signature: Signature::default(), | ||||
|             account_keys, | ||||
|             last_id, | ||||
|             fee, | ||||
|             program_keys, | ||||
|             instructions, | ||||
|         }; | ||||
|         tx.sign(from_keypair); | ||||
|         tx | ||||
|     } | ||||
|  | ||||
|     pub fn userdata(&self, program_index: usize) -> &[u8] { | ||||
|         &self.instructions[program_index].userdata | ||||
|     } | ||||
|     pub fn key(&self, program_index: usize, kix: usize) -> Option<&Pubkey> { | ||||
|         self.instructions | ||||
|             .get(program_index) | ||||
|             .and_then(|p| p.accounts.get(kix)) | ||||
|             .and_then(|ai| self.account_keys.get(*ai as usize)) | ||||
|     } | ||||
|     pub fn program_id(&self, program_index: usize) -> &Pubkey { | ||||
|         &self.program_keys[self.instructions[program_index].program_id as usize] | ||||
|     } | ||||
|     /// Get the transaction data to sign. | ||||
|     pub fn get_sign_data(&self) -> Vec<u8> { | ||||
|         let mut data = serialize(&(&self.keys)).expect("serialize keys"); | ||||
|         let mut data = serialize(&self.account_keys).expect("serialize account_keys"); | ||||
|  | ||||
|         let program_id = serialize(&(&self.program_id)).expect("serialize program_id"); | ||||
|         data.extend_from_slice(&program_id); | ||||
|  | ||||
|         let last_id_data = serialize(&(&self.last_id)).expect("serialize last_id"); | ||||
|         let last_id_data = serialize(&self.last_id).expect("serialize last_id"); | ||||
|         data.extend_from_slice(&last_id_data); | ||||
|  | ||||
|         let fee_data = serialize(&(&self.fee)).expect("serialize last_id"); | ||||
|         let fee_data = serialize(&self.fee).expect("serialize last_id"); | ||||
|         data.extend_from_slice(&fee_data); | ||||
|  | ||||
|         let userdata = serialize(&(&self.userdata)).expect("serialize userdata"); | ||||
|         data.extend_from_slice(&userdata); | ||||
|         let program_keys = serialize(&self.program_keys).expect("serialize program_keys"); | ||||
|         data.extend_from_slice(&program_keys); | ||||
|  | ||||
|         let instructions = serialize(&self.instructions).expect("serialize instructions"); | ||||
|         data.extend_from_slice(&instructions); | ||||
|         data | ||||
|     } | ||||
|  | ||||
| @@ -98,8 +148,23 @@ impl Transaction { | ||||
|             .verify(&self.from().as_ref(), &self.get_sign_data()) | ||||
|     } | ||||
|  | ||||
|     pub fn from(&self) -> &Pubkey { | ||||
|         &self.keys[0] | ||||
|     /// Verify that references in the instructions are valid | ||||
|     pub fn verify_refs(&self) -> bool { | ||||
|         for instruction in &self.instructions { | ||||
|             if (instruction.program_id as usize) >= self.program_keys.len() { | ||||
|                 return false; | ||||
|             } | ||||
|             for account_index in &instruction.accounts { | ||||
|                 if (*account_index as usize) >= self.account_keys.len() { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         true | ||||
|     } | ||||
|  | ||||
|     fn from(&self) -> &Pubkey { | ||||
|         &self.account_keys[0] | ||||
|     } | ||||
|  | ||||
|     // a hash of a slice of transactions only needs to hash the signatures | ||||
| @@ -118,6 +183,81 @@ mod tests { | ||||
|     use bincode::serialize; | ||||
|     use signature::GenKeys; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_refs() { | ||||
|         let key = Keypair::new(); | ||||
|         let key1 = Keypair::new().pubkey(); | ||||
|         let key2 = Keypair::new().pubkey(); | ||||
|         let prog1 = Keypair::new().pubkey(); | ||||
|         let prog2 = Keypair::new().pubkey(); | ||||
|         let instructions = vec![ | ||||
|             Instruction { | ||||
|                 program_id: 0, | ||||
|                 userdata: vec![], | ||||
|                 accounts: vec![0, 1], | ||||
|             }, | ||||
|             Instruction { | ||||
|                 program_id: 1, | ||||
|                 userdata: vec![], | ||||
|                 accounts: vec![0, 2], | ||||
|             }, | ||||
|         ]; | ||||
|         let tx = Transaction::new_with_instructions( | ||||
|             &key, | ||||
|             &[key1, key2], | ||||
|             Default::default(), | ||||
|             0, | ||||
|             vec![prog1, prog2], | ||||
|             instructions, | ||||
|         ); | ||||
|         assert!(tx.verify_refs()); | ||||
|         assert_eq!(tx.key(0, 0), Some(&key.pubkey())); | ||||
|         assert_eq!(tx.key(1, 0), Some(&key.pubkey())); | ||||
|         assert_eq!(tx.key(0, 1), Some(&key1)); | ||||
|         assert_eq!(tx.key(1, 1), Some(&key2)); | ||||
|         assert_eq!(tx.key(2, 0), None); | ||||
|         assert_eq!(tx.key(0, 2), None); | ||||
|         assert_eq!(*tx.program_id(0), prog1); | ||||
|         assert_eq!(*tx.program_id(1), prog2); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_refs_invalid_program_id() { | ||||
|         let key = Keypair::new(); | ||||
|         let instructions = vec![Instruction { | ||||
|             program_id: 1, | ||||
|             userdata: vec![], | ||||
|             accounts: vec![], | ||||
|         }]; | ||||
|         let tx = Transaction::new_with_instructions( | ||||
|             &key, | ||||
|             &[], | ||||
|             Default::default(), | ||||
|             0, | ||||
|             vec![], | ||||
|             instructions, | ||||
|         ); | ||||
|         assert!(!tx.verify_refs()); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_refs_invalid_account() { | ||||
|         let key = Keypair::new(); | ||||
|         let instructions = vec![Instruction { | ||||
|             program_id: 0, | ||||
|             userdata: vec![], | ||||
|             accounts: vec![1], | ||||
|         }]; | ||||
|         let tx = Transaction::new_with_instructions( | ||||
|             &key, | ||||
|             &[], | ||||
|             Default::default(), | ||||
|             0, | ||||
|             vec![Default::default()], | ||||
|             instructions, | ||||
|         ); | ||||
|         assert_eq!(*tx.program_id(0), Default::default()); | ||||
|         assert!(!tx.verify_refs()); | ||||
|     } | ||||
|  | ||||
|     /// Detect binary changes in the serialized contract userdata, which could have a downstream | ||||
|     /// affect on SDKs and DApps | ||||
|     #[test] | ||||
| @@ -144,18 +284,19 @@ mod tests { | ||||
|         assert_eq!( | ||||
|             serialize(&tx).unwrap(), | ||||
|             vec![ | ||||
|                 88, 1, 212, 176, 31, 197, 35, 156, 135, 24, 30, 57, 204, 253, 224, 28, 89, 189, 53, | ||||
|                 64, 27, 148, 42, 199, 43, 236, 85, 182, 150, 64, 96, 53, 255, 235, 90, 197, 228, 6, | ||||
|                 105, 22, 140, 209, 206, 221, 85, 117, 125, 126, 11, 1, 176, 130, 57, 236, 7, 155, | ||||
|                 127, 58, 130, 92, 230, 219, 254, 0, 3, 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, 201, 177, | ||||
|                 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, | ||||
|                 16, 252, 180, 72, 134, 137, 247, 161, 68, 32, 253, 186, 201, 177, 11, 117, 135, | ||||
|                 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, | ||||
|                 72, 134, 137, 247, 161, 68, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, | ||||
|                 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, | ||||
|                 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3 | ||||
|                 234, 139, 34, 5, 120, 28, 107, 203, 69, 25, 236, 200, 164, 1, 12, 47, 147, 53, 41, | ||||
|                 143, 23, 116, 230, 203, 59, 228, 153, 14, 22, 241, 103, 226, 186, 169, 181, 65, 49, | ||||
|                 215, 44, 2, 61, 214, 113, 216, 184, 206, 147, 104, 140, 225, 138, 21, 172, 135, | ||||
|                 211, 80, 103, 80, 216, 106, 249, 86, 194, 1, 3, 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, | ||||
|                 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, | ||||
|                 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 32, 253, 186, 201, 177, 11, | ||||
|                 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, | ||||
|                 252, 180, 72, 134, 137, 247, 161, 68, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, | ||||
|                 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, | ||||
|                 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, | ||||
|                 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, | ||||
|                 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3 | ||||
|             ], | ||||
|         ); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user