diff --git a/src/budget_contract.rs b/src/budget_contract.rs index a7c9a90f30..6558207ea3 100644 --- a/src/budget_contract.rs +++ b/src/budget_contract.rs @@ -4,9 +4,7 @@ use bincode::{self, deserialize, serialize_into, serialized_size}; use chrono::prelude::{DateTime, Utc}; use instruction::{Instruction, Plan}; use payment_plan::{PaymentPlan, Witness}; -use signature::{Pubkey, Signature}; -use std::collections::hash_map::Entry::Occupied; -use std::collections::HashMap; +use signature::Pubkey; use std::io; use transaction::Transaction; @@ -19,14 +17,13 @@ pub enum BudgetError { UninitializedContract(Pubkey), NegativeTokens, DestinationMissing(Pubkey), - FailedWitness(Signature), - SignatureUnoccupied(Signature), + FailedWitness, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] pub struct BudgetContract { pub initialized: bool, - pub pending: HashMap, + pub pending_plan: Option, pub last_error: Option, } @@ -35,7 +32,7 @@ pub const BUDGET_CONTRACT_ID: [u8; 32] = [ ]; impl BudgetContract { fn is_pending(&self) -> bool { - !self.pending.is_empty() + self.pending_plan != None } pub fn id() -> Pubkey { Pubkey::new(&BUDGET_CONTRACT_ID) @@ -48,30 +45,23 @@ impl BudgetContract { /// will progress one step. fn apply_signature( &mut self, - tx: &Transaction, - signature: Signature, + keys: &[Pubkey], account: &mut [Account], ) -> Result<(), BudgetError> { - if let Occupied(mut e) = self.pending.entry(signature) { - e.get_mut().apply_witness(&Witness::Signature, &tx.keys[0]); - if let Some(payment) = e.get().final_payment() { - if tx.keys.len() > 1 && payment.to == tx.keys[2] { - trace!("apply_witness refund"); - //move the tokens back to the from account - account[1].tokens -= payment.tokens; - account[2].tokens += payment.tokens; - e.remove_entry(); - } else { - trace!("destination is missing"); - return Err(BudgetError::DestinationMissing(payment.to)); - } - } else { - trace!("failed apply_witness"); - return Err(BudgetError::FailedWitness(signature)); + let mut final_payment = None; + if let Some(ref mut plan) = self.pending_plan { + plan.apply_witness(&Witness::Signature, &keys[0]); + final_payment = plan.final_payment(); + } + + if let Some(payment) = final_payment { + if keys.len() < 2 || payment.to != keys[2] { + trace!("destination missing"); + return Err(BudgetError::DestinationMissing(payment.to)); } - } else { - trace!("apply_witness signature unoccupied"); - return Err(BudgetError::SignatureUnoccupied(signature)); + self.pending_plan = None; + account[1].tokens -= payment.tokens; + account[2].tokens += payment.tokens; } Ok(()) } @@ -80,29 +70,26 @@ impl BudgetContract { /// will progress one step. fn apply_timestamp( &mut self, - tx: &Transaction, + keys: &[Pubkey], accounts: &mut [Account], dt: DateTime, ) -> Result<(), BudgetError> { // Check to see if any timelocked transactions can be completed. - let mut completed = vec![]; + let mut final_payment = None; - // 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'. - for (key, plan) in &mut self.pending { - plan.apply_witness(&Witness::Timestamp(dt), &tx.keys[0]); - if let Some(payment) = plan.final_payment() { - if tx.keys.len() < 2 || payment.to != tx.keys[2] { - trace!("destination missing"); - return Err(BudgetError::DestinationMissing(payment.to)); - } - completed.push(key.clone()); - accounts[2].tokens += payment.tokens; - accounts[1].tokens -= payment.tokens; - } + if let Some(ref mut plan) = self.pending_plan { + plan.apply_witness(&Witness::Timestamp(dt), &keys[0]); + final_payment = plan.final_payment(); } - for key in completed { - self.pending.remove(&key); + + if let Some(payment) = final_payment { + if keys.len() < 2 || payment.to != keys[2] { + trace!("destination missing"); + return Err(BudgetError::DestinationMissing(payment.to)); + } + self.pending_plan = None; + accounts[1].tokens -= payment.tokens; + accounts[2].tokens += payment.tokens; } Ok(()) } @@ -157,7 +144,7 @@ impl BudgetContract { Err(BudgetError::ContractAlreadyExists(tx.keys[1])) } else { let mut state = BudgetContract::default(); - state.pending.insert(tx.signature, plan); + state.pending_plan = Some(plan); accounts[1].tokens += contract.tokens; state.initialized = true; state.serialize(&mut accounts[1].userdata); @@ -168,29 +155,28 @@ impl BudgetContract { Instruction::ApplyTimestamp(dt) => { let mut state = Self::deserialize(&accounts[1].userdata).unwrap(); if !state.is_pending() { - return Err(BudgetError::ContractNotPending(tx.keys[1])); - } - if !state.initialized { + Err(BudgetError::ContractNotPending(tx.keys[1])) + } else if !state.initialized { trace!("contract is uninitialized"); Err(BudgetError::UninitializedContract(tx.keys[1])) } else { - state.apply_timestamp(tx, accounts, *dt)?; + trace!("apply timestamp"); + state.apply_timestamp(&tx.keys, accounts, *dt)?; trace!("apply timestamp committed"); state.serialize(&mut accounts[1].userdata); Ok(()) } } - Instruction::ApplySignature(signature) => { + Instruction::ApplySignature => { let mut state = Self::deserialize(&accounts[1].userdata).unwrap(); if !state.is_pending() { - return Err(BudgetError::ContractNotPending(tx.keys[1])); - } - if !state.initialized { + Err(BudgetError::ContractNotPending(tx.keys[1])) + } else if !state.initialized { trace!("contract is uninitialized"); Err(BudgetError::UninitializedContract(tx.keys[1])) } else { trace!("apply signature"); - state.apply_signature(tx, *signature, accounts)?; + state.apply_signature(&tx.keys, accounts)?; trace!("apply signature committed"); state.serialize(&mut accounts[1].userdata); Ok(()) @@ -220,7 +206,7 @@ impl BudgetContract { return Err(Box::new(bincode::ErrorKind::SizeLimit)); } let len: u64 = deserialize(&input[..8]).unwrap(); - if len < 8 { + if len < 3 { return Err(Box::new(bincode::ErrorKind::SizeLimit)); } if input.len() < 8 + len as usize { @@ -256,9 +242,9 @@ impl BudgetContract { //TODO the contract needs to provide a "get_balance" introspection call of the userdata pub fn get_balance(account: &Account) -> i64 { - if let Ok(pending) = deserialize(&account.userdata) { - let pending: BudgetContract = pending; - if pending.is_pending() { + if let Ok(state) = deserialize(&account.userdata) { + let state: BudgetContract = state; + if state.is_pending() { 0 } else { account.tokens @@ -275,7 +261,7 @@ mod test { use budget_contract::{BudgetContract, BudgetError}; use chrono::prelude::{DateTime, NaiveDate, Utc}; use hash::Hash; - use signature::{GenKeys, Keypair, KeypairUtil, Pubkey, Signature}; + use signature::{GenKeys, Keypair, KeypairUtil, Pubkey}; use transaction::Transaction; #[test] fn test_serializer() { @@ -388,7 +374,6 @@ mod test { 1, Hash::default(), ); - let sig = tx.signature; BudgetContract::process_transaction(&tx, &mut accounts); assert_eq!(accounts[from_account].tokens, 0); assert_eq!(accounts[contract_account].tokens, 1); @@ -397,13 +382,8 @@ mod test { assert!(state.is_pending()); // Attack! try to put the tokens into the wrong account with cancel - let tx = Transaction::budget_new_signature( - &to, - contract.pubkey(), - to.pubkey(), - sig, - Hash::default(), - ); + let tx = + 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 BudgetContract::process_transaction(&tx, &mut accounts); @@ -412,34 +392,12 @@ mod test { assert_eq!(accounts[contract_account].tokens, 1); // this would be the `to.pubkey()` account assert_eq!(accounts[pay_account].tokens, 0); - let state = BudgetContract::deserialize(&accounts[contract_account].userdata).unwrap(); - assert_eq!(state.last_error, Some(BudgetError::FailedWitness(sig))); - - // Attack! try canceling with a bad signature - let badsig = tx.signature; - let tx = Transaction::budget_new_signature( - &from, - contract.pubkey(), - from.pubkey(), - badsig, - Hash::default(), - ); - BudgetContract::process_transaction(&tx, &mut accounts); - assert_eq!(accounts[from_account].tokens, 0); - assert_eq!(accounts[contract_account].tokens, 1); - assert_eq!(accounts[pay_account].tokens, 0); - let state = BudgetContract::deserialize(&accounts[contract_account].userdata).unwrap(); - assert_eq!( - state.last_error, - Some(BudgetError::SignatureUnoccupied(badsig)) - ); // Now, cancel the transaction. from gets her funds back let tx = Transaction::budget_new_signature( &from, contract.pubkey(), from.pubkey(), - sig, Hash::default(), ); BudgetContract::process_transaction(&tx, &mut accounts); @@ -452,7 +410,6 @@ mod test { &from, contract.pubkey(), from.pubkey(), - sig, Hash::default(), ); BudgetContract::process_transaction(&tx, &mut accounts); @@ -480,11 +437,6 @@ mod test { 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, ]); - let signature = Signature::new(&[ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 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, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, - 6, 5, 4, 3, 2, 1, - ]); let date = DateTime::::from_utc(NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11), Utc); @@ -535,20 +487,7 @@ mod test { ); // ApplySignature - let tx = Transaction::budget_new_signature( - &keypair, - keypair.pubkey(), - to, - signature, - Hash::default(), - ); - assert_eq!( - tx.userdata, - vec![ - 2, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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, 0, 0, 0, 0, - 0, 0, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1 - ] - ); + let tx = Transaction::budget_new_signature(&keypair, keypair.pubkey(), to, Hash::default()); + assert_eq!(tx.userdata, vec![2, 0, 0, 0]); } } diff --git a/src/entry.rs b/src/entry.rs index d1c0bf4988..6025a7ec2e 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -256,13 +256,8 @@ mod tests { Utc::now(), zero, ); - let tx1 = Transaction::budget_new_signature( - &keypair, - keypair.pubkey(), - keypair.pubkey(), - Default::default(), - zero, - ); + let tx1 = + Transaction::budget_new_signature(&keypair, keypair.pubkey(), keypair.pubkey(), zero); let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()], false); assert!(e0.verify(&zero)); diff --git a/src/instruction.rs b/src/instruction.rs index 98e84622e9..002160f6ce 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -2,7 +2,6 @@ use budget::Budget; use chrono::prelude::{DateTime, Utc}; use payment_plan::{Payment, PaymentPlan, Witness}; use signature::Pubkey; -use signature::Signature; /// The type of payment plan. Each item must implement the PaymentPlan trait. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -61,7 +60,7 @@ pub enum Instruction { /// Tell the payment plan that the `NewContract` with `Signature` has been /// signed by the containing transaction's `Pubkey`. - ApplySignature(Signature), + ApplySignature, /// Vote for a PoH that is equal to the lastid of this transaction NewVote(Vote), diff --git a/src/transaction.rs b/src/transaction.rs index c9bce39450..7af4ff755c 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -127,10 +127,9 @@ impl Transaction { from_keypair: &Keypair, contract: Pubkey, to: Pubkey, - signature: Signature, last_id: Hash, ) -> Self { - let instruction = Instruction::ApplySignature(signature); + let instruction = Instruction::ApplySignature; let userdata = serialize(&instruction).unwrap(); Self::new_with_userdata( from_keypair,