5
5
use hash:: Hash ;
6
6
use entry:: Entry ;
7
7
use event:: Event ;
8
- use transaction:: Transaction ;
8
+ use transaction:: { Condition , Transaction } ;
9
9
use signature:: { KeyPair , PublicKey , Signature } ;
10
10
use mint:: Mint ;
11
11
use historian:: { reserve_signature, Historian } ;
12
12
use std:: sync:: mpsc:: SendError ;
13
- use std:: collections:: HashMap ;
13
+ use std:: collections:: { HashMap , HashSet } ;
14
14
use std:: result;
15
+ use chrono:: prelude:: * ;
15
16
16
17
#[ derive( Debug , PartialEq , Eq ) ]
17
18
pub enum AccountingError {
@@ -28,6 +29,9 @@ pub struct Accountant {
28
29
pub balances : HashMap < PublicKey , i64 > ,
29
30
pub first_id : Hash ,
30
31
pub last_id : Hash ,
32
+ pending : HashMap < Signature , Transaction < i64 > > ,
33
+ time_sources : HashSet < PublicKey > ,
34
+ last_time : DateTime < Utc > ,
31
35
}
32
36
33
37
impl Accountant {
@@ -48,6 +52,9 @@ impl Accountant {
48
52
balances : HashMap :: new ( ) ,
49
53
first_id : start_hash,
50
54
last_id : start_hash,
55
+ pending : HashMap :: new ( ) ,
56
+ time_sources : HashSet :: new ( ) ,
57
+ last_time : Utc . timestamp ( 0 , 0 ) ,
51
58
} ;
52
59
53
60
// The second item in the log is a special transaction where the to and from
@@ -94,6 +101,24 @@ impl Accountant {
94
101
Ok ( ( ) )
95
102
}
96
103
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
+
97
122
fn process_verified_transaction (
98
123
self : & mut Self ,
99
124
tr : & Transaction < i64 > ,
@@ -103,18 +128,97 @@ impl Accountant {
103
128
return Err ( AccountingError :: InvalidTransferSignature ) ;
104
129
}
105
130
131
+ if !tr. unless_any . is_empty ( ) {
132
+ // TODO: Check to see if the transaction is expired.
133
+ }
134
+
106
135
if !Self :: is_deposit ( allow_deposits, & tr. from , & tr. to ) {
107
136
if let Some ( x) = self . balances . get_mut ( & tr. from ) {
108
137
* x -= tr. asset ;
109
138
}
110
139
}
111
140
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;
115
189
}
116
190
} 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
+ }
118
222
}
119
223
120
224
Ok ( ( ) )
@@ -124,6 +228,8 @@ impl Accountant {
124
228
match * event {
125
229
Event :: Tick => Ok ( ( ) ) ,
126
230
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) ,
127
233
}
128
234
}
129
235
@@ -138,6 +244,18 @@ impl Accountant {
138
244
self . process_transaction ( tr) . map ( |_| sig)
139
245
}
140
246
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
+
141
259
pub fn get_balance ( self : & Self , pubkey : & PublicKey ) -> Option < i64 > {
142
260
self . balances . get ( pubkey) . map ( |x| * x)
143
261
}
@@ -204,4 +322,56 @@ mod tests {
204
322
ExitReason :: RecvDisconnected
205
323
) ;
206
324
}
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
+ }
207
377
}
0 commit comments