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