diff --git a/src/accountant.rs b/src/accountant.rs index 72b03eab9c..45d2ed525d 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -5,7 +5,8 @@ use hash::Hash; use entry::Entry; use event::Event; -use transaction::{Action, Plan, Transaction}; +use plan::{Action, Plan, PlanEvent}; +use transaction::Transaction; use signature::{KeyPair, PublicKey, Signature}; use mint::Mint; use historian::{reserve_signature, Historian}; @@ -30,7 +31,7 @@ pub struct Accountant { pub balances: HashMap, pub first_id: Hash, pub last_id: Hash, - pending: HashMap>, + pending: HashMap, time_sources: HashSet, last_time: DateTime, } @@ -83,7 +84,7 @@ impl Accountant { self.last_id } - fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { + fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { if let Plan::Action(Action::Pay(ref payment)) = *plan { allow_deposits && *from == payment.to } else { @@ -91,7 +92,7 @@ impl Accountant { } } - pub fn process_transaction(self: &mut Self, tr: Transaction) -> Result<()> { + pub fn process_transaction(self: &mut Self, tr: Transaction) -> Result<()> { if !tr.verify() { return Err(AccountingError::InvalidTransfer); } @@ -112,7 +113,7 @@ impl Accountant { } /// Commit funds to the 'to' party. - fn complete_transaction(self: &mut Self, plan: &Plan) { + 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) { @@ -126,7 +127,7 @@ impl Accountant { fn process_verified_transaction( self: &mut Self, - tr: &Transaction, + tr: &Transaction, allow_deposits: bool, ) -> Result<()> { if !reserve_signature(&mut self.historian.signatures, &tr.sig) { @@ -140,7 +141,7 @@ impl Accountant { } let mut plan = tr.plan.clone(); - let actionable = plan.process_verified_timestamp(self.last_time); + let actionable = plan.process_event(PlanEvent::Timestamp(self.last_time)); if !actionable { self.pending.insert(tr.sig, plan); @@ -153,7 +154,7 @@ impl Accountant { 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_verified_sig(from) + plan.process_event(PlanEvent::Signature(from)) } else { false }; @@ -185,7 +186,7 @@ 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_verified_timestamp(self.last_time) { + if plan.process_event(PlanEvent::Timestamp(self.last_time)) { completed.push(key.clone()); } } diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index d1b4b3a77e..58729e6129 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -19,7 +19,7 @@ pub struct AccountantSkel { #[derive(Serialize, Deserialize, Debug)] pub enum Request { - Transaction(Transaction), + Transaction(Transaction), GetBalance { key: PublicKey }, GetEntries { last_id: Hash }, GetId { is_last: bool }, diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 73177b0450..d023716775 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -26,7 +26,7 @@ impl AccountantStub { } } - pub fn transfer_signed(&self, tr: Transaction) -> io::Result { + pub fn transfer_signed(&self, tr: Transaction) -> io::Result { let req = Request::Transaction(tr); let data = serialize(&req).unwrap(); self.socket.send_to(&data, &self.addr) diff --git a/src/event.rs b/src/event.rs index 51a71fba90..bfaa58c87b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -7,7 +7,7 @@ use bincode::serialize; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Event { - Transaction(Transaction), + Transaction(Transaction), Signature { from: PublicKey, tx_sig: Signature, diff --git a/src/lib.rs b/src/lib.rs index 560a5fa570..089399a5e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(feature = "unstable", feature(test))] pub mod signature; pub mod hash; +pub mod plan; pub mod transaction; pub mod event; pub mod entry; diff --git a/src/mint.rs b/src/mint.rs index ff21fc3b5d..2040b09482 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -58,7 +58,7 @@ impl Mint { mod tests { use super::*; use log::verify_slice; - use transaction::{Action, Plan}; + use plan::{Action, Plan}; #[test] fn test_create_events() { diff --git a/src/plan.rs b/src/plan.rs new file mode 100644 index 0000000000..d71506b6e4 --- /dev/null +++ b/src/plan.rs @@ -0,0 +1,188 @@ +//! The `plan` crate provides functionality for creating spending plans. + +use signature::PublicKey; +use chrono::prelude::*; +use std::mem; + +#[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 + } + _ => 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.asset, + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Payment { + pub asset: i64, + pub to: PublicKey, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Plan { + Action(Action), + After(Condition, Action), + Race((Condition, Action), (Condition, Action)), +} + +impl Plan { + pub fn new_payment(asset: i64, to: PublicKey) -> Self { + Plan::Action(Action::Pay(Payment { asset, to })) + } + + pub fn new_authorized_payment(from: PublicKey, asset: i64, to: PublicKey) -> Self { + Plan::After( + Condition::Signature(from), + Action::Pay(Payment { asset, to }), + ) + } + + pub fn new_future_payment(dt: DateTime, asset: i64, to: PublicKey) -> Self { + Plan::After(Condition::Timestamp(dt), Action::Pay(Payment { asset, to })) + } + + pub fn new_cancelable_future_payment( + dt: DateTime, + from: PublicKey, + asset: i64, + to: PublicKey, + ) -> Self { + Plan::Race( + (Condition::Timestamp(dt), Action::Pay(Payment { asset, to })), + ( + Condition::Signature(from), + Action::Pay(Payment { asset, to: from }), + ), + ) + } + + pub fn verify(&self, spendable_assets: i64) -> bool { + match *self { + Plan::Action(ref action) => action.spendable() == spendable_assets, + Plan::After(_, ref action) => action.spendable() == spendable_assets, + Plan::Race(ref a, ref b) => { + a.1.spendable() == spendable_assets && b.1.spendable() == spendable_assets + } + } + } + + 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()); + } + } + } + + if let Some(action) = new_action { + mem::replace(self, Plan::Action(action)); + true + } else { + false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_signature_satisfied() { + let sig = PublicKey::default(); + assert!(Condition::Signature(sig).is_satisfied(&PlanEvent::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))); + } + + #[test] + fn test_verify_plan() { + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let from = PublicKey::default(); + let to = PublicKey::default(); + assert!(Plan::new_payment(42, to).verify(42)); + assert!(Plan::new_authorized_payment(from, 42, to).verify(42)); + assert!(Plan::new_future_payment(dt, 42, to).verify(42)); + assert!(Plan::new_cancelable_future_payment(dt, from, 42, to).verify(42)); + } + + #[test] + fn test_authorized_payment() { + let from = PublicKey::default(); + let to = PublicKey::default(); + + let mut plan = Plan::new_authorized_payment(from, 42, to); + assert!(plan.process_event(PlanEvent::Signature(from))); + assert_eq!(plan, Plan::new_payment(42, to)); + } + + #[test] + fn test_future_payment() { + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let to = PublicKey::default(); + + let mut plan = Plan::new_future_payment(dt, 42, to); + assert!(plan.process_event(PlanEvent::Timestamp(dt))); + assert_eq!(plan, Plan::new_payment(42, to)); + } + + #[test] + fn test_cancelable_future_payment() { + let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); + let from = PublicKey::default(); + let to = PublicKey::default(); + + let mut plan = Plan::new_cancelable_future_payment(dt, from, 42, to); + assert!(plan.process_event(PlanEvent::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))); + assert_eq!(plan, Plan::new_payment(42, from)); + } +} diff --git a/src/transaction.rs b/src/transaction.rs index 9cf7a68f69..afc72dac4e 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,147 +1,24 @@ //! The `transaction` crate provides functionality for creating log transactions. use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil}; -use serde::Serialize; use bincode::serialize; use hash::Hash; use chrono::prelude::*; -use std::mem; +use plan::{Action, Condition, Payment, Plan}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Condition { - Timestamp(DateTime), - Signature(PublicKey), -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Action { - Pay(Payment), -} - -impl Action { - pub fn spendable(&self) -> T { - match *self { - Action::Pay(ref payment) => payment.asset.clone(), - } - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Payment { - pub asset: T, - pub to: PublicKey, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Plan { - Action(Action), - After(Condition, Action), - Race(Box>, Box>), -} - -impl Plan { - pub fn verify(&self, spendable_assets: &T) -> bool { - match *self { - Plan::Action(ref action) => action.spendable() == *spendable_assets, - Plan::Race(ref plan_a, ref plan_b) => { - plan_a.verify(spendable_assets) && plan_b.verify(spendable_assets) - } - Plan::After(_, ref action) => action.spendable() == *spendable_assets, - } - } - - pub fn run_race(&mut self) -> bool { - let new_plan = if let Plan::Race(ref a, ref b) = *self { - if let Plan::Action(_) = **a { - Some((**a).clone()) - } else if let Plan::Action(_) = **b { - Some((**b).clone()) - } else { - None - } - } else { - None - }; - - if let Some(plan) = new_plan { - mem::replace(self, plan); - true - } else { - false - } - } - - pub fn process_verified_sig(&mut self, from: PublicKey) -> bool { - let mut new_plan = None; - match *self { - Plan::Action(_) => return true, - Plan::Race(ref mut plan_a, ref mut plan_b) => { - plan_a.process_verified_sig(from); - plan_b.process_verified_sig(from); - } - Plan::After(Condition::Signature(pubkey), ref action) => { - if from == pubkey { - new_plan = Some(Plan::Action(action.clone())); - } - } - _ => (), - } - if self.run_race() { - return true; - } - - if let Some(plan) = new_plan { - mem::replace(self, plan); - true - } else { - false - } - } - - pub fn process_verified_timestamp(&mut self, last_time: DateTime) -> bool { - let mut new_plan = None; - match *self { - Plan::Action(_) => return true, - Plan::Race(ref mut plan_a, ref mut plan_b) => { - plan_a.process_verified_timestamp(last_time); - plan_b.process_verified_timestamp(last_time); - } - Plan::After(Condition::Timestamp(dt), ref action) => { - if dt <= last_time { - new_plan = Some(Plan::Action(action.clone())); - } - } - _ => (), - } - if self.run_race() { - return true; - } - - if let Some(plan) = new_plan { - mem::replace(self, plan); - true - } else { - false - } - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Transaction { +pub struct Transaction { pub from: PublicKey, - pub plan: Plan, - pub asset: T, + pub plan: Plan, + pub asset: i64, pub last_id: Hash, pub sig: Signature, } -impl Transaction { - pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { +impl Transaction { + pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: i64, last_id: Hash) -> Self { let from = from_keypair.pubkey(); - let plan = Plan::Action(Action::Pay(Payment { - asset: asset.clone(), - to, - })); + let plan = Plan::Action(Action::Pay(Payment { asset, to })); let mut tr = Transaction { from, plan, @@ -157,25 +34,16 @@ impl Transaction { from_keypair: &KeyPair, to: PublicKey, dt: DateTime, - asset: T, + asset: i64, last_id: Hash, ) -> Self { let from = from_keypair.pubkey(); let plan = Plan::Race( - Box::new(Plan::After( - Condition::Timestamp(dt), - Action::Pay(Payment { - asset: asset.clone(), - to, - }), - )), - Box::new(Plan::After( + (Condition::Timestamp(dt), Action::Pay(Payment { asset, to })), + ( Condition::Signature(from), - Action::Pay(Payment { - asset: asset.clone(), - to: from, - }), - )), + Action::Pay(Payment { asset, to: from }), + ), ); let mut tr = Transaction { from, @@ -198,7 +66,7 @@ impl Transaction { } pub fn verify(&self) -> bool { - self.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(&self.asset) + self.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(self.asset) } } @@ -206,14 +74,12 @@ impl Transaction { mod tests { use super::*; use bincode::{deserialize, serialize}; - use hash::hash; #[test] fn test_claim() { let keypair = KeyPair::new(); - let asset = hash(b"hello, world"); let zero = Hash::default(); - let tr0 = Transaction::new(&keypair, keypair.pubkey(), asset, zero); + let tr0 = Transaction::new(&keypair, keypair.pubkey(), 42, zero); assert!(tr0.verify()); } @@ -223,8 +89,7 @@ mod tests { let keypair0 = KeyPair::new(); let keypair1 = KeyPair::new(); let pubkey1 = keypair1.pubkey(); - let asset = hash(b"hello, world"); - let tr0 = Transaction::new(&keypair0, pubkey1, asset, zero); + let tr0 = Transaction::new(&keypair0, pubkey1, 42, zero); assert!(tr0.verify()); } @@ -237,12 +102,12 @@ mod tests { let claim0 = Transaction { from: Default::default(), plan, - asset: 0u8, + asset: 0, last_id: Default::default(), sig: Default::default(), }; let buf = serialize(&claim0).unwrap(); - let claim1: Transaction = deserialize(&buf).unwrap(); + let claim1: Transaction = deserialize(&buf).unwrap(); assert_eq!(claim1, claim0); } @@ -251,9 +116,9 @@ mod tests { let zero = Hash::default(); let keypair = KeyPair::new(); let pubkey = keypair.pubkey(); - let mut tr = Transaction::new(&keypair, pubkey, hash(b"hello, world"), zero); + let mut tr = Transaction::new(&keypair, pubkey, 42, zero); tr.sign(&keypair); - tr.asset = hash(b"goodbye cruel world"); // <-- attack! + tr.asset = 1_000_000; // <-- attack! assert!(!tr.verify()); } @@ -264,8 +129,7 @@ mod tests { let thief_keypair = KeyPair::new(); let pubkey1 = keypair1.pubkey(); let zero = Hash::default(); - let asset = hash(b"hello, world"); - let mut tr = Transaction::new(&keypair0, pubkey1, asset, zero); + let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero); tr.sign(&keypair0); if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan { payment.to = thief_keypair.pubkey(); // <-- attack!