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 "taler_auditordb_plugin.h"
24 : #include "taler_exchangedb_lib.h"
25 : #include "taler_bank_service.h"
26 : #include "taler_signatures.h"
27 : #include "report-lib.h"
28 : #include "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 : .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 : if (0 > qs)
363 : {
364 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
365 0 : return qs;
366 : }
367 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
368 : {
369 0 : ps->had_pi = false;
370 0 : GNUNET_assert (GNUNET_OK ==
371 : TALER_amount_set_zero (TALER_ARL_currency,
372 : &ps->balance));
373 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
374 : "Creating fresh purse `%s'\n",
375 : TALER_B2S (&ps->purse_pub));
376 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
377 : }
378 0 : ps->had_pi = true;
379 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
380 : "Auditor remembers purse `%s' has balance %s\n",
381 : TALER_B2S (&ps->purse_pub),
382 : TALER_amount2s (&ps->balance));
383 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
384 : }
385 :
386 :
387 : /**
388 : * Closure to the various callbacks we make while checking a purse.
389 : */
390 : struct PurseContext
391 : {
392 : /**
393 : * Map from hash of purse's public key to a `struct PurseSummary`.
394 : */
395 : struct GNUNET_CONTAINER_MultiHashMap *purses;
396 :
397 : /**
398 : * Transaction status code, set to error codes if applicable.
399 : */
400 : enum GNUNET_DB_QueryStatus qs;
401 :
402 : };
403 :
404 :
405 : /**
406 : * Create a new purse for @a purse_pub in @a pc.
407 : *
408 : * @param[in,out] pc context to update
409 : * @param purse_pub key for which to create a purse
410 : * @return NULL on error
411 : */
412 : static struct PurseSummary *
413 0 : setup_purse (struct PurseContext *pc,
414 : const struct TALER_PurseContractPublicKeyP *purse_pub)
415 : {
416 : struct PurseSummary *ps;
417 : struct GNUNET_HashCode key;
418 : enum GNUNET_DB_QueryStatus qs;
419 :
420 0 : GNUNET_CRYPTO_hash (purse_pub,
421 : sizeof (*purse_pub),
422 : &key);
423 0 : ps = GNUNET_CONTAINER_multihashmap_get (pc->purses,
424 : &key);
425 0 : if (NULL != ps)
426 0 : return ps;
427 0 : ps = GNUNET_new (struct PurseSummary);
428 0 : ps->purse_pub = *purse_pub;
429 0 : GNUNET_assert (GNUNET_OK ==
430 : TALER_amount_set_zero (TALER_ARL_currency,
431 : &ps->balance));
432 : /* get purse meta-data from exchange DB */
433 0 : qs = TALER_ARL_edb->select_purse (TALER_ARL_edb->cls,
434 : purse_pub,
435 : &ps->creation_date,
436 : &ps->expiration_date,
437 : &ps->total_value,
438 : &ps->exchange_balance,
439 : &ps->h_contract_terms,
440 : &ps->merge_timestamp,
441 : &ps->purse_deleted,
442 : &ps->purse_refunded);
443 0 : if (0 >= qs)
444 : {
445 0 : GNUNET_free (ps);
446 0 : pc->qs = qs;
447 0 : return NULL;
448 : }
449 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
450 0 : qs = load_auditor_purse_summary (ps);
451 0 : if (0 > qs)
452 : {
453 0 : GNUNET_free (ps);
454 0 : pc->qs = qs;
455 0 : return NULL;
456 : }
457 0 : GNUNET_assert (GNUNET_OK ==
458 : GNUNET_CONTAINER_multihashmap_put (pc->purses,
459 : &key,
460 : ps,
461 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
462 0 : return ps;
463 : }
464 :
465 :
466 : /**
467 : * Function called on purse requests.
468 : *
469 : * @param cls closure
470 : * @param rowid which row in the database was the request stored in
471 : * @param purse_pub public key of the purse
472 : * @param merge_pub public key representing the merge capability
473 : * @param purse_creation when was the purse created
474 : * @param purse_expiration when would an unmerged purse expire
475 : * @param h_contract_terms contract associated with the purse
476 : * @param age_limit the age limit for deposits into the purse
477 : * @param target_amount amount to be put into the purse
478 : * @param purse_sig signature of the purse over the initialization data
479 : * @return #GNUNET_OK to continue to iterate
480 : */
481 : static enum GNUNET_GenericReturnValue
482 0 : handle_purse_requested (
483 : void *cls,
484 : uint64_t rowid,
485 : const struct TALER_PurseContractPublicKeyP *purse_pub,
486 : const struct TALER_PurseMergePublicKeyP *merge_pub,
487 : struct GNUNET_TIME_Timestamp purse_creation,
488 : struct GNUNET_TIME_Timestamp purse_expiration,
489 : const struct TALER_PrivateContractHashP *h_contract_terms,
490 : uint32_t age_limit,
491 : const struct TALER_Amount *target_amount,
492 : const struct TALER_PurseContractSignatureP *purse_sig)
493 : {
494 0 : struct PurseContext *pc = cls;
495 : struct PurseSummary *ps;
496 : struct GNUNET_HashCode key;
497 : enum GNUNET_DB_QueryStatus qs;
498 :
499 0 : TALER_ARL_USE_PP (purse_request_serial_id) = rowid;
500 0 : if (GNUNET_OK !=
501 0 : TALER_wallet_purse_create_verify (purse_expiration,
502 : h_contract_terms,
503 : merge_pub,
504 : age_limit,
505 : target_amount,
506 : purse_pub,
507 : purse_sig))
508 : {
509 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
510 : .problem_row_id = rowid,
511 : .operation = (char *) "purse-request",
512 : .loss = *target_amount,
513 : .operation_specific_pub = purse_pub->eddsa_pub
514 : };
515 :
516 0 : qs = TALER_ARL_adb->insert_bad_sig_losses (
517 0 : TALER_ARL_adb->cls,
518 : &bsl);
519 0 : if (qs < 0)
520 : {
521 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
522 0 : pc->qs = qs;
523 0 : return GNUNET_SYSERR;
524 : }
525 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
526 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
527 : target_amount);
528 : }
529 0 : GNUNET_CRYPTO_hash (purse_pub,
530 : sizeof (*purse_pub),
531 : &key);
532 0 : ps = GNUNET_new (struct PurseSummary);
533 0 : ps->purse_pub = *purse_pub;
534 0 : GNUNET_assert (GNUNET_OK ==
535 : TALER_amount_set_zero (TALER_ARL_currency,
536 : &ps->balance));
537 0 : ps->creation_date = purse_creation;
538 0 : ps->expiration_date = purse_expiration;
539 0 : ps->total_value = *target_amount;
540 0 : ps->h_contract_terms = *h_contract_terms;
541 0 : GNUNET_assert (GNUNET_OK ==
542 : GNUNET_CONTAINER_multihashmap_put (pc->purses,
543 : &key,
544 : ps,
545 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
546 0 : return GNUNET_OK;
547 : }
548 :
549 :
550 : /**
551 : * Function called with details about purse deposits that have been made, with
552 : * the goal of auditing the deposit's execution.
553 : *
554 : * @param cls closure
555 : * @param rowid unique serial ID for the deposit in our DB
556 : * @param deposit deposit details
557 : * @param reserve_pub which reserve is the purse merged into, NULL if unknown
558 : * @param flags purse flags
559 : * @param auditor_balance purse balance (according to the
560 : * auditor during auditing)
561 : * @param purse_total target amount the purse should reach
562 : * @param denom_pub denomination public key of @a coin_pub
563 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
564 : */
565 : static enum GNUNET_GenericReturnValue
566 0 : handle_purse_deposits (
567 : void *cls,
568 : uint64_t rowid,
569 : const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
570 : const struct TALER_ReservePublicKeyP *reserve_pub,
571 : enum TALER_WalletAccountMergeFlags flags,
572 : const struct TALER_Amount *auditor_balance,
573 : const struct TALER_Amount *purse_total,
574 : const struct TALER_DenominationPublicKey *denom_pub)
575 : {
576 0 : struct PurseContext *pc = cls;
577 : struct TALER_Amount amount_minus_fee;
578 : struct PurseSummary *ps;
579 0 : const char *base_url
580 0 : = (NULL == deposit->exchange_base_url)
581 : ? TALER_ARL_exchange_url
582 0 : : deposit->exchange_base_url;
583 : struct TALER_DenominationHashP h_denom_pub;
584 : enum GNUNET_DB_QueryStatus qs;
585 :
586 : /* should be monotonically increasing */
587 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id));
588 0 : TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1;
589 :
590 : {
591 : const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
592 :
593 :
594 0 : qs = TALER_ARL_get_denomination_info (denom_pub,
595 : &issue,
596 : &h_denom_pub);
597 0 : if (0 > qs)
598 : {
599 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
600 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
601 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
602 : "Hard database error trying to get denomination %s from database!\n",
603 : TALER_B2S (denom_pub));
604 0 : pc->qs = qs;
605 0 : return GNUNET_SYSERR;
606 : }
607 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
608 : {
609 0 : qs = report_row_inconsistency ("purse-deposit",
610 : rowid,
611 : "denomination key not found");
612 0 : if (0 > qs)
613 : {
614 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
615 0 : pc->qs = qs;
616 0 : return GNUNET_SYSERR;
617 : }
618 0 : return GNUNET_OK;
619 : }
620 0 : TALER_ARL_amount_subtract (&amount_minus_fee,
621 : &deposit->amount,
622 : &issue->fees.deposit);
623 : }
624 :
625 0 : if (GNUNET_OK !=
626 0 : TALER_wallet_purse_deposit_verify (base_url,
627 : &deposit->purse_pub,
628 : &deposit->amount,
629 : &h_denom_pub,
630 : &deposit->h_age_commitment,
631 : &deposit->coin_pub,
632 : &deposit->coin_sig))
633 : {
634 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
635 : .problem_row_id = rowid,
636 : .operation = (char *) "purse-deposit",
637 : .loss = deposit->amount,
638 : .operation_specific_pub = deposit->coin_pub.eddsa_pub
639 : };
640 :
641 0 : qs = TALER_ARL_adb->insert_bad_sig_losses (
642 0 : TALER_ARL_adb->cls,
643 : &bsl);
644 0 : if (qs < 0)
645 : {
646 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
647 0 : pc->qs = qs;
648 0 : return GNUNET_SYSERR;
649 : }
650 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
651 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
652 : &deposit->amount);
653 0 : return GNUNET_OK;
654 : }
655 :
656 0 : ps = setup_purse (pc,
657 : &deposit->purse_pub);
658 0 : if (NULL == ps)
659 : {
660 0 : if (0 > pc->qs)
661 : {
662 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
663 0 : return GNUNET_SYSERR;
664 : }
665 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
666 0 : qs = report_row_inconsistency ("purse_deposit",
667 : rowid,
668 : "purse not found");
669 0 : if (0 > qs)
670 : {
671 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
672 0 : pc->qs = qs;
673 0 : return GNUNET_SYSERR;
674 : }
675 0 : return GNUNET_OK;
676 : }
677 0 : TALER_ARL_amount_add (&ps->balance,
678 : &ps->balance,
679 : &amount_minus_fee);
680 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
681 : &TALER_ARL_USE_AB (purse_global_balance),
682 : &amount_minus_fee);
683 0 : return GNUNET_OK;
684 : }
685 :
686 :
687 : /**
688 : * Function called with details about purse merges that have been made, with
689 : * the goal of auditing the purse merge execution.
690 : *
691 : * @param cls closure
692 : * @param rowid unique serial ID for the deposit in our DB
693 : * @param partner_base_url where is the reserve, NULL for this exchange
694 : * @param amount total amount expected in the purse
695 : * @param balance current balance in the purse
696 : * @param flags purse flags
697 : * @param merge_pub merge capability key
698 : * @param reserve_pub reserve the merge affects
699 : * @param merge_sig signature affirming the merge
700 : * @param purse_pub purse key
701 : * @param merge_timestamp when did the merge happen
702 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
703 : */
704 : static enum GNUNET_GenericReturnValue
705 0 : handle_purse_merged (
706 : void *cls,
707 : uint64_t rowid,
708 : const char *partner_base_url,
709 : const struct TALER_Amount *amount,
710 : const struct TALER_Amount *balance,
711 : enum TALER_WalletAccountMergeFlags flags,
712 : const struct TALER_PurseMergePublicKeyP *merge_pub,
713 : const struct TALER_ReservePublicKeyP *reserve_pub,
714 : const struct TALER_PurseMergeSignatureP *merge_sig,
715 : const struct TALER_PurseContractPublicKeyP *purse_pub,
716 : struct GNUNET_TIME_Timestamp merge_timestamp)
717 : {
718 0 : struct PurseContext *pc = cls;
719 : struct PurseSummary *ps;
720 : enum GNUNET_DB_QueryStatus qs;
721 :
722 : /* should be monotonically increasing */
723 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
724 0 : TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
725 :
726 : {
727 : struct TALER_NormalizedPayto reserve_url;
728 :
729 : reserve_url
730 0 : = TALER_reserve_make_payto (NULL == partner_base_url
731 : ? TALER_ARL_exchange_url
732 : : partner_base_url,
733 : reserve_pub);
734 0 : if (GNUNET_OK !=
735 0 : TALER_wallet_purse_merge_verify (reserve_url,
736 : merge_timestamp,
737 : purse_pub,
738 : merge_pub,
739 : merge_sig))
740 : {
741 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
742 : .problem_row_id = rowid,
743 : .operation = (char *) "merge-purse",
744 : .loss = *amount,
745 : .operation_specific_pub = merge_pub->eddsa_pub
746 : };
747 :
748 0 : GNUNET_free (reserve_url.normalized_payto);
749 0 : qs = TALER_ARL_adb->insert_bad_sig_losses (
750 0 : TALER_ARL_adb->cls,
751 : &bsl);
752 0 : if (qs < 0)
753 : {
754 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
755 0 : pc->qs = qs;
756 0 : return GNUNET_SYSERR;
757 : }
758 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
759 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
760 : amount);
761 0 : return GNUNET_OK;
762 : }
763 0 : GNUNET_free (reserve_url.normalized_payto);
764 : }
765 :
766 0 : ps = setup_purse (pc,
767 : purse_pub);
768 0 : if (NULL == ps)
769 : {
770 0 : if (0 < pc->qs)
771 : {
772 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
773 0 : return GNUNET_SYSERR;
774 : }
775 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
776 0 : qs = report_row_inconsistency ("purse-merge",
777 : rowid,
778 : "purse not found");
779 0 : if (qs < 0)
780 : {
781 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
782 0 : pc->qs = qs;
783 0 : return GNUNET_SYSERR;
784 : }
785 0 : return GNUNET_OK;
786 : }
787 0 : GNUNET_break (0 ==
788 : GNUNET_TIME_timestamp_cmp (merge_timestamp,
789 : ==,
790 : ps->merge_timestamp));
791 0 : TALER_ARL_amount_add (&ps->balance,
792 : &ps->balance,
793 : amount);
794 0 : return GNUNET_OK;
795 : }
796 :
797 :
798 : /**
799 : * Function called with details about account merge requests that have been
800 : * made, with the goal of auditing the account merge execution.
801 : *
802 : * @param cls closure
803 : * @param rowid unique serial ID for the deposit in our DB
804 : * @param reserve_pub reserve affected by the merge
805 : * @param purse_pub purse being merged
806 : * @param h_contract_terms hash over contract of the purse
807 : * @param purse_expiration when would the purse expire
808 : * @param amount total amount in the purse
809 : * @param min_age minimum age of all coins deposited into the purse
810 : * @param flags how was the purse created
811 : * @param purse_fee if a purse fee was paid, how high is it
812 : * @param merge_timestamp when was the merge approved
813 : * @param reserve_sig signature by reserve approving the merge
814 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
815 : */
816 : static enum GNUNET_GenericReturnValue
817 0 : handle_account_merged (
818 : void *cls,
819 : uint64_t rowid,
820 : const struct TALER_ReservePublicKeyP *reserve_pub,
821 : const struct TALER_PurseContractPublicKeyP *purse_pub,
822 : const struct TALER_PrivateContractHashP *h_contract_terms,
823 : struct GNUNET_TIME_Timestamp purse_expiration,
824 : const struct TALER_Amount *amount,
825 : uint32_t min_age,
826 : enum TALER_WalletAccountMergeFlags flags,
827 : const struct TALER_Amount *purse_fee,
828 : struct GNUNET_TIME_Timestamp merge_timestamp,
829 : const struct TALER_ReserveSignatureP *reserve_sig)
830 : {
831 0 : struct PurseContext *pc = cls;
832 : struct PurseSummary *ps;
833 : enum GNUNET_DB_QueryStatus qs;
834 :
835 : /* should be monotonically increasing */
836 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
837 0 : TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
838 0 : if (GNUNET_OK !=
839 0 : TALER_wallet_account_merge_verify (merge_timestamp,
840 : purse_pub,
841 : purse_expiration,
842 : h_contract_terms,
843 : amount,
844 : purse_fee,
845 : min_age,
846 : flags,
847 : reserve_pub,
848 : reserve_sig))
849 : {
850 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
851 : .problem_row_id = rowid,
852 : .operation = (char *) "account-merge",
853 : .loss = *purse_fee,
854 : .operation_specific_pub = reserve_pub->eddsa_pub
855 : };
856 :
857 0 : qs = TALER_ARL_adb->insert_bad_sig_losses (
858 0 : TALER_ARL_adb->cls,
859 : &bsl);
860 0 : if (qs < 0)
861 : {
862 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
863 0 : pc->qs = qs;
864 0 : return GNUNET_SYSERR;
865 : }
866 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
867 : &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
868 : purse_fee);
869 0 : return GNUNET_OK;
870 : }
871 0 : ps = setup_purse (pc,
872 : purse_pub);
873 0 : if (NULL == ps)
874 : {
875 0 : if (0 > pc->qs)
876 : {
877 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
878 0 : return GNUNET_SYSERR;
879 : }
880 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
881 0 : qs = report_row_inconsistency ("account-merge",
882 : rowid,
883 : "purse not found");
884 0 : if (0 > qs)
885 : {
886 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
887 0 : pc->qs = qs;
888 0 : return GNUNET_SYSERR;
889 : }
890 0 : return GNUNET_OK;
891 : }
892 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
893 : &TALER_ARL_USE_AB (purse_global_balance),
894 : purse_fee);
895 0 : TALER_ARL_amount_add (&ps->balance,
896 : &ps->balance,
897 : purse_fee);
898 0 : return GNUNET_OK;
899 : }
900 :
901 :
902 : /**
903 : * Function called with details about purse decisions that have been made.
904 : *
905 : * @param cls closure
906 : * @param rowid unique serial ID for the deposit in our DB
907 : * @param purse_pub which purse was the decision made on
908 : * @param refunded true if decision was to refund
909 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
910 : */
911 : static enum GNUNET_GenericReturnValue
912 0 : handle_purse_decision (
913 : void *cls,
914 : uint64_t rowid,
915 : const struct TALER_PurseContractPublicKeyP *purse_pub,
916 : bool refunded)
917 : {
918 0 : struct PurseContext *pc = cls;
919 : struct PurseSummary *ps;
920 : struct GNUNET_HashCode key;
921 : enum GNUNET_DB_QueryStatus qs;
922 : struct TALER_Amount purse_fee;
923 : struct TALER_Amount balance_without_purse_fee;
924 :
925 0 : TALER_ARL_USE_PP (purse_decision_serial_id) = rowid;
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 : qs = report_row_inconsistency ("purse-decision",
936 : rowid,
937 : "purse not found");
938 0 : if (0 > qs)
939 : {
940 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
941 0 : pc->qs = qs;
942 0 : return GNUNET_SYSERR;
943 : }
944 0 : return GNUNET_OK;
945 : }
946 0 : qs = get_purse_fee (ps->creation_date,
947 : &purse_fee);
948 0 : if (0 > qs)
949 : {
950 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
951 0 : pc->qs = qs;
952 0 : return GNUNET_SYSERR;
953 : }
954 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
955 0 : return GNUNET_OK; /* already reported */
956 0 : if (0 >
957 0 : TALER_amount_subtract (&balance_without_purse_fee,
958 0 : &ps->balance,
959 : &purse_fee))
960 : {
961 0 : qs = report_row_inconsistency ("purse-request",
962 : rowid,
963 : "purse fee higher than balance");
964 0 : if (0 > qs)
965 : {
966 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
967 0 : pc->qs = qs;
968 0 : return GNUNET_SYSERR;
969 : }
970 0 : GNUNET_assert (GNUNET_OK ==
971 : TALER_amount_set_zero (TALER_ARL_currency,
972 : &balance_without_purse_fee));
973 : }
974 :
975 0 : if (refunded)
976 : {
977 0 : if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
978 0 : &ps->total_value))
979 : {
980 0 : qs = report_amount_arithmetic_inconsistency ("purse-decision: refund",
981 : rowid,
982 : &balance_without_purse_fee,
983 0 : &ps->total_value,
984 : 0);
985 0 : if (0 > qs)
986 : {
987 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
988 0 : pc->qs = qs;
989 0 : return GNUNET_SYSERR;
990 : }
991 : }
992 0 : if ( (internal_checks) &&
993 0 : (! ps->purse_refunded) )
994 : {
995 0 : qs = report_row_inconsistency (
996 : "purse-decision",
997 : rowid,
998 : "purse not marked as refunded (internal check)");
999 0 : if (qs < 0)
1000 : {
1001 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1002 0 : pc->qs = qs;
1003 0 : return GNUNET_SYSERR;
1004 : }
1005 : }
1006 : }
1007 : else
1008 : {
1009 0 : if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
1010 0 : &ps->total_value))
1011 : {
1012 0 : qs = report_amount_arithmetic_inconsistency ("purse-decision: merge",
1013 : rowid,
1014 0 : &ps->total_value,
1015 : &balance_without_purse_fee,
1016 : 0);
1017 0 : if (0 > qs)
1018 : {
1019 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1020 0 : pc->qs = qs;
1021 0 : return GNUNET_SYSERR;
1022 : }
1023 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1024 : purse_total_balance_insufficient_loss),
1025 : &TALER_ARL_USE_AB (
1026 : purse_total_balance_insufficient_loss),
1027 : &ps->total_value);
1028 : }
1029 : }
1030 :
1031 0 : qs = TALER_ARL_adb->delete_purse_info (TALER_ARL_adb->cls,
1032 : purse_pub);
1033 0 : if (qs < 0)
1034 : {
1035 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1036 0 : pc->qs = qs;
1037 0 : return GNUNET_SYSERR;
1038 : }
1039 0 : GNUNET_CRYPTO_hash (purse_pub,
1040 : sizeof (*purse_pub),
1041 : &key);
1042 0 : GNUNET_assert (GNUNET_YES ==
1043 : GNUNET_CONTAINER_multihashmap_remove (pc->purses,
1044 : &key,
1045 : ps));
1046 0 : GNUNET_free (ps);
1047 0 : return GNUNET_OK;
1048 : }
1049 :
1050 :
1051 : /**
1052 : * Function called on explicitly deleted purses.
1053 : *
1054 : * @param cls closure
1055 : * @param deletion_serial_id row ID with the deletion data
1056 : * @param purse_pub public key of the purse
1057 : * @param purse_sig signature affirming deletion of the purse
1058 : * @return #GNUNET_OK to continue to iterate
1059 : */
1060 : static enum GNUNET_GenericReturnValue
1061 0 : handle_purse_deletion (
1062 : void *cls,
1063 : uint64_t deletion_serial_id,
1064 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1065 : const struct TALER_PurseContractSignatureP *purse_sig)
1066 : {
1067 0 : struct PurseContext *pc = cls;
1068 : struct PurseSummary *ps;
1069 :
1070 0 : ps = setup_purse (pc,
1071 : purse_pub);
1072 0 : if (NULL == ps)
1073 : {
1074 0 : GNUNET_break (0);
1075 0 : return GNUNET_SYSERR;
1076 : }
1077 0 : if (GNUNET_OK !=
1078 0 : TALER_wallet_purse_delete_verify (purse_pub,
1079 : purse_sig))
1080 : {
1081 : enum GNUNET_DB_QueryStatus qs;
1082 :
1083 0 : qs = report_row_inconsistency (
1084 : "purse-delete",
1085 : deletion_serial_id,
1086 : "purse deletion signature invalid");
1087 0 : if (qs < 0)
1088 : {
1089 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1090 0 : pc->qs = qs;
1091 0 : return GNUNET_SYSERR;
1092 : }
1093 : }
1094 : else
1095 : {
1096 0 : if ( (internal_checks) &&
1097 0 : (! ps->purse_deleted) )
1098 : {
1099 : enum GNUNET_DB_QueryStatus qs;
1100 :
1101 0 : qs = report_row_inconsistency (
1102 : "purse-delete",
1103 : deletion_serial_id,
1104 : "purse not marked as deleted (internal check)");
1105 0 : if (qs < 0)
1106 : {
1107 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1108 0 : pc->qs = qs;
1109 0 : return GNUNET_SYSERR;
1110 : }
1111 : }
1112 : }
1113 0 : return GNUNET_OK;
1114 : }
1115 :
1116 :
1117 : /**
1118 : * Function called on expired purses.
1119 : *
1120 : * @param cls closure
1121 : * @param purse_pub public key of the purse
1122 : * @param balance amount of money in the purse
1123 : * @param expiration_date when did the purse expire?
1124 : * @return #GNUNET_OK to continue to iterate
1125 : */
1126 : static enum GNUNET_GenericReturnValue
1127 0 : handle_purse_expired (
1128 : void *cls,
1129 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1130 : const struct TALER_Amount *balance,
1131 : struct GNUNET_TIME_Timestamp expiration_date)
1132 : {
1133 0 : struct PurseContext *pc = cls;
1134 : enum GNUNET_DB_QueryStatus qs;
1135 0 : struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = {
1136 : .amount = *balance,
1137 : .expiration_date = expiration_date.abs_time,
1138 : .purse_pub = purse_pub->eddsa_pub
1139 : };
1140 :
1141 0 : qs = TALER_ARL_adb->insert_purse_not_closed_inconsistencies (
1142 0 : TALER_ARL_adb->cls,
1143 : &pnci);
1144 0 : if (qs < 0)
1145 : {
1146 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1147 0 : pc->qs = qs;
1148 0 : return GNUNET_SYSERR;
1149 : }
1150 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions),
1151 : &TALER_ARL_USE_AB (purse_total_delayed_decisions),
1152 : balance);
1153 0 : return GNUNET_OK;
1154 : }
1155 :
1156 :
1157 : /**
1158 : * Check that the purse summary matches what the exchange database
1159 : * thinks about the purse, and update our own state of the purse.
1160 : *
1161 : * Remove all purses that we are happy with from the DB.
1162 : *
1163 : * @param cls our `struct PurseContext`
1164 : * @param key hash of the purse public key
1165 : * @param value a `struct PurseSummary`
1166 : * @return #GNUNET_OK to process more entries
1167 : */
1168 : static enum GNUNET_GenericReturnValue
1169 0 : verify_purse_balance (void *cls,
1170 : const struct GNUNET_HashCode *key,
1171 : void *value)
1172 : {
1173 0 : struct PurseContext *pc = cls;
1174 0 : struct PurseSummary *ps = value;
1175 : enum GNUNET_DB_QueryStatus qs;
1176 :
1177 0 : if (internal_checks)
1178 : {
1179 : struct TALER_Amount pf;
1180 : struct TALER_Amount balance_without_purse_fee;
1181 :
1182 : /* subtract purse fee from ps->balance to get actual balance we expect, as
1183 : we track the balance including purse fee, while the exchange subtracts
1184 : the purse fee early on. */
1185 0 : qs = get_purse_fee (ps->creation_date,
1186 : &pf);
1187 0 : if (qs < 0)
1188 : {
1189 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1190 0 : pc->qs = qs;
1191 0 : return GNUNET_SYSERR;
1192 : }
1193 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1194 0 : return GNUNET_OK; /* error already reported */
1195 0 : if (0 >
1196 0 : TALER_amount_subtract (&balance_without_purse_fee,
1197 0 : &ps->balance,
1198 : &pf))
1199 : {
1200 0 : qs = report_row_inconsistency ("purse",
1201 : 0,
1202 : "purse fee higher than balance");
1203 0 : if (qs < 0)
1204 : {
1205 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1206 0 : pc->qs = qs;
1207 0 : return GNUNET_SYSERR;
1208 : }
1209 0 : GNUNET_assert (GNUNET_OK ==
1210 : TALER_amount_set_zero (TALER_ARL_currency,
1211 : &balance_without_purse_fee));
1212 : }
1213 :
1214 0 : if (0 != TALER_amount_cmp (&ps->exchange_balance,
1215 : &balance_without_purse_fee))
1216 : {
1217 0 : qs = report_amount_arithmetic_inconsistency ("purse-balance",
1218 : 0,
1219 0 : &ps->exchange_balance,
1220 : &balance_without_purse_fee,
1221 : 0);
1222 0 : if (qs < 0)
1223 : {
1224 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1225 0 : pc->qs = qs;
1226 0 : return GNUNET_SYSERR;
1227 : }
1228 : }
1229 : }
1230 :
1231 0 : if (ps->had_pi)
1232 0 : qs = TALER_ARL_adb->update_purse_info (TALER_ARL_adb->cls,
1233 0 : &ps->purse_pub,
1234 0 : &ps->balance);
1235 : else
1236 0 : qs = TALER_ARL_adb->insert_purse_info (TALER_ARL_adb->cls,
1237 0 : &ps->purse_pub,
1238 0 : &ps->balance,
1239 : ps->expiration_date);
1240 0 : if (qs < 0)
1241 : {
1242 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1243 0 : pc->qs = qs;
1244 0 : return GNUNET_SYSERR;
1245 : }
1246 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1247 0 : return GNUNET_OK;
1248 : }
1249 :
1250 :
1251 : /**
1252 : * Clear memory from the purses hash map.
1253 : *
1254 : * @param cls our `struct PurseContext`
1255 : * @param key hash of the purse public key
1256 : * @param value a `struct PurseSummary`
1257 : * @return #GNUNET_OK to process more entries
1258 : */
1259 : static enum GNUNET_GenericReturnValue
1260 0 : release_purse_balance (void *cls,
1261 : const struct GNUNET_HashCode *key,
1262 : void *value)
1263 : {
1264 0 : struct PurseContext *pc = cls;
1265 0 : struct PurseSummary *ps = value;
1266 :
1267 0 : GNUNET_assert (GNUNET_YES ==
1268 : GNUNET_CONTAINER_multihashmap_remove (pc->purses,
1269 : key,
1270 : ps));
1271 0 : GNUNET_free (ps);
1272 0 : return GNUNET_OK;
1273 : }
1274 :
1275 :
1276 : /**
1277 : * Analyze purses for being well-formed.
1278 : *
1279 : * @param cls NULL
1280 : * @return transaction status code
1281 : */
1282 : static enum GNUNET_DB_QueryStatus
1283 74 : analyze_purses (void *cls)
1284 : {
1285 : struct PurseContext pc;
1286 : enum GNUNET_DB_QueryStatus qs;
1287 : bool had_pp;
1288 : bool had_bal;
1289 :
1290 : (void) cls;
1291 74 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1292 : "Analyzing purses\n");
1293 74 : qs = TALER_ARL_adb->get_auditor_progress (
1294 74 : TALER_ARL_adb->cls,
1295 : TALER_ARL_GET_PP (purse_account_merge_serial_id),
1296 : TALER_ARL_GET_PP (purse_decision_serial_id),
1297 : TALER_ARL_GET_PP (purse_deletion_serial_id),
1298 : TALER_ARL_GET_PP (purse_deposits_serial_id),
1299 : TALER_ARL_GET_PP (purse_merges_serial_id),
1300 : TALER_ARL_GET_PP (purse_request_serial_id),
1301 : TALER_ARL_GET_PP (purse_open_counter),
1302 : NULL);
1303 74 : if (0 > qs)
1304 : {
1305 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1306 0 : return qs;
1307 : }
1308 74 : had_pp = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1309 74 : if (had_pp)
1310 : {
1311 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1312 : "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu\n",
1313 : (unsigned long long) TALER_ARL_USE_PP (
1314 : purse_request_serial_id),
1315 : (unsigned long long) TALER_ARL_USE_PP (
1316 : purse_decision_serial_id),
1317 : (unsigned long long) TALER_ARL_USE_PP (
1318 : purse_deletion_serial_id),
1319 : (unsigned long long) TALER_ARL_USE_PP (
1320 : purse_merges_serial_id),
1321 : (unsigned long long) TALER_ARL_USE_PP (
1322 : purse_deposits_serial_id),
1323 : (unsigned long long) TALER_ARL_USE_PP (
1324 : purse_account_merge_serial_id));
1325 : }
1326 : else
1327 : {
1328 74 : GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
1329 : "First analysis using this auditor, starting audit from scratch\n");
1330 : }
1331 74 : pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1332 74 : qs = TALER_ARL_adb->get_balance (
1333 74 : TALER_ARL_adb->cls,
1334 : TALER_ARL_GET_AB (purse_global_balance),
1335 : TALER_ARL_GET_AB (purse_total_balance_insufficient_loss),
1336 : TALER_ARL_GET_AB (purse_total_delayed_decisions),
1337 : TALER_ARL_GET_AB (purse_total_balance_purse_not_closed),
1338 : TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus),
1339 : TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus),
1340 : TALER_ARL_GET_AB (purse_total_bad_sig_loss),
1341 : NULL);
1342 74 : if (qs < 0)
1343 : {
1344 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1345 0 : return qs;
1346 : }
1347 74 : had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1348 74 : pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
1349 : GNUNET_NO);
1350 :
1351 74 : qs = TALER_ARL_edb->select_purse_requests_above_serial_id (
1352 74 : TALER_ARL_edb->cls,
1353 : TALER_ARL_USE_PP (purse_request_serial_id),
1354 : &handle_purse_requested,
1355 : &pc);
1356 74 : if (qs < 0)
1357 : {
1358 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1359 0 : return qs;
1360 : }
1361 74 : if (pc.qs < 0)
1362 : {
1363 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1364 0 : return pc.qs;
1365 : }
1366 74 : qs = TALER_ARL_edb->select_purse_merges_above_serial_id (
1367 74 : TALER_ARL_edb->cls,
1368 : TALER_ARL_USE_PP (purse_merges_serial_id),
1369 : &handle_purse_merged,
1370 : &pc);
1371 74 : if (qs < 0)
1372 : {
1373 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1374 0 : return qs;
1375 : }
1376 74 : if (pc.qs < 0)
1377 : {
1378 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1379 0 : return pc.qs;
1380 : }
1381 :
1382 74 : qs = TALER_ARL_edb->select_purse_deposits_above_serial_id (
1383 74 : TALER_ARL_edb->cls,
1384 : TALER_ARL_USE_PP (purse_deposits_serial_id),
1385 : &handle_purse_deposits,
1386 : &pc);
1387 74 : if (qs < 0)
1388 : {
1389 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1390 0 : return qs;
1391 : }
1392 74 : if (pc.qs < 0)
1393 : {
1394 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1395 0 : return pc.qs;
1396 : }
1397 :
1398 : /* Charge purse fee! */
1399 74 : qs = TALER_ARL_edb->select_account_merges_above_serial_id (
1400 74 : TALER_ARL_edb->cls,
1401 : TALER_ARL_USE_PP (purse_account_merge_serial_id),
1402 : &handle_account_merged,
1403 : &pc);
1404 74 : if (qs < 0)
1405 : {
1406 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1407 0 : return qs;
1408 : }
1409 74 : if (pc.qs < 0)
1410 : {
1411 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1412 0 : return pc.qs;
1413 : }
1414 :
1415 74 : qs = TALER_ARL_edb->select_all_purse_decisions_above_serial_id (
1416 74 : TALER_ARL_edb->cls,
1417 : TALER_ARL_USE_PP (purse_decision_serial_id),
1418 : &handle_purse_decision,
1419 : &pc);
1420 74 : if (qs < 0)
1421 : {
1422 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1423 0 : return qs;
1424 : }
1425 74 : if (pc.qs < 0)
1426 : {
1427 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1428 0 : return pc.qs;
1429 : }
1430 :
1431 74 : qs = TALER_ARL_edb->select_all_purse_deletions_above_serial_id (
1432 74 : TALER_ARL_edb->cls,
1433 : TALER_ARL_USE_PP (purse_deletion_serial_id),
1434 : &handle_purse_deletion,
1435 : &pc);
1436 74 : if (qs < 0)
1437 : {
1438 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1439 0 : return qs;
1440 : }
1441 74 : if (pc.qs < 0)
1442 : {
1443 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1444 0 : return pc.qs;
1445 : }
1446 :
1447 74 : qs = TALER_ARL_adb->select_purse_expired (
1448 74 : TALER_ARL_adb->cls,
1449 : &handle_purse_expired,
1450 : &pc);
1451 74 : if (qs < 0)
1452 : {
1453 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1454 0 : return qs;
1455 : }
1456 74 : if (pc.qs < 0)
1457 : {
1458 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1459 0 : return pc.qs;
1460 : }
1461 :
1462 74 : GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
1463 : &verify_purse_balance,
1464 : &pc);
1465 74 : GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
1466 : &release_purse_balance,
1467 : &pc);
1468 74 : GNUNET_break (0 ==
1469 : GNUNET_CONTAINER_multihashmap_size (pc.purses));
1470 74 : GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
1471 74 : if (pc.qs < 0)
1472 : {
1473 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
1474 0 : return pc.qs;
1475 : }
1476 74 : if (had_bal)
1477 0 : qs = TALER_ARL_adb->update_balance (
1478 0 : TALER_ARL_adb->cls,
1479 : TALER_ARL_SET_AB (purse_global_balance),
1480 : TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
1481 : TALER_ARL_SET_AB (purse_total_delayed_decisions),
1482 : TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
1483 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
1484 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
1485 : TALER_ARL_SET_AB (purse_total_bad_sig_loss),
1486 : NULL);
1487 : else
1488 74 : qs = TALER_ARL_adb->insert_balance (
1489 74 : TALER_ARL_adb->cls,
1490 : TALER_ARL_SET_AB (purse_global_balance),
1491 : TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
1492 : TALER_ARL_SET_AB (purse_total_delayed_decisions),
1493 : TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
1494 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
1495 : TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
1496 : TALER_ARL_SET_AB (purse_total_bad_sig_loss),
1497 : NULL);
1498 74 : if (0 > qs)
1499 : {
1500 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1501 : "Failed to update auditor DB, not recording progress\n");
1502 5 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1503 5 : return qs;
1504 : }
1505 69 : if (had_pp)
1506 0 : qs = TALER_ARL_adb->update_auditor_progress (
1507 0 : TALER_ARL_adb->cls,
1508 : TALER_ARL_SET_PP (purse_account_merge_serial_id),
1509 : TALER_ARL_SET_PP (purse_decision_serial_id),
1510 : TALER_ARL_SET_PP (purse_deletion_serial_id),
1511 : TALER_ARL_SET_PP (purse_deposits_serial_id),
1512 : TALER_ARL_SET_PP (purse_merges_serial_id),
1513 : TALER_ARL_SET_PP (purse_request_serial_id),
1514 : TALER_ARL_SET_PP (purse_open_counter),
1515 : NULL);
1516 : else
1517 69 : qs = TALER_ARL_adb->insert_auditor_progress (
1518 69 : TALER_ARL_adb->cls,
1519 : TALER_ARL_SET_PP (purse_account_merge_serial_id),
1520 : TALER_ARL_SET_PP (purse_decision_serial_id),
1521 : TALER_ARL_SET_PP (purse_deletion_serial_id),
1522 : TALER_ARL_SET_PP (purse_deposits_serial_id),
1523 : TALER_ARL_SET_PP (purse_merges_serial_id),
1524 : TALER_ARL_SET_PP (purse_request_serial_id),
1525 : TALER_ARL_SET_PP (purse_open_counter),
1526 : NULL);
1527 69 : if (0 > qs)
1528 : {
1529 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1530 : "Failed to update auditor DB, not recording progress\n");
1531 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1532 0 : return qs;
1533 : }
1534 69 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1535 : "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n",
1536 : (unsigned long long) TALER_ARL_USE_PP (
1537 : purse_request_serial_id),
1538 : (unsigned long long) TALER_ARL_USE_PP (
1539 : purse_decision_serial_id),
1540 : (unsigned long long) TALER_ARL_USE_PP (
1541 : purse_deletion_serial_id),
1542 : (unsigned long long) TALER_ARL_USE_PP (
1543 : purse_merges_serial_id),
1544 : (unsigned long long) TALER_ARL_USE_PP (
1545 : purse_deposits_serial_id),
1546 : (unsigned long long) TALER_ARL_USE_PP (
1547 : purse_account_merge_serial_id));
1548 69 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1549 : }
1550 :
1551 :
1552 : /**
1553 : * Function called on events received from Postgres.
1554 : *
1555 : * @param cls closure, NULL
1556 : * @param extra additional event data provided
1557 : * @param extra_size number of bytes in @a extra
1558 : */
1559 : static void
1560 0 : db_notify (void *cls,
1561 : const void *extra,
1562 : size_t extra_size)
1563 : {
1564 : (void) cls;
1565 : (void) extra;
1566 : (void) extra_size;
1567 :
1568 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1569 : "Received notification to wake purses\n");
1570 0 : if (GNUNET_OK !=
1571 0 : TALER_ARL_setup_sessions_and_run (&analyze_purses,
1572 : NULL))
1573 : {
1574 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1575 : "Audit failed\n");
1576 0 : GNUNET_SCHEDULER_shutdown ();
1577 0 : global_ret = EXIT_FAILURE;
1578 0 : return;
1579 : }
1580 : }
1581 :
1582 :
1583 : /**
1584 : * Function called on shutdown.
1585 : */
1586 : static void
1587 74 : do_shutdown (void *cls)
1588 : {
1589 : (void) cls;
1590 :
1591 74 : if (NULL != eh)
1592 : {
1593 6 : TALER_ARL_adb->event_listen_cancel (eh);
1594 6 : eh = NULL;
1595 : }
1596 74 : TALER_ARL_done ();
1597 74 : }
1598 :
1599 :
1600 : /**
1601 : * Main function that will be run.
1602 : *
1603 : * @param cls closure
1604 : * @param args remaining command-line arguments
1605 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1606 : * @param c configuration
1607 : */
1608 : static void
1609 74 : run (void *cls,
1610 : char *const *args,
1611 : const char *cfgfile,
1612 : const struct GNUNET_CONFIGURATION_Handle *c)
1613 : {
1614 : (void) cls;
1615 : (void) args;
1616 : (void) cfgfile;
1617 :
1618 74 : cfg = c;
1619 74 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1620 : NULL);
1621 74 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1622 : "Launching purses auditor\n");
1623 74 : if (GNUNET_OK !=
1624 74 : TALER_ARL_init (c))
1625 : {
1626 0 : global_ret = EXIT_FAILURE;
1627 0 : return;
1628 : }
1629 74 : if (test_mode != 1)
1630 : {
1631 6 : struct GNUNET_DB_EventHeaderP es = {
1632 6 : .size = htons (sizeof (es)),
1633 6 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES)
1634 : };
1635 :
1636 6 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1637 : "Running helper indefinitely\n");
1638 6 : eh = TALER_ARL_adb->event_listen (TALER_ARL_adb->cls,
1639 : &es,
1640 6 : GNUNET_TIME_UNIT_FOREVER_REL,
1641 : &db_notify,
1642 : NULL);
1643 : }
1644 74 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1645 : "Starting audit\n");
1646 74 : if (GNUNET_OK !=
1647 74 : TALER_ARL_setup_sessions_and_run (&analyze_purses,
1648 : NULL))
1649 : {
1650 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1651 : "Audit failed\n");
1652 0 : GNUNET_SCHEDULER_shutdown ();
1653 0 : global_ret = EXIT_FAILURE;
1654 0 : return;
1655 : }
1656 : }
1657 :
1658 :
1659 : /**
1660 : * The main function to check the database's handling of purses.
1661 : *
1662 : * @param argc number of arguments from the command line
1663 : * @param argv command line arguments
1664 : * @return 0 ok, 1 on error
1665 : */
1666 : int
1667 74 : main (int argc,
1668 : char *const *argv)
1669 : {
1670 74 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
1671 74 : GNUNET_GETOPT_option_flag ('i',
1672 : "internal",
1673 : "perform checks only applicable for exchange-internal audits",
1674 : &internal_checks),
1675 74 : GNUNET_GETOPT_option_flag ('t',
1676 : "test",
1677 : "run in test mode and exit when idle",
1678 : &test_mode),
1679 74 : GNUNET_GETOPT_option_timetravel ('T',
1680 : "timetravel"),
1681 : GNUNET_GETOPT_OPTION_END
1682 : };
1683 : enum GNUNET_GenericReturnValue ret;
1684 :
1685 74 : ret = GNUNET_PROGRAM_run (
1686 : TALER_AUDITOR_project_data (),
1687 : argc,
1688 : argv,
1689 : "taler-helper-auditor-purses",
1690 : gettext_noop ("Audit Taler exchange purse handling"),
1691 : options,
1692 : &run,
1693 : NULL);
1694 74 : if (GNUNET_SYSERR == ret)
1695 0 : return EXIT_INVALIDARGUMENT;
1696 74 : if (GNUNET_NO == ret)
1697 0 : return EXIT_SUCCESS;
1698 74 : return global_ret;
1699 : }
1700 :
1701 :
1702 : /* end of taler-helper-auditor-purses.c */
|