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 0 : .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 : .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 : TALER_ARL_amount_subtract (&amount_minus_fee,
691 : &deposit->amount,
692 : &issue->fees.deposit);
693 : }
694 :
695 0 : if (GNUNET_OK !=
696 0 : TALER_wallet_purse_deposit_verify (base_url,
697 : &deposit->purse_pub,
698 : &deposit->amount,
699 : &h_denom_pub,
700 : &deposit->h_age_commitment,
701 : &deposit->coin_pub,
702 : &deposit->coin_sig))
703 : {
704 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
705 : .problem_row_id = rowid,
706 : .operation = (char *) "purse-deposit",
707 : .loss = deposit->amount,
708 : .operation_specific_pub = deposit->coin_pub.eddsa_pub
709 : };
710 : enum GNUNET_DB_QueryStatus qs;
711 :
712 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
713 : "Failed to verify purse deposit signature on `%s' in record %llu\n",
714 : TALER_B2S (&deposit->purse_pub),
715 : (unsigned long long) rowid);
716 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
717 : TALER_ARL_adb,
718 : &bsl);
719 0 : if (qs < 0)
720 : {
721 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
722 0 : pc->qs = qs;
723 0 : return GNUNET_SYSERR;
724 : }
725 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
726 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
727 : &deposit->amount);
728 0 : return GNUNET_OK;
729 : }
730 :
731 : {
732 : struct PurseSummary *ps;
733 :
734 0 : ps = setup_purse (pc,
735 : &deposit->purse_pub);
736 0 : if (NULL == ps)
737 : {
738 : enum GNUNET_DB_QueryStatus qs;
739 :
740 0 : if (0 > pc->qs)
741 : {
742 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
743 0 : return GNUNET_SYSERR;
744 : }
745 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
746 0 : qs = report_row_inconsistency ("purse_deposit",
747 : rowid,
748 : "purse not found");
749 0 : if (0 > qs)
750 : {
751 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
752 0 : pc->qs = qs;
753 0 : return GNUNET_SYSERR;
754 : }
755 0 : return GNUNET_OK;
756 : }
757 0 : TALER_ARL_amount_add (&ps->balance,
758 : &ps->balance,
759 : &amount_minus_fee);
760 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
761 : &TALER_ARL_USE_AB (purse_global_balance),
762 : &amount_minus_fee);
763 : }
764 0 : return GNUNET_OK;
765 : }
766 :
767 :
768 : /**
769 : * Function called with details about purse merges that have been made, with
770 : * the goal of auditing the purse merge execution.
771 : *
772 : * @param pc closure
773 : * @param rowid unique serial ID for the deposit in our DB
774 : * @param partner_base_url where is the reserve, NULL for this exchange
775 : * @param amount total amount expected in the purse
776 : * @param balance current balance in the purse
777 : * @param flags purse flags
778 : * @param merge_pub merge capability key
779 : * @param reserve_pub reserve the merge affects
780 : * @param merge_sig signature affirming the merge
781 : * @param purse_pub purse key
782 : * @param merge_timestamp when did the merge happen
783 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
784 : */
785 : static enum GNUNET_GenericReturnValue
786 0 : handle_purse_merged (
787 : struct PurseContext *pc,
788 : uint64_t rowid,
789 : const char *partner_base_url,
790 : const struct TALER_Amount *amount,
791 : const struct TALER_Amount *balance,
792 : enum TALER_WalletAccountMergeFlags flags,
793 : const struct TALER_PurseMergePublicKeyP *merge_pub,
794 : const struct TALER_ReservePublicKeyP *reserve_pub,
795 : const struct TALER_PurseMergeSignatureP *merge_sig,
796 : const struct TALER_PurseContractPublicKeyP *purse_pub,
797 : struct GNUNET_TIME_Timestamp merge_timestamp)
798 : {
799 : struct PurseSummary *ps;
800 : enum GNUNET_DB_QueryStatus qs;
801 :
802 : /* should be monotonically increasing */
803 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
804 : "Handling purse merged `%s'\n",
805 : TALER_B2S (purse_pub));
806 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
807 0 : TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
808 :
809 : {
810 : struct TALER_NormalizedPayto reserve_url;
811 :
812 : reserve_url
813 0 : = TALER_reserve_make_payto (NULL == partner_base_url
814 : ? TALER_ARL_exchange_url
815 : : partner_base_url,
816 : reserve_pub);
817 0 : if (GNUNET_OK !=
818 0 : TALER_wallet_purse_merge_verify (reserve_url,
819 : merge_timestamp,
820 : purse_pub,
821 : merge_pub,
822 : merge_sig))
823 : {
824 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
825 : .problem_row_id = rowid,
826 : .operation = (char *) "merge-purse",
827 : .loss = *amount,
828 : .operation_specific_pub = merge_pub->eddsa_pub
829 : };
830 :
831 0 : GNUNET_free (reserve_url.normalized_payto);
832 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
833 : TALER_ARL_adb,
834 : &bsl);
835 0 : if (qs < 0)
836 : {
837 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
838 0 : pc->qs = qs;
839 0 : return GNUNET_SYSERR;
840 : }
841 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
842 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
843 : amount);
844 0 : return GNUNET_OK;
845 : }
846 0 : GNUNET_free (reserve_url.normalized_payto);
847 : }
848 :
849 0 : ps = setup_purse (pc,
850 : purse_pub);
851 0 : if (NULL == ps)
852 : {
853 0 : if (pc->qs < 0)
854 : {
855 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
856 0 : return GNUNET_SYSERR;
857 : }
858 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
859 0 : qs = report_row_inconsistency ("purse-merge",
860 : rowid,
861 : "purse not found");
862 0 : if (qs < 0)
863 : {
864 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
865 0 : pc->qs = qs;
866 0 : return GNUNET_SYSERR;
867 : }
868 0 : return GNUNET_OK;
869 : }
870 0 : GNUNET_break (0 ==
871 : GNUNET_TIME_timestamp_cmp (merge_timestamp,
872 : ==,
873 : ps->merge_timestamp));
874 0 : TALER_ARL_amount_add (&ps->balance,
875 : &ps->balance,
876 : amount);
877 0 : return GNUNET_OK;
878 : }
879 :
880 :
881 : /**
882 : * Function called with details about account merge requests that have been
883 : * made, with the goal of auditing the account merge execution.
884 : *
885 : * @param pc closure
886 : * @param rowid unique serial ID for the deposit in our DB
887 : * @param reserve_pub reserve affected by the merge
888 : * @param purse_pub purse being merged
889 : * @param h_contract_terms hash over contract of the purse
890 : * @param purse_expiration when would the purse expire
891 : * @param amount total amount in the purse
892 : * @param min_age minimum age of all coins deposited into the purse
893 : * @param flags how was the purse created
894 : * @param purse_fee if a purse fee was paid, how high is it
895 : * @param merge_timestamp when was the merge approved
896 : * @param reserve_sig signature by reserve approving the merge
897 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
898 : */
899 : static enum GNUNET_GenericReturnValue
900 0 : handle_account_merged (
901 : struct PurseContext *pc,
902 : uint64_t rowid,
903 : const struct TALER_ReservePublicKeyP *reserve_pub,
904 : const struct TALER_PurseContractPublicKeyP *purse_pub,
905 : const struct TALER_PrivateContractHashP *h_contract_terms,
906 : struct GNUNET_TIME_Timestamp purse_expiration,
907 : const struct TALER_Amount *amount,
908 : uint32_t min_age,
909 : enum TALER_WalletAccountMergeFlags flags,
910 : const struct TALER_Amount *purse_fee,
911 : struct GNUNET_TIME_Timestamp merge_timestamp,
912 : const struct TALER_ReserveSignatureP *reserve_sig)
913 : {
914 : struct PurseSummary *ps;
915 : enum GNUNET_DB_QueryStatus qs;
916 :
917 : /* should be monotonically increasing */
918 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
919 : "Handling account merge on purse `%s'\n",
920 : TALER_B2S (purse_pub));
921 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
922 0 : TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
923 0 : if (GNUNET_OK !=
924 0 : TALER_wallet_account_merge_verify (merge_timestamp,
925 : purse_pub,
926 : purse_expiration,
927 : h_contract_terms,
928 : amount,
929 : purse_fee,
930 : min_age,
931 : flags,
932 : reserve_pub,
933 : reserve_sig))
934 : {
935 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
936 : .problem_row_id = rowid,
937 : .operation = (char *) "account-merge",
938 : .loss = *purse_fee,
939 : .operation_specific_pub = reserve_pub->eddsa_pub
940 : };
941 :
942 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
943 : TALER_ARL_adb,
944 : &bsl);
945 0 : if (qs < 0)
946 : {
947 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
948 0 : pc->qs = qs;
949 0 : return GNUNET_SYSERR;
950 : }
951 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
952 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
953 : purse_fee);
954 0 : return GNUNET_OK;
955 : }
956 0 : ps = setup_purse (pc,
957 : purse_pub);
958 0 : if (NULL == ps)
959 : {
960 0 : if (0 > pc->qs)
961 : {
962 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
963 0 : return GNUNET_SYSERR;
964 : }
965 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
966 0 : qs = report_row_inconsistency ("account-merge",
967 : rowid,
968 : "purse not found");
969 0 : if (0 > qs)
970 : {
971 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
972 0 : pc->qs = qs;
973 0 : return GNUNET_SYSERR;
974 : }
975 0 : return GNUNET_OK;
976 : }
977 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
978 : &TALER_ARL_USE_AB (purse_global_balance),
979 : purse_fee);
980 0 : TALER_ARL_amount_add (&ps->balance,
981 : &ps->balance,
982 : purse_fee);
983 0 : return GNUNET_OK;
984 : }
985 :
986 :
987 : /**
988 : * Function called with details about purse decisions that have been made.
989 : *
990 : * @param pc closure
991 : * @param rowid unique serial ID for the deposit in our DB
992 : * @param purse_pub which purse was the decision made on
993 : * @param refunded true if decision was to refund
994 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
995 : */
996 : static enum GNUNET_GenericReturnValue
997 0 : handle_purse_decision (
998 : struct PurseContext *pc,
999 : uint64_t rowid,
1000 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1001 : bool refunded)
1002 : {
1003 : struct PurseSummary *ps;
1004 : struct GNUNET_HashCode key;
1005 : enum GNUNET_DB_QueryStatus qs;
1006 : struct TALER_Amount purse_fee;
1007 : struct TALER_Amount balance_without_purse_fee;
1008 :
1009 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_decision_serial_id));
1010 0 : TALER_ARL_USE_PP (purse_decision_serial_id) = rowid + 1;
1011 0 : ps = setup_purse (pc,
1012 : purse_pub);
1013 0 : if (NULL == ps)
1014 : {
1015 0 : if (0 > pc->qs)
1016 : {
1017 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
1018 0 : return GNUNET_SYSERR;
1019 : }
1020 0 : qs = report_row_inconsistency ("purse-decision",
1021 : rowid,
1022 : "purse not found");
1023 0 : if (0 > qs)
1024 : {
1025 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1026 0 : pc->qs = qs;
1027 0 : return GNUNET_SYSERR;
1028 : }
1029 0 : return GNUNET_OK;
1030 : }
1031 0 : qs = get_purse_fee (ps->creation_date,
1032 : &purse_fee);
1033 0 : if (0 > qs)
1034 : {
1035 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1036 0 : pc->qs = qs;
1037 0 : return GNUNET_SYSERR;
1038 : }
1039 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1040 0 : return GNUNET_OK; /* already reported */
1041 0 : if (0 >
1042 0 : TALER_amount_subtract (&balance_without_purse_fee,
1043 0 : &ps->balance,
1044 : &purse_fee))
1045 : {
1046 0 : qs = report_row_inconsistency ("purse-request",
1047 : rowid,
1048 : "purse fee higher than balance");
1049 0 : if (0 > qs)
1050 : {
1051 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1052 0 : pc->qs = qs;
1053 0 : return GNUNET_SYSERR;
1054 : }
1055 0 : GNUNET_assert (GNUNET_OK ==
1056 : TALER_amount_set_zero (TALER_ARL_currency,
1057 : &balance_without_purse_fee));
1058 : }
1059 :
1060 0 : if (refunded)
1061 : {
1062 0 : if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
1063 0 : &ps->total_value))
1064 : {
1065 0 : qs = report_amount_arithmetic_inconsistency ("purse-decision: refund",
1066 : rowid,
1067 : &balance_without_purse_fee,
1068 0 : &ps->total_value,
1069 : 0);
1070 0 : if (0 > qs)
1071 : {
1072 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1073 0 : pc->qs = qs;
1074 0 : return GNUNET_SYSERR;
1075 : }
1076 : }
1077 0 : if ( (internal_checks) &&
1078 0 : (! ps->purse_refunded) )
1079 : {
1080 0 : qs = report_row_inconsistency (
1081 : "purse-decision",
1082 : rowid,
1083 : "purse not marked as refunded (internal check)");
1084 0 : if (qs < 0)
1085 : {
1086 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1087 0 : pc->qs = qs;
1088 0 : return GNUNET_SYSERR;
1089 : }
1090 : }
1091 : }
1092 : else
1093 : {
1094 0 : if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
1095 0 : &ps->total_value))
1096 : {
1097 0 : qs = report_amount_arithmetic_inconsistency ("purse-decision: merge",
1098 : rowid,
1099 0 : &ps->total_value,
1100 : &balance_without_purse_fee,
1101 : 0);
1102 0 : if (0 > qs)
1103 : {
1104 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1105 0 : pc->qs = qs;
1106 0 : return GNUNET_SYSERR;
1107 : }
1108 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1109 : purse_total_balance_insufficient_loss),
1110 : &TALER_ARL_USE_AB (
1111 : purse_total_balance_insufficient_loss),
1112 : &ps->total_value);
1113 : }
1114 : }
1115 :
1116 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1117 : "Deleting purse with decision `%s'\n",
1118 : TALER_B2S (&ps->purse_pub));
1119 : /* The purse ceases to exist, so the money it held is no longer
1120 : part of the balance the exchange holds in open purses. */
1121 0 : if (0 >
1122 0 : TALER_amount_subtract (&TALER_ARL_USE_AB (purse_global_balance),
1123 : &TALER_ARL_USE_AB (purse_global_balance),
1124 0 : &ps->balance))
1125 : {
1126 : /* Should be impossible: we added this balance to the global
1127 : balance when we learned about it. */
1128 0 : GNUNET_break (0);
1129 0 : GNUNET_assert (GNUNET_OK ==
1130 : TALER_amount_set_zero (TALER_ARL_currency,
1131 : &TALER_ARL_USE_AB (
1132 : purse_global_balance)));
1133 : }
1134 0 : qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb,
1135 : purse_pub);
1136 0 : if (qs < 0)
1137 : {
1138 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1139 0 : pc->qs = qs;
1140 0 : return GNUNET_SYSERR;
1141 : }
1142 0 : GNUNET_CRYPTO_hash (purse_pub,
1143 : sizeof (*purse_pub),
1144 : &key);
1145 0 : GNUNET_assert (GNUNET_YES ==
1146 : GNUNET_CONTAINER_multihashmap_remove (pc->purses,
1147 : &key,
1148 : ps));
1149 0 : GNUNET_free (ps);
1150 0 : return GNUNET_OK;
1151 : }
1152 :
1153 :
1154 : /**
1155 : * Function called on explicitly deleted purses.
1156 : *
1157 : * @param pc closure
1158 : * @param deletion_serial_id row ID with the deletion data
1159 : * @param purse_pub public key of the purse
1160 : * @param purse_sig signature affirming deletion of the purse
1161 : * @return #GNUNET_OK to continue to iterate
1162 : */
1163 : static enum GNUNET_GenericReturnValue
1164 0 : handle_purse_deletion (
1165 : struct PurseContext *pc,
1166 : uint64_t deletion_serial_id,
1167 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1168 : const struct TALER_PurseContractSignatureP *purse_sig)
1169 : {
1170 : struct PurseSummary *ps;
1171 :
1172 0 : GNUNET_assert (deletion_serial_id >=
1173 : TALER_ARL_USE_PP (purse_deletion_serial_id));
1174 0 : TALER_ARL_USE_PP (purse_deletion_serial_id) = deletion_serial_id + 1;
1175 0 : ps = setup_purse (pc,
1176 : purse_pub);
1177 0 : if (NULL == ps)
1178 : {
1179 0 : GNUNET_break (0);
1180 0 : return GNUNET_SYSERR;
1181 : }
1182 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1183 : "Handling purse `%s' deletion\n",
1184 : TALER_B2S (purse_pub));
1185 0 : if (GNUNET_OK !=
1186 0 : TALER_wallet_purse_delete_verify (purse_pub,
1187 : purse_sig))
1188 : {
1189 : enum GNUNET_DB_QueryStatus qs;
1190 :
1191 0 : qs = report_row_inconsistency (
1192 : "purse-delete",
1193 : deletion_serial_id,
1194 : "purse deletion signature invalid");
1195 0 : if (qs < 0)
1196 : {
1197 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1198 0 : pc->qs = qs;
1199 0 : return GNUNET_SYSERR;
1200 : }
1201 : }
1202 : else
1203 : {
1204 0 : if ( (internal_checks) &&
1205 0 : (! ps->purse_deleted) )
1206 : {
1207 : enum GNUNET_DB_QueryStatus qs;
1208 :
1209 0 : qs = report_row_inconsistency (
1210 : "purse-delete",
1211 : deletion_serial_id,
1212 : "purse not marked as deleted (internal check)");
1213 0 : if (qs < 0)
1214 : {
1215 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1216 0 : pc->qs = qs;
1217 0 : return GNUNET_SYSERR;
1218 : }
1219 : }
1220 : }
1221 0 : return GNUNET_OK;
1222 : }
1223 :
1224 :
1225 : /**
1226 : * Function called on expired purses.
1227 : *
1228 : * @param pc closure
1229 : * @param purse_pub public key of the purse
1230 : * @param balance amount of money in the purse
1231 : * @param expiration_date when did the purse expire?
1232 : * @return #GNUNET_OK to continue to iterate
1233 : */
1234 : static enum GNUNET_GenericReturnValue
1235 0 : handle_purse_expired (
1236 : struct PurseContext *pc,
1237 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1238 : const struct TALER_Amount *balance,
1239 : struct GNUNET_TIME_Timestamp expiration_date)
1240 : {
1241 : enum GNUNET_DB_QueryStatus qs;
1242 0 : struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = {
1243 : .amount = *balance,
1244 : .expiration_date = expiration_date.abs_time,
1245 : .purse_pub = purse_pub->eddsa_pub
1246 : };
1247 :
1248 0 : qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (
1249 : TALER_ARL_adb,
1250 : &pnci);
1251 0 : if (qs < 0)
1252 : {
1253 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1254 0 : pc->qs = qs;
1255 0 : return GNUNET_SYSERR;
1256 : }
1257 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1258 : "Handling purse expiration `%s'\n",
1259 : TALER_B2S (purse_pub));
1260 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions),
1261 : &TALER_ARL_USE_AB (purse_total_delayed_decisions),
1262 : balance);
1263 0 : return GNUNET_OK;
1264 : }
1265 :
1266 :
1267 : /**
1268 : * Check that the purse summary matches what the exchange database
1269 : * thinks about the purse, and update our own state of the purse.
1270 : *
1271 : * Remove all purses that we are happy with from the DB.
1272 : *
1273 : * @param cls our `struct PurseContext`
1274 : * @param key hash of the purse public key
1275 : * @param value a `struct PurseSummary`
1276 : * @return #GNUNET_OK to process more entries
1277 : */
1278 : static enum GNUNET_GenericReturnValue
1279 0 : verify_purse_balance (void *cls,
1280 : const struct GNUNET_HashCode *key,
1281 : void *value)
1282 : {
1283 0 : struct PurseContext *pc = cls;
1284 0 : struct PurseSummary *ps = value;
1285 : enum GNUNET_DB_QueryStatus qs;
1286 :
1287 0 : if (internal_checks)
1288 : {
1289 : struct TALER_Amount pf;
1290 : struct TALER_Amount balance_without_purse_fee;
1291 :
1292 : /* subtract purse fee from ps->balance to get actual balance we expect, as
1293 : we track the balance including purse fee, while the exchange subtracts
1294 : the purse fee early on. */
1295 0 : qs = get_purse_fee (ps->creation_date,
1296 : &pf);
1297 0 : if (qs < 0)
1298 : {
1299 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1300 0 : pc->qs = qs;
1301 0 : return GNUNET_SYSERR;
1302 : }
1303 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1304 0 : return GNUNET_OK; /* error already reported */
1305 0 : if (0 >
1306 0 : TALER_amount_subtract (&balance_without_purse_fee,
1307 0 : &ps->balance,
1308 : &pf))
1309 : {
1310 0 : qs = report_row_inconsistency ("purse",
1311 : 0,
1312 : "purse fee higher than balance");
1313 0 : if (qs < 0)
1314 : {
1315 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1316 0 : pc->qs = qs;
1317 0 : return GNUNET_SYSERR;
1318 : }
1319 0 : GNUNET_assert (GNUNET_OK ==
1320 : TALER_amount_set_zero (TALER_ARL_currency,
1321 : &balance_without_purse_fee));
1322 : }
1323 :
1324 0 : if (0 != TALER_amount_cmp (&ps->exchange_balance,
1325 : &balance_without_purse_fee))
1326 : {
1327 0 : qs = report_amount_arithmetic_inconsistency ("purse-balance",
1328 : 0,
1329 0 : &ps->exchange_balance,
1330 : &balance_without_purse_fee,
1331 : 0);
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 : }
1339 : }
1340 :
1341 0 : if (ps->had_pi)
1342 : {
1343 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1344 : "Updating purse `%s'\n",
1345 : TALER_B2S (&ps->purse_pub));
1346 0 : qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb,
1347 0 : &ps->purse_pub,
1348 0 : &ps->balance);
1349 : }
1350 : else
1351 : {
1352 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1353 : "Inserting purse `%s'\n",
1354 : TALER_B2S (&ps->purse_pub));
1355 0 : qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb,
1356 0 : &ps->purse_pub,
1357 0 : &ps->balance,
1358 0 : &ps->total_value,
1359 : ps->expiration_date);
1360 0 : ps->had_pi = true;
1361 : }
1362 0 : if (qs < 0)
1363 : {
1364 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1365 0 : pc->qs = qs;
1366 0 : return GNUNET_SYSERR;
1367 : }
1368 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1369 0 : return GNUNET_OK;
1370 : }
1371 :
1372 :
1373 : /**
1374 : * Clear memory from the purses hash map.
1375 : *
1376 : * @param cls our `struct PurseContext`
1377 : * @param key hash of the purse public key
1378 : * @param value a `struct PurseSummary`
1379 : * @return #GNUNET_OK to process more entries
1380 : */
1381 : static enum GNUNET_GenericReturnValue
1382 0 : release_purse_balance (void *cls,
1383 : const struct GNUNET_HashCode *key,
1384 : void *value)
1385 : {
1386 0 : struct PurseContext *pc = cls;
1387 0 : struct PurseSummary *ps = value;
1388 :
1389 0 : GNUNET_assert (GNUNET_YES ==
1390 : GNUNET_CONTAINER_multihashmap_remove (pc->purses,
1391 : key,
1392 : ps));
1393 0 : GNUNET_free (ps);
1394 0 : return GNUNET_OK;
1395 : }
1396 :
1397 :
1398 : /**
1399 : * Analyze purses for being well-formed.
1400 : *
1401 : * @param cls NULL
1402 : * @return transaction status code
1403 : */
1404 : static enum GNUNET_DB_QueryStatus
1405 7 : analyze_purses (void *cls)
1406 : {
1407 : struct PurseContext pc;
1408 : enum GNUNET_DB_QueryStatus qs;
1409 :
1410 : (void) cls;
1411 7 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1412 : "Analyzing purses\n");
1413 7 : qs = TALER_AUDITORDB_get_auditor_progress (
1414 : TALER_ARL_adb,
1415 : TALER_ARL_GET_PP (purse_account_merge_serial_id),
1416 : TALER_ARL_GET_PP (purse_decision_serial_id),
1417 : TALER_ARL_GET_PP (purse_deletion_serial_id),
1418 : TALER_ARL_GET_PP (purse_deposits_serial_id),
1419 : TALER_ARL_GET_PP (purse_merges_serial_id),
1420 : TALER_ARL_GET_PP (purse_request_serial_id),
1421 : TALER_ARL_GET_PP (purse_open_counter),
1422 : NULL);
1423 7 : if (0 > qs)
1424 : {
1425 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1426 0 : return qs;
1427 : }
1428 7 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1429 : "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
1430 : (unsigned long long) TALER_ARL_USE_PP (
1431 : purse_open_counter),
1432 : (unsigned long long) TALER_ARL_USE_PP (
1433 : purse_request_serial_id),
1434 : (unsigned long long) TALER_ARL_USE_PP (
1435 : purse_decision_serial_id),
1436 : (unsigned long long) TALER_ARL_USE_PP (
1437 : purse_deletion_serial_id),
1438 : (unsigned long long) TALER_ARL_USE_PP (
1439 : purse_merges_serial_id),
1440 : (unsigned long long) TALER_ARL_USE_PP (
1441 : purse_deposits_serial_id),
1442 : (unsigned long long) TALER_ARL_USE_PP (
1443 : purse_account_merge_serial_id));
1444 7 : pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1445 7 : qs = TALER_AUDITORDB_get_balance (
1446 : TALER_ARL_adb,
1447 : TALER_ARL_GET_AB (purse_global_balance),
1448 : TALER_ARL_GET_AB (purse_total_balance_insufficient_loss),
1449 : TALER_ARL_GET_AB (purse_total_delayed_decisions),
1450 : TALER_ARL_GET_AB (purse_total_balance_purse_not_closed),
1451 : TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus),
1452 : TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus),
1453 : TALER_ARL_GET_AB (purse_total_bad_sig_loss),
1454 : NULL);
1455 7 : if (qs < 0)
1456 : {
1457 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1458 0 : return qs;
1459 : }
1460 7 : pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
1461 : GNUNET_NO);
1462 :
1463 7 : qs = TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
1464 : TALER_ARL_edb,
1465 : TALER_ARL_USE_PP (purse_request_serial_id),
1466 : &handle_purse_requested,
1467 : &pc);
1468 7 : if (qs < 0)
1469 : {
1470 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1471 0 : return qs;
1472 : }
1473 7 : if (pc.qs < 0)
1474 : {
1475 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1476 0 : return pc.qs;
1477 : }
1478 7 : qs = TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
1479 : TALER_ARL_edb,
1480 : TALER_ARL_USE_PP (purse_merges_serial_id),
1481 : &handle_purse_merged,
1482 : &pc);
1483 7 : if (qs < 0)
1484 : {
1485 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1486 0 : return qs;
1487 : }
1488 7 : if (pc.qs < 0)
1489 : {
1490 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1491 0 : return pc.qs;
1492 : }
1493 :
1494 7 : qs = TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id (
1495 : TALER_ARL_edb,
1496 : TALER_ARL_USE_PP (purse_deposits_serial_id),
1497 : &handle_purse_deposits,
1498 : &pc);
1499 7 : if (qs < 0)
1500 : {
1501 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1502 0 : return qs;
1503 : }
1504 7 : if (pc.qs < 0)
1505 : {
1506 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1507 0 : return pc.qs;
1508 : }
1509 :
1510 : /* Charge purse fee! */
1511 7 : qs = TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
1512 : TALER_ARL_edb,
1513 : TALER_ARL_USE_PP (purse_account_merge_serial_id),
1514 : &handle_account_merged,
1515 : &pc);
1516 7 : if (qs < 0)
1517 : {
1518 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1519 0 : return qs;
1520 : }
1521 7 : if (pc.qs < 0)
1522 : {
1523 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1524 0 : return pc.qs;
1525 : }
1526 :
1527 7 : qs = TALER_EXCHANGEDB_iterate_all_purse_decisions_above_serial_id (
1528 : TALER_ARL_edb,
1529 : TALER_ARL_USE_PP (purse_decision_serial_id),
1530 : &handle_purse_decision,
1531 : &pc);
1532 7 : if (qs < 0)
1533 : {
1534 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1535 0 : return qs;
1536 : }
1537 7 : if (pc.qs < 0)
1538 : {
1539 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1540 0 : return pc.qs;
1541 : }
1542 :
1543 7 : qs = TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
1544 : TALER_ARL_edb,
1545 : TALER_ARL_USE_PP (purse_deletion_serial_id),
1546 : &handle_purse_deletion,
1547 : &pc);
1548 7 : if (qs < 0)
1549 : {
1550 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1551 0 : return qs;
1552 : }
1553 7 : if (pc.qs < 0)
1554 : {
1555 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1556 0 : return pc.qs;
1557 : }
1558 :
1559 7 : qs = TALER_AUDITORDB_iterate_purse_expired (
1560 : TALER_ARL_adb,
1561 : &handle_purse_expired,
1562 : &pc);
1563 7 : if (qs < 0)
1564 : {
1565 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1566 0 : return qs;
1567 : }
1568 7 : if (pc.qs < 0)
1569 : {
1570 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1571 0 : return pc.qs;
1572 : }
1573 :
1574 7 : GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
1575 : &verify_purse_balance,
1576 : &pc);
1577 7 : GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
1578 : &release_purse_balance,
1579 : &pc);
1580 7 : GNUNET_break (0 ==
1581 : GNUNET_CONTAINER_multihashmap_size (pc.purses));
1582 7 : GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
1583 7 : if (pc.qs < 0)
1584 : {
1585 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1586 0 : return pc.qs;
1587 : }
1588 7 : qs = TALER_AUDITORDB_insert_balance (
1589 : TALER_ARL_adb,
1590 : TALER_ARL_SET_AB (purse_global_balance),
1591 : TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
1592 : TALER_ARL_SET_AB (purse_total_delayed_decisions),
1593 : TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
1594 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
1595 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
1596 : TALER_ARL_SET_AB (purse_total_bad_sig_loss),
1597 : NULL);
1598 7 : if (0 > qs)
1599 : {
1600 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1601 : "Failed to update auditor DB, not recording progress\n");
1602 3 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1603 3 : return qs;
1604 : }
1605 4 : qs = TALER_AUDITORDB_insert_auditor_progress (
1606 : TALER_ARL_adb,
1607 : TALER_ARL_SET_PP (purse_account_merge_serial_id),
1608 : TALER_ARL_SET_PP (purse_decision_serial_id),
1609 : TALER_ARL_SET_PP (purse_deletion_serial_id),
1610 : TALER_ARL_SET_PP (purse_deposits_serial_id),
1611 : TALER_ARL_SET_PP (purse_merges_serial_id),
1612 : TALER_ARL_SET_PP (purse_request_serial_id),
1613 : TALER_ARL_SET_PP (purse_open_counter),
1614 : NULL);
1615 4 : if (0 > qs)
1616 : {
1617 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1618 : "Failed to update auditor DB, not recording progress\n");
1619 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1620 0 : return qs;
1621 : }
1622 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1623 : "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n",
1624 : (unsigned long long) TALER_ARL_USE_PP (
1625 : purse_request_serial_id),
1626 : (unsigned long long) TALER_ARL_USE_PP (
1627 : purse_decision_serial_id),
1628 : (unsigned long long) TALER_ARL_USE_PP (
1629 : purse_deletion_serial_id),
1630 : (unsigned long long) TALER_ARL_USE_PP (
1631 : purse_merges_serial_id),
1632 : (unsigned long long) TALER_ARL_USE_PP (
1633 : purse_deposits_serial_id),
1634 : (unsigned long long) TALER_ARL_USE_PP (
1635 : purse_account_merge_serial_id));
1636 4 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1637 : }
1638 :
1639 :
1640 : /**
1641 : * Function called on events received from Postgres.
1642 : *
1643 : * @param cls closure, NULL
1644 : * @param extra additional event data provided
1645 : * @param extra_size number of bytes in @a extra
1646 : */
1647 : static void
1648 0 : db_notify (void *cls,
1649 : const void *extra,
1650 : size_t extra_size)
1651 : {
1652 : (void) cls;
1653 : (void) extra;
1654 : (void) extra_size;
1655 :
1656 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1657 : "Received notification to wake purses\n");
1658 0 : if (GNUNET_OK !=
1659 0 : TALER_ARL_setup_sessions_and_run (&analyze_purses,
1660 : NULL))
1661 : {
1662 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1663 : "Audit failed\n");
1664 0 : GNUNET_SCHEDULER_shutdown ();
1665 0 : global_ret = EXIT_FAILURE;
1666 0 : return;
1667 : }
1668 : }
1669 :
1670 :
1671 : /**
1672 : * Function called on shutdown.
1673 : */
1674 : static void
1675 4 : do_shutdown (void *cls)
1676 : {
1677 : (void) cls;
1678 :
1679 4 : if (NULL != eh)
1680 : {
1681 4 : TALER_AUDITORDB_event_listen_cancel (eh);
1682 4 : eh = NULL;
1683 : }
1684 4 : TALER_ARL_done ();
1685 4 : }
1686 :
1687 :
1688 : /**
1689 : * Main function that will be run.
1690 : *
1691 : * @param cls closure
1692 : * @param args remaining command-line arguments
1693 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1694 : * @param c configuration
1695 : */
1696 : static void
1697 4 : run (void *cls,
1698 : char *const *args,
1699 : const char *cfgfile,
1700 : const struct GNUNET_CONFIGURATION_Handle *c)
1701 : {
1702 : (void) cls;
1703 : (void) args;
1704 : (void) cfgfile;
1705 :
1706 4 : cfg = c;
1707 4 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1708 : NULL);
1709 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1710 : "Launching purses auditor\n");
1711 4 : if (EXIT_SUCCESS !=
1712 4 : (global_ret = TALER_ARL_init (c)))
1713 : {
1714 0 : return;
1715 : }
1716 4 : if (test_mode != 1)
1717 : {
1718 4 : struct GNUNET_DB_EventHeaderP es = {
1719 4 : .size = htons (sizeof (es)),
1720 4 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES)
1721 : };
1722 :
1723 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1724 : "Running helper indefinitely\n");
1725 4 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
1726 : &es,
1727 4 : GNUNET_TIME_UNIT_FOREVER_REL,
1728 : &db_notify,
1729 : NULL);
1730 : }
1731 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1732 : "Starting audit\n");
1733 4 : if (GNUNET_OK !=
1734 4 : TALER_ARL_setup_sessions_and_run (&analyze_purses,
1735 : NULL))
1736 : {
1737 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1738 : "Audit failed\n");
1739 0 : GNUNET_SCHEDULER_shutdown ();
1740 0 : global_ret = EXIT_FAILURE;
1741 0 : return;
1742 : }
1743 : }
1744 :
1745 :
1746 : /**
1747 : * The main function to check the database's handling of purses.
1748 : *
1749 : * @param argc number of arguments from the command line
1750 : * @param argv command line arguments
1751 : * @return 0 ok, 1 on error
1752 : */
1753 : int
1754 4 : main (int argc,
1755 : char *const *argv)
1756 : {
1757 4 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
1758 4 : GNUNET_GETOPT_option_flag ('i',
1759 : "internal",
1760 : "perform checks only applicable for exchange-internal audits",
1761 : &internal_checks),
1762 4 : GNUNET_GETOPT_option_flag ('t',
1763 : "test",
1764 : "run in test mode and exit when idle",
1765 : &test_mode),
1766 4 : GNUNET_GETOPT_option_timetravel ('T',
1767 : "timetravel"),
1768 : GNUNET_GETOPT_OPTION_END
1769 : };
1770 : enum GNUNET_GenericReturnValue ret;
1771 :
1772 4 : ret = GNUNET_PROGRAM_run (
1773 : TALER_AUDITOR_project_data (),
1774 : argc,
1775 : argv,
1776 : "taler-helper-auditor-purses",
1777 : gettext_noop ("Audit Taler exchange purse handling"),
1778 : options,
1779 : &run,
1780 : NULL);
1781 4 : if (GNUNET_SYSERR == ret)
1782 0 : return EXIT_NOTCONFIGURED;
1783 4 : if (GNUNET_NO == ret)
1784 0 : return EXIT_SUCCESS;
1785 4 : return global_ret;
1786 : }
1787 :
1788 :
1789 : /* end of taler-helper-auditor-purses.c */
|