diff --git a/src/accountant.rs b/src/accountant.rs index 78fefa8f9d..aaf206e347 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -5,7 +5,7 @@ use hash::Hash; use entry::Entry; use event::Event; -use plan::{Action, Plan, PlanEvent}; +use plan::{Plan, Witness}; use transaction::Transaction; use signature::{KeyPair, PublicKey, Signature}; use mint::Mint; @@ -13,6 +13,7 @@ use historian::{reserve_signature, Historian}; use recorder::Signal; use std::sync::mpsc::SendError; use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::Occupied; use std::result; use chrono::prelude::*; @@ -26,6 +27,19 @@ pub enum AccountingError { pub type Result = result::Result; +/// Commit funds to the 'to' party. +fn complete_transaction(balances: &mut HashMap, plan: &Plan) { + if let Plan::Pay(ref payment) = *plan { + if balances.contains_key(&payment.to) { + if let Some(x) = balances.get_mut(&payment.to) { + *x += payment.tokens; + } + } else { + balances.insert(payment.to, payment.tokens); + } + } +} + pub struct Accountant { pub historian: Historian, pub balances: HashMap, @@ -85,7 +99,7 @@ impl Accountant { } fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { - if let Plan::Action(Action::Pay(ref payment)) = *plan { + if let Plan::Pay(ref payment) = *plan { allow_deposits && *from == payment.to } else { false @@ -112,19 +126,6 @@ impl Accountant { Ok(()) } - /// Commit funds to the 'to' party. - fn complete_transaction(self: &mut Self, plan: &Plan) { - if let Plan::Action(Action::Pay(ref payment)) = *plan { - if self.balances.contains_key(&payment.to) { - if let Some(x) = self.balances.get_mut(&payment.to) { - *x += payment.tokens; - } - } else { - self.balances.insert(payment.to, payment.tokens); - } - } - } - fn process_verified_transaction( self: &mut Self, tr: &Transaction, @@ -141,29 +142,25 @@ impl Accountant { } let mut plan = tr.plan.clone(); - let actionable = plan.process_event(PlanEvent::Timestamp(self.last_time)); + plan.apply_witness(Witness::Timestamp(self.last_time)); - if !actionable { + if plan.is_complete() { + complete_transaction(&mut self.balances, &plan); + } else { self.pending.insert(tr.sig, plan); - return Ok(()); } - self.complete_transaction(&plan); Ok(()) } fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> { - let actionable = if let Some(plan) = self.pending.get_mut(&tx_sig) { - plan.process_event(PlanEvent::Signature(from)) - } else { - false - }; - - if actionable { - if let Some(plan) = self.pending.remove(&tx_sig) { - self.complete_transaction(&plan); + if let Occupied(mut e) = self.pending.entry(tx_sig) { + e.get_mut().apply_witness(Witness::Signature(from)); + if e.get().is_complete() { + complete_transaction(&mut self.balances, e.get()); + e.remove_entry(); } - } + }; Ok(()) } @@ -186,15 +183,15 @@ impl Accountant { // Check to see if any timelocked transactions can be completed. let mut completed = vec![]; for (key, plan) in &mut self.pending { - if plan.process_event(PlanEvent::Timestamp(self.last_time)) { + plan.apply_witness(Witness::Timestamp(self.last_time)); + if plan.is_complete() { + complete_transaction(&mut self.balances, &plan); completed.push(key.clone()); } } for key in completed { - if let Some(plan) = self.pending.remove(&key) { - self.complete_transaction(&plan); - } + self.pending.remove(&key); } Ok(()) @@ -288,7 +285,7 @@ mod tests { let mut acc = Accountant::new(&alice, None); let bob_pubkey = KeyPair::new().pubkey(); let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.seed()); - if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { + if let Plan::Pay(ref mut payment) = tr.plan { payment.tokens = 2; // <-- attack! } assert_eq!( @@ -297,7 +294,7 @@ mod tests { ); // Also, ensure all branchs of the plan spend all tokens - if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { + if let Plan::Pay(ref mut payment) = tr.plan { payment.tokens = 0; // <-- whoops! } assert_eq!( diff --git a/src/mint.rs b/src/mint.rs index f2a91da87d..74339cd690 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -58,13 +58,13 @@ impl Mint { mod tests { use super::*; use ledger::verify_slice; - use plan::{Action, Plan}; + use plan::Plan; #[test] fn test_create_events() { let mut events = Mint::new(100).create_events().into_iter(); if let Event::Transaction(tr) = events.next().unwrap() { - if let Plan::Action(Action::Pay(payment)) = tr.plan { + if let Plan::Pay(payment) = tr.plan { assert_eq!(tr.from, payment.to); } } diff --git a/src/plan.rs b/src/plan.rs index dcaf72cbc3..e455218ea9 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -1,45 +1,33 @@ -//! The `plan` crate provides functionality for creating spending plans. +//! A domain-specific language for payment plans. Users create Plan objects that +//! are given to an interpreter. The interpreter listens for `Witness` events, +//! which it uses to reduce the payment plan. When the plan is reduced to a +//! `Payment`, the payment is executed. use signature::PublicKey; use chrono::prelude::*; use std::mem; +pub enum Witness { + Timestamp(DateTime), + Signature(PublicKey), +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Condition { Timestamp(DateTime), Signature(PublicKey), } -pub enum PlanEvent { - Timestamp(DateTime), - Signature(PublicKey), -} - impl Condition { - pub fn is_satisfied(&self, event: &PlanEvent) -> bool { - match (self, event) { - (&Condition::Signature(ref pubkey), &PlanEvent::Signature(ref from)) => pubkey == from, - (&Condition::Timestamp(ref dt), &PlanEvent::Timestamp(ref last_time)) => { - dt <= last_time - } + pub fn is_satisfied(&self, witness: &Witness) -> bool { + match (self, witness) { + (&Condition::Signature(ref pubkey), &Witness::Signature(ref from)) => pubkey == from, + (&Condition::Timestamp(ref dt), &Witness::Timestamp(ref last_time)) => dt <= last_time, _ => false, } } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Action { - Pay(Payment), -} - -impl Action { - pub fn spendable(&self) -> i64 { - match *self { - Action::Pay(ref payment) => payment.tokens, - } - } -} - #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Payment { pub tokens: i64, @@ -48,28 +36,22 @@ pub struct Payment { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Plan { - Action(Action), - After(Condition, Action), - Race((Condition, Action), (Condition, Action)), + Pay(Payment), + After(Condition, Payment), + Race((Condition, Payment), (Condition, Payment)), } impl Plan { pub fn new_payment(tokens: i64, to: PublicKey) -> Self { - Plan::Action(Action::Pay(Payment { tokens, to })) + Plan::Pay(Payment { tokens, to }) } pub fn new_authorized_payment(from: PublicKey, tokens: i64, to: PublicKey) -> Self { - Plan::After( - Condition::Signature(from), - Action::Pay(Payment { tokens, to }), - ) + Plan::After(Condition::Signature(from), Payment { tokens, to }) } pub fn new_future_payment(dt: DateTime, tokens: i64, to: PublicKey) -> Self { - Plan::After( - Condition::Timestamp(dt), - Action::Pay(Payment { tokens, to }), - ) + Plan::After(Condition::Timestamp(dt), Payment { tokens, to }) } pub fn new_cancelable_future_payment( @@ -79,50 +61,40 @@ impl Plan { to: PublicKey, ) -> Self { Plan::Race( - ( - Condition::Timestamp(dt), - Action::Pay(Payment { tokens, to }), - ), - ( - Condition::Signature(from), - Action::Pay(Payment { tokens, to: from }), - ), + (Condition::Timestamp(dt), Payment { tokens, to }), + (Condition::Signature(from), Payment { tokens, to: from }), ) } + pub fn is_complete(&self) -> bool { + match *self { + Plan::Pay(_) => true, + _ => false, + } + } + pub fn verify(&self, spendable_tokens: i64) -> bool { match *self { - Plan::Action(ref action) => action.spendable() == spendable_tokens, - Plan::After(_, ref action) => action.spendable() == spendable_tokens, + Plan::Pay(ref payment) => payment.tokens == spendable_tokens, + Plan::After(_, ref payment) => payment.tokens == spendable_tokens, Plan::Race(ref a, ref b) => { - a.1.spendable() == spendable_tokens && b.1.spendable() == spendable_tokens + a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens } } } - pub fn process_event(&mut self, event: PlanEvent) -> bool { - let mut new_action = None; - match *self { - Plan::Action(_) => return true, - Plan::After(ref cond, ref action) => { - if cond.is_satisfied(&event) { - new_action = Some(action.clone()); - } - } - Plan::Race(ref a, ref b) => { - if a.0.is_satisfied(&event) { - new_action = Some(a.1.clone()); - } else if b.0.is_satisfied(&event) { - new_action = Some(b.1.clone()); - } - } - } + /// Apply a witness to the spending plan to see if the plan can be reduced. + /// If so, modify the plan in-place. + pub fn apply_witness(&mut self, witness: Witness) { + let new_payment = match *self { + Plan::After(ref cond, ref payment) if cond.is_satisfied(&witness) => Some(payment), + Plan::Race((ref cond, ref payment), _) if cond.is_satisfied(&witness) => Some(payment), + Plan::Race(_, (ref cond, ref payment)) if cond.is_satisfied(&witness) => Some(payment), + _ => None, + }.map(|x| x.clone()); - if let Some(action) = new_action { - mem::replace(self, Plan::Action(action)); - true - } else { - false + if let Some(payment) = new_payment { + mem::replace(self, Plan::Pay(payment)); } } } @@ -134,16 +106,16 @@ mod tests { #[test] fn test_signature_satisfied() { let sig = PublicKey::default(); - assert!(Condition::Signature(sig).is_satisfied(&PlanEvent::Signature(sig))); + assert!(Condition::Signature(sig).is_satisfied(&Witness::Signature(sig))); } #[test] fn test_timestamp_satisfied() { let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); - assert!(Condition::Timestamp(dt1).is_satisfied(&PlanEvent::Timestamp(dt1))); - assert!(Condition::Timestamp(dt1).is_satisfied(&PlanEvent::Timestamp(dt2))); - assert!(!Condition::Timestamp(dt2).is_satisfied(&PlanEvent::Timestamp(dt1))); + assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt1))); + assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt2))); + assert!(!Condition::Timestamp(dt2).is_satisfied(&Witness::Timestamp(dt1))); } #[test] @@ -163,7 +135,7 @@ mod tests { let to = PublicKey::default(); let mut plan = Plan::new_authorized_payment(from, 42, to); - assert!(plan.process_event(PlanEvent::Signature(from))); + plan.apply_witness(Witness::Signature(from)); assert_eq!(plan, Plan::new_payment(42, to)); } @@ -173,7 +145,7 @@ mod tests { let to = PublicKey::default(); let mut plan = Plan::new_future_payment(dt, 42, to); - assert!(plan.process_event(PlanEvent::Timestamp(dt))); + plan.apply_witness(Witness::Timestamp(dt)); assert_eq!(plan, Plan::new_payment(42, to)); } @@ -184,11 +156,11 @@ mod tests { let to = PublicKey::default(); let mut plan = Plan::new_cancelable_future_payment(dt, from, 42, to); - assert!(plan.process_event(PlanEvent::Timestamp(dt))); + plan.apply_witness(Witness::Timestamp(dt)); assert_eq!(plan, Plan::new_payment(42, to)); let mut plan = Plan::new_cancelable_future_payment(dt, from, 42, to); - assert!(plan.process_event(PlanEvent::Signature(from))); + plan.apply_witness(Witness::Signature(from)); assert_eq!(plan, Plan::new_payment(42, from)); } } diff --git a/src/transaction.rs b/src/transaction.rs index 9ee7d45651..482bdddf57 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -4,7 +4,7 @@ use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil}; use bincode::serialize; use hash::Hash; use chrono::prelude::*; -use plan::{Action, Condition, Payment, Plan}; +use plan::{Condition, Payment, Plan}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { @@ -18,7 +18,7 @@ pub struct Transaction { impl Transaction { pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self { let from = from_keypair.pubkey(); - let plan = Plan::Action(Action::Pay(Payment { tokens, to })); + let plan = Plan::Pay(Payment { tokens, to }); let mut tr = Transaction { from, plan, @@ -39,14 +39,8 @@ impl Transaction { ) -> Self { let from = from_keypair.pubkey(); let plan = Plan::Race( - ( - Condition::Timestamp(dt), - Action::Pay(Payment { tokens, to }), - ), - ( - Condition::Signature(from), - Action::Pay(Payment { tokens, to: from }), - ), + (Condition::Timestamp(dt), Payment { tokens, to }), + (Condition::Signature(from), Payment { tokens, to: from }), ); let mut tr = Transaction { from, @@ -98,10 +92,10 @@ mod tests { #[test] fn test_serialize_claim() { - let plan = Plan::Action(Action::Pay(Payment { + let plan = Plan::Pay(Payment { tokens: 0, to: Default::default(), - })); + }); let claim0 = Transaction { from: Default::default(), plan, @@ -134,7 +128,7 @@ mod tests { let zero = Hash::default(); let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero); tr.sign(&keypair0); - if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { + if let Plan::Pay(ref mut payment) = tr.plan { payment.to = thief_keypair.pubkey(); // <-- attack! }; assert!(!tr.verify());