diff --git a/src/bank.rs b/src/bank.rs index ed8687787b..af96026ac7 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -3,7 +3,7 @@ //! on behalf of the caller, and a low-level API for when they have //! already been signed and verified. -use bincode::serialize; +use bincode::{deserialize, serialize}; use chrono::prelude::*; use counter::Counter; use entry::Entry; @@ -36,7 +36,7 @@ pub const MAX_ENTRY_IDS: usize = 1024 * 16; pub const VERIFY_BLOCK_SIZE: usize = 16; /// Reasons a transaction might be rejected. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum BankError { /// Attempt to debit from `Pubkey`, but no found no record of a prior credit. AccountNotFound(Pubkey), @@ -61,6 +61,10 @@ pub enum BankError { /// Proof of History verification failed. LedgerVerificationFailed, + /// Contract's transaction token balance does not equal the balance after the transaction + UnbalancedTransaction(Signature), + /// ContractAlreadyPending + ContractAlreadyPending(Pubkey), } pub type Result = result::Result; @@ -73,17 +77,17 @@ pub struct Account { /// A transaction can write to its userdata pub userdata: Vec, } - +#[derive(Default)] +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. pub struct Bank { /// A map of account public keys to the balance in that account. accounts: RwLock>, - /// A map of smart contract transaction signatures to what remains of its payment - /// plan. Each transaction that targets the plan should cause it to be reduced. - /// Once it cannot be reduced, final payments are made and it is discarded. - pending: RwLock>, - /// A FIFO queue of `last_id` items, where each item is a set of signatures /// that have been processed using that `last_id`. Rejected `last_id` /// values are so old that the `last_id` has been pulled out of the queue. @@ -109,7 +113,6 @@ impl Default for Bank { fn default() -> Self { Bank { accounts: RwLock::new(HashMap::new()), - pending: RwLock::new(HashMap::new()), last_ids: RwLock::new(VecDeque::new()), last_ids_sigs: RwLock::new(HashMap::new()), transaction_count: AtomicUsize::new(0), @@ -129,7 +132,11 @@ impl Bank { /// Create an Bank using a deposit. pub fn new_from_deposit(deposit: &Payment) -> Self { let bank = Self::default(); - bank.apply_payment(deposit, &mut bank.accounts.write().unwrap()); + { + let mut accounts = bank.accounts.write().unwrap(); + let account = accounts.entry(deposit.to).or_insert_with(Account::default); + Self::apply_payment(deposit, account); + } bank } @@ -144,12 +151,10 @@ impl Bank { bank } - /// Commit funds to the `payment.to` party. - fn apply_payment(&self, payment: &Payment, accounts: &mut HashMap) { - accounts - .entry(payment.to) - .or_insert_with(Account::default) - .tokens += payment.tokens; + /// Commit funds to the given account + fn apply_payment(payment: &Payment, account: &mut Account) { + trace!("apply payments {}", payment.tokens); + account.tokens += payment.tokens; } /// Return the last entry ID registered. @@ -171,23 +176,6 @@ impl Bank { Ok(()) } - /// Forget the given `signature` because its transaction was rejected. - fn forget_signature(signatures: &mut HashSet, signature: &Signature) { - signatures.remove(signature); - } - - /// Forget the given `signature` with `last_id` because the transaction was rejected. - fn forget_signature_with_last_id(&self, signature: &Signature, last_id: &Hash) { - if let Some(entry) = self - .last_ids_sigs - .write() - .expect("'last_ids' read lock in forget_signature_with_last_id") - .get_mut(last_id) - { - Self::forget_signature(&mut entry.0, signature); - } - } - /// Forget all signatures. Useful for benchmarking. pub fn clear_signatures(&self) { for (_, sigs) in self.last_ids_sigs.write().unwrap().iter_mut() { @@ -247,141 +235,230 @@ impl Bank { /// Deduct tokens from the 'from' address the account has sufficient /// funds and isn't a duplicate. fn apply_debits( - &self, tx: &Transaction, - accounts: &mut HashMap, - vote_no_account_err: &mut usize, - no_account_err: &mut usize, + accounts: &mut [Account], + instruction: &Instruction, ) -> Result<()> { - let mut purge = false; { - let option = accounts.get_mut(&tx.from); - if option.is_none() { - if let Instruction::NewVote(_) = &tx.instruction { - *vote_no_account_err += 1; - } else { - *no_account_err += 1; - } - return Err(BankError::AccountNotFound(tx.from)); - } - let bal = option.unwrap(); - - self.reserve_signature_with_last_id(&tx.signature, &tx.last_id)?; - - if let Instruction::NewContract(contract) = &tx.instruction { + let empty = accounts[0].userdata.is_empty(); + let tokens = if !empty { 0 } else { accounts[0].tokens }; + if let Instruction::NewContract(contract) = &instruction { if contract.tokens < 0 { return Err(BankError::NegativeTokens); } - if bal.tokens < contract.tokens { - self.forget_signature_with_last_id(&tx.signature, &tx.last_id); - return Err(BankError::InsufficientFunds(tx.from)); - } else if bal.tokens == contract.tokens { - purge = true; + if tokens < contract.tokens { + return Err(BankError::InsufficientFunds(tx.keys[0])); } else { + let bal = &mut accounts[0]; bal.tokens -= contract.tokens; } }; } - - if purge { - accounts.remove(&tx.from); - } - Ok(()) } /// Apply only a transaction's credits. /// Note: It is safe to apply credits from multiple transactions in parallel. - fn apply_credits(&self, tx: &Transaction, accounts: &mut HashMap) { - match &tx.instruction { + fn apply_credits( + tx: &Transaction, + accounts: &mut [Account], + instruction: &Instruction, + ) -> Result<()> { + match instruction { Instruction::NewContract(contract) => { let plan = contract.plan.clone(); if let Some(payment) = plan.final_payment() { - self.apply_payment(&payment, accounts); + Self::apply_payment(&payment, &mut accounts[1]); + Ok(()) + } else if !accounts[1].userdata.is_empty() { + Err(BankError::ContractAlreadyPending(tx.keys[1])) } else { - let mut pending = self - .pending - .write() - .expect("'pending' write lock in apply_credits"); + let mut pending = HashMap::new(); pending.insert(tx.signature, plan); + //TODO this is a temporary on demand allocaiton + //until system contract requires explicit allocation of memory + accounts[1].userdata = serialize(&pending).unwrap(); + accounts[1].tokens += contract.tokens; + Ok(()) } } Instruction::ApplyTimestamp(dt) => { - let _ = self.apply_timestamp(tx.from, *dt); + Self::apply_timestamp(tx.keys[0], *dt, &mut accounts[1]); + Ok(()) } Instruction::ApplySignature(signature) => { - let _ = self.apply_signature(tx.from, *signature); + Self::apply_signature(tx.keys[0], *signature, accounts); + Ok(()) } Instruction::NewVote(_vote) => { - trace!("GOT VOTE! last_id={:?}", &tx.last_id.as_ref()[..8]); // TODO: record the vote in the stake table... + trace!("GOT VOTE! last_id={}", tx.last_id); + Ok(()) } } } - fn save_data(&self, tx: &Transaction, accounts: &mut HashMap) { - //TODO This is a temporary implementation until the full rules on memory management for - //smart contracts are implemented. See github issue #953 - if !tx.userdata.is_empty() { - if let Some(ref mut account) = accounts.get_mut(&tx.from) { - if account.userdata.len() != tx.userdata.len() { - account.userdata.resize(tx.userdata.len(), 0); - } - account.userdata.copy_from_slice(&tx.userdata); + /// Budget DSL contract interface + /// * tx - the transaction + /// * accounts[0] - The source of the tokens + /// * accounts[1] - The contract context. Once the contract has been completed, the tokens can + /// be spent from this account . + pub fn process_transaction_of_budget_instruction( + tx: &Transaction, + accounts: &mut [Account], + ) -> Result<()> { + let instruction = tx.instruction(); + Self::apply_debits(tx, accounts, &instruction)?; + Self::apply_credits(tx, accounts, &instruction) + } + //TODO the contract needs to provide a "get_balance" introspection call of the userdata + pub fn get_balance_of_budget_payment_plan(account: &Account) -> i64 { + if let Ok(pending) = deserialize(&account.userdata) { + let pending: HashMap = pending; + if !pending.is_empty() { + 0 + } else { + account.tokens } + } else { + account.tokens } } /// Process a Transaction. If it contains a payment plan that requires a witness /// to progress, the payment plan will be stored in the bank. pub fn process_transaction(&self, tx: &Transaction) -> Result<()> { - let accounts = &mut self.accounts.write().unwrap(); - let mut vote_no_account_err = 0; - let mut no_account_err = 0; - self.apply_debits(tx, accounts, &mut vote_no_account_err, &mut no_account_err)?; - self.apply_credits(tx, accounts); - self.save_data(tx, accounts); - self.transaction_count.fetch_add(1, Ordering::Relaxed); - Ok(()) + match self.process_transactions(vec![tx.clone()])[0] { + Err(ref e) => { + info!("process_transaction error: {:?}", e); + Err((*e).clone()) + } + Ok(_) => Ok(()), + } + } + + fn load_account( + &self, + tx: &Transaction, + accounts: &HashMap, + error_counters: &mut ErrorCounters, + ) -> Result> { + // Copy all the accounts + if accounts.get(&tx.keys[0]).is_none() { + if !self.is_leader { + error_counters.account_not_found_validator += 1; + } else { + error_counters.account_not_found_leader += 1; + } + if let Instruction::NewVote(_vote) = tx.instruction() { + error_counters.account_not_found_vote += 1; + } + Err(BankError::AccountNotFound(*tx.from())) + } else if accounts.get(&tx.keys[0]).unwrap().tokens < tx.fee { + Err(BankError::InsufficientFunds(*tx.from())) + } else { + let mut called_accounts: Vec = tx + .keys + .iter() + .map(|key| accounts.get(key).cloned().unwrap_or(Account::default())) + .collect(); + // There is no way to predict what contract will execute without an error + // If a fee can pay for execution then the contract will be scheduled + self.reserve_signature_with_last_id(&tx.signature, &tx.last_id)?; + called_accounts[0].tokens -= tx.fee; + Ok(called_accounts) + } + } + fn load_accounts( + &self, + txs: &Vec, + accounts: &HashMap, + error_counters: &mut ErrorCounters, + ) -> Vec>> { + txs.iter() + .map(|tx| self.load_account(tx, accounts, error_counters)) + .collect() + } + + pub fn execute_transaction(tx: Transaction, accounts: &mut [Account]) -> Result { + let pre_total: i64 = accounts.iter().map(|a| a.tokens).sum(); + + // TODO next steps is to add hooks to call arbitrary contracts here + // Call the contract method + // It's up to the contract to implement its own rules on moving funds + let e = Self::process_transaction_of_budget_instruction(&tx, accounts); + + // Verify the transaction + // TODO, At the moment there is only 1 contract, so 1-3 are not checked + // 1. For accounts assigned to the contract, the total sum of all the tokens in these accounts cannot increase. + // 2. For accounts unassigned to the contract, the individual balance of each accounts cannot decrease. + // 3. For accounts unassigned to the contract, the userdata cannot change. + + // 4. The total sum of all the tokens in all the pages cannot change. + let post_total: i64 = accounts.iter().map(|a| a.tokens).sum(); + if pre_total != post_total { + Err(BankError::UnbalancedTransaction(tx.signature)) + } else if let Err(err) = e { + Err(err) + } else { + Ok(tx) + } + } + + pub fn store_accounts( + res: &Vec>, + loaded: &Vec>>, + accounts: &mut HashMap, + ) { + loaded.iter().zip(res.iter()).for_each(|(racc, rtx)| { + if let (Ok(acc), Ok(tx)) = (racc, rtx) { + tx.keys.iter().zip(acc.iter()).for_each(|(key, account)| { + //purge if 0 + if account.tokens == 0 { + accounts.remove(&key); + } else { + *accounts.entry(*key).or_insert_with(Account::default) = account.clone(); + assert_eq!(accounts.get(key).unwrap().tokens, account.tokens); + } + }); + }; + }); } /// Process a batch of transactions. #[must_use] pub fn process_transactions(&self, txs: Vec) -> Vec> { - let accounts = &mut self.accounts.write().unwrap(); debug!("processing Transactions {}", txs.len()); + // TODO right now a single write lock is held for the duration of processing all the + // transactions + // To break this lock each account needs to be locked to prevent concurrent access + let mut accounts = self.accounts.write().unwrap(); let txs_len = txs.len(); - let mut vote_no_account_err = 0; - let mut no_account_err = 0; + let mut error_counters = ErrorCounters::default(); let now = Instant::now(); - let results: Vec<_> = txs - .into_iter() - .map(|tx| { - self.apply_debits(&tx, accounts, &mut vote_no_account_err, &mut no_account_err) - .map(|_| tx) - }) - .collect(); // Calling collect() here forces all debits to complete before moving on. - - let debits = now.elapsed(); + let mut loaded_accounts = self.load_accounts(&txs, &mut accounts, &mut error_counters); + let load_elapsed = now.elapsed(); let now = Instant::now(); - let res: Vec<_> = results - .into_iter() - .map(|result| { - result.map(|tx| { - self.apply_credits(&tx, accounts); - tx - }) + let res: Vec> = loaded_accounts + .iter_mut() + .zip(txs.into_iter()) + .map(|(acc, tx)| match acc { + Err(e) => Err(e.clone()), + Ok(ref mut accounts) => Self::execute_transaction(tx, accounts), }) .collect(); - + let execution_elapsed = now.elapsed(); + let now = Instant::now(); + Self::store_accounts(&res, &loaded_accounts, &mut accounts); + let write_elapsed = now.elapsed(); debug!( - "debits: {} us credits: {:?} us tx: {}", - duration_as_us(&debits), - duration_as_us(&now.elapsed()), + "load: {} us execution: {} us write: {} us tx: {}", + duration_as_us(&load_elapsed), + duration_as_us(&execution_elapsed), + duration_as_us(&write_elapsed), txs_len ); - let mut tx_count = 0; let mut err_count = 0; for r in &res { @@ -389,7 +466,7 @@ impl Bank { tx_count += 1; } else { if err_count == 0 { - info!("tx error: {:?}", r); + trace!("tx error: {:?}", r); } err_count += 1; } @@ -400,14 +477,17 @@ impl Bank { inc_new_counter_info!("bank-process_transactions_err-validator", err_count); inc_new_counter_info!( "bank-appy_debits-account_not_found-validator", - no_account_err + error_counters.account_not_found_validator ); } else { inc_new_counter_info!("bank-process_transactions_err-leader", err_count); - inc_new_counter_info!("bank-appy_debits-generic_account_not_found", no_account_err); + inc_new_counter_info!( + "bank-appy_debits-account_not_found-leader", + error_counters.account_not_found_leader + ); inc_new_counter_info!( "bank-appy_debits-vote_account_not_found", - vote_no_account_err + error_counters.account_not_found_vote ); } } @@ -507,13 +587,18 @@ impl Bank { .expect("invalid ledger: need at least 2 entries"); { let tx = &entry1.transactions[0]; - let deposit = if let Instruction::NewContract(contract) = &tx.instruction { + let instruction = tx.instruction(); + let deposit = if let Instruction::NewContract(contract) = instruction { contract.plan.final_payment() } else { None }.expect("invalid ledger, needs to start with a contract"); - - self.apply_payment(&deposit, &mut self.accounts.write().unwrap()); + { + let mut accounts = self.accounts.write().unwrap(); + let entry = accounts.entry(tx.keys[0]).or_insert_with(Account::default); + Self::apply_payment(&deposit, entry); + trace!("applied genesis payment {:?} {:?}", deposit, entry); + } } self.register_entry_id(&entry0.id); self.register_entry_id(&entry1.id); @@ -535,39 +620,40 @@ impl Bank { /// Process a Witness Signature. Any payment plans waiting on this signature /// will progress one step. - fn apply_signature(&self, from: Pubkey, signature: Signature) -> Result<()> { - if let Occupied(mut e) = self - .pending - .write() - .expect("write() in apply_signature") - .entry(signature) - { + fn apply_signature(from: Pubkey, signature: Signature, account: &mut [Account]) { + let mut pending: HashMap = + deserialize(&account[1].userdata).unwrap_or(HashMap::new()); + if let Occupied(mut e) = pending.entry(signature) { e.get_mut().apply_witness(&Witness::Signature, &from); if let Some(payment) = e.get().final_payment() { - self.apply_payment(&payment, &mut self.accounts.write().unwrap()); + //move the tokens back to the from account + account[0].tokens += payment.tokens; + account[1].tokens -= payment.tokens; e.remove_entry(); } }; - - Ok(()) + //TODO this allocation needs to be changed once the runtime only allows for explitly + //allocated memory + account[1].userdata = if pending.is_empty() { + vec![] + } else { + serialize(&pending).unwrap() + }; } /// Process a Witness Timestamp. Any payment plans waiting on this timestamp /// will progress one step. - fn apply_timestamp(&self, from: Pubkey, dt: DateTime) -> Result<()> { + fn apply_timestamp(from: Pubkey, dt: DateTime, account: &mut Account) { + let mut pending: HashMap = + deserialize(&account.userdata).unwrap_or(HashMap::new()); // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; // Hold 'pending' write lock until the end of this function. Otherwise another thread can // double-spend if it enters before the modified plan is removed from 'pending'. - let mut pending = self - .pending - .write() - .expect("'pending' write lock in apply_timestamp"); for (key, plan) in pending.iter_mut() { plan.apply_witness(&Witness::Timestamp(dt), &from); - if let Some(payment) = plan.final_payment() { - self.apply_payment(&payment, &mut self.accounts.write().unwrap()); + if let Some(_payment) = plan.final_payment() { completed.push(key.clone()); } } @@ -575,8 +661,13 @@ impl Bank { for key in completed { pending.remove(&key); } - - Ok(()) + //TODO this allocation needs to be changed once the runtime only allows for explitly + //allocated memory + account.userdata = if pending.is_empty() { + vec![] + } else { + serialize(&pending).unwrap() + }; } /// Create, sign, and process a Transaction from `keypair` to `to` of @@ -610,7 +701,9 @@ impl Bank { } pub fn get_balance(&self, pubkey: &Pubkey) -> i64 { - self.get_account(pubkey).map(|a| a.tokens).unwrap_or(0) + self.get_account(pubkey) + .map(|x| Self::get_balance_of_budget_payment_plan(&x)) + .unwrap_or(0) } pub fn get_account(&self, pubkey: &Pubkey) -> Option { @@ -672,6 +765,13 @@ mod tests { use std::io::{BufReader, Cursor, Seek, SeekFrom}; use std::mem::size_of; + #[test] + fn test_bank_new() { + let mint = Mint::new(10_000); + let bank = Bank::new(&mint); + assert_eq!(bank.get_balance(&mint.pubkey()), 10_000); + } + #[test] fn test_two_payments_to_one_party() { let mint = Mint::new(10_000); @@ -701,6 +801,23 @@ mod tests { assert_eq!(bank.transaction_count(), 0); } + // TODO: This test verifies potentially undesirable behavior + // See github issue 1157 (https://github.com/solana-labs/solana/issues/1157) + #[test] + fn test_detect_failed_duplicate_transactions_issue_1157() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + + let tx = Transaction::new(&mint.keypair(), mint.keypair().pubkey(), -1, mint.last_id()); + let signature = tx.signature; + assert!(!bank.has_signature(&signature)); + assert_eq!( + bank.process_transaction(&tx), + Err(BankError::NegativeTokens) + ); + assert!(bank.has_signature(&signature)); + } + #[test] fn test_account_not_found() { let mint = Mint::new(1); @@ -721,6 +838,7 @@ mod tests { bank.transfer(1_000, &mint.keypair(), pubkey, mint.last_id()) .unwrap(); assert_eq!(bank.transaction_count(), 1); + assert_eq!(bank.get_balance(&pubkey), 1_000); assert_eq!( bank.transfer(10_001, &mint.keypair(), pubkey, mint.last_id()), Err(BankError::InsufficientFunds(mint.pubkey())) @@ -742,56 +860,48 @@ mod tests { assert_eq!(bank.get_balance(&pubkey), 500); } - #[test] - fn test_userdata() { - let mint = Mint::new(10_000); - let bank = Bank::new(&mint); - let pubkey = mint.keypair().pubkey(); - - let mut tx = Transaction::new(&mint.keypair(), pubkey, 0, bank.last_id()); - tx.userdata = vec![1, 2, 3]; - let rv = bank.process_transaction(&tx); - assert!(rv.is_ok()); - let account = bank.get_account(&pubkey); - assert!(account.is_some()); - assert_eq!(account.unwrap().userdata, vec![1, 2, 3]); - } - #[test] fn test_transfer_on_date() { - let mint = Mint::new(1); + let mint = Mint::new(2); let bank = Bank::new(&mint); let pubkey = Keypair::new().pubkey(); let dt = Utc::now(); bank.transfer_on_date(1, &mint.keypair(), pubkey, dt, mint.last_id()) .unwrap(); - // Mint's balance will be zero because all funds are locked up. - assert_eq!(bank.get_balance(&mint.pubkey()), 0); + // Mint's balance will be 1 because 1 of the tokens is locked up + assert_eq!(bank.get_balance(&mint.pubkey()), 1); // tx count is 1, because debits were applied. assert_eq!(bank.transaction_count(), 1); - // pubkey's balance will be None because the funds have not been + // pubkey's balance will be 0 because the funds have not been // sent. assert_eq!(bank.get_balance(&pubkey), 0); // Now, acknowledge the time in the condition occurred and // that pubkey's funds are now available. - bank.apply_timestamp(mint.pubkey(), dt).unwrap(); + let tx = Transaction::new_timestamp(&mint.keypair(), pubkey, dt, bank.last_id()); + let res = bank.process_transaction(&tx); + assert!(res.is_ok()); assert_eq!(bank.get_balance(&pubkey), 1); - // tx count is still 1, because we chose not to count timestamp transactions - // tx count. - assert_eq!(bank.transaction_count(), 1); + // tx count is 2 + assert_eq!(bank.transaction_count(), 2); - bank.apply_timestamp(mint.pubkey(), dt).unwrap(); // <-- Attack! Attempt to process completed transaction. - assert_ne!(bank.get_balance(&pubkey), 2); + // try to replay the timestamp contract + bank.register_entry_id(&hash(bank.last_id().as_ref())); + let tx = Transaction::new_timestamp(&mint.keypair(), pubkey, dt, bank.last_id()); + let res = bank.process_transaction(&tx); + assert!(res.is_ok()); + + assert_eq!(bank.get_balance(&pubkey), 1); } #[test] fn test_cancel_transfer() { - let mint = Mint::new(1); + // mint needs to have a balance to modify the external contract + let mint = Mint::new(2); let bank = Bank::new(&mint); let pubkey = Keypair::new().pubkey(); let dt = Utc::now(); @@ -802,23 +912,31 @@ mod tests { // Assert the debit counts as a transaction. assert_eq!(bank.transaction_count(), 1); - // Mint's balance will be zero because all funds are locked up. - assert_eq!(bank.get_balance(&mint.pubkey()), 0); + // Mint's balance will be 1 because 1 of the tokens is locked up. + assert_eq!(bank.get_balance(&mint.pubkey()), 1); - // pubkey's balance will be None because the funds have not been - // sent. + // pubkey's balance will be 0 because the funds are locked up assert_eq!(bank.get_balance(&pubkey), 0); // Now, cancel the trancaction. Mint gets her funds back, pubkey never sees them. - bank.apply_signature(mint.pubkey(), signature).unwrap(); - assert_eq!(bank.get_balance(&mint.pubkey()), 1); + let tx = Transaction::new_signature(&mint.keypair(), pubkey, signature, bank.last_id()); + let res = bank.process_transaction(&tx); + assert!(res.is_ok()); assert_eq!(bank.get_balance(&pubkey), 0); + assert_eq!(bank.get_balance(&mint.pubkey()), 2); - // Assert cancel doesn't cause count to go backward. - assert_eq!(bank.transaction_count(), 1); + // Assert cancel counts as a tx + assert_eq!(bank.transaction_count(), 2); - bank.apply_signature(mint.pubkey(), signature).unwrap(); // <-- Attack! Attempt to cancel completed transaction. - assert_ne!(bank.get_balance(&mint.pubkey()), 2); + // try to replay the signature contract + bank.register_entry_id(&hash(bank.last_id().as_ref())); + let tx = Transaction::new_signature(&mint.keypair(), pubkey, signature, bank.last_id()); + let res = bank.process_transaction(&tx); //<-- attack! try to get budget dsl to pay out with another signature + assert!(res.is_ok()); + // balance is is still 2 for the mint + assert_eq!(bank.get_balance(&mint.pubkey()), 2); + // balance is is still 0 for the contract + assert_eq!(bank.get_balance(&pubkey), 0); } #[test] @@ -837,13 +955,13 @@ mod tests { } #[test] - fn test_forget_signature() { + fn test_clear_signatures() { let mint = Mint::new(1); let bank = Bank::new(&mint); let signature = Signature::default(); bank.reserve_signature_with_last_id(&signature, &mint.last_id()) .unwrap(); - bank.forget_signature_with_last_id(&signature, &mint.last_id()); + bank.clear_signatures(); assert!( bank.reserve_signature_with_last_id(&signature, &mint.last_id()) .is_ok() @@ -1128,5 +1246,4 @@ mod tests { def_bank.set_finality(90); assert_eq!(def_bank.finality(), 90); } - } diff --git a/src/entry.rs b/src/entry.rs index 4741633823..3952e25a16 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -248,8 +248,8 @@ mod tests { // First, verify entries let keypair = Keypair::new(); - let tx0 = Transaction::new_timestamp(&keypair, Utc::now(), zero); - let tx1 = Transaction::new_signature(&keypair, Default::default(), zero); + let tx0 = Transaction::new_timestamp(&keypair, keypair.pubkey(), Utc::now(), zero); + let tx1 = Transaction::new_signature(&keypair, keypair.pubkey(), Default::default(), zero); let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()], false); assert!(e0.verify(&zero)); @@ -271,7 +271,7 @@ mod tests { assert_eq!(tick.id, zero); let keypair = Keypair::new(); - let tx0 = Transaction::new_timestamp(&keypair, Utc::now(), zero); + let tx0 = Transaction::new_timestamp(&keypair, keypair.pubkey(), Utc::now(), zero); let entry0 = next_entry(&zero, 1, vec![tx0.clone()]); assert_eq!(entry0.num_hashes, 1); assert_eq!(entry0.id, next_hash(&zero, 1, &vec![tx0])); diff --git a/src/entry_writer.rs b/src/entry_writer.rs index f357bd7ca7..c6b181066c 100644 --- a/src/entry_writer.rs +++ b/src/entry_writer.rs @@ -101,9 +101,11 @@ pub fn read_entries(reader: R) -> impl Iterator= PACKET_DATA_SIZE); + let threshold = (BLOB_DATA_SIZE / tx_size) - 1; // PACKET_DATA_SIZE is transaction size // Verify large entries are split up and the first sets has_more. let txs = vec![tx.clone(); threshold * 2]; diff --git a/src/ledger.rs b/src/ledger.rs index c6ee6276bc..1bcd37d790 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -590,7 +590,12 @@ mod tests { Entry::new_mut( &mut id, &mut num_hashes, - vec![Transaction::new_timestamp(&keypair, Utc::now(), one)], + vec![Transaction::new_timestamp( + &keypair, + keypair.pubkey(), + Utc::now(), + one, + )], false, ) }) @@ -610,7 +615,7 @@ mod tests { one, 1, ); - let tx1 = Transaction::new_timestamp(&keypair, Utc::now(), one); + let tx1 = Transaction::new_timestamp(&keypair, keypair.pubkey(), Utc::now(), one); // // TODO: this magic number and the mix of transaction types // is designed to fill up a Blob more or less exactly, diff --git a/src/mint.rs b/src/mint.rs index 4f3388a064..fd518c55ca 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -74,9 +74,9 @@ mod tests { fn test_create_transactions() { let mut transactions = Mint::new(100).create_transactions().into_iter(); let tx = transactions.next().unwrap(); - if let Instruction::NewContract(contract) = tx.instruction { + if let Instruction::NewContract(contract) = tx.instruction() { if let Plan::Budget(Budget::Pay(payment)) = contract.plan { - assert_eq!(tx.from, payment.to); + assert_eq!(*tx.from(), payment.to); } } assert_eq!(transactions.next(), None); diff --git a/src/packet.rs b/src/packet.rs index 9e649d1063..e9f6c960e8 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -503,7 +503,7 @@ impl Blob { mod tests { use packet::{ to_packets, Blob, BlobRecycler, Meta, Packet, PacketRecycler, Packets, Recycler, Reset, - BLOB_HEADER_SIZE, NUM_PACKETS, + BLOB_HEADER_SIZE, NUM_PACKETS, PACKET_DATA_SIZE, }; use request::Request; use std::io; @@ -577,12 +577,12 @@ mod tests { p.write().unwrap().packets.resize(10, Packet::default()); for m in p.write().unwrap().packets.iter_mut() { m.meta.set_addr(&addr); - m.meta.size = 256; + m.meta.size = PACKET_DATA_SIZE; } p.read().unwrap().send_to(&sender).unwrap(); p.write().unwrap().recv_from(&reader).unwrap(); for m in p.write().unwrap().packets.iter_mut() { - assert_eq!(m.meta.size, 256); + assert_eq!(m.meta.size, PACKET_DATA_SIZE); assert_eq!(m.meta.addr(), saddr); } diff --git a/src/sigverify.rs b/src/sigverify.rs index 7b76ad0e31..032ffcb902 100644 --- a/src/sigverify.rs +++ b/src/sigverify.rs @@ -105,7 +105,7 @@ pub fn ed25519_verify_cpu(batches: &[SharedPackets]) -> Vec> { pub fn ed25519_verify_disabled(batches: &[SharedPackets]) -> Vec> { use rayon::prelude::*; let count = batch_size(batches); - info!("CPU ECDSA for {}", batch_size(batches)); + info!("disabled ECDSA for {}", batch_size(batches)); let rv = batches .into_par_iter() .map(|p| { diff --git a/src/thin_client.rs b/src/thin_client.rs index 27df1ce6c5..c7684d7c42 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -3,7 +3,7 @@ //! messages to the network directly. The binary encoding of its messages are //! unstable and may change in future releases. -use bank::Account; +use bank::{Account, Bank}; use bincode::{deserialize, serialize}; use crdt::{Crdt, CrdtError, NodeInfo}; use hash::Hash; @@ -177,9 +177,12 @@ impl ThinClient { } self.process_response(&resp); } + trace!("get_balance {:?}", self.balances.get(pubkey)); + //TODO: call the contract specific get_balance for contract_id's thin client can introspect + //instead of hard coding to budget_dsl only self.balances .get(pubkey) - .map(|a| a.tokens) + .map(Bank::get_balance_of_budget_payment_plan) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "nokey")) } @@ -508,10 +511,12 @@ mod tests { let last_id = client.get_last_id(); let mut tr2 = Transaction::new(&alice.keypair(), bob_pubkey, 501, last_id); - if let Instruction::NewContract(contract) = &mut tr2.instruction { + let mut instruction2 = tr2.instruction(); + if let Instruction::NewContract(ref mut contract) = instruction2 { contract.tokens = 502; contract.plan = Plan::Budget(Budget::new_payment(502, bob_pubkey)); } + tr2.userdata = serialize(&instruction2).unwrap(); let signature = client.transfer_signed(&tr2).unwrap(); client.poll_for_signature(&signature).unwrap(); diff --git a/src/transaction.rs b/src/transaction.rs index 73fed6c81e..30f8bd5d8a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,6 +1,6 @@ //! The `transaction` module provides functionality for creating log transactions. -use bincode::serialize; +use bincode::{deserialize, serialize}; use budget::{Budget, Condition}; use chrono::prelude::*; use hash::Hash; @@ -8,9 +8,9 @@ use payment_plan::{Payment, PaymentPlan, Witness}; use signature::{Keypair, KeypairUtil, Pubkey, Signature}; use std::mem::size_of; -pub const SIGNED_DATA_OFFSET: usize = PUB_KEY_OFFSET + size_of::(); +pub const SIGNED_DATA_OFFSET: usize = size_of::(); pub const SIG_OFFSET: usize = 0; -pub const PUB_KEY_OFFSET: usize = size_of::(); +pub const PUB_KEY_OFFSET: usize = size_of::() + size_of::(); /// The type of payment plan. Each item must implement the PaymentPlan trait. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -82,38 +82,45 @@ pub struct Transaction { /// A digital signature of `instruction`, `last_id` and `fee`, signed by `Pubkey`. pub signature: Signature, - /// The `Pubkey` of the entity that signed the transaction data. - pub from: Pubkey, - - /// The action the server should take. - pub instruction: Instruction, + /// The `Pubkeys` that are executing this transaction userdata. The meaning of each key is + /// contract-specific. + /// * keys[0] - Typically this is the `from` public key. `signature` is verified with keys[0]. + /// In the future which key pays the fee and which keys have signatures would be configurable. + /// * keys[1] - Typically this is the contract context or the recepient of the tokens + pub keys: Vec, /// The ID of a recent ledger entry. pub last_id: Hash, /// The number of tokens paid for processing and storage of this transaction. pub fee: i64, - /// Optional user data to be stored in the account - /// TODO: This will be a required field for all contract operations including a simple spend. - /// `instruction` will be serialized into `userdata` once Budget is its own generic contract. + + /// Userdata to be stored in the account pub userdata: Vec, } impl Transaction { - /// Create a signed transaction with userdata and an instruction - fn new_with_userdata_and_instruction( + /// Create a signed transaction from the given `Instruction`. + /// * `from_keypair` - The key used to sign the transcation. This key is stored as keys[0] + /// * `transaction_keys` - The keys for the transaction. These are the contract state + /// instances or token recepient keys. + /// * `userdata` - The input data that the contract will execute with + /// * `last_id` - The PoH hash. + /// * `fee` - The transaction fee. + fn new_with_userdata( from_keypair: &Keypair, - instruction: Instruction, + transaction_keys: &[Pubkey], + userdata: Vec, last_id: Hash, fee: i64, - userdata: Vec, ) -> Self { let from = from_keypair.pubkey(); + let mut keys = vec![from]; + keys.extend_from_slice(transaction_keys); let mut tx = Transaction { signature: Signature::default(), - instruction, + keys, last_id, - from, fee, userdata, }; @@ -123,29 +130,31 @@ impl Transaction { /// Create a signed transaction from the given `Instruction`. fn new_from_instruction( from_keypair: &Keypair, + contract: Pubkey, instruction: Instruction, last_id: Hash, fee: i64, ) -> Self { - Self::new_with_userdata_and_instruction(from_keypair, instruction, last_id, fee, vec![]) + let userdata = serialize(&instruction).expect("serealize instruction"); + Self::new_with_userdata(from_keypair, &[contract], userdata, last_id, fee) } /// Create and sign a new Transaction. Used for unit-testing. pub fn new_taxed( from_keypair: &Keypair, - to: Pubkey, + contract: Pubkey, tokens: i64, fee: i64, last_id: Hash, ) -> Self { let payment = Payment { tokens: tokens - fee, - to, + to: contract, }; let budget = Budget::Pay(payment); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens }); - Self::new_from_instruction(from_keypair, instruction, last_id, fee) + Self::new_from_instruction(from_keypair, contract, instruction, last_id, fee) } /// Create and sign a new Transaction. Used for unit-testing. @@ -154,19 +163,31 @@ impl Transaction { } /// Create and sign a new Witness Timestamp. Used for unit-testing. - pub fn new_timestamp(from_keypair: &Keypair, dt: DateTime, last_id: Hash) -> Self { + pub fn new_timestamp( + from_keypair: &Keypair, + contract: Pubkey, + dt: DateTime, + last_id: Hash, + ) -> Self { let instruction = Instruction::ApplyTimestamp(dt); - Self::new_from_instruction(from_keypair, instruction, last_id, 0) + Self::new_from_instruction(from_keypair, contract, instruction, last_id, 0) } /// Create and sign a new Witness Signature. Used for unit-testing. - pub fn new_signature(from_keypair: &Keypair, signature: Signature, last_id: Hash) -> Self { + pub fn new_signature( + from_keypair: &Keypair, + contract: Pubkey, + signature: Signature, + last_id: Hash, + ) -> Self { let instruction = Instruction::ApplySignature(signature); - Self::new_from_instruction(from_keypair, instruction, last_id, 0) + Self::new_from_instruction(from_keypair, contract, instruction, last_id, 0) } pub fn new_vote(from_keypair: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self { - Transaction::new_from_instruction(&from_keypair, Instruction::NewVote(vote), last_id, fee) + let instruction = Instruction::NewVote(vote); + let userdata = serialize(&instruction).expect("serealize instruction"); + Self::new_with_userdata(from_keypair, &[], userdata, last_id, fee) } /// Create and sign a postdated Transaction. Used for unit-testing. @@ -184,12 +205,14 @@ impl Transaction { ); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens }); - Self::new_from_instruction(from_keypair, instruction, last_id, 0) + let userdata = serialize(&instruction).expect("serealize instruction"); + Self::new_with_userdata(from_keypair, &[to], userdata, last_id, 0) } /// Get the transaction data to sign. fn get_sign_data(&self) -> Vec { - let mut data = serialize(&(&self.instruction)).expect("serialize Contract"); + let mut data = serialize(&(&self.keys)).expect("serialize keys"); + let last_id_data = serialize(&(&self.last_id)).expect("serialize last_id"); data.extend_from_slice(&last_id_data); @@ -198,7 +221,6 @@ impl Transaction { let userdata = serialize(&(&self.userdata)).expect("serialize userdata"); data.extend_from_slice(&userdata); - data } @@ -212,12 +234,13 @@ impl Transaction { pub fn verify_signature(&self) -> bool { warn!("transaction signature verification called"); self.signature - .verify(&self.from.as_ref(), &self.get_sign_data()) + .verify(&self.from().as_ref(), &self.get_sign_data()) } /// Verify only the payment plan. pub fn verify_plan(&self) -> bool { - if let Instruction::NewContract(contract) = &self.instruction { + let instruction = deserialize(&self.userdata); + if let Ok(Instruction::NewContract(contract)) = instruction { self.fee >= 0 && self.fee <= contract.tokens && contract.plan.verify(contract.tokens - self.fee) @@ -225,14 +248,19 @@ impl Transaction { true } } - pub fn vote(&self) -> Option<(Pubkey, Vote, Hash)> { - if let Instruction::NewVote(ref vote) = self.instruction { - Some((self.from, vote.clone(), self.last_id)) + if let Instruction::NewVote(vote) = self.instruction() { + Some((*self.from(), vote, self.last_id)) } else { None } } + pub fn from(&self) -> &Pubkey { + &self.keys[0] + } + pub fn instruction(&self) -> Instruction { + deserialize(&self.userdata).unwrap() + } } pub fn test_tx() -> Transaction { @@ -258,6 +286,7 @@ pub fn memfind(a: &[A], b: &[A]) -> Option { mod tests { use super::*; use bincode::{deserialize, serialize}; + use packet::PACKET_DATA_SIZE; #[test] fn test_claim() { @@ -295,13 +324,13 @@ mod tests { }); let plan = Plan::Budget(budget); let instruction = Instruction::NewContract(Contract { plan, tokens: 0 }); + let userdata = serialize(&instruction).unwrap(); let claim0 = Transaction { - instruction, - from: Default::default(), + keys: vec![], last_id: Default::default(), signature: Default::default(), fee: 0, - userdata: vec![], + userdata, }; let buf = serialize(&claim0).unwrap(); let claim1: Transaction = deserialize(&buf).unwrap(); @@ -314,12 +343,14 @@ mod tests { let keypair = Keypair::new(); let pubkey = keypair.pubkey(); let mut tx = Transaction::new(&keypair, pubkey, 42, zero); - if let Instruction::NewContract(contract) = &mut tx.instruction { + let mut instruction = tx.instruction(); + if let Instruction::NewContract(ref mut contract) = instruction { contract.tokens = 1_000_000; // <-- attack, part 1! if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = contract.tokens; // <-- attack, part 2! } } + tx.userdata = serialize(&instruction).unwrap(); assert!(tx.verify_plan()); assert!(!tx.verify_signature()); } @@ -332,11 +363,13 @@ mod tests { let pubkey1 = keypair1.pubkey(); let zero = Hash::default(); let mut tx = Transaction::new(&keypair0, pubkey1, 42, zero); - if let Instruction::NewContract(contract) = &mut tx.instruction { + let mut instruction = tx.instruction(); + if let Instruction::NewContract(ref mut contract) = instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.to = thief_keypair.pubkey(); // <-- attack! } } + tx.userdata = serialize(&instruction).unwrap(); assert!(tx.verify_plan()); assert!(!tx.verify_signature()); } @@ -345,9 +378,13 @@ mod tests { let tx = test_tx(); let sign_data = tx.get_sign_data(); let tx_bytes = serialize(&tx).unwrap(); - assert_matches!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET)); - assert_matches!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET)); - assert_matches!(memfind(&tx_bytes, &tx.from.as_ref()), Some(PUB_KEY_OFFSET)); + 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()), + Some(PUB_KEY_OFFSET) + ); + assert!(tx.verify_signature()); } #[test] fn test_userdata_layout() { @@ -355,13 +392,16 @@ mod tests { tx0.userdata = vec![1, 2, 3]; let sign_data0a = tx0.get_sign_data(); let tx_bytes = serialize(&tx0).unwrap(); - assert!(tx_bytes.len() < 256); + assert!(tx_bytes.len() < PACKET_DATA_SIZE); assert_eq!(memfind(&tx_bytes, &sign_data0a), Some(SIGNED_DATA_OFFSET)); assert_eq!( memfind(&tx_bytes, &tx0.signature.as_ref()), Some(SIG_OFFSET) ); - assert_eq!(memfind(&tx_bytes, &tx0.from.as_ref()), Some(PUB_KEY_OFFSET)); + assert_eq!( + memfind(&tx_bytes, &tx0.from().as_ref()), + Some(PUB_KEY_OFFSET) + ); let tx1 = deserialize(&tx_bytes).unwrap(); assert_eq!(tx0, tx1); assert_eq!(tx1.userdata, vec![1, 2, 3]); @@ -377,19 +417,23 @@ mod tests { let keypair1 = Keypair::new(); let zero = Hash::default(); let mut tx = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero); - if let Instruction::NewContract(contract) = &mut tx.instruction { + let mut instruction = tx.instruction(); + if let Instruction::NewContract(ref mut contract) = instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = 2; // <-- attack! } } + tx.userdata = serialize(&instruction).unwrap(); assert!(!tx.verify_plan()); // Also, ensure all branchs of the plan spend all tokens - if let Instruction::NewContract(contract) = &mut tx.instruction { + let mut instruction = tx.instruction(); + if let Instruction::NewContract(ref mut contract) = instruction { if let Plan::Budget(Budget::Pay(ref mut payment)) = contract.plan { payment.tokens = 0; // <-- whoops! } } + tx.userdata = serialize(&instruction).unwrap(); assert!(!tx.verify_plan()); } } diff --git a/tests/multinode.rs b/tests/multinode.rs index 76aa406328..f4472bee2e 100644 --- a/tests/multinode.rs +++ b/tests/multinode.rs @@ -522,7 +522,7 @@ fn test_multi_node_dynamic_network() { Ok(val) => val .parse() .expect(&format!("env var {} is not parse-able as usize", key)), - Err(_) => 160, + Err(_) => 120, }; let leader_keypair = Keypair::new();