55use hash:: Hash ;
66use entry:: Entry ;
77use event:: Event ;
8- use transaction:: Transaction ;
8+ use transaction:: { Condition , Transaction } ;
99use signature:: { KeyPair , PublicKey , Signature } ;
1010use mint:: Mint ;
1111use historian:: { reserve_signature, Historian } ;
1212use std:: sync:: mpsc:: SendError ;
13- use std:: collections:: HashMap ;
13+ use std:: collections:: { HashMap , HashSet } ;
1414use std:: result;
15+ use chrono:: prelude:: * ;
1516
1617#[ derive( Debug , PartialEq , Eq ) ]
1718pub enum AccountingError {
@@ -28,6 +29,9 @@ pub struct Accountant {
2829 pub balances : HashMap < PublicKey , i64 > ,
2930 pub first_id : Hash ,
3031 pub last_id : Hash ,
32+ pending : HashMap < Signature , Transaction < i64 > > ,
33+ time_sources : HashSet < PublicKey > ,
34+ last_time : DateTime < Utc > ,
3135}
3236
3337impl Accountant {
@@ -48,6 +52,9 @@ impl Accountant {
4852 balances : HashMap :: new ( ) ,
4953 first_id : start_hash,
5054 last_id : start_hash,
55+ pending : HashMap :: new ( ) ,
56+ time_sources : HashSet :: new ( ) ,
57+ last_time : Utc . timestamp ( 0 , 0 ) ,
5158 } ;
5259
5360 // The second item in the log is a special transaction where the to and from
@@ -94,6 +101,24 @@ impl Accountant {
94101 Ok ( ( ) )
95102 }
96103
104+ /// Commit funds to the 'to' party.
105+ fn complete_transaction ( self : & mut Self , tr : & Transaction < i64 > ) {
106+ if self . balances . contains_key ( & tr. to ) {
107+ if let Some ( x) = self . balances . get_mut ( & tr. to ) {
108+ * x += tr. asset ;
109+ }
110+ } else {
111+ self . balances . insert ( tr. to , tr. asset ) ;
112+ }
113+ }
114+
115+ /// Return funds to the 'from' party.
116+ fn cancel_transaction ( self : & mut Self , tr : & Transaction < i64 > ) {
117+ if let Some ( x) = self . balances . get_mut ( & tr. from ) {
118+ * x += tr. asset ;
119+ }
120+ }
121+
97122 fn process_verified_transaction (
98123 self : & mut Self ,
99124 tr : & Transaction < i64 > ,
@@ -103,18 +128,97 @@ impl Accountant {
103128 return Err ( AccountingError :: InvalidTransferSignature ) ;
104129 }
105130
131+ if !tr. unless_any . is_empty ( ) {
132+ // TODO: Check to see if the transaction is expired.
133+ }
134+
106135 if !Self :: is_deposit ( allow_deposits, & tr. from , & tr. to ) {
107136 if let Some ( x) = self . balances . get_mut ( & tr. from ) {
108137 * x -= tr. asset ;
109138 }
110139 }
111140
112- if self . balances . contains_key ( & tr. to ) {
113- if let Some ( x) = self . balances . get_mut ( & tr. to ) {
114- * x += tr. asset ;
141+ if !tr. if_all . is_empty ( ) {
142+ self . pending . insert ( tr. sig , tr. clone ( ) ) ;
143+ return Ok ( ( ) ) ;
144+ }
145+
146+ self . complete_transaction ( tr) ;
147+ Ok ( ( ) )
148+ }
149+
150+ fn process_verified_sig ( & mut self , from : PublicKey , tx_sig : Signature ) -> Result < ( ) > {
151+ let mut cancel = false ;
152+ if let Some ( tr) = self . pending . get ( & tx_sig) {
153+ // Cancel:
154+ // if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map.
155+
156+ // TODO: Use find().
157+ for cond in & tr. unless_any {
158+ if let Condition :: Signature ( pubkey) = * cond {
159+ if from == pubkey {
160+ cancel = true ;
161+ break ;
162+ }
163+ }
164+ }
165+ }
166+
167+ if cancel {
168+ if let Some ( tr) = self . pending . remove ( & tx_sig) {
169+ self . cancel_transaction ( & tr) ;
170+ }
171+ }
172+
173+ // Process Multisig:
174+ // otherwise, if "Signature(from) is in if_all, remove it. If that causes that list
175+ // to be empty, add the asset to to, and remove the tx from this map.
176+ Ok ( ( ) )
177+ }
178+
179+ fn process_verified_timestamp ( & mut self , from : PublicKey , dt : DateTime < Utc > ) -> Result < ( ) > {
180+ // If this is the first timestamp we've seen, it probably came from the genesis block,
181+ // so we'll trust it.
182+ if self . last_time == Utc . timestamp ( 0 , 0 ) {
183+ self . time_sources . insert ( from) ;
184+ }
185+
186+ if self . time_sources . contains ( & from) {
187+ if dt > self . last_time {
188+ self . last_time = dt;
115189 }
116190 } else {
117- self . balances . insert ( tr. to , tr. asset ) ;
191+ return Ok ( ( ) ) ;
192+ }
193+ // TODO: Lookup pending Transaction waiting on time, signed by a whitelisted PublicKey.
194+
195+ // Expire:
196+ // if a Timestamp after this DateTime is in unless_any, return funds to tx.from,
197+ // and remove the tx from this map.
198+
199+ // Check to see if any timelocked transactions can be completed.
200+ let mut completed = vec ! [ ] ;
201+ for ( key, tr) in & self . pending {
202+ for cond in & tr. if_all {
203+ if let Condition :: Timestamp ( dt) = * cond {
204+ if self . last_time >= dt {
205+ if tr. if_all . len ( ) == 1 {
206+ completed. push ( * key) ;
207+ }
208+ }
209+ }
210+ }
211+ // TODO: Add this in once we start removing constraints
212+ //if tr.if_all.is_empty() {
213+ // // TODO: Remove tr from pending
214+ // self.complete_transaction(tr);
215+ //}
216+ }
217+
218+ for key in completed {
219+ if let Some ( tr) = self . pending . remove ( & key) {
220+ self . complete_transaction ( & tr) ;
221+ }
118222 }
119223
120224 Ok ( ( ) )
@@ -124,6 +228,8 @@ impl Accountant {
124228 match * event {
125229 Event :: Tick => Ok ( ( ) ) ,
126230 Event :: Transaction ( ref tr) => self . process_verified_transaction ( tr, allow_deposits) ,
231+ Event :: Signature { from, tx_sig, .. } => self . process_verified_sig ( from, tx_sig) ,
232+ Event :: Timestamp { from, dt, .. } => self . process_verified_timestamp ( from, dt) ,
127233 }
128234 }
129235
@@ -138,6 +244,18 @@ impl Accountant {
138244 self . process_transaction ( tr) . map ( |_| sig)
139245 }
140246
247+ pub fn transfer_on_date (
248+ self : & mut Self ,
249+ n : i64 ,
250+ keypair : & KeyPair ,
251+ to : PublicKey ,
252+ dt : DateTime < Utc > ,
253+ ) -> Result < Signature > {
254+ let tr = Transaction :: new_on_date ( keypair, to, dt, n, self . last_id ) ;
255+ let sig = tr. sig ;
256+ self . process_transaction ( tr) . map ( |_| sig)
257+ }
258+
141259 pub fn get_balance ( self : & Self , pubkey : & PublicKey ) -> Option < i64 > {
142260 self . balances . get ( pubkey) . map ( |x| * x)
143261 }
@@ -204,4 +322,56 @@ mod tests {
204322 ExitReason :: RecvDisconnected
205323 ) ;
206324 }
325+
326+ #[ test]
327+ fn test_transfer_on_date ( ) {
328+ let alice = Mint :: new ( 1 ) ;
329+ let mut acc = Accountant :: new ( & alice, Some ( 2 ) ) ;
330+ let alice_keypair = alice. keypair ( ) ;
331+ let bob_pubkey = KeyPair :: new ( ) . pubkey ( ) ;
332+ let dt = Utc :: now ( ) ;
333+ acc. transfer_on_date ( 1 , & alice_keypair, bob_pubkey, dt)
334+ . unwrap ( ) ;
335+
336+ // Alice's balance will be zero because all funds are locked up.
337+ assert_eq ! ( acc. get_balance( & alice. pubkey( ) ) , Some ( 0 ) ) ;
338+
339+ // Bob's balance will be None because the funds have not been
340+ // sent.
341+ assert_eq ! ( acc. get_balance( & bob_pubkey) , None ) ;
342+
343+ // Now, acknowledge the time in the condition occurred and
344+ // that bob's funds are now available.
345+ acc. process_verified_timestamp ( alice. pubkey ( ) , dt) . unwrap ( ) ;
346+ assert_eq ! ( acc. get_balance( & bob_pubkey) , Some ( 1 ) ) ;
347+
348+ acc. process_verified_timestamp ( alice. pubkey ( ) , dt) . unwrap ( ) ; // <-- Attack! Attempt to process completed transaction.
349+ assert_ne ! ( acc. get_balance( & bob_pubkey) , Some ( 2 ) ) ;
350+ }
351+
352+ #[ test]
353+ fn test_cancel_transfer ( ) {
354+ let alice = Mint :: new ( 1 ) ;
355+ let mut acc = Accountant :: new ( & alice, Some ( 2 ) ) ;
356+ let alice_keypair = alice. keypair ( ) ;
357+ let bob_pubkey = KeyPair :: new ( ) . pubkey ( ) ;
358+ let dt = Utc :: now ( ) ;
359+ let sig = acc. transfer_on_date ( 1 , & alice_keypair, bob_pubkey, dt)
360+ . unwrap ( ) ;
361+
362+ // Alice's balance will be zero because all funds are locked up.
363+ assert_eq ! ( acc. get_balance( & alice. pubkey( ) ) , Some ( 0 ) ) ;
364+
365+ // Bob's balance will be None because the funds have not been
366+ // sent.
367+ assert_eq ! ( acc. get_balance( & bob_pubkey) , None ) ;
368+
369+ // Now, cancel the trancaction. Alice gets her funds back, Bob never sees them.
370+ acc. process_verified_sig ( alice. pubkey ( ) , sig) . unwrap ( ) ;
371+ assert_eq ! ( acc. get_balance( & alice. pubkey( ) ) , Some ( 1 ) ) ;
372+ assert_eq ! ( acc. get_balance( & bob_pubkey) , None ) ;
373+
374+ acc. process_verified_sig ( alice. pubkey ( ) , sig) . unwrap ( ) ; // <-- Attack! Attempt to cancel completed transaction.
375+ assert_ne ! ( acc. get_balance( & alice. pubkey( ) ) , Some ( 2 ) ) ;
376+ }
207377}
0 commit comments