From f82cbf3a27d4a724d9e5f38af67a2e68226e4928 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 29 May 2018 13:28:07 -0600 Subject: [PATCH] Move Budget EDSL into its own module --- src/bank.rs | 3 +- src/budget.rs | 168 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/mint.rs | 2 +- src/plan.rs | 143 -------------------------------------- src/thin_client.rs | 2 +- src/transaction.rs | 3 +- 7 files changed, 175 insertions(+), 147 deletions(-) create mode 100644 src/budget.rs diff --git a/src/bank.rs b/src/bank.rs index af8fa2d061..bc2c6813bf 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -5,11 +5,12 @@ extern crate libc; +use budget::Budget; use chrono::prelude::*; use entry::Entry; use hash::Hash; use mint::Mint; -use plan::{Budget, Payment, PaymentPlan, Witness}; +use plan::{Payment, PaymentPlan, Witness}; use rayon::prelude::*; use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; diff --git a/src/budget.rs b/src/budget.rs new file mode 100644 index 0000000000..a36c030681 --- /dev/null +++ b/src/budget.rs @@ -0,0 +1,168 @@ +//! The `budget` module provides a domain-specific language for payment plans. Users create Budget objects that +//! are given to an interpreter. The interpreter listens for `Witness` transactions, +//! which it uses to reduce the payment plan. When the budget is reduced to a +//! `Payment`, the payment is executed. + +use chrono::prelude::*; +use payment_plan::{Payment, PaymentPlan, Witness}; +use signature::PublicKey; +use std::mem; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Condition { + Timestamp(DateTime), + Signature(PublicKey), +} + +impl Condition { + /// Return true if the given Witness satisfies this Condition. + 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, + } + } +} + +#[repr(C)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Budget { + Pay(Payment), + After(Condition, Payment), + Race((Condition, Payment), (Condition, Payment)), +} + +impl Budget { + /// Create the simplest spending plan - one that pays `tokens` to PublicKey. + pub fn new_payment(tokens: i64, to: PublicKey) -> Self { + Budget::Pay(Payment { tokens, to }) + } + + /// Create a spending plan that pays `tokens` to `to` after being witnessed by `from`. + pub fn new_authorized_payment(from: PublicKey, tokens: i64, to: PublicKey) -> Self { + Budget::After(Condition::Signature(from), Payment { tokens, to }) + } + + /// Create a spending plan that pays `tokens` to `to` after the given DateTime. + pub fn new_future_payment(dt: DateTime, tokens: i64, to: PublicKey) -> Self { + Budget::After(Condition::Timestamp(dt), Payment { tokens, to }) + } + + /// Create a spending plan that pays `tokens` to `to` after the given DateTime + /// unless cancelled by `from`. + pub fn new_cancelable_future_payment( + dt: DateTime, + from: PublicKey, + tokens: i64, + to: PublicKey, + ) -> Self { + Budget::Race( + (Condition::Timestamp(dt), Payment { tokens, to }), + (Condition::Signature(from), Payment { tokens, to: from }), + ) + } +} + +impl PaymentPlan for Budget { + /// Return Payment if the spending plan requires no additional Witnesses. + fn final_payment(&self) -> Option { + match *self { + Budget::Pay(ref payment) => Some(payment.clone()), + _ => None, + } + } + + /// Return true if the plan spends exactly `spendable_tokens`. + fn verify(&self, spendable_tokens: i64) -> bool { + match *self { + Budget::Pay(ref payment) | Budget::After(_, ref payment) => { + payment.tokens == spendable_tokens + } + Budget::Race(ref a, ref b) => { + a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens + } + } + } + + /// Apply a witness to the spending plan to see if the plan can be reduced. + /// If so, modify the plan in-place. + fn apply_witness(&mut self, witness: &Witness) { + let new_payment = match *self { + Budget::After(ref cond, ref payment) if cond.is_satisfied(witness) => Some(payment), + Budget::Race((ref cond, ref payment), _) if cond.is_satisfied(witness) => Some(payment), + Budget::Race(_, (ref cond, ref payment)) if cond.is_satisfied(witness) => Some(payment), + _ => None, + }.cloned(); + + if let Some(payment) = new_payment { + mem::replace(self, Budget::Pay(payment)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_signature_satisfied() { + let sig = PublicKey::default(); + 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(&Witness::Timestamp(dt1))); + assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt2))); + assert!(!Condition::Timestamp(dt2).is_satisfied(&Witness::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!(Budget::new_payment(42, to).verify(42)); + assert!(Budget::new_authorized_payment(from, 42, to).verify(42)); + assert!(Budget::new_future_payment(dt, 42, to).verify(42)); + assert!(Budget::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 = Budget::new_authorized_payment(from, 42, to); + plan.apply_witness(&Witness::Signature(from)); + assert_eq!(plan, Budget::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 = Budget::new_future_payment(dt, 42, to); + plan.apply_witness(&Witness::Timestamp(dt)); + assert_eq!(plan, Budget::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 = Budget::new_cancelable_future_payment(dt, from, 42, to); + plan.apply_witness(&Witness::Timestamp(dt)); + assert_eq!(plan, Budget::new_payment(42, to)); + + let mut plan = Budget::new_cancelable_future_payment(dt, from, 42, to); + plan.apply_witness(&Witness::Signature(from)); + assert_eq!(plan, Budget::new_payment(42, from)); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7ce12c2444..228ba290cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(feature = "unstable", feature(test))] pub mod bank; pub mod banking_stage; +pub mod budget; pub mod crdt; pub mod entry; pub mod entry_writer; diff --git a/src/mint.rs b/src/mint.rs index 3f8090ae6d..b8c811b7f8 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -68,8 +68,8 @@ pub struct MintDemo { #[cfg(test)] mod tests { use super::*; + use budget::Budget; use ledger::Block; - use plan::Budget; use transaction::Instruction; #[test] diff --git a/src/plan.rs b/src/plan.rs index abc251e275..11bbddab77 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -5,7 +5,6 @@ use chrono::prelude::*; use signature::PublicKey; -use std::mem; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Witness { @@ -47,145 +46,3 @@ pub trait PaymentPlan { /// If so, modify the plan in-place. fn apply_witness(&mut self, witness: &Witness); } - -#[repr(C)] -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Budget { - Pay(Payment), - After(Condition, Payment), - Race((Condition, Payment), (Condition, Payment)), -} - -impl Budget { - /// Create the simplest spending plan - one that pays `tokens` to PublicKey. - pub fn new_payment(tokens: i64, to: PublicKey) -> Self { - Budget::Pay(Payment { tokens, to }) - } - - /// Create a spending plan that pays `tokens` to `to` after being witnessed by `from`. - pub fn new_authorized_payment(from: PublicKey, tokens: i64, to: PublicKey) -> Self { - Budget::After(Condition::Signature(from), Payment { tokens, to }) - } - - /// Create a spending plan that pays `tokens` to `to` after the given DateTime. - pub fn new_future_payment(dt: DateTime, tokens: i64, to: PublicKey) -> Self { - Budget::After(Condition::Timestamp(dt), Payment { tokens, to }) - } - - /// Create a spending plan that pays `tokens` to `to` after the given DateTime - /// unless cancelled by `from`. - pub fn new_cancelable_future_payment( - dt: DateTime, - from: PublicKey, - tokens: i64, - to: PublicKey, - ) -> Self { - Budget::Race( - (Condition::Timestamp(dt), Payment { tokens, to }), - (Condition::Signature(from), Payment { tokens, to: from }), - ) - } -} - -impl PaymentPlan for Budget { - /// Return Payment if the spending plan requires no additional Witnesses. - fn final_payment(&self) -> Option { - match *self { - Budget::Pay(ref payment) => Some(payment.clone()), - _ => None, - } - } - - /// Return true if the plan spends exactly `spendable_tokens`. - fn verify(&self, spendable_tokens: i64) -> bool { - match *self { - Budget::Pay(ref payment) | Budget::After(_, ref payment) => { - payment.tokens == spendable_tokens - } - Budget::Race(ref a, ref b) => { - a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens - } - } - } - - /// Apply a witness to the spending plan to see if the plan can be reduced. - /// If so, modify the plan in-place. - fn apply_witness(&mut self, witness: &Witness) { - let new_payment = match *self { - Budget::After(ref cond, ref payment) if cond.is_satisfied(witness) => Some(payment), - Budget::Race((ref cond, ref payment), _) if cond.is_satisfied(witness) => Some(payment), - Budget::Race(_, (ref cond, ref payment)) if cond.is_satisfied(witness) => Some(payment), - _ => None, - }.cloned(); - - if let Some(payment) = new_payment { - mem::replace(self, Budget::Pay(payment)); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_signature_satisfied() { - let sig = PublicKey::default(); - 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(&Witness::Timestamp(dt1))); - assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt2))); - assert!(!Condition::Timestamp(dt2).is_satisfied(&Witness::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!(Budget::new_payment(42, to).verify(42)); - assert!(Budget::new_authorized_payment(from, 42, to).verify(42)); - assert!(Budget::new_future_payment(dt, 42, to).verify(42)); - assert!(Budget::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 = Budget::new_authorized_payment(from, 42, to); - plan.apply_witness(&Witness::Signature(from)); - assert_eq!(plan, Budget::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 = Budget::new_future_payment(dt, 42, to); - plan.apply_witness(&Witness::Timestamp(dt)); - assert_eq!(plan, Budget::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 = Budget::new_cancelable_future_payment(dt, from, 42, to); - plan.apply_witness(&Witness::Timestamp(dt)); - assert_eq!(plan, Budget::new_payment(42, to)); - - let mut plan = Budget::new_cancelable_future_payment(dt, from, 42, to); - plan.apply_witness(&Witness::Signature(from)); - assert_eq!(plan, Budget::new_payment(42, from)); - } -} diff --git a/src/thin_client.rs b/src/thin_client.rs index 8a5bc0db28..1b9af5f1d9 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -176,10 +176,10 @@ impl ThinClient { mod tests { use super::*; use bank::Bank; + use budget::Budget; use futures::Future; use logger; use mint::Mint; - use plan::Budget; use server::Server; use signature::{KeyPair, KeyPairUtil}; use std::io::sink; diff --git a/src/transaction.rs b/src/transaction.rs index 1dc6446af7..6eb278e710 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,9 +1,10 @@ //! The `transaction` module provides functionality for creating log transactions. use bincode::serialize; +use budget::{Budget, Condition}; use chrono::prelude::*; use hash::Hash; -use plan::{Budget, Condition, Payment, PaymentPlan}; +use plan::{Payment, PaymentPlan}; use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil}; pub const SIGNED_DATA_OFFSET: usize = 112;