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