Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2016-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Affero Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file auditor/taler-helper-auditor-purses.c
18 : * @brief audits the purses of an exchange database
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include "auditordb_lib.h"
24 : #include "exchangedb_lib.h"
25 : #include "taler/taler_bank_service.h"
26 : #include "taler/taler_signatures.h"
27 : #include "report-lib.h"
28 : #include "taler/taler_dbevents.h"
29 : #include "auditor-database/delete_purse_info.h"
30 : #include "auditor-database/event_listen.h"
31 : #include "auditor-database/get_auditor_progress.h"
32 : #include "auditor-database/get_balance.h"
33 : #include "auditor-database/get_purse_info.h"
34 : #include "auditor-database/insert_amount_arithmetic_inconsistency.h"
35 : #include "auditor-database/insert_auditor_progress.h"
36 : #include "auditor-database/insert_bad_sig_losses.h"
37 : #include "auditor-database/insert_balance.h"
38 : #include "auditor-database/insert_purse_info.h"
39 : #include "auditor-database/insert_purse_not_closed_inconsistencies.h"
40 : #include "auditor-database/insert_row_inconsistency.h"
41 : struct PurseContext;
42 : #define TALER_AUDITORDB_EXPIRED_PURSE_RESULT_CLOSURE struct PurseContext
43 : #include "auditor-database/iterate_purse_expired.h"
44 : #include "auditor-database/update_purse_info.h"
45 : #include "exchange-database/get_global_fee.h"
46 : struct PurseContext;
47 : #define TALER_EXCHANGEDB_ACCOUNT_MERGE_RESULT_CLOSURE struct PurseContext
48 : #define TALER_EXCHANGEDB_ALL_PURSE_DECISION_RESULT_CLOSURE struct PurseContext
49 : #define TALER_EXCHANGEDB_ALL_PURSE_DELETIONS_RESULT_CLOSURE struct PurseContext
50 : #define TALER_EXCHANGEDB_PURSE_DEPOSIT_RESULT_CLOSURE struct PurseContext
51 : #define TALER_EXCHANGEDB_PURSE_MERGE_RESULT_CLOSURE struct PurseContext
52 : #define TALER_EXCHANGEDB_PURSE_REQUEST_RESULT_CLOSURE struct PurseContext
53 : #include "exchange-database/iterate_account_merges_above_serial_id.h"
54 : #include "exchange-database/iterate_all_purse_decisions_above_serial_id.h"
55 : #include "exchange-database/iterate_all_purse_deletions_above_serial_id.h"
56 : #include "exchange-database/get_purse.h"
57 : #include "exchange-database/iterate_purse_deposits_above_serial_id.h"
58 : #include "exchange-database/iterate_purse_merges_above_serial_id.h"
59 : #include "exchange-database/iterate_purse_requests_above_serial_id.h"
60 :
61 :
62 : /**
63 : * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
64 : */
65 : #define EXPIRATION_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
66 :
67 : /**
68 : * Return value from main().
69 : */
70 : static int global_ret;
71 :
72 : /**
73 : * Run in test mode. Exit when idle instead of
74 : * going to sleep and waiting for more work.
75 : */
76 : static int test_mode;
77 :
78 : /**
79 : * Checkpointing our progress for purses.
80 : */
81 : static TALER_ARL_DEF_PP (purse_account_merge_serial_id);
82 : static TALER_ARL_DEF_PP (purse_decision_serial_id);
83 : static TALER_ARL_DEF_PP (purse_deletion_serial_id);
84 : static TALER_ARL_DEF_PP (purse_deposits_serial_id);
85 : static TALER_ARL_DEF_PP (purse_merges_serial_id);
86 : static TALER_ARL_DEF_PP (purse_request_serial_id);
87 : static TALER_ARL_DEF_PP (purse_open_counter);
88 : static TALER_ARL_DEF_AB (purse_global_balance);
89 :
90 : /**
91 : * Total amount purses were merged with insufficient balance.
92 : */
93 : static TALER_ARL_DEF_AB (purse_total_balance_insufficient_loss);
94 :
95 : /**
96 : * Total amount purse decisions are delayed past deadline.
97 : */
98 : static TALER_ARL_DEF_AB (purse_total_delayed_decisions);
99 :
100 : /**
101 : * Total amount affected by purses not having been closed on time.
102 : */
103 : static TALER_ARL_DEF_AB (purse_total_balance_purse_not_closed);
104 :
105 : /**
106 : * Profits the exchange made by bad amount calculations.
107 : */
108 : static TALER_ARL_DEF_AB (purse_total_arithmetic_delta_plus);
109 :
110 : /**
111 : * Losses the exchange made by bad amount calculations.
112 : */
113 : static TALER_ARL_DEF_AB (purse_total_arithmetic_delta_minus);
114 :
115 : /**
116 : * Total amount lost by operations for which signatures were invalid.
117 : */
118 : static TALER_ARL_DEF_AB (purse_total_bad_sig_loss);
119 :
120 : /**
121 : * Should we run checks that only work for exchange-internal audits?
122 : */
123 : static int internal_checks;
124 :
125 : static struct GNUNET_DB_EventHandler *eh;
126 :
127 : /**
128 : * The auditors's configuration.
129 : */
130 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
131 :
132 : /* ***************************** Report logic **************************** */
133 :
134 :
135 : /**
136 : * Report a (serious) inconsistency in the exchange's database with
137 : * respect to calculations involving amounts.
138 : *
139 : * @param operation what operation had the inconsistency
140 : * @param rowid affected row, 0 if row is missing
141 : * @param exchange amount calculated by exchange
142 : * @param auditor amount calculated by auditor
143 : * @param profitable 1 if @a exchange being larger than @a auditor is
144 : * profitable for the exchange for this operation,
145 : * -1 if @a exchange being smaller than @a auditor is
146 : * profitable for the exchange, and 0 if it is unclear
147 : * @return transaction status
148 : */
149 : static enum GNUNET_DB_QueryStatus
150 0 : report_amount_arithmetic_inconsistency (
151 : const char *operation,
152 : uint64_t rowid,
153 : const struct TALER_Amount *exchange,
154 : const struct TALER_Amount *auditor,
155 : int profitable)
156 : {
157 : struct TALER_Amount delta;
158 : struct TALER_Amount *target;
159 : enum GNUNET_DB_QueryStatus qs;
160 :
161 0 : if (0 < TALER_amount_cmp (exchange,
162 : auditor))
163 : {
164 : /* exchange > auditor */
165 0 : TALER_ARL_amount_subtract (&delta,
166 : exchange,
167 : auditor);
168 : }
169 : else
170 : {
171 : /* exchange <= auditor */
172 0 : profitable = -profitable;
173 0 : TALER_ARL_amount_subtract (&delta,
174 : auditor,
175 : exchange);
176 : }
177 :
178 : {
179 0 : struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
180 : .profitable = profitable,
181 : .problem_row_id = rowid,
182 : .operation = (char *) operation,
183 : .exchange_amount = *exchange,
184 : .auditor_amount = *auditor
185 : };
186 :
187 0 : qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
188 : TALER_ARL_adb,
189 : &aai);
190 :
191 0 : if (qs < 0)
192 : {
193 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
194 0 : return qs;
195 : }
196 : }
197 :
198 0 : if (0 != profitable)
199 : {
200 0 : target = (1 == profitable)
201 : ? &TALER_ARL_USE_AB (purse_total_arithmetic_delta_plus)
202 0 : : &TALER_ARL_USE_AB (purse_total_arithmetic_delta_minus);
203 0 : TALER_ARL_amount_add (target,
204 : target,
205 : &delta);
206 : }
207 0 : return qs;
208 : }
209 :
210 :
211 : /**
212 : * Report a (serious) inconsistency in the exchange's database.
213 : *
214 : * @param table affected table
215 : * @param rowid affected row, 0 if row is missing
216 : * @param diagnostic message explaining the problem
217 : * @return transaction status
218 : */
219 : static enum GNUNET_DB_QueryStatus
220 0 : report_row_inconsistency (const char *table,
221 : uint64_t rowid,
222 : const char *diagnostic)
223 : {
224 : enum GNUNET_DB_QueryStatus qs;
225 0 : struct TALER_AUDITORDB_RowInconsistency ri = {
226 : .diagnostic = (char *) diagnostic,
227 : .row_table = (char *) table,
228 : .problem_row_id = rowid
229 : };
230 :
231 0 : qs = TALER_AUDITORDB_insert_row_inconsistency (
232 : TALER_ARL_adb,
233 : &ri);
234 :
235 0 : if (qs < 0)
236 : {
237 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
238 0 : return qs;
239 : }
240 0 : return qs;
241 : }
242 :
243 :
244 : /**
245 : * Obtain the purse fee for a purse created at @a time.
246 : *
247 : * @param atime when was the purse created
248 : * @param[out] fee set to the purse fee
249 : * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success
250 : */
251 : static enum GNUNET_DB_QueryStatus
252 0 : get_purse_fee (struct GNUNET_TIME_Timestamp atime,
253 : struct TALER_Amount *fee)
254 : {
255 : enum GNUNET_DB_QueryStatus qs;
256 : struct TALER_MasterSignatureP master_sig;
257 : struct GNUNET_TIME_Timestamp start_date;
258 : struct GNUNET_TIME_Timestamp end_date;
259 : struct TALER_GlobalFeeSet fees;
260 : struct GNUNET_TIME_Relative ptimeout;
261 : struct GNUNET_TIME_Relative hexp;
262 : uint32_t pacl;
263 :
264 0 : qs = TALER_EXCHANGEDB_get_global_fee (TALER_ARL_edb,
265 : atime,
266 : &start_date,
267 : &end_date,
268 : &fees,
269 : &ptimeout,
270 : &hexp,
271 : &pacl,
272 : &master_sig);
273 0 : if (0 > qs)
274 : {
275 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
276 0 : return qs;
277 : }
278 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
279 : {
280 : char *diag;
281 :
282 0 : GNUNET_asprintf (&diag,
283 : "purse fee unavailable at %s\n",
284 : GNUNET_TIME_timestamp2s (atime));
285 0 : qs = report_row_inconsistency ("purse-fee",
286 : atime.abs_time.abs_value_us,
287 : diag);
288 0 : GNUNET_free (diag);
289 0 : if (0 > qs)
290 : {
291 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
292 0 : return qs;
293 : }
294 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
295 : }
296 0 : *fee = fees.purse;
297 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
298 : }
299 :
300 :
301 : /* ***************************** Analyze purses ************************ */
302 : /* This logic checks the purses_requests, purse_deposits,
303 : purse_refunds, purse_merges and account_merges */
304 :
305 : /**
306 : * Summary data we keep per purse.
307 : */
308 : struct PurseSummary
309 : {
310 : /**
311 : * Public key of the purse.
312 : * Always set when the struct is first initialized.
313 : */
314 : struct TALER_PurseContractPublicKeyP purse_pub;
315 :
316 : /**
317 : * Balance of the purse from deposits (includes purse fee, excludes deposit
318 : * fees), as calculated by auditor.
319 : */
320 : struct TALER_Amount balance;
321 :
322 : /**
323 : * Expected value of the purse, excludes purse fee.
324 : */
325 : struct TALER_Amount total_value;
326 :
327 : /**
328 : * Purse balance according to exchange DB.
329 : */
330 : struct TALER_Amount exchange_balance;
331 :
332 : /**
333 : * Contract terms of the purse.
334 : */
335 : struct TALER_PrivateContractHashP h_contract_terms;
336 :
337 : /**
338 : * Merge timestamp (as per exchange DB).
339 : */
340 : struct GNUNET_TIME_Timestamp merge_timestamp;
341 :
342 : /**
343 : * Purse creation date. This is when the merge
344 : * fee is applied.
345 : */
346 : struct GNUNET_TIME_Timestamp creation_date;
347 :
348 : /**
349 : * Purse expiration date.
350 : */
351 : struct GNUNET_TIME_Timestamp expiration_date;
352 :
353 : /**
354 : * Did we have a previous purse info? Used to decide between UPDATE and
355 : * INSERT later. Initialized in #load_auditor_purse_summary().
356 : */
357 : bool had_pi;
358 :
359 : /**
360 : * Was the purse deleted? Note: as this is set via an UPDATE, it
361 : * may be false at the auditor even if the purse was deleted. Thus,
362 : * this value is only meaningful for *internal* checks.
363 : */
364 : bool purse_deleted;
365 :
366 : /**
367 : * Was the purse refunded? Note: as this is set via an UPDATE, it
368 : * may be false at the auditor even if the purse was deleted. Thus,
369 : * this value is only meaningful for *internal* checks.
370 : */
371 : bool purse_refunded;
372 :
373 : };
374 :
375 :
376 : /**
377 : * Load the auditor's remembered state about the purse into @a ps.
378 : *
379 : * @param[in,out] ps purse summary to (fully) initialize
380 : * @return transaction status code
381 : */
382 : static enum GNUNET_DB_QueryStatus
383 0 : load_auditor_purse_summary (struct PurseSummary *ps)
384 : {
385 : enum GNUNET_DB_QueryStatus qs;
386 : uint64_t rowid;
387 :
388 0 : qs = TALER_AUDITORDB_get_purse_info (TALER_ARL_adb,
389 0 : &ps->purse_pub,
390 : &rowid,
391 : &ps->balance,
392 : &ps->expiration_date);
393 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
394 : "Loaded purse `%s' info (%d)\n",
395 : TALER_B2S (&ps->purse_pub),
396 : (int) qs);
397 0 : if (0 > qs)
398 : {
399 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
400 0 : return qs;
401 : }
402 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
403 : {
404 0 : ps->had_pi = false;
405 0 : GNUNET_assert (GNUNET_OK ==
406 : TALER_amount_set_zero (TALER_ARL_currency,
407 : &ps->balance));
408 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
409 : "Creating fresh purse `%s'\n",
410 : TALER_B2S (&ps->purse_pub));
411 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
412 : }
413 0 : ps->had_pi = true;
414 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
415 : "Auditor remembers purse `%s' has balance %s\n",
416 : TALER_B2S (&ps->purse_pub),
417 : TALER_amount2s (&ps->balance));
418 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
419 : }
420 :
421 :
422 : /**
423 : * Closure to the various callbacks we make while checking a purse.
424 : */
425 : struct PurseContext
426 : {
427 : /**
428 : * Map from hash of purse's public key to a `struct PurseSummary`.
429 : */
430 : struct GNUNET_CONTAINER_MultiHashMap *purses;
431 :
432 : /**
433 : * Transaction status code, set to error codes if applicable.
434 : */
435 : enum GNUNET_DB_QueryStatus qs;
436 :
437 : };
438 :
439 :
440 : /**
441 : * Create a new purse for @a purse_pub in @a pc.
442 : *
443 : * @param[in,out] pc context to update
444 : * @param purse_pub key for which to create a purse
445 : * @return NULL on error
446 : */
447 : static struct PurseSummary *
448 0 : setup_purse (struct PurseContext *pc,
449 : const struct TALER_PurseContractPublicKeyP *purse_pub)
450 : {
451 : struct PurseSummary *ps;
452 : struct GNUNET_HashCode key;
453 : enum GNUNET_DB_QueryStatus qs;
454 :
455 0 : GNUNET_CRYPTO_hash (purse_pub,
456 : sizeof (*purse_pub),
457 : &key);
458 0 : ps = GNUNET_CONTAINER_multihashmap_get (pc->purses,
459 : &key);
460 0 : if (NULL != ps)
461 : {
462 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
463 : "Found purse `%s' summary in cache\n",
464 : TALER_B2S (&ps->purse_pub));
465 0 : return ps;
466 : }
467 0 : ps = GNUNET_new (struct PurseSummary);
468 0 : ps->purse_pub = *purse_pub;
469 0 : GNUNET_assert (GNUNET_OK ==
470 : TALER_amount_set_zero (TALER_ARL_currency,
471 : &ps->balance));
472 : /* get purse meta-data from exchange DB */
473 0 : qs = TALER_EXCHANGEDB_get_purse (TALER_ARL_edb,
474 : purse_pub,
475 : &ps->creation_date,
476 : &ps->expiration_date,
477 : &ps->total_value,
478 : &ps->exchange_balance,
479 : &ps->h_contract_terms,
480 : &ps->merge_timestamp,
481 : &ps->purse_deleted,
482 : &ps->purse_refunded);
483 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
484 : "Loaded purse `%s' meta-data (%d)\n",
485 : TALER_B2S (purse_pub),
486 : (int) qs);
487 0 : if (0 >= qs)
488 : {
489 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
490 : "Failed to load meta-data of purse `%s'\n",
491 : TALER_B2S (&ps->purse_pub));
492 0 : GNUNET_free (ps);
493 0 : pc->qs = qs;
494 0 : return NULL;
495 : }
496 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
497 0 : qs = load_auditor_purse_summary (ps);
498 0 : if (0 > qs)
499 : {
500 0 : GNUNET_free (ps);
501 0 : pc->qs = qs;
502 0 : return NULL;
503 : }
504 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
505 : "Starting purse `%s' analysis\n",
506 : TALER_B2S (purse_pub));
507 0 : GNUNET_assert (GNUNET_OK ==
508 : GNUNET_CONTAINER_multihashmap_put (pc->purses,
509 : &key,
510 : ps,
511 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)
512 : );
513 0 : return ps;
514 : }
515 :
516 :
517 : /**
518 : * Function called on purse requests.
519 : *
520 : * @param pc closure
521 : * @param rowid which row in the database was the request stored in
522 : * @param purse_pub public key of the purse
523 : * @param merge_pub public key representing the merge capability
524 : * @param purse_creation when was the purse created
525 : * @param purse_expiration when would an unmerged purse expire
526 : * @param h_contract_terms contract associated with the purse
527 : * @param age_limit the age limit for deposits into the purse
528 : * @param target_amount amount to be put into the purse
529 : * @param purse_sig signature of the purse over the initialization data
530 : * @return #GNUNET_OK to continue to iterate
531 : */
532 : static enum GNUNET_GenericReturnValue
533 0 : handle_purse_requested (
534 : struct PurseContext *pc,
535 : uint64_t rowid,
536 : const struct TALER_PurseContractPublicKeyP *purse_pub,
537 : const struct TALER_PurseMergePublicKeyP *merge_pub,
538 : struct GNUNET_TIME_Timestamp purse_creation,
539 : struct GNUNET_TIME_Timestamp purse_expiration,
540 : const struct TALER_PrivateContractHashP *h_contract_terms,
541 : uint32_t age_limit,
542 : const struct TALER_Amount *target_amount,
543 : const struct TALER_PurseContractSignatureP *purse_sig)
544 : {
545 : struct PurseSummary *ps;
546 : struct GNUNET_HashCode key;
547 :
548 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
549 : "Handling purse request `%s'\n",
550 : TALER_B2S (purse_pub));
551 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_request_serial_id));
552 0 : TALER_ARL_USE_PP (purse_request_serial_id) = rowid + 1;
553 0 : if (GNUNET_OK !=
554 0 : TALER_wallet_purse_create_verify (purse_expiration,
555 : h_contract_terms,
556 : merge_pub,
557 : age_limit,
558 : target_amount,
559 : purse_pub,
560 : purse_sig))
561 : {
562 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
563 : .problem_row_id = rowid,
564 : .operation = (char *) "purse-request",
565 : .loss = *target_amount,
566 : .operation_specific_pub = purse_pub->eddsa_pub
567 : };
568 : enum GNUNET_DB_QueryStatus qs;
569 :
570 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
571 : TALER_ARL_adb,
572 : &bsl);
573 0 : if (qs < 0)
574 : {
575 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
576 0 : pc->qs = qs;
577 0 : return GNUNET_SYSERR;
578 : }
579 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
580 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
581 : target_amount);
582 : }
583 0 : GNUNET_CRYPTO_hash (purse_pub,
584 : sizeof (*purse_pub),
585 : &key);
586 0 : ps = GNUNET_new (struct PurseSummary);
587 0 : ps->purse_pub = *purse_pub;
588 0 : GNUNET_assert (GNUNET_OK ==
589 : TALER_amount_set_zero (TALER_ARL_currency,
590 : &ps->balance));
591 0 : ps->creation_date = purse_creation;
592 0 : ps->expiration_date = purse_expiration;
593 0 : ps->total_value = *target_amount;
594 0 : ps->h_contract_terms = *h_contract_terms;
595 : {
596 : enum GNUNET_DB_QueryStatus qs;
597 :
598 0 : qs = load_auditor_purse_summary (ps);
599 0 : if (0 > qs)
600 : {
601 0 : GNUNET_free (ps);
602 0 : pc->qs = qs;
603 0 : return GNUNET_SYSERR;
604 : }
605 : }
606 0 : GNUNET_assert (GNUNET_OK ==
607 : GNUNET_CONTAINER_multihashmap_put (pc->purses,
608 : &key,
609 : ps,
610 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)
611 : );
612 0 : return GNUNET_OK;
613 : }
614 :
615 :
616 : /**
617 : * Function called with details about purse deposits that have been made, with
618 : * the goal of auditing the deposit's execution.
619 : *
620 : * @param pc closure
621 : * @param rowid unique serial ID for the deposit in our DB
622 : * @param deposit deposit details
623 : * @param reserve_pub which reserve is the purse merged into, NULL if unknown
624 : * @param flags purse flags
625 : * @param auditor_balance purse balance (according to the
626 : * auditor during auditing)
627 : * @param purse_total target amount the purse should reach
628 : * @param denom_pub denomination public key of @a coin_pub
629 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
630 : */
631 : static enum GNUNET_GenericReturnValue
632 0 : handle_purse_deposits (
633 : struct PurseContext *pc,
634 : uint64_t rowid,
635 : const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
636 : const struct TALER_ReservePublicKeyP *reserve_pub,
637 : enum TALER_WalletAccountMergeFlags flags,
638 : const struct TALER_Amount *auditor_balance,
639 : const struct TALER_Amount *purse_total,
640 : const struct TALER_DenominationPublicKey *denom_pub)
641 : {
642 : struct TALER_Amount amount_minus_fee;
643 0 : const char *base_url
644 0 : = (NULL == deposit->exchange_base_url)
645 : ? TALER_ARL_exchange_url
646 0 : : deposit->exchange_base_url;
647 : struct TALER_DenominationHashP h_denom_pub;
648 :
649 : /* should be monotonically increasing */
650 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
651 : "Handling purse deposit `%s'\n",
652 : TALER_B2S (&deposit->purse_pub));
653 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id));
654 0 : TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1;
655 :
656 : {
657 : const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
658 : enum GNUNET_DB_QueryStatus qs;
659 :
660 0 : qs = TALER_ARL_get_denomination_info (denom_pub,
661 : &issue,
662 : &h_denom_pub);
663 0 : if (0 > qs)
664 : {
665 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
666 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
667 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
668 : "Hard database error trying to get denomination %s from database!\n",
669 : TALER_B2S (denom_pub));
670 0 : pc->qs = qs;
671 0 : return GNUNET_SYSERR;
672 : }
673 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
674 : {
675 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
676 : "Failed to find denomination key for purse deposit `%s' in record %llu\n",
677 : TALER_B2S (&deposit->purse_pub),
678 : (unsigned long long) rowid);
679 0 : qs = report_row_inconsistency ("purse-deposit",
680 : rowid,
681 : "denomination key not found");
682 0 : if (0 > qs)
683 : {
684 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
685 0 : pc->qs = qs;
686 0 : return GNUNET_SYSERR;
687 : }
688 0 : return GNUNET_OK;
689 : }
690 0 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
691 0 : TALER_ARL_amount_subtract_neg (&amount_minus_fee,
692 : &deposit->amount,
693 : &issue->fees.deposit))
694 : {
695 : /* The deposited amount does not even cover the deposit fee of the
696 : denomination; report the bogus row instead of dying here. */
697 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
698 : "Purse deposit `%s' in record %llu is below the deposit fee of its denomination\n",
699 : TALER_B2S (&deposit->purse_pub),
700 : (unsigned long long) rowid);
701 0 : qs = report_row_inconsistency ("purse-deposit",
702 : rowid,
703 : "deposited amount below deposit fee");
704 0 : if (0 > qs)
705 : {
706 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
707 0 : pc->qs = qs;
708 0 : return GNUNET_SYSERR;
709 : }
710 0 : return GNUNET_OK;
711 : }
712 : }
713 :
714 0 : if (GNUNET_OK !=
715 0 : TALER_wallet_purse_deposit_verify (base_url,
716 : &deposit->purse_pub,
717 : &deposit->amount,
718 : &h_denom_pub,
719 : &deposit->h_age_commitment,
720 : &deposit->coin_pub,
721 : &deposit->coin_sig))
722 : {
723 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
724 : .problem_row_id = rowid,
725 : .operation = (char *) "purse-deposit",
726 : .loss = deposit->amount,
727 : .operation_specific_pub = deposit->coin_pub.eddsa_pub
728 : };
729 : enum GNUNET_DB_QueryStatus qs;
730 :
731 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
732 : "Failed to verify purse deposit signature on `%s' in record %llu\n",
733 : TALER_B2S (&deposit->purse_pub),
734 : (unsigned long long) rowid);
735 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
736 : TALER_ARL_adb,
737 : &bsl);
738 0 : if (qs < 0)
739 : {
740 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
741 0 : pc->qs = qs;
742 0 : return GNUNET_SYSERR;
743 : }
744 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
745 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
746 : &deposit->amount);
747 0 : return GNUNET_OK;
748 : }
749 :
750 : {
751 : struct PurseSummary *ps;
752 :
753 0 : ps = setup_purse (pc,
754 : &deposit->purse_pub);
755 0 : if (NULL == ps)
756 : {
757 : enum GNUNET_DB_QueryStatus qs;
758 :
759 0 : if (0 > pc->qs)
760 : {
761 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
762 0 : return GNUNET_SYSERR;
763 : }
764 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
765 0 : qs = report_row_inconsistency ("purse_deposit",
766 : rowid,
767 : "purse not found");
768 0 : if (0 > qs)
769 : {
770 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
771 0 : pc->qs = qs;
772 0 : return GNUNET_SYSERR;
773 : }
774 0 : return GNUNET_OK;
775 : }
776 0 : TALER_ARL_amount_add (&ps->balance,
777 : &ps->balance,
778 : &amount_minus_fee);
779 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
780 : &TALER_ARL_USE_AB (purse_global_balance),
781 : &amount_minus_fee);
782 : }
783 0 : return GNUNET_OK;
784 : }
785 :
786 :
787 : /**
788 : * Function called with details about purse merges that have been made, with
789 : * the goal of auditing the purse merge execution.
790 : *
791 : * @param pc closure
792 : * @param rowid unique serial ID for the deposit in our DB
793 : * @param partner_base_url where is the reserve, NULL for this exchange
794 : * @param amount total amount expected in the purse
795 : * @param balance current balance in the purse
796 : * @param flags purse flags
797 : * @param merge_pub merge capability key
798 : * @param reserve_pub reserve the merge affects
799 : * @param merge_sig signature affirming the merge
800 : * @param purse_pub purse key
801 : * @param merge_timestamp when did the merge happen
802 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
803 : */
804 : static enum GNUNET_GenericReturnValue
805 0 : handle_purse_merged (
806 : struct PurseContext *pc,
807 : uint64_t rowid,
808 : const char *partner_base_url,
809 : const struct TALER_Amount *amount,
810 : const struct TALER_Amount *balance,
811 : enum TALER_WalletAccountMergeFlags flags,
812 : const struct TALER_PurseMergePublicKeyP *merge_pub,
813 : const struct TALER_ReservePublicKeyP *reserve_pub,
814 : const struct TALER_PurseMergeSignatureP *merge_sig,
815 : const struct TALER_PurseContractPublicKeyP *purse_pub,
816 : struct GNUNET_TIME_Timestamp merge_timestamp)
817 : {
818 : struct PurseSummary *ps;
819 : enum GNUNET_DB_QueryStatus qs;
820 :
821 : /* should be monotonically increasing */
822 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
823 : "Handling purse merged `%s'\n",
824 : TALER_B2S (purse_pub));
825 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
826 0 : TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
827 :
828 : {
829 : struct TALER_NormalizedPayto reserve_url;
830 :
831 : reserve_url
832 0 : = TALER_reserve_make_payto (NULL == partner_base_url
833 : ? TALER_ARL_exchange_url
834 : : partner_base_url,
835 : reserve_pub);
836 0 : if (GNUNET_OK !=
837 0 : TALER_wallet_purse_merge_verify (reserve_url,
838 : merge_timestamp,
839 : purse_pub,
840 : merge_pub,
841 : merge_sig))
842 : {
843 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
844 : .problem_row_id = rowid,
845 : .operation = (char *) "merge-purse",
846 : .loss = *amount,
847 : .operation_specific_pub = merge_pub->eddsa_pub
848 : };
849 :
850 0 : GNUNET_free (reserve_url.normalized_payto);
851 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
852 : TALER_ARL_adb,
853 : &bsl);
854 0 : if (qs < 0)
855 : {
856 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
857 0 : pc->qs = qs;
858 0 : return GNUNET_SYSERR;
859 : }
860 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
861 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
862 : amount);
863 0 : return GNUNET_OK;
864 : }
865 0 : GNUNET_free (reserve_url.normalized_payto);
866 : }
867 :
868 0 : ps = setup_purse (pc,
869 : purse_pub);
870 0 : if (NULL == ps)
871 : {
872 0 : if (pc->qs < 0)
873 : {
874 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
875 0 : return GNUNET_SYSERR;
876 : }
877 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
878 0 : qs = report_row_inconsistency ("purse-merge",
879 : rowid,
880 : "purse not found");
881 0 : if (qs < 0)
882 : {
883 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
884 0 : pc->qs = qs;
885 0 : return GNUNET_SYSERR;
886 : }
887 0 : return GNUNET_OK;
888 : }
889 0 : GNUNET_break (0 ==
890 : GNUNET_TIME_timestamp_cmp (merge_timestamp,
891 : ==,
892 : ps->merge_timestamp));
893 0 : TALER_ARL_amount_add (&ps->balance,
894 : &ps->balance,
895 : amount);
896 0 : return GNUNET_OK;
897 : }
898 :
899 :
900 : /**
901 : * Function called with details about account merge requests that have been
902 : * made, with the goal of auditing the account merge execution.
903 : *
904 : * @param pc closure
905 : * @param rowid unique serial ID for the deposit in our DB
906 : * @param reserve_pub reserve affected by the merge
907 : * @param purse_pub purse being merged
908 : * @param h_contract_terms hash over contract of the purse
909 : * @param purse_expiration when would the purse expire
910 : * @param amount total amount in the purse
911 : * @param min_age minimum age of all coins deposited into the purse
912 : * @param flags how was the purse created
913 : * @param purse_fee if a purse fee was paid, how high is it
914 : * @param merge_timestamp when was the merge approved
915 : * @param reserve_sig signature by reserve approving the merge
916 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
917 : */
918 : static enum GNUNET_GenericReturnValue
919 0 : handle_account_merged (
920 : struct PurseContext *pc,
921 : uint64_t rowid,
922 : const struct TALER_ReservePublicKeyP *reserve_pub,
923 : const struct TALER_PurseContractPublicKeyP *purse_pub,
924 : const struct TALER_PrivateContractHashP *h_contract_terms,
925 : struct GNUNET_TIME_Timestamp purse_expiration,
926 : const struct TALER_Amount *amount,
927 : uint32_t min_age,
928 : enum TALER_WalletAccountMergeFlags flags,
929 : const struct TALER_Amount *purse_fee,
930 : struct GNUNET_TIME_Timestamp merge_timestamp,
931 : const struct TALER_ReserveSignatureP *reserve_sig)
932 : {
933 : struct PurseSummary *ps;
934 : enum GNUNET_DB_QueryStatus qs;
935 :
936 : /* should be monotonically increasing */
937 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
938 : "Handling account merge on purse `%s'\n",
939 : TALER_B2S (purse_pub));
940 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
941 0 : TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
942 0 : if (GNUNET_OK !=
943 0 : TALER_wallet_account_merge_verify (merge_timestamp,
944 : purse_pub,
945 : purse_expiration,
946 : h_contract_terms,
947 : amount,
948 : purse_fee,
949 : min_age,
950 : flags,
951 : reserve_pub,
952 : reserve_sig))
953 : {
954 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
955 : .problem_row_id = rowid,
956 : .operation = (char *) "account-merge",
957 : .loss = *purse_fee,
958 : .operation_specific_pub = reserve_pub->eddsa_pub
959 : };
960 :
961 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
962 : TALER_ARL_adb,
963 : &bsl);
964 0 : if (qs < 0)
965 : {
966 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
967 0 : pc->qs = qs;
968 0 : return GNUNET_SYSERR;
969 : }
970 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
971 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
972 : purse_fee);
973 0 : return GNUNET_OK;
974 : }
975 0 : ps = setup_purse (pc,
976 : purse_pub);
977 0 : if (NULL == ps)
978 : {
979 0 : if (0 > pc->qs)
980 : {
981 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
982 0 : return GNUNET_SYSERR;
983 : }
984 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
985 0 : qs = report_row_inconsistency ("account-merge",
986 : rowid,
987 : "purse not found");
988 0 : if (0 > qs)
989 : {
990 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
991 0 : pc->qs = qs;
992 0 : return GNUNET_SYSERR;
993 : }
994 0 : return GNUNET_OK;
995 : }
996 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
997 : &TALER_ARL_USE_AB (purse_global_balance),
998 : purse_fee);
999 0 : TALER_ARL_amount_add (&ps->balance,
1000 : &ps->balance,
1001 : purse_fee);
1002 0 : return GNUNET_OK;
1003 : }
1004 :
1005 :
1006 : /**
1007 : * Function called with details about purse decisions that have been made.
1008 : *
1009 : * @param pc closure
1010 : * @param rowid unique serial ID for the deposit in our DB
1011 : * @param purse_pub which purse was the decision made on
1012 : * @param refunded true if decision was to refund
1013 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
1014 : */
1015 : static enum GNUNET_GenericReturnValue
1016 0 : handle_purse_decision (
1017 : struct PurseContext *pc,
1018 : uint64_t rowid,
1019 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1020 : bool refunded)
1021 : {
1022 : struct PurseSummary *ps;
1023 : struct GNUNET_HashCode key;
1024 : enum GNUNET_DB_QueryStatus qs;
1025 : struct TALER_Amount purse_fee;
1026 : struct TALER_Amount balance_without_purse_fee;
1027 :
1028 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_decision_serial_id));
1029 0 : TALER_ARL_USE_PP (purse_decision_serial_id) = rowid + 1;
1030 0 : ps = setup_purse (pc,
1031 : purse_pub);
1032 0 : if (NULL == ps)
1033 : {
1034 0 : if (0 > pc->qs)
1035 : {
1036 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
1037 0 : return GNUNET_SYSERR;
1038 : }
1039 0 : qs = report_row_inconsistency ("purse-decision",
1040 : rowid,
1041 : "purse not found");
1042 0 : if (0 > qs)
1043 : {
1044 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1045 0 : pc->qs = qs;
1046 0 : return GNUNET_SYSERR;
1047 : }
1048 0 : return GNUNET_OK;
1049 : }
1050 0 : qs = get_purse_fee (ps->creation_date,
1051 : &purse_fee);
1052 0 : if (0 > qs)
1053 : {
1054 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1055 0 : pc->qs = qs;
1056 0 : return GNUNET_SYSERR;
1057 : }
1058 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1059 0 : return GNUNET_OK; /* already reported */
1060 0 : if (0 >
1061 0 : TALER_amount_subtract (&balance_without_purse_fee,
1062 0 : &ps->balance,
1063 : &purse_fee))
1064 : {
1065 0 : qs = report_row_inconsistency ("purse-request",
1066 : rowid,
1067 : "purse fee higher than balance");
1068 0 : if (0 > qs)
1069 : {
1070 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1071 0 : pc->qs = qs;
1072 0 : return GNUNET_SYSERR;
1073 : }
1074 0 : GNUNET_assert (GNUNET_OK ==
1075 : TALER_amount_set_zero (TALER_ARL_currency,
1076 : &balance_without_purse_fee));
1077 : }
1078 :
1079 0 : if (refunded)
1080 : {
1081 0 : if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
1082 0 : &ps->total_value))
1083 : {
1084 0 : qs = report_amount_arithmetic_inconsistency ("purse-decision: refund",
1085 : rowid,
1086 : &balance_without_purse_fee,
1087 0 : &ps->total_value,
1088 : 0);
1089 0 : if (0 > qs)
1090 : {
1091 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1092 0 : pc->qs = qs;
1093 0 : return GNUNET_SYSERR;
1094 : }
1095 : }
1096 0 : if ( (internal_checks) &&
1097 0 : (! ps->purse_refunded) )
1098 : {
1099 0 : qs = report_row_inconsistency (
1100 : "purse-decision",
1101 : rowid,
1102 : "purse not marked as refunded (internal check)");
1103 0 : if (qs < 0)
1104 : {
1105 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1106 0 : pc->qs = qs;
1107 0 : return GNUNET_SYSERR;
1108 : }
1109 : }
1110 : }
1111 : else
1112 : {
1113 0 : if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
1114 0 : &ps->total_value))
1115 : {
1116 0 : qs = report_amount_arithmetic_inconsistency ("purse-decision: merge",
1117 : rowid,
1118 0 : &ps->total_value,
1119 : &balance_without_purse_fee,
1120 : 0);
1121 0 : if (0 > qs)
1122 : {
1123 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1124 0 : pc->qs = qs;
1125 0 : return GNUNET_SYSERR;
1126 : }
1127 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1128 : purse_total_balance_insufficient_loss),
1129 : &TALER_ARL_USE_AB (
1130 : purse_total_balance_insufficient_loss),
1131 : &ps->total_value);
1132 : }
1133 : }
1134 :
1135 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1136 : "Deleting purse with decision `%s'\n",
1137 : TALER_B2S (&ps->purse_pub));
1138 : /* The purse ceases to exist, so the money it held is no longer
1139 : part of the balance the exchange holds in open purses. */
1140 0 : if (0 >
1141 0 : TALER_amount_subtract (&TALER_ARL_USE_AB (purse_global_balance),
1142 : &TALER_ARL_USE_AB (purse_global_balance),
1143 0 : &ps->balance))
1144 : {
1145 : /* Should be impossible: we added this balance to the global
1146 : balance when we learned about it. */
1147 0 : GNUNET_break (0);
1148 0 : GNUNET_assert (GNUNET_OK ==
1149 : TALER_amount_set_zero (TALER_ARL_currency,
1150 : &TALER_ARL_USE_AB (
1151 : purse_global_balance)));
1152 : }
1153 0 : qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb,
1154 : purse_pub);
1155 0 : if (qs < 0)
1156 : {
1157 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1158 0 : pc->qs = qs;
1159 0 : return GNUNET_SYSERR;
1160 : }
1161 0 : GNUNET_CRYPTO_hash (purse_pub,
1162 : sizeof (*purse_pub),
1163 : &key);
1164 0 : GNUNET_assert (GNUNET_YES ==
1165 : GNUNET_CONTAINER_multihashmap_remove (pc->purses,
1166 : &key,
1167 : ps));
1168 0 : GNUNET_free (ps);
1169 0 : return GNUNET_OK;
1170 : }
1171 :
1172 :
1173 : /**
1174 : * Function called on explicitly deleted purses.
1175 : *
1176 : * @param pc closure
1177 : * @param deletion_serial_id row ID with the deletion data
1178 : * @param purse_pub public key of the purse
1179 : * @param purse_sig signature affirming deletion of the purse
1180 : * @return #GNUNET_OK to continue to iterate
1181 : */
1182 : static enum GNUNET_GenericReturnValue
1183 0 : handle_purse_deletion (
1184 : struct PurseContext *pc,
1185 : uint64_t deletion_serial_id,
1186 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1187 : const struct TALER_PurseContractSignatureP *purse_sig)
1188 : {
1189 : struct PurseSummary *ps;
1190 :
1191 0 : GNUNET_assert (deletion_serial_id >=
1192 : TALER_ARL_USE_PP (purse_deletion_serial_id));
1193 0 : TALER_ARL_USE_PP (purse_deletion_serial_id) = deletion_serial_id + 1;
1194 0 : ps = setup_purse (pc,
1195 : purse_pub);
1196 0 : if (NULL == ps)
1197 : {
1198 0 : GNUNET_break (0);
1199 0 : return GNUNET_SYSERR;
1200 : }
1201 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1202 : "Handling purse `%s' deletion\n",
1203 : TALER_B2S (purse_pub));
1204 0 : if (GNUNET_OK !=
1205 0 : TALER_wallet_purse_delete_verify (purse_pub,
1206 : purse_sig))
1207 : {
1208 : enum GNUNET_DB_QueryStatus qs;
1209 :
1210 0 : qs = report_row_inconsistency (
1211 : "purse-delete",
1212 : deletion_serial_id,
1213 : "purse deletion signature invalid");
1214 0 : if (qs < 0)
1215 : {
1216 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1217 0 : pc->qs = qs;
1218 0 : return GNUNET_SYSERR;
1219 : }
1220 : }
1221 : else
1222 : {
1223 0 : if ( (internal_checks) &&
1224 0 : (! ps->purse_deleted) )
1225 : {
1226 : enum GNUNET_DB_QueryStatus qs;
1227 :
1228 0 : qs = report_row_inconsistency (
1229 : "purse-delete",
1230 : deletion_serial_id,
1231 : "purse not marked as deleted (internal check)");
1232 0 : if (qs < 0)
1233 : {
1234 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1235 0 : pc->qs = qs;
1236 0 : return GNUNET_SYSERR;
1237 : }
1238 : }
1239 : }
1240 0 : return GNUNET_OK;
1241 : }
1242 :
1243 :
1244 : /**
1245 : * Function called on expired purses.
1246 : *
1247 : * @param pc closure
1248 : * @param purse_pub public key of the purse
1249 : * @param balance amount of money in the purse
1250 : * @param expiration_date when did the purse expire?
1251 : * @return #GNUNET_OK to continue to iterate
1252 : */
1253 : static enum GNUNET_GenericReturnValue
1254 0 : handle_purse_expired (
1255 : struct PurseContext *pc,
1256 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1257 : const struct TALER_Amount *balance,
1258 : struct GNUNET_TIME_Timestamp expiration_date)
1259 : {
1260 : enum GNUNET_DB_QueryStatus qs;
1261 0 : struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = {
1262 : .amount = *balance,
1263 : .expiration_date = expiration_date.abs_time,
1264 : .purse_pub = purse_pub->eddsa_pub
1265 : };
1266 :
1267 0 : qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (
1268 : TALER_ARL_adb,
1269 : &pnci);
1270 0 : if (qs < 0)
1271 : {
1272 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1273 0 : pc->qs = qs;
1274 0 : return GNUNET_SYSERR;
1275 : }
1276 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1277 : "Handling purse expiration `%s'\n",
1278 : TALER_B2S (purse_pub));
1279 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions),
1280 : &TALER_ARL_USE_AB (purse_total_delayed_decisions),
1281 : balance);
1282 0 : return GNUNET_OK;
1283 : }
1284 :
1285 :
1286 : /**
1287 : * Check that the purse summary matches what the exchange database
1288 : * thinks about the purse, and update our own state of the purse.
1289 : *
1290 : * Remove all purses that we are happy with from the DB.
1291 : *
1292 : * @param cls our `struct PurseContext`
1293 : * @param key hash of the purse public key
1294 : * @param value a `struct PurseSummary`
1295 : * @return #GNUNET_OK to process more entries
1296 : */
1297 : static enum GNUNET_GenericReturnValue
1298 0 : verify_purse_balance (void *cls,
1299 : const struct GNUNET_HashCode *key,
1300 : void *value)
1301 : {
1302 0 : struct PurseContext *pc = cls;
1303 0 : struct PurseSummary *ps = value;
1304 : enum GNUNET_DB_QueryStatus qs;
1305 :
1306 0 : if (internal_checks)
1307 : {
1308 : struct TALER_Amount pf;
1309 : struct TALER_Amount balance_without_purse_fee;
1310 :
1311 : /* subtract purse fee from ps->balance to get actual balance we expect, as
1312 : we track the balance including purse fee, while the exchange subtracts
1313 : the purse fee early on. */
1314 0 : qs = get_purse_fee (ps->creation_date,
1315 : &pf);
1316 0 : if (qs < 0)
1317 : {
1318 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1319 0 : pc->qs = qs;
1320 0 : return GNUNET_SYSERR;
1321 : }
1322 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1323 0 : return GNUNET_OK; /* error already reported */
1324 0 : if (0 >
1325 0 : TALER_amount_subtract (&balance_without_purse_fee,
1326 0 : &ps->balance,
1327 : &pf))
1328 : {
1329 0 : qs = report_row_inconsistency ("purse",
1330 : 0,
1331 : "purse fee higher than balance");
1332 0 : if (qs < 0)
1333 : {
1334 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1335 0 : pc->qs = qs;
1336 0 : return GNUNET_SYSERR;
1337 : }
1338 0 : GNUNET_assert (GNUNET_OK ==
1339 : TALER_amount_set_zero (TALER_ARL_currency,
1340 : &balance_without_purse_fee));
1341 : }
1342 :
1343 0 : if (0 != TALER_amount_cmp (&ps->exchange_balance,
1344 : &balance_without_purse_fee))
1345 : {
1346 0 : qs = report_amount_arithmetic_inconsistency ("purse-balance",
1347 : 0,
1348 0 : &ps->exchange_balance,
1349 : &balance_without_purse_fee,
1350 : 0);
1351 0 : if (qs < 0)
1352 : {
1353 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1354 0 : pc->qs = qs;
1355 0 : return GNUNET_SYSERR;
1356 : }
1357 : }
1358 : }
1359 :
1360 0 : if (ps->had_pi)
1361 : {
1362 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1363 : "Updating purse `%s'\n",
1364 : TALER_B2S (&ps->purse_pub));
1365 0 : qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb,
1366 0 : &ps->purse_pub,
1367 0 : &ps->balance);
1368 : }
1369 : else
1370 : {
1371 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1372 : "Inserting purse `%s'\n",
1373 : TALER_B2S (&ps->purse_pub));
1374 0 : qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb,
1375 0 : &ps->purse_pub,
1376 0 : &ps->balance,
1377 0 : &ps->total_value,
1378 : ps->expiration_date);
1379 0 : ps->had_pi = true;
1380 : }
1381 0 : if (qs < 0)
1382 : {
1383 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1384 0 : pc->qs = qs;
1385 0 : return GNUNET_SYSERR;
1386 : }
1387 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1388 0 : return GNUNET_OK;
1389 : }
1390 :
1391 :
1392 : /**
1393 : * Clear memory from the purses hash map.
1394 : *
1395 : * @param cls our `struct PurseContext`
1396 : * @param key hash of the purse public key
1397 : * @param value a `struct PurseSummary`
1398 : * @return #GNUNET_OK to process more entries
1399 : */
1400 : static enum GNUNET_GenericReturnValue
1401 0 : release_purse_balance (void *cls,
1402 : const struct GNUNET_HashCode *key,
1403 : void *value)
1404 : {
1405 0 : struct PurseContext *pc = cls;
1406 0 : struct PurseSummary *ps = value;
1407 :
1408 0 : GNUNET_assert (GNUNET_YES ==
1409 : GNUNET_CONTAINER_multihashmap_remove (pc->purses,
1410 : key,
1411 : ps));
1412 0 : GNUNET_free (ps);
1413 0 : return GNUNET_OK;
1414 : }
1415 :
1416 :
1417 : /**
1418 : * Analyze purses for being well-formed.
1419 : *
1420 : * @param cls NULL
1421 : * @return transaction status code
1422 : */
1423 : static enum GNUNET_DB_QueryStatus
1424 13 : analyze_purses (void *cls)
1425 : {
1426 : struct PurseContext pc;
1427 : enum GNUNET_DB_QueryStatus qs;
1428 :
1429 : (void) cls;
1430 13 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1431 : "Analyzing purses\n");
1432 13 : qs = TALER_AUDITORDB_get_auditor_progress (
1433 : TALER_ARL_adb,
1434 : TALER_ARL_GET_PP (purse_account_merge_serial_id),
1435 : TALER_ARL_GET_PP (purse_decision_serial_id),
1436 : TALER_ARL_GET_PP (purse_deletion_serial_id),
1437 : TALER_ARL_GET_PP (purse_deposits_serial_id),
1438 : TALER_ARL_GET_PP (purse_merges_serial_id),
1439 : TALER_ARL_GET_PP (purse_request_serial_id),
1440 : TALER_ARL_GET_PP (purse_open_counter),
1441 : NULL);
1442 13 : if (0 > qs)
1443 : {
1444 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1445 0 : return qs;
1446 : }
1447 13 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1448 : "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
1449 : (unsigned long long) TALER_ARL_USE_PP (
1450 : purse_open_counter),
1451 : (unsigned long long) TALER_ARL_USE_PP (
1452 : purse_request_serial_id),
1453 : (unsigned long long) TALER_ARL_USE_PP (
1454 : purse_decision_serial_id),
1455 : (unsigned long long) TALER_ARL_USE_PP (
1456 : purse_deletion_serial_id),
1457 : (unsigned long long) TALER_ARL_USE_PP (
1458 : purse_merges_serial_id),
1459 : (unsigned long long) TALER_ARL_USE_PP (
1460 : purse_deposits_serial_id),
1461 : (unsigned long long) TALER_ARL_USE_PP (
1462 : purse_account_merge_serial_id));
1463 13 : pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1464 13 : qs = TALER_AUDITORDB_get_balance (
1465 : TALER_ARL_adb,
1466 : TALER_ARL_GET_AB (purse_global_balance),
1467 : TALER_ARL_GET_AB (purse_total_balance_insufficient_loss),
1468 : TALER_ARL_GET_AB (purse_total_delayed_decisions),
1469 : TALER_ARL_GET_AB (purse_total_balance_purse_not_closed),
1470 : TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus),
1471 : TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus),
1472 : TALER_ARL_GET_AB (purse_total_bad_sig_loss),
1473 : NULL);
1474 13 : if (qs < 0)
1475 : {
1476 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1477 0 : return qs;
1478 : }
1479 13 : pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
1480 : GNUNET_NO);
1481 :
1482 13 : qs = TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
1483 : TALER_ARL_edb,
1484 : TALER_ARL_USE_PP (purse_request_serial_id),
1485 : &handle_purse_requested,
1486 : &pc);
1487 13 : if (qs < 0)
1488 : {
1489 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1490 0 : return qs;
1491 : }
1492 13 : if (pc.qs < 0)
1493 : {
1494 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1495 0 : return pc.qs;
1496 : }
1497 13 : qs = TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
1498 : TALER_ARL_edb,
1499 : TALER_ARL_USE_PP (purse_merges_serial_id),
1500 : &handle_purse_merged,
1501 : &pc);
1502 13 : if (qs < 0)
1503 : {
1504 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1505 0 : return qs;
1506 : }
1507 13 : if (pc.qs < 0)
1508 : {
1509 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1510 0 : return pc.qs;
1511 : }
1512 :
1513 13 : qs = TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id (
1514 : TALER_ARL_edb,
1515 : TALER_ARL_USE_PP (purse_deposits_serial_id),
1516 : &handle_purse_deposits,
1517 : &pc);
1518 13 : if (qs < 0)
1519 : {
1520 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1521 0 : return qs;
1522 : }
1523 13 : if (pc.qs < 0)
1524 : {
1525 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1526 0 : return pc.qs;
1527 : }
1528 :
1529 : /* Charge purse fee! */
1530 13 : qs = TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
1531 : TALER_ARL_edb,
1532 : TALER_ARL_USE_PP (purse_account_merge_serial_id),
1533 : &handle_account_merged,
1534 : &pc);
1535 13 : if (qs < 0)
1536 : {
1537 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1538 0 : return qs;
1539 : }
1540 13 : if (pc.qs < 0)
1541 : {
1542 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1543 0 : return pc.qs;
1544 : }
1545 :
1546 13 : qs = TALER_EXCHANGEDB_iterate_all_purse_decisions_above_serial_id (
1547 : TALER_ARL_edb,
1548 : TALER_ARL_USE_PP (purse_decision_serial_id),
1549 : &handle_purse_decision,
1550 : &pc);
1551 13 : if (qs < 0)
1552 : {
1553 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1554 0 : return qs;
1555 : }
1556 13 : if (pc.qs < 0)
1557 : {
1558 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1559 0 : return pc.qs;
1560 : }
1561 :
1562 13 : qs = TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
1563 : TALER_ARL_edb,
1564 : TALER_ARL_USE_PP (purse_deletion_serial_id),
1565 : &handle_purse_deletion,
1566 : &pc);
1567 13 : if (qs < 0)
1568 : {
1569 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1570 0 : return qs;
1571 : }
1572 13 : if (pc.qs < 0)
1573 : {
1574 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1575 0 : return pc.qs;
1576 : }
1577 :
1578 13 : qs = TALER_AUDITORDB_iterate_purse_expired (
1579 : TALER_ARL_adb,
1580 : &handle_purse_expired,
1581 : &pc);
1582 13 : if (qs < 0)
1583 : {
1584 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1585 0 : return qs;
1586 : }
1587 13 : if (pc.qs < 0)
1588 : {
1589 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1590 0 : return pc.qs;
1591 : }
1592 :
1593 13 : GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
1594 : &verify_purse_balance,
1595 : &pc);
1596 13 : GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
1597 : &release_purse_balance,
1598 : &pc);
1599 13 : GNUNET_break (0 ==
1600 : GNUNET_CONTAINER_multihashmap_size (pc.purses));
1601 13 : GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
1602 13 : if (pc.qs < 0)
1603 : {
1604 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1605 0 : return pc.qs;
1606 : }
1607 13 : qs = TALER_AUDITORDB_insert_balance (
1608 : TALER_ARL_adb,
1609 : TALER_ARL_SET_AB (purse_global_balance),
1610 : TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
1611 : TALER_ARL_SET_AB (purse_total_delayed_decisions),
1612 : TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
1613 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
1614 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
1615 : TALER_ARL_SET_AB (purse_total_bad_sig_loss),
1616 : NULL);
1617 13 : if (0 > qs)
1618 : {
1619 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1620 : "Failed to update auditor DB, not recording progress\n");
1621 3 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1622 3 : return qs;
1623 : }
1624 10 : qs = TALER_AUDITORDB_insert_auditor_progress (
1625 : TALER_ARL_adb,
1626 : TALER_ARL_SET_PP (purse_account_merge_serial_id),
1627 : TALER_ARL_SET_PP (purse_decision_serial_id),
1628 : TALER_ARL_SET_PP (purse_deletion_serial_id),
1629 : TALER_ARL_SET_PP (purse_deposits_serial_id),
1630 : TALER_ARL_SET_PP (purse_merges_serial_id),
1631 : TALER_ARL_SET_PP (purse_request_serial_id),
1632 : TALER_ARL_SET_PP (purse_open_counter),
1633 : NULL);
1634 10 : if (0 > qs)
1635 : {
1636 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1637 : "Failed to update auditor DB, not recording progress\n");
1638 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1639 0 : return qs;
1640 : }
1641 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1642 : "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n",
1643 : (unsigned long long) TALER_ARL_USE_PP (
1644 : purse_request_serial_id),
1645 : (unsigned long long) TALER_ARL_USE_PP (
1646 : purse_decision_serial_id),
1647 : (unsigned long long) TALER_ARL_USE_PP (
1648 : purse_deletion_serial_id),
1649 : (unsigned long long) TALER_ARL_USE_PP (
1650 : purse_merges_serial_id),
1651 : (unsigned long long) TALER_ARL_USE_PP (
1652 : purse_deposits_serial_id),
1653 : (unsigned long long) TALER_ARL_USE_PP (
1654 : purse_account_merge_serial_id));
1655 10 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1656 : }
1657 :
1658 :
1659 : /**
1660 : * Function called on events received from Postgres.
1661 : *
1662 : * @param cls closure, NULL
1663 : * @param extra additional event data provided
1664 : * @param extra_size number of bytes in @a extra
1665 : */
1666 : static void
1667 0 : db_notify (void *cls,
1668 : const void *extra,
1669 : size_t extra_size)
1670 : {
1671 : (void) cls;
1672 : (void) extra;
1673 : (void) extra_size;
1674 :
1675 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1676 : "Received notification to wake purses\n");
1677 0 : if (GNUNET_OK !=
1678 0 : TALER_ARL_setup_sessions_and_run (&analyze_purses,
1679 : NULL))
1680 : {
1681 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1682 : "Audit failed\n");
1683 0 : GNUNET_SCHEDULER_shutdown ();
1684 0 : global_ret = EXIT_FAILURE;
1685 0 : return;
1686 : }
1687 : }
1688 :
1689 :
1690 : /**
1691 : * Function called on shutdown.
1692 : */
1693 : static void
1694 10 : do_shutdown (void *cls)
1695 : {
1696 : (void) cls;
1697 :
1698 10 : if (NULL != eh)
1699 : {
1700 4 : TALER_AUDITORDB_event_listen_cancel (eh);
1701 4 : eh = NULL;
1702 : }
1703 10 : TALER_ARL_done ();
1704 10 : }
1705 :
1706 :
1707 : /**
1708 : * Main function that will be run.
1709 : *
1710 : * @param cls closure
1711 : * @param args remaining command-line arguments
1712 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1713 : * @param c configuration
1714 : */
1715 : static void
1716 10 : run (void *cls,
1717 : char *const *args,
1718 : const char *cfgfile,
1719 : const struct GNUNET_CONFIGURATION_Handle *c)
1720 : {
1721 : (void) cls;
1722 : (void) args;
1723 : (void) cfgfile;
1724 :
1725 10 : cfg = c;
1726 10 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1727 : NULL);
1728 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1729 : "Launching purses auditor\n");
1730 10 : if (EXIT_SUCCESS !=
1731 10 : (global_ret = TALER_ARL_init (c)))
1732 : {
1733 0 : return;
1734 : }
1735 10 : if (test_mode != 1)
1736 : {
1737 4 : struct GNUNET_DB_EventHeaderP es = {
1738 4 : .size = htons (sizeof (es)),
1739 4 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES)
1740 : };
1741 :
1742 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1743 : "Running helper indefinitely\n");
1744 4 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
1745 : &es,
1746 4 : GNUNET_TIME_UNIT_FOREVER_REL,
1747 : &db_notify,
1748 : NULL);
1749 : }
1750 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1751 : "Starting audit\n");
1752 10 : if (GNUNET_OK !=
1753 10 : TALER_ARL_setup_sessions_and_run (&analyze_purses,
1754 : NULL))
1755 : {
1756 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1757 : "Audit failed\n");
1758 0 : GNUNET_SCHEDULER_shutdown ();
1759 0 : global_ret = EXIT_FAILURE;
1760 0 : return;
1761 : }
1762 : }
1763 :
1764 :
1765 : /**
1766 : * The main function to check the database's handling of purses.
1767 : *
1768 : * @param argc number of arguments from the command line
1769 : * @param argv command line arguments
1770 : * @return 0 ok, 1 on error
1771 : */
1772 : int
1773 10 : main (int argc,
1774 : char *const *argv)
1775 : {
1776 10 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
1777 10 : GNUNET_GETOPT_option_flag ('i',
1778 : "internal",
1779 : "perform checks only applicable for exchange-internal audits",
1780 : &internal_checks),
1781 10 : GNUNET_GETOPT_option_flag ('t',
1782 : "test",
1783 : "run in test mode and exit when idle",
1784 : &test_mode),
1785 10 : GNUNET_GETOPT_option_timetravel ('T',
1786 : "timetravel"),
1787 : GNUNET_GETOPT_OPTION_END
1788 : };
1789 : enum GNUNET_GenericReturnValue ret;
1790 :
1791 10 : ret = GNUNET_PROGRAM_run (
1792 : TALER_AUDITOR_project_data (),
1793 : argc,
1794 : argv,
1795 : "taler-helper-auditor-purses",
1796 : gettext_noop ("Audit Taler exchange purse handling"),
1797 : options,
1798 : &run,
1799 : NULL);
1800 10 : if (GNUNET_SYSERR == ret)
1801 0 : return EXIT_NOTCONFIGURED;
1802 10 : if (GNUNET_NO == ret)
1803 0 : return EXIT_SUCCESS;
1804 10 : return global_ret;
1805 : }
1806 :
1807 :
1808 : /* end of taler-helper-auditor-purses.c */
|