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-reserves.c
18 : * @brief audits the reserves of an exchange database
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : /* RESERVE_IN's callback typedef also lives in exchangedb_lib.h (pulled in
23 : transitively by report-lib.h below), so its closure override must be
24 : established before any of those includes. */
25 : struct ReserveContext;
26 : #define TALER_EXCHANGEDB_RESERVE_IN_RESULT_CLOSURE struct ReserveContext
27 : #include "auditordb_lib.h"
28 : #include "report-lib.h"
29 : #include "taler/taler_dbevents.h"
30 : #include "exchangedb_lib.h"
31 : #include "exchange-database/get_reserve_close_request_info.h"
32 : #include "auditor-database/delete_reserve_info.h"
33 : #include "auditor-database/event_listen.h"
34 : #include "auditor-database/get_auditor_progress.h"
35 : #include "auditor-database/get_balance.h"
36 : #include "auditor-database/get_reserve_info.h"
37 : #include "auditor-database/insert_amount_arithmetic_inconsistency.h"
38 : #include "auditor-database/insert_auditor_progress.h"
39 : #include "auditor-database/insert_bad_sig_losses.h"
40 : #include "auditor-database/insert_balance.h"
41 : #include \
42 : "auditor-database/insert_denomination_key_validity_withdraw_inconsistency.h"
43 : #include \
44 : "auditor-database/insert_reserve_balance_insufficient_inconsistency.h"
45 : #include \
46 : "auditor-database/insert_reserve_balance_summary_wrong_inconsistency.h"
47 : #include "auditor-database/insert_reserve_info.h"
48 : #include "auditor-database/insert_reserve_not_closed_inconsistency.h"
49 : #include "auditor-database/insert_row_inconsistency.h"
50 : #include "auditor-database/update_reserve_info.h"
51 : #include "exchange-database/get_denomination_revocation.h"
52 : #include "exchange-database/get_wire_fee.h"
53 : #include "exchange-database/get_reserve.h"
54 : struct ReserveContext;
55 : #define TALER_EXCHANGEDB_ACCOUNT_MERGE_RESULT_CLOSURE struct ReserveContext
56 : #define TALER_EXCHANGEDB_PURSE_DECISION_RESULT_CLOSURE struct ReserveContext
57 : #define TALER_EXCHANGEDB_RECOUP_RESULT_CLOSURE struct ReserveContext
58 : #define TALER_EXCHANGEDB_RESERVE_CLOSED_RESULT_CLOSURE struct ReserveContext
59 : #define TALER_EXCHANGEDB_RESERVE_OPEN_RESULT_CLOSURE struct ReserveContext
60 : #define TALER_EXCHANGEDB_WITHDRAW_RESULT_CLOSURE struct ReserveContext
61 : #include "exchange-database/iterate_account_merges_above_serial_id.h"
62 : #include "exchange-database/iterate_purse_decisions_above_serial_id.h"
63 : #include "exchange-database/iterate_recoups_above_serial_id.h"
64 : #include "exchange-database/iterate_reserve_closed_above_serial_id.h"
65 : #include "exchange-database/iterate_reserve_open_above_serial_id.h"
66 : #include "exchange-database/iterate_reserves_in_above_serial_id.h"
67 : #include "exchange-database/iterate_withdrawals_above_serial_id.h"
68 :
69 : /**
70 : * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
71 : */
72 : #define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
73 :
74 : /**
75 : * Return value from main().
76 : */
77 : static int global_ret;
78 :
79 : /**
80 : * State of the last database transaction.
81 : */
82 : static enum GNUNET_DB_QueryStatus global_qs;
83 :
84 : /**
85 : * Run in test mode. Exit when idle instead of
86 : * going to sleep and waiting for more work.
87 : */
88 : static int test_mode;
89 :
90 : /**
91 : * After how long should idle reserves be closed?
92 : */
93 : static struct GNUNET_TIME_Relative idle_reserve_expiration_time;
94 :
95 : /**
96 : * Checkpointing our progress for reserves.
97 : */
98 : static TALER_ARL_DEF_PP (reserves_reserve_in_serial_id);
99 : static TALER_ARL_DEF_PP (reserves_withdraw_serial_id);
100 : static TALER_ARL_DEF_PP (reserves_reserve_recoup_serial_id);
101 : static TALER_ARL_DEF_PP (reserves_reserve_open_serial_id);
102 : static TALER_ARL_DEF_PP (reserves_reserve_close_serial_id);
103 : static TALER_ARL_DEF_PP (reserves_purse_decisions_serial_id);
104 : static TALER_ARL_DEF_PP (reserves_account_merges_serial_id);
105 : static TALER_ARL_DEF_PP (reserves_history_requests_serial_id);
106 :
107 : /**
108 : * Tracked global reserve balances.
109 : */
110 : static TALER_ARL_DEF_AB (reserves_reserve_total_balance);
111 : static TALER_ARL_DEF_AB (reserves_reserve_loss);
112 : static TALER_ARL_DEF_AB (reserves_withdraw_fee_revenue);
113 : static TALER_ARL_DEF_AB (reserves_close_fee_revenue);
114 : static TALER_ARL_DEF_AB (reserves_purse_fee_revenue);
115 : static TALER_ARL_DEF_AB (reserves_open_fee_revenue);
116 : static TALER_ARL_DEF_AB (reserves_history_fee_revenue);
117 :
118 : /**
119 : * Total amount lost by operations for which signatures were invalid.
120 : */
121 : static TALER_ARL_DEF_AB (reserves_total_bad_sig_loss);
122 :
123 : /**
124 : * Total amount affected by reserves not having been closed on time.
125 : */
126 : static TALER_ARL_DEF_AB (total_balance_reserve_not_closed);
127 :
128 : /**
129 : * Total delta between expected and stored reserve balance summaries,
130 : * for positive deltas. Used only when internal checks are
131 : * enabled.
132 : */
133 : static TALER_ARL_DEF_AB (total_balance_summary_delta_plus);
134 :
135 : /**
136 : * Total delta between expected and stored reserve balance summaries,
137 : * for negative deltas. Used only when internal checks are
138 : * enabled.
139 : */
140 : static TALER_ARL_DEF_AB (total_balance_summary_delta_minus);
141 :
142 : /**
143 : * Profits the exchange made by bad amount calculations.
144 : */
145 : static TALER_ARL_DEF_AB (reserves_total_arithmetic_delta_plus);
146 :
147 : /**
148 : * Losses the exchange made by bad amount calculations.
149 : */
150 : static TALER_ARL_DEF_AB (reserves_total_arithmetic_delta_minus);
151 :
152 : /**
153 : * Should we run checks that only work for exchange-internal audits?
154 : */
155 : static int internal_checks;
156 :
157 : static struct GNUNET_DB_EventHandler *eh;
158 :
159 : /**
160 : * The auditors's configuration.
161 : */
162 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
163 :
164 : /* ***************************** Report logic **************************** */
165 :
166 :
167 : /**
168 : * Report a (serious) inconsistency in the exchange's database with
169 : * respect to calculations involving amounts.
170 : *
171 : * @param operation what operation had the inconsistency
172 : * @param rowid affected row, 0 if row is missing
173 : * @param exchange amount calculated by exchange
174 : * @param auditor amount calculated by auditor
175 : * @param profitable 1 if @a exchange being larger than @a auditor is
176 : * profitable for the exchange for this operation,
177 : * -1 if @a exchange being smaller than @a auditor is
178 : * profitable for the exchange, and 0 if it is unclear
179 : */
180 : static void
181 0 : report_amount_arithmetic_inconsistency (
182 : const char *operation,
183 : uint64_t rowid,
184 : const struct TALER_Amount *exchange,
185 : const struct TALER_Amount *auditor,
186 : int profitable)
187 : {
188 : struct TALER_Amount delta;
189 : struct TALER_Amount *target;
190 : enum GNUNET_DB_QueryStatus qs;
191 :
192 0 : if (0 < TALER_amount_cmp (exchange,
193 : auditor))
194 : {
195 : /* exchange > auditor */
196 0 : TALER_ARL_amount_subtract (&delta,
197 : exchange,
198 : auditor);
199 : }
200 : else
201 : {
202 : /* exchange <= auditor */
203 0 : profitable = -profitable;
204 0 : TALER_ARL_amount_subtract (&delta,
205 : auditor,
206 : exchange);
207 : }
208 :
209 : {
210 0 : struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
211 : .problem_row_id = rowid,
212 : .profitable = profitable,
213 : .operation = (char *) operation,
214 : .exchange_amount = *exchange,
215 : .auditor_amount = *auditor,
216 : };
217 :
218 0 : qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
219 : TALER_ARL_adb,
220 : &aai);
221 :
222 0 : if (qs < 0)
223 : {
224 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
225 0 : global_qs = qs;
226 0 : return;
227 : }
228 : }
229 :
230 0 : if (0 != profitable)
231 : {
232 0 : target = (1 == profitable)
233 : ? &TALER_ARL_USE_AB (reserves_total_arithmetic_delta_plus)
234 0 : : &TALER_ARL_USE_AB (reserves_total_arithmetic_delta_minus);
235 0 : TALER_ARL_amount_add (target,
236 : target,
237 : &delta);
238 : }
239 : }
240 :
241 :
242 : /**
243 : * Report a (serious) inconsistency in the exchange's database.
244 : *
245 : * @param table affected table
246 : * @param rowid affected row, 0 if row is missing
247 : * @param diagnostic message explaining the problem
248 : */
249 : static void
250 0 : report_row_inconsistency (const char *table,
251 : uint64_t rowid,
252 : const char *diagnostic)
253 : {
254 : enum GNUNET_DB_QueryStatus qs;
255 0 : struct TALER_AUDITORDB_RowInconsistency ri = {
256 : .diagnostic = (char *) diagnostic,
257 : .row_table = (char *) table,
258 : .problem_row_id = rowid
259 : };
260 :
261 0 : qs = TALER_AUDITORDB_insert_row_inconsistency (
262 : TALER_ARL_adb,
263 : &ri);
264 :
265 0 : if (qs < 0)
266 : {
267 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
268 0 : global_qs = qs;
269 0 : return;
270 : }
271 : }
272 :
273 :
274 : /* ***************************** Analyze reserves ************************ */
275 : /* This logic checks the reserves_in, withdraw and reserves-tables */
276 :
277 : /**
278 : * Summary data we keep per reserve.
279 : */
280 : struct ReserveSummary
281 : {
282 : /**
283 : * Public key of the reserve.
284 : * Always set when the struct is first initialized.
285 : */
286 : struct TALER_ReservePublicKeyP reserve_pub;
287 :
288 : /**
289 : * Sum of all incoming transfers during this transaction.
290 : * Updated only in #handle_reserve_in().
291 : */
292 : struct TALER_Amount total_in;
293 :
294 : /**
295 : * Sum of all outgoing transfers during this transaction (includes fees).
296 : * Updated only in #handle_withdrawals().
297 : */
298 : struct TALER_Amount total_out;
299 :
300 : /**
301 : * Sum of balance and fees encountered during this transaction.
302 : */
303 : struct TALER_AUDITORDB_ReserveFeeBalance curr_balance;
304 :
305 : /**
306 : * Previous balances of the reserve as remembered by the auditor.
307 : * (updated based on @e total_in and @e total_out at the end).
308 : */
309 : struct TALER_AUDITORDB_ReserveFeeBalance prev_balance;
310 :
311 : /**
312 : * Previous reserve expiration data, as remembered by the auditor.
313 : * (updated on-the-fly in #handle_reserve_in()).
314 : */
315 : struct GNUNET_TIME_Timestamp a_expiration_date;
316 :
317 : /**
318 : * Which account did originally put money into the reserve?
319 : */
320 : struct TALER_FullPayto sender_account;
321 :
322 : /**
323 : * Did we have a previous reserve info? Used to decide between
324 : * UPDATE and INSERT later. Initialized in
325 : * #load_auditor_reserve_summary() together with the a-* values
326 : * (if available).
327 : */
328 : bool had_ri;
329 :
330 : };
331 :
332 :
333 : /**
334 : * Load the auditor's remembered state about the reserve into @a rs.
335 : * The "total_in" and "total_out" amounts of @a rs must already be
336 : * initialized (so we can determine the currency).
337 : *
338 : * @param[in,out] rs reserve summary to (fully) initialize
339 : * @return transaction status code
340 : */
341 : static enum GNUNET_DB_QueryStatus
342 16 : load_auditor_reserve_summary (struct ReserveSummary *rs)
343 : {
344 : enum GNUNET_DB_QueryStatus qs;
345 : uint64_t rowid;
346 :
347 16 : qs = TALER_AUDITORDB_get_reserve_info (TALER_ARL_adb,
348 16 : &rs->reserve_pub,
349 : &rowid,
350 : &rs->prev_balance,
351 : &rs->a_expiration_date,
352 : &rs->sender_account);
353 16 : if (0 > qs)
354 : {
355 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
356 0 : return qs;
357 : }
358 16 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
359 : {
360 16 : rs->had_ri = false;
361 16 : GNUNET_assert (GNUNET_OK ==
362 : TALER_amount_set_zero (rs->total_in.currency,
363 : &rs->prev_balance.reserve_balance));
364 16 : GNUNET_assert (GNUNET_OK ==
365 : TALER_amount_set_zero (rs->total_in.currency,
366 : &rs->prev_balance.reserve_loss));
367 16 : GNUNET_assert (GNUNET_OK ==
368 : TALER_amount_set_zero (rs->total_in.currency,
369 : &rs->prev_balance.withdraw_fee_balance
370 : ));
371 16 : GNUNET_assert (GNUNET_OK ==
372 : TALER_amount_set_zero (rs->total_in.currency,
373 : &rs->prev_balance.close_fee_balance));
374 16 : GNUNET_assert (GNUNET_OK ==
375 : TALER_amount_set_zero (rs->total_in.currency,
376 : &rs->prev_balance.purse_fee_balance));
377 16 : GNUNET_assert (GNUNET_OK ==
378 : TALER_amount_set_zero (rs->total_in.currency,
379 : &rs->prev_balance.open_fee_balance));
380 16 : GNUNET_assert (GNUNET_OK ==
381 : TALER_amount_set_zero (rs->total_in.currency,
382 : &rs->prev_balance.history_fee_balance)
383 : );
384 16 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
385 : "Creating fresh reserve `%s'\n",
386 : TALER_B2S (&rs->reserve_pub));
387 16 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
388 : }
389 0 : rs->had_ri = true;
390 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
391 : "Auditor remembers reserve `%s' has balance %s\n",
392 : TALER_B2S (&rs->reserve_pub),
393 : TALER_amount2s (&rs->prev_balance.reserve_balance));
394 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
395 : }
396 :
397 :
398 : /**
399 : * Closure to the various callbacks we make while checking a reserve.
400 : */
401 : struct ReserveContext
402 : {
403 : /**
404 : * Map from hash of reserve's public key to a `struct ReserveSummary`.
405 : */
406 : struct GNUNET_CONTAINER_MultiHashMap *reserves;
407 :
408 : /**
409 : * Map from hash of denomination's public key to a
410 : * static string "revoked" for keys that have been revoked,
411 : * or "master signature invalid" in case the revocation is
412 : * there but bogus.
413 : */
414 : struct GNUNET_CONTAINER_MultiHashMap *revoked;
415 :
416 : /**
417 : * Transaction status code, set to error codes if applicable.
418 : */
419 : enum GNUNET_DB_QueryStatus qs;
420 :
421 : };
422 :
423 :
424 : /**
425 : * Create a new reserve for @a reserve_pub in @a rc.
426 : *
427 : * @param[in,out] rc context to update
428 : * @param reserve_pub key for which to create a reserve
429 : * @return NULL on error
430 : */
431 : static struct ReserveSummary *
432 58 : setup_reserve (struct ReserveContext *rc,
433 : const struct TALER_ReservePublicKeyP *reserve_pub)
434 : {
435 : struct ReserveSummary *rs;
436 : struct GNUNET_HashCode key;
437 : enum GNUNET_DB_QueryStatus qs;
438 :
439 58 : GNUNET_CRYPTO_hash (reserve_pub,
440 : sizeof (*reserve_pub),
441 : &key);
442 58 : rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
443 : &key);
444 58 : if (NULL != rs)
445 42 : return rs;
446 16 : rs = GNUNET_new (struct ReserveSummary);
447 16 : rs->reserve_pub = *reserve_pub;
448 16 : GNUNET_assert (GNUNET_OK ==
449 : TALER_amount_set_zero (TALER_ARL_currency,
450 : &rs->total_in));
451 16 : GNUNET_assert (GNUNET_OK ==
452 : TALER_amount_set_zero (TALER_ARL_currency,
453 : &rs->total_out));
454 16 : GNUNET_assert (GNUNET_OK ==
455 : TALER_amount_set_zero (TALER_ARL_currency,
456 : &rs->curr_balance.reserve_balance));
457 16 : GNUNET_assert (GNUNET_OK ==
458 : TALER_amount_set_zero (TALER_ARL_currency,
459 : &rs->curr_balance.reserve_loss));
460 16 : GNUNET_assert (GNUNET_OK ==
461 : TALER_amount_set_zero (TALER_ARL_currency,
462 : &rs->curr_balance.withdraw_fee_balance))
463 : ;
464 16 : GNUNET_assert (GNUNET_OK ==
465 : TALER_amount_set_zero (TALER_ARL_currency,
466 : &rs->curr_balance.close_fee_balance));
467 16 : GNUNET_assert (GNUNET_OK ==
468 : TALER_amount_set_zero (TALER_ARL_currency,
469 : &rs->curr_balance.purse_fee_balance));
470 16 : GNUNET_assert (GNUNET_OK ==
471 : TALER_amount_set_zero (TALER_ARL_currency,
472 : &rs->curr_balance.open_fee_balance));
473 16 : GNUNET_assert (GNUNET_OK ==
474 : TALER_amount_set_zero (TALER_ARL_currency,
475 : &rs->curr_balance.history_fee_balance));
476 16 : if (0 > (qs = load_auditor_reserve_summary (rs)))
477 : {
478 0 : GNUNET_free (rs);
479 0 : rc->qs = qs;
480 0 : return NULL;
481 : }
482 16 : GNUNET_assert (GNUNET_OK ==
483 : GNUNET_CONTAINER_multihashmap_put (rc->reserves,
484 : &key,
485 : rs,
486 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)
487 : );
488 16 : return rs;
489 : }
490 :
491 :
492 : /**
493 : * Function called with details about incoming wire transfers.
494 : *
495 : * @param rc our `struct ReserveContext`
496 : * @param rowid unique serial ID for the refresh session in our DB
497 : * @param reserve_pub public key of the reserve (also the WTID)
498 : * @param credit amount that was received
499 : * @param sender_account_details information about the sender's bank account
500 : * @param wire_reference unique reference identifying the wire transfer
501 : * @param execution_date when did we receive the funds
502 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
503 : */
504 : static enum GNUNET_GenericReturnValue
505 16 : handle_reserve_in (
506 : struct ReserveContext *rc,
507 : uint64_t rowid,
508 : const struct TALER_ReservePublicKeyP *reserve_pub,
509 : const struct TALER_Amount *credit,
510 : const struct TALER_FullPayto sender_account_details,
511 : uint64_t wire_reference,
512 : struct GNUNET_TIME_Timestamp execution_date)
513 : {
514 : struct ReserveSummary *rs;
515 : struct GNUNET_TIME_Timestamp expiry;
516 :
517 : (void) wire_reference;
518 : /* should be monotonically increasing */
519 16 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_in_serial_id));
520 16 : TALER_ARL_USE_PP (reserves_reserve_in_serial_id) = rowid + 1;
521 16 : rs = setup_reserve (rc,
522 : reserve_pub);
523 16 : if (NULL == rs)
524 : {
525 0 : GNUNET_break (0);
526 0 : return GNUNET_SYSERR;
527 : }
528 16 : if (NULL == rs->sender_account.full_payto)
529 : rs->sender_account.full_payto
530 16 : = GNUNET_strdup (sender_account_details.full_payto);
531 16 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
532 : "Additional incoming wire transfer for reserve `%s' of %s\n",
533 : TALER_B2S (reserve_pub),
534 : TALER_amount2s (credit));
535 16 : expiry = GNUNET_TIME_absolute_to_timestamp (
536 : GNUNET_TIME_absolute_add (execution_date.abs_time,
537 : idle_reserve_expiration_time));
538 16 : rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date,
539 : expiry);
540 16 : TALER_ARL_amount_add (&rs->total_in,
541 : &rs->total_in,
542 : credit);
543 16 : return GNUNET_OK;
544 : }
545 :
546 :
547 : /**
548 : * Function called with details about withdraw operations. Verifies
549 : * the signature and updates the reserve's balance.
550 : *
551 : * @param rc our `struct ReserveContext`
552 : * @param rowid unique serial ID for the refresh session in our DB
553 : * @param num_denom_serials number of elements in @e denom_serials array
554 : * @param denom_serials array with length @e num_denom_serials of serial ID's of denominations in our DB
555 : * @param selected_h hash over the gamma-selected planchets
556 : * @param h_planchets running hash over all hashes of blinded planchets in the original withdraw request
557 : * @param blinding_seed the blinding seed for CS denominations that was provided during withdraw; might be NULL
558 : * @param age_proof_required true if the withdraw request required an age proof.
559 : * @param max_age if @e age_proof_required is true, the maximum age that was set on the coins.
560 : * @param noreveal_index if @e age_proof_required is true, the index that was returned by the exchange for the reveal phase.
561 : * @param reserve_pub public key of the reserve
562 : * @param reserve_sig signature over the withdraw operation
563 : * @param execution_date when did the wallet withdraw the coin
564 : * @param amount_with_fee amount that was withdrawn
565 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
566 : */
567 : static enum GNUNET_GenericReturnValue
568 36 : handle_withdrawals (
569 : struct ReserveContext *rc,
570 : uint64_t rowid,
571 : size_t num_denom_serials,
572 : const uint64_t *denom_serials,
573 : const struct TALER_HashBlindedPlanchetsP *selected_h,
574 : const struct TALER_HashBlindedPlanchetsP *h_planchets,
575 : const struct TALER_BlindingMasterSeedP *blinding_seed,
576 : bool age_proof_required,
577 : uint8_t max_age,
578 : uint8_t noreveal_index,
579 : const struct TALER_ReservePublicKeyP *reserve_pub,
580 : const struct TALER_ReserveSignatureP *reserve_sig,
581 : struct GNUNET_TIME_Timestamp execution_date,
582 : const struct TALER_Amount *amount_with_fee)
583 : {
584 : struct ReserveSummary *rs;
585 : struct TALER_Amount auditor_amount;
586 : struct TALER_Amount auditor_fee;
587 : struct TALER_Amount auditor_amount_with_fee;
588 36 : struct TALER_AgeMask age_mask = { 0 };
589 36 : bool have_age_mask = false;
590 36 : bool missing_denom = false;
591 : enum GNUNET_DB_QueryStatus qs;
592 :
593 : /* should be monotonically increasing */
594 36 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
595 : "Analyzing withdrawal row %llu\n",
596 : (unsigned long long) rowid);
597 36 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_withdraw_serial_id));
598 36 : TALER_ARL_USE_PP (reserves_withdraw_serial_id) = rowid + 1;
599 :
600 36 : GNUNET_assert (GNUNET_OK ==
601 : TALER_amount_set_zero (TALER_ARL_currency,
602 : &auditor_amount));
603 36 : GNUNET_assert (GNUNET_OK ==
604 : TALER_amount_set_zero (TALER_ARL_currency,
605 : &auditor_fee));
606 36 : GNUNET_assert (GNUNET_OK ==
607 : TALER_amount_set_zero (TALER_ARL_currency,
608 : &auditor_amount_with_fee));
609 36 : rs = setup_reserve (rc,
610 : reserve_pub);
611 36 : if (NULL == rs)
612 : {
613 0 : GNUNET_break (0);
614 0 : return GNUNET_SYSERR;
615 : }
616 72 : for (size_t i = 0; i < num_denom_serials; i++)
617 : {
618 : const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
619 :
620 : /* lookup denomination pub data (make sure denom_pub is valid, establish fees);
621 : initializes wsrd.h_denomination_pub! */
622 36 : qs = TALER_ARL_get_denomination_info_by_serial (denom_serials[i],
623 : &issue);
624 36 : if (0 > qs)
625 : {
626 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
627 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
628 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
629 : "Hard database error trying to get denomination by serial %llu (%s) from database!\n",
630 : (unsigned long long) denom_serials[i],
631 : GNUNET_h2s (&h_planchets->hash));
632 0 : rc->qs = qs;
633 0 : return GNUNET_SYSERR;
634 : }
635 36 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
636 : {
637 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
638 : "Denomination #%llu not found\n",
639 : (unsigned long long) denom_serials[i]);
640 0 : report_row_inconsistency ("withdraw",
641 : rowid,
642 : "denomination key not found");
643 0 : if (global_qs < 0)
644 0 : return GNUNET_SYSERR;
645 : /* We cannot account for this planchet, but the other planchets
646 : of the batch must still be processed. */
647 0 : missing_denom = true;
648 0 : continue;
649 : }
650 36 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
651 : "Analyzing withdrawn denomination #%llu (%s)\n",
652 : (unsigned long long) denom_serials[i],
653 : TALER_amount2s (&issue->value));
654 36 : if (! have_age_mask)
655 : {
656 36 : age_mask = issue->age_mask;
657 36 : have_age_mask = true;
658 : }
659 0 : else if (age_mask.bits != issue->age_mask.bits)
660 : {
661 : /* All denominations of one batch must share the age mask, as the
662 : reserve signature is made over a single age mask. */
663 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
664 : "Denominations of withdraw row %llu disagree on the age mask\n",
665 : (unsigned long long) rowid);
666 0 : report_row_inconsistency ("withdraw",
667 : rowid,
668 : "denominations of the batch disagree on the age mask");
669 0 : if (global_qs < 0)
670 0 : return GNUNET_SYSERR;
671 : }
672 :
673 : /* check that execution date is within withdraw range for denom_pub */
674 36 : GNUNET_log (
675 : GNUNET_ERROR_TYPE_DEBUG,
676 : "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n",
677 : (unsigned long long) issue->start.abs_time.abs_value_us,
678 : (unsigned long long) issue->expire_withdraw.abs_time.abs_value_us,
679 : (unsigned long long) execution_date.abs_time.abs_value_us);
680 36 : if (GNUNET_TIME_timestamp_cmp (issue->start,
681 : >,
682 36 : execution_date) ||
683 36 : GNUNET_TIME_timestamp_cmp (issue->expire_withdraw,
684 : <,
685 : execution_date))
686 : {
687 : struct TALER_AUDITORDB_DenominationKeyValidityWithdrawInconsistency
688 0 : dkvwi = {
689 : .problem_row_id = rowid,
690 : .execution_date = execution_date.abs_time,
691 0 : .denompub_h = issue->denom_hash,
692 : .reserve_pub = *reserve_pub
693 : };
694 :
695 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
696 : "Withdraw outside of denomination #%llu validity period detected\n",
697 : (unsigned long long) denom_serials[i]);
698 : qs =
699 0 : TALER_AUDITORDB_insert_denomination_key_validity_withdraw_inconsistency
700 : (
701 : TALER_ARL_adb,
702 : &dkvwi);
703 :
704 0 : if (qs < 0)
705 : {
706 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
707 0 : rc->qs = qs;
708 0 : return GNUNET_SYSERR;
709 : }
710 : }
711 36 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
712 : "Adding withdraw fee of denomination (%s)\n",
713 : TALER_amount2s (&issue->fees.withdraw));
714 36 : TALER_ARL_amount_add (&auditor_amount,
715 : &auditor_amount,
716 : &issue->value);
717 36 : TALER_ARL_amount_add (&auditor_fee,
718 : &auditor_fee,
719 : &issue->fees.withdraw);
720 : {
721 : struct TALER_Amount issue_amount_with_fee;
722 :
723 36 : TALER_ARL_amount_add (&issue_amount_with_fee,
724 : &issue->value,
725 : &issue->fees.withdraw);
726 36 : TALER_ARL_amount_add (&auditor_amount_with_fee,
727 : &auditor_amount_with_fee,
728 : &issue_amount_with_fee);
729 : }
730 36 : TALER_ARL_amount_add (&rs->curr_balance.withdraw_fee_balance,
731 : &rs->curr_balance.withdraw_fee_balance,
732 : &issue->fees.withdraw);
733 36 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_withdraw_fee_revenue),
734 : &TALER_ARL_USE_AB (reserves_withdraw_fee_revenue),
735 : &issue->fees.withdraw);
736 : } /* end for all coins in batch */
737 :
738 36 : if (missing_denom)
739 : {
740 : /* We do not know the value of the entire batch, so we can check
741 : neither the reserve signature nor the total; still debit the
742 : reserve by the amount the exchange claims. The row inconsistency
743 : reported above states that we could not verify it. */
744 0 : TALER_ARL_amount_add (&rs->total_out,
745 : &rs->total_out,
746 : amount_with_fee);
747 0 : return GNUNET_OK;
748 : }
749 36 : if (age_proof_required &&
750 0 : (! have_age_mask))
751 : {
752 : /* Without a denomination we do not know the age mask the reserve
753 : signature was made over. */
754 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
755 : "Age-restricted withdraw row %llu has no denominations\n",
756 : (unsigned long long) rowid);
757 0 : report_row_inconsistency ("withdraw",
758 : rowid,
759 : "no denomination given for age-restricted withdraw");
760 0 : if (global_qs < 0)
761 0 : return GNUNET_SYSERR;
762 0 : return GNUNET_OK;
763 : }
764 :
765 : /* check reserve_sig (first: setup remaining members of wsrd) */
766 36 : if (GNUNET_OK !=
767 36 : TALER_wallet_withdraw_verify (
768 : &auditor_amount,
769 : &auditor_fee,
770 : h_planchets,
771 : blinding_seed,
772 : age_proof_required
773 : ? &age_mask
774 : : NULL,
775 : age_proof_required
776 : ? max_age
777 : : 0,
778 : reserve_pub,
779 : reserve_sig))
780 : {
781 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
782 : .problem_row_id = rowid,
783 : .operation = (char *) "withdraw",
784 : .loss = *amount_with_fee,
785 : .operation_specific_pub = reserve_pub->eddsa_pub
786 : };
787 :
788 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
789 : "Withdraw signature invalid (row #%llu)\n",
790 : (unsigned long long) rowid);
791 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
792 : TALER_ARL_adb,
793 : &bsl);
794 0 : if (qs < 0)
795 : {
796 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
797 0 : rc->qs = qs;
798 0 : return GNUNET_SYSERR;
799 : }
800 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
801 : &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
802 : amount_with_fee);
803 0 : return GNUNET_OK; /* exit function here, we cannot add this to the legitimate withdrawals */
804 : }
805 :
806 36 : if (0 !=
807 36 : TALER_amount_cmp (&auditor_amount_with_fee,
808 : amount_with_fee))
809 : {
810 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
811 : "Withdraw fee inconsistent (row #%llu)\n",
812 : (unsigned long long) rowid);
813 0 : report_row_inconsistency ("withdraw",
814 : rowid,
815 : "amount with fee from exchange does not match denomination value plus fee");
816 0 : if (global_qs < 0)
817 : {
818 0 : GNUNET_break (0);
819 0 : return GNUNET_SYSERR;
820 : }
821 : }
822 36 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
823 : "Reserve `%s' reduced by %s from withdraw\n",
824 : TALER_B2S (reserve_pub),
825 : TALER_amount2s (&auditor_amount_with_fee));
826 36 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
827 : "Increasing withdraw profits by fee %s\n",
828 : TALER_amount2s (&auditor_fee));
829 36 : TALER_ARL_amount_add (&rs->total_out,
830 : &rs->total_out,
831 : &auditor_amount_with_fee);
832 36 : return GNUNET_OK;
833 : }
834 :
835 :
836 : /**
837 : * Function called with details about withdraw operations. Verifies
838 : * the signature and updates the reserve's balance.
839 : *
840 : * @param rc our `struct ReserveContext`
841 : * @param rowid unique serial ID for the refresh session in our DB
842 : * @param timestamp when did we receive the recoup request
843 : * @param amount how much should be added back to the reserve
844 : * @param reserve_pub public key of the reserve
845 : * @param coin public information about the coin, denomination signature is
846 : * already verified in #check_recoup()
847 : * @param denom_pub public key of the denomionation of @a coin
848 : * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
849 : * @param coin_blind blinding factor used to blind the coin
850 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
851 : */
852 : static enum GNUNET_GenericReturnValue
853 4 : handle_recoup_by_reserve (
854 : struct ReserveContext *rc,
855 : uint64_t rowid,
856 : struct GNUNET_TIME_Timestamp timestamp,
857 : const struct TALER_Amount *amount,
858 : const struct TALER_ReservePublicKeyP *reserve_pub,
859 : const struct TALER_CoinPublicInfo *coin,
860 : const struct TALER_DenominationPublicKey *denom_pub,
861 : const struct TALER_CoinSpendSignatureP *coin_sig,
862 : const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
863 : {
864 : struct ReserveSummary *rs;
865 : struct GNUNET_TIME_Timestamp expiry;
866 : struct TALER_MasterSignatureP msig;
867 : uint64_t rev_rowid;
868 : enum GNUNET_DB_QueryStatus qs;
869 : const char *rev;
870 :
871 : (void) denom_pub;
872 : /* should be monotonically increasing */
873 4 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id));
874 4 : TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id) = rowid + 1;
875 : /* We know that denom_pub matches denom_pub_hash because this
876 : is how the SQL statement joined the tables. */
877 4 : if (GNUNET_OK !=
878 4 : TALER_wallet_recoup_verify (&coin->denom_pub_hash,
879 : coin_blind,
880 : &coin->coin_pub,
881 : coin_sig))
882 : {
883 0 : struct TALER_AUDITORDB_BadSigLosses bslr = {
884 : .problem_row_id = rowid,
885 : .operation = (char *) "recoup",
886 : .loss = *amount,
887 : .operation_specific_pub = coin->coin_pub.eddsa_pub
888 : };
889 :
890 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
891 : TALER_ARL_adb,
892 : &bslr);
893 :
894 0 : if (qs < 0)
895 : {
896 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
897 0 : rc->qs = qs;
898 0 : return GNUNET_SYSERR;
899 : }
900 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
901 : &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
902 : amount);
903 : /* We continue here intentionally to credit the reserve anyway,
904 : we already accounted for the loss, so we need to continue to
905 : avoid getting another error when the reserve is drained which
906 : would double-count the damage. */
907 : }
908 :
909 : /* check that the coin was eligible for recoup!*/
910 4 : rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked,
911 : &coin->denom_pub_hash.hash);
912 4 : if (NULL == rev)
913 : {
914 4 : qs = TALER_EXCHANGEDB_get_denomination_revocation (TALER_ARL_edb,
915 : &coin->denom_pub_hash,
916 : &msig,
917 : &rev_rowid);
918 4 : if (0 > qs)
919 : {
920 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
921 0 : rc->qs = qs;
922 0 : return GNUNET_SYSERR;
923 : }
924 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
925 : {
926 0 : report_row_inconsistency ("recoup",
927 : rowid,
928 : "denomination key not in revocation set");
929 0 : if (global_qs < 0)
930 0 : return GNUNET_SYSERR;
931 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
932 : &TALER_ARL_USE_AB (reserves_reserve_loss),
933 : amount);
934 : }
935 : else
936 : {
937 4 : if (GNUNET_OK !=
938 4 : TALER_exchange_offline_denomination_revoke_verify (
939 : &coin->denom_pub_hash,
940 : &TALER_ARL_master_pub,
941 : &msig))
942 : {
943 0 : rev = "master signature invalid";
944 : }
945 : else
946 : {
947 4 : rev = "revoked";
948 : }
949 4 : GNUNET_assert (
950 : GNUNET_OK ==
951 : GNUNET_CONTAINER_multihashmap_put (
952 : rc->revoked,
953 : &coin->denom_pub_hash.hash,
954 : (void *) rev,
955 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
956 : }
957 : }
958 : else
959 : {
960 0 : rev_rowid = 0; /* reported elsewhere */
961 : }
962 4 : if ((NULL != rev) &&
963 4 : (0 == strcmp (rev,
964 : "master signature invalid")))
965 : {
966 0 : struct TALER_AUDITORDB_BadSigLosses bslrm = {
967 : .problem_row_id = rev_rowid,
968 : .operation = (char *) "recoup-master",
969 : .loss = *amount,
970 : .operation_specific_pub = TALER_ARL_master_pub.eddsa_pub
971 : };
972 :
973 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
974 : TALER_ARL_adb,
975 : &bslrm);
976 :
977 0 : if (qs < 0)
978 : {
979 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
980 0 : rc->qs = qs;
981 0 : return GNUNET_SYSERR;
982 : }
983 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
984 : &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
985 : amount);
986 : }
987 :
988 4 : rs = setup_reserve (rc,
989 : reserve_pub);
990 4 : if (NULL == rs)
991 : {
992 0 : GNUNET_break (0);
993 0 : return GNUNET_SYSERR;
994 : }
995 4 : TALER_ARL_amount_add (&rs->total_in,
996 : &rs->total_in,
997 : amount);
998 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
999 : "Additional /recoup value to for reserve `%s' of %s\n",
1000 : TALER_B2S (reserve_pub),
1001 : TALER_amount2s (amount));
1002 4 : expiry = GNUNET_TIME_absolute_to_timestamp (
1003 : GNUNET_TIME_absolute_add (timestamp.abs_time,
1004 : idle_reserve_expiration_time));
1005 4 : rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date,
1006 : expiry);
1007 4 : return GNUNET_OK;
1008 : }
1009 :
1010 :
1011 : /**
1012 : * Obtain the closing fee for a transfer at @a time for target
1013 : * @a receiver_account.
1014 : *
1015 : * @param receiver_account payto:// URI of the target account
1016 : * @param atime when was the transfer made
1017 : * @param[out] fee set to the closing fee
1018 : * @return #GNUNET_OK on success
1019 : */
1020 : static enum GNUNET_GenericReturnValue
1021 2 : get_closing_fee (const struct TALER_FullPayto receiver_account,
1022 : struct GNUNET_TIME_Timestamp atime,
1023 : struct TALER_Amount *fee)
1024 : {
1025 : struct TALER_MasterSignatureP master_sig;
1026 : struct GNUNET_TIME_Timestamp start_date;
1027 : struct GNUNET_TIME_Timestamp end_date;
1028 : struct TALER_WireFeeSet fees;
1029 : char *method;
1030 : uint64_t rowid;
1031 :
1032 2 : method = TALER_payto_get_method (receiver_account.full_payto);
1033 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1034 : "Method is `%s'\n",
1035 : method);
1036 2 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1037 2 : TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
1038 : method,
1039 : atime,
1040 : &rowid,
1041 : &start_date,
1042 : &end_date,
1043 : &fees,
1044 : &master_sig))
1045 : {
1046 : char *diag;
1047 :
1048 0 : GNUNET_asprintf (&diag,
1049 : "closing fee for `%s' unavailable at %s\n",
1050 : method,
1051 : GNUNET_TIME_timestamp2s (atime));
1052 0 : report_row_inconsistency ("closing-fee",
1053 : rowid,
1054 : diag);
1055 0 : GNUNET_free (diag);
1056 0 : GNUNET_free (method);
1057 0 : return GNUNET_SYSERR;
1058 : }
1059 2 : *fee = fees.closing;
1060 2 : GNUNET_free (method);
1061 2 : return GNUNET_OK;
1062 : }
1063 :
1064 :
1065 : /**
1066 : * Function called about reserve opening operations.
1067 : *
1068 : * @param rc closure
1069 : * @param rowid row identifier used to uniquely identify the reserve closing operation
1070 : * @param reserve_payment how much to pay from the
1071 : * reserve's own balance for opening the reserve
1072 : * @param request_timestamp when was the request created
1073 : * @param reserve_expiration desired expiration time for the reserve
1074 : * @param purse_limit minimum number of purses the client
1075 : * wants to have concurrently open for this reserve
1076 : * @param reserve_pub public key of the reserve
1077 : * @param reserve_sig signature affirming the operation
1078 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
1079 : */
1080 : static enum GNUNET_GenericReturnValue
1081 0 : handle_reserve_open (
1082 : struct ReserveContext *rc,
1083 : uint64_t rowid,
1084 : const struct TALER_Amount *reserve_payment,
1085 : struct GNUNET_TIME_Timestamp request_timestamp,
1086 : struct GNUNET_TIME_Timestamp reserve_expiration,
1087 : uint32_t purse_limit,
1088 : const struct TALER_ReservePublicKeyP *reserve_pub,
1089 : const struct TALER_ReserveSignatureP *reserve_sig)
1090 : {
1091 : struct ReserveSummary *rs;
1092 : enum GNUNET_DB_QueryStatus qs;
1093 :
1094 : /* should be monotonically increasing */
1095 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_open_serial_id));
1096 0 : TALER_ARL_USE_PP (reserves_reserve_open_serial_id) = rowid + 1;
1097 :
1098 0 : rs = setup_reserve (rc,
1099 : reserve_pub);
1100 0 : if (NULL == rs)
1101 : {
1102 0 : GNUNET_break (0);
1103 0 : return GNUNET_SYSERR;
1104 : }
1105 0 : if (GNUNET_OK !=
1106 0 : TALER_wallet_reserve_open_verify (reserve_payment,
1107 : request_timestamp,
1108 : reserve_expiration,
1109 : purse_limit,
1110 : reserve_pub,
1111 : reserve_sig))
1112 : {
1113 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
1114 : .problem_row_id = rowid,
1115 : .operation = (char *) "reserve-open",
1116 : .loss = *reserve_payment,
1117 : .operation_specific_pub = reserve_pub->eddsa_pub
1118 : };
1119 :
1120 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
1121 : TALER_ARL_adb,
1122 : &bsl);
1123 :
1124 0 : if (qs < 0)
1125 : {
1126 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1127 0 : rc->qs = qs;
1128 0 : return GNUNET_SYSERR;
1129 : }
1130 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
1131 : &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
1132 : reserve_payment);
1133 0 : return GNUNET_OK;
1134 : }
1135 0 : TALER_ARL_amount_add (&rs->curr_balance.open_fee_balance,
1136 : &rs->curr_balance.open_fee_balance,
1137 : reserve_payment);
1138 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_open_fee_revenue),
1139 : &TALER_ARL_USE_AB (reserves_open_fee_revenue),
1140 : reserve_payment);
1141 0 : TALER_ARL_amount_add (&rs->total_out,
1142 : &rs->total_out,
1143 : reserve_payment);
1144 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1145 : "Additional open operation for reserve `%s' of %s\n",
1146 : TALER_B2S (reserve_pub),
1147 : TALER_amount2s (reserve_payment));
1148 0 : return GNUNET_OK;
1149 : }
1150 :
1151 :
1152 : /**
1153 : * Function called about reserve closing operations
1154 : * the aggregator triggered.
1155 : *
1156 : * @param rc closure
1157 : * @param rowid row identifier used to uniquely identify the reserve closing operation
1158 : * @param execution_date when did we execute the close operation
1159 : * @param amount_with_fee how much did we debit the reserve
1160 : * @param closing_fee how much did we charge for closing the reserve
1161 : * @param reserve_pub public key of the reserve
1162 : * @param receiver_account where did we send the funds
1163 : * @param transfer_details details about the wire transfer
1164 : * @param close_request_row which close request triggered the operation?
1165 : * 0 if it was a timeout
1166 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
1167 : */
1168 : static enum GNUNET_GenericReturnValue
1169 2 : handle_reserve_closed (
1170 : struct ReserveContext *rc,
1171 : uint64_t rowid,
1172 : struct GNUNET_TIME_Timestamp execution_date,
1173 : const struct TALER_Amount *amount_with_fee,
1174 : const struct TALER_Amount *closing_fee,
1175 : const struct TALER_ReservePublicKeyP *reserve_pub,
1176 : const struct TALER_FullPayto receiver_account,
1177 : const struct TALER_WireTransferIdentifierRawP *transfer_details,
1178 : uint64_t close_request_row)
1179 : {
1180 : struct ReserveSummary *rs;
1181 :
1182 : (void) transfer_details;
1183 : /* should be monotonically increasing */
1184 2 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_close_serial_id));
1185 2 : TALER_ARL_USE_PP (reserves_reserve_close_serial_id) = rowid + 1;
1186 :
1187 2 : rs = setup_reserve (rc,
1188 : reserve_pub);
1189 2 : if (NULL == rs)
1190 : {
1191 0 : GNUNET_break (0);
1192 0 : return GNUNET_SYSERR;
1193 : }
1194 : {
1195 : struct TALER_Amount expected_fee;
1196 :
1197 : /* verify closing_fee is correct! */
1198 2 : if (GNUNET_OK !=
1199 2 : get_closing_fee (receiver_account,
1200 : execution_date,
1201 : &expected_fee))
1202 : {
1203 0 : GNUNET_break (0);
1204 : }
1205 2 : else if (0 != TALER_amount_cmp (&expected_fee,
1206 : closing_fee))
1207 : {
1208 0 : report_amount_arithmetic_inconsistency (
1209 : "closing aggregation fee",
1210 : rowid,
1211 : closing_fee,
1212 : &expected_fee,
1213 : 1);
1214 0 : if (global_qs < 0)
1215 0 : return GNUNET_SYSERR;
1216 : }
1217 : }
1218 :
1219 2 : TALER_ARL_amount_add (&rs->curr_balance.close_fee_balance,
1220 : &rs->curr_balance.close_fee_balance,
1221 : closing_fee);
1222 2 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_close_fee_revenue),
1223 : &TALER_ARL_USE_AB (reserves_close_fee_revenue),
1224 : closing_fee);
1225 2 : TALER_ARL_amount_add (&rs->total_out,
1226 : &rs->total_out,
1227 : amount_with_fee);
1228 2 : if (0 != close_request_row)
1229 : {
1230 : struct TALER_ReserveSignatureP reserve_sig;
1231 : struct GNUNET_TIME_Timestamp request_timestamp;
1232 : struct TALER_Amount close_balance;
1233 : struct TALER_Amount close_fee;
1234 0 : struct TALER_FullPayto payto_uri = { NULL };
1235 : enum GNUNET_DB_QueryStatus qs;
1236 :
1237 0 : qs = TALER_EXCHANGEDB_get_reserve_close_request_info (
1238 : TALER_ARL_edb,
1239 : reserve_pub,
1240 : close_request_row,
1241 : &reserve_sig,
1242 : &request_timestamp,
1243 : &close_balance,
1244 : &close_fee,
1245 : &payto_uri);
1246 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
1247 : {
1248 0 : report_row_inconsistency ("reserves_close",
1249 : rowid,
1250 : "reserve close request unknown");
1251 0 : if (global_qs < 0)
1252 0 : return GNUNET_SYSERR;
1253 : }
1254 : else
1255 : {
1256 : struct TALER_FullPaytoHashP h_payto;
1257 :
1258 0 : TALER_full_payto_hash (payto_uri,
1259 : &h_payto);
1260 0 : if (GNUNET_OK !=
1261 0 : TALER_wallet_reserve_close_verify (
1262 : request_timestamp,
1263 : &h_payto,
1264 : reserve_pub,
1265 : &reserve_sig))
1266 : {
1267 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
1268 : .problem_row_id = close_request_row,
1269 : .operation = (char *) "close-request",
1270 : .loss = *amount_with_fee,
1271 : .operation_specific_pub = reserve_pub->eddsa_pub
1272 : };
1273 :
1274 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
1275 : TALER_ARL_adb,
1276 : &bsl);
1277 :
1278 0 : if (qs < 0)
1279 : {
1280 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1281 0 : rc->qs = qs;
1282 0 : GNUNET_free (payto_uri.full_payto);
1283 0 : return GNUNET_SYSERR;
1284 : }
1285 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
1286 : &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
1287 : amount_with_fee);
1288 : }
1289 : }
1290 0 : if ( (NULL == payto_uri.full_payto) &&
1291 0 : (NULL == rs->sender_account.full_payto) )
1292 : {
1293 0 : GNUNET_break (! rs->had_ri);
1294 0 : report_row_inconsistency ("reserves_close",
1295 : rowid,
1296 : "target account not verified, auditor does not know reserve");
1297 0 : if (global_qs < 0)
1298 0 : return GNUNET_SYSERR;
1299 : }
1300 0 : if (NULL == payto_uri.full_payto)
1301 : {
1302 0 : if ((NULL == rs->sender_account.full_payto) ||
1303 0 : (0 != TALER_full_payto_cmp (rs->sender_account,
1304 : receiver_account)))
1305 : {
1306 0 : report_row_inconsistency ("reserves_close",
1307 : rowid,
1308 : "target account does not match origin account");
1309 0 : if (global_qs < 0)
1310 0 : return GNUNET_SYSERR;
1311 : }
1312 : }
1313 : else
1314 : {
1315 0 : if (0 != TALER_full_payto_cmp (payto_uri,
1316 : receiver_account))
1317 : {
1318 0 : report_row_inconsistency ("reserves_close",
1319 : rowid,
1320 : "target account does not match origin account");
1321 0 : if (global_qs < 0)
1322 : {
1323 0 : GNUNET_free (payto_uri.full_payto);
1324 0 : return GNUNET_SYSERR;
1325 : }
1326 : }
1327 : }
1328 0 : GNUNET_free (payto_uri.full_payto);
1329 : }
1330 : else
1331 : {
1332 2 : if (NULL == rs->sender_account.full_payto)
1333 : {
1334 0 : GNUNET_break (! rs->had_ri);
1335 0 : report_row_inconsistency ("reserves_close",
1336 : rowid,
1337 : "target account not verified, auditor does not know reserve");
1338 0 : if (global_qs < 0)
1339 0 : return GNUNET_SYSERR;
1340 : }
1341 2 : else if (0 != TALER_full_payto_cmp (rs->sender_account,
1342 : receiver_account))
1343 : {
1344 0 : report_row_inconsistency ("reserves_close",
1345 : rowid,
1346 : "target account does not match origin account");
1347 0 : if (global_qs < 0)
1348 0 : return GNUNET_SYSERR;
1349 : }
1350 : }
1351 :
1352 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1353 : "Additional closing operation for reserve `%s' of %s\n",
1354 : TALER_B2S (reserve_pub),
1355 : TALER_amount2s (amount_with_fee));
1356 2 : return GNUNET_OK;
1357 : }
1358 :
1359 :
1360 : /**
1361 : * Function called with details about account merge requests that have been
1362 : * made, with the goal of accounting for the merge fee paid by the reserve (if
1363 : * applicable).
1364 : *
1365 : * @param rc closure
1366 : * @param rowid unique serial ID for the deposit in our DB
1367 : * @param reserve_pub reserve affected by the merge
1368 : * @param purse_pub purse being merged
1369 : * @param h_contract_terms hash over contract of the purse
1370 : * @param purse_expiration when would the purse expire
1371 : * @param amount total amount in the purse
1372 : * @param min_age minimum age of all coins deposited into the purse
1373 : * @param flags how was the purse created
1374 : * @param purse_fee if a purse fee was paid, how high is it
1375 : * @param merge_timestamp when was the merge approved
1376 : * @param reserve_sig signature by reserve approving the merge
1377 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
1378 : */
1379 : static enum GNUNET_GenericReturnValue
1380 0 : handle_account_merged (
1381 : struct ReserveContext *rc,
1382 : uint64_t rowid,
1383 : const struct TALER_ReservePublicKeyP *reserve_pub,
1384 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1385 : const struct TALER_PrivateContractHashP *h_contract_terms,
1386 : struct GNUNET_TIME_Timestamp purse_expiration,
1387 : const struct TALER_Amount *amount,
1388 : uint32_t min_age,
1389 : enum TALER_WalletAccountMergeFlags flags,
1390 : const struct TALER_Amount *purse_fee,
1391 : struct GNUNET_TIME_Timestamp merge_timestamp,
1392 : const struct TALER_ReserveSignatureP *reserve_sig)
1393 : {
1394 : struct ReserveSummary *rs;
1395 : enum GNUNET_DB_QueryStatus qs;
1396 :
1397 : /* should be monotonically increasing */
1398 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_account_merges_serial_id));
1399 0 : TALER_ARL_USE_PP (reserves_account_merges_serial_id) = rowid + 1;
1400 0 : if (GNUNET_OK !=
1401 0 : TALER_wallet_account_merge_verify (merge_timestamp,
1402 : purse_pub,
1403 : purse_expiration,
1404 : h_contract_terms,
1405 : amount,
1406 : purse_fee,
1407 : min_age,
1408 : flags,
1409 : reserve_pub,
1410 : reserve_sig))
1411 : {
1412 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
1413 : .problem_row_id = rowid,
1414 : .operation = (char *) "account-merge",
1415 : .loss = *purse_fee,
1416 : .operation_specific_pub = reserve_pub->eddsa_pub
1417 : };
1418 :
1419 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
1420 : TALER_ARL_adb,
1421 : &bsl);
1422 0 : if (qs < 0)
1423 : {
1424 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1425 0 : rc->qs = qs;
1426 0 : return GNUNET_SYSERR;
1427 : }
1428 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
1429 : &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
1430 : purse_fee);
1431 0 : return GNUNET_OK;
1432 : }
1433 0 : if ((flags & TALER_WAMF_MERGE_MODE_MASK) !=
1434 : TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE)
1435 0 : return GNUNET_OK; /* no impact on reserve balance */
1436 0 : rs = setup_reserve (rc,
1437 : reserve_pub);
1438 0 : if (NULL == rs)
1439 : {
1440 0 : GNUNET_break (0);
1441 0 : return GNUNET_SYSERR;
1442 : }
1443 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_purse_fee_revenue),
1444 : &TALER_ARL_USE_AB (reserves_purse_fee_revenue),
1445 : purse_fee);
1446 0 : TALER_ARL_amount_add (&rs->curr_balance.purse_fee_balance,
1447 : &rs->curr_balance.purse_fee_balance,
1448 : purse_fee);
1449 0 : TALER_ARL_amount_add (&rs->total_out,
1450 : &rs->total_out,
1451 : purse_fee);
1452 0 : return GNUNET_OK;
1453 : }
1454 :
1455 :
1456 : /**
1457 : * Function called with details about a purse that was merged into an account.
1458 : * Only updates the reserve balance, the actual verifications are done in the
1459 : * purse helper.
1460 : *
1461 : * @param rc closure
1462 : * @param rowid unique serial ID for the refund in our DB
1463 : * @param purse_pub public key of the purse
1464 : * @param reserve_pub which reserve is the purse credited to
1465 : * @param purse_value what is the target value of the purse
1466 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
1467 : */
1468 : static enum GNUNET_GenericReturnValue
1469 0 : purse_decision_cb (struct ReserveContext *rc,
1470 : uint64_t rowid,
1471 : const struct TALER_PurseContractPublicKeyP *purse_pub,
1472 : const struct TALER_ReservePublicKeyP *reserve_pub,
1473 : const struct TALER_Amount *purse_value)
1474 : {
1475 : struct ReserveSummary *rs;
1476 :
1477 0 : GNUNET_assert (rowid >= TALER_ARL_USE_PP (
1478 : reserves_purse_decisions_serial_id)); /* should be monotonically increasing */
1479 0 : TALER_ARL_USE_PP (reserves_purse_decisions_serial_id) = rowid + 1;
1480 0 : rs = setup_reserve (rc,
1481 : reserve_pub);
1482 0 : if (NULL == rs)
1483 : {
1484 0 : GNUNET_break (0);
1485 0 : return GNUNET_SYSERR;
1486 : }
1487 0 : TALER_ARL_amount_add (&rs->total_in,
1488 : &rs->total_in,
1489 : purse_value);
1490 0 : return GNUNET_OK;
1491 : }
1492 :
1493 :
1494 : /**
1495 : * Check that the reserve summary matches what the exchange database
1496 : * thinks about the reserve, and update our own state of the reserve.
1497 : *
1498 : * Remove all reserves that we are happy with from the DB.
1499 : *
1500 : * @param cls our `struct ReserveContext`
1501 : * @param key hash of the reserve public key
1502 : * @param value a `struct ReserveSummary`
1503 : * @return #GNUNET_OK to process more entries
1504 : */
1505 : static enum GNUNET_GenericReturnValue
1506 16 : verify_reserve_balance (void *cls,
1507 : const struct GNUNET_HashCode *key,
1508 : void *value)
1509 : {
1510 16 : struct ReserveContext *rc = cls;
1511 16 : struct ReserveSummary *rs = value;
1512 : struct TALER_Amount mbalance;
1513 : struct TALER_Amount nbalance;
1514 : enum GNUNET_DB_QueryStatus qs;
1515 : enum GNUNET_GenericReturnValue ret;
1516 :
1517 16 : ret = GNUNET_OK;
1518 : /* Check our reserve summary balance calculation shows that
1519 : the reserve balance is acceptable (i.e. non-negative) */
1520 16 : TALER_ARL_amount_add (&mbalance,
1521 : &rs->total_in,
1522 : &rs->prev_balance.reserve_balance);
1523 16 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
1524 16 : TALER_ARL_amount_subtract_neg (&nbalance,
1525 : &mbalance,
1526 : &rs->total_out))
1527 : {
1528 0 : struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiil = {
1529 : .reserve_pub = rs->reserve_pub.eddsa_pub,
1530 : .inconsistency_gain = false
1531 : };
1532 :
1533 0 : TALER_ARL_amount_subtract (&rbiil.inconsistency_amount,
1534 : &rs->total_out,
1535 : &mbalance);
1536 0 : TALER_ARL_amount_add (&rs->curr_balance.reserve_loss,
1537 : &rs->prev_balance.reserve_loss,
1538 : &rbiil.inconsistency_amount);
1539 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
1540 : &TALER_ARL_USE_AB (reserves_reserve_loss),
1541 : &rbiil.inconsistency_amount);
1542 0 : qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency (
1543 : TALER_ARL_adb,
1544 : &rbiil);
1545 :
1546 0 : if (qs < 0)
1547 : {
1548 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1549 0 : rc->qs = qs;
1550 0 : return GNUNET_SYSERR;
1551 : }
1552 : /* Continue with a reserve balance of zero */
1553 0 : GNUNET_assert (GNUNET_OK ==
1554 : TALER_amount_set_zero (TALER_ARL_currency,
1555 : &rs->curr_balance.reserve_balance));
1556 0 : nbalance = rs->curr_balance.reserve_balance;
1557 : }
1558 : else
1559 : {
1560 : /* Update remaining reserve balance! */
1561 16 : rs->curr_balance.reserve_balance = nbalance;
1562 : }
1563 :
1564 16 : if (internal_checks)
1565 : {
1566 : /* Now check OUR balance calculation vs. the one the exchange has
1567 : in its database. This can only be done when we are doing an
1568 : internal audit, as otherwise the balance of the 'reserves' table
1569 : is not replicated at the auditor. */
1570 16 : struct TALER_EXCHANGEDB_Reserve reserve = {
1571 : .pub = rs->reserve_pub
1572 : };
1573 :
1574 16 : qs = TALER_EXCHANGEDB_get_reserve (TALER_ARL_edb,
1575 : &reserve);
1576 16 : if (0 > qs)
1577 : {
1578 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1579 0 : rc->qs = qs;
1580 0 : return GNUNET_SYSERR;
1581 : }
1582 16 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1583 : {
1584 : /* If the exchange doesn't have this reserve in the summary, it
1585 : is like the exchange 'lost' that amount from its records,
1586 : making an illegitimate gain over the amount it dropped.
1587 : We don't add the amount to some total simply because it is
1588 : not an actualized gain and could be trivially corrected by
1589 : restoring the summary. */
1590 0 : struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiig = {
1591 : .reserve_pub = rs->reserve_pub.eddsa_pub,
1592 : .inconsistency_amount = nbalance,
1593 : .inconsistency_gain = true
1594 : };
1595 :
1596 0 : qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency (
1597 : TALER_ARL_adb,
1598 : &rbiig);
1599 :
1600 0 : if (qs < 0)
1601 : {
1602 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1603 0 : rc->qs = qs;
1604 0 : return GNUNET_SYSERR;
1605 : }
1606 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1607 : {
1608 0 : GNUNET_break (0);
1609 0 : qs = GNUNET_DB_STATUS_HARD_ERROR;
1610 : }
1611 0 : rc->qs = qs;
1612 : }
1613 : else
1614 : {
1615 : /* Check that exchange's balance matches our expected balance for the reserve */
1616 16 : if (0 != TALER_amount_cmp (&rs->curr_balance.reserve_balance,
1617 : &reserve.balance))
1618 : {
1619 : struct TALER_Amount delta;
1620 :
1621 0 : if (0 < TALER_amount_cmp (&rs->curr_balance.reserve_balance,
1622 : &reserve.balance))
1623 : {
1624 : /* balance > reserve.balance */
1625 0 : TALER_ARL_amount_subtract (&delta,
1626 : &rs->curr_balance.reserve_balance,
1627 : &reserve.balance);
1628 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1629 : total_balance_summary_delta_plus),
1630 : &TALER_ARL_USE_AB (
1631 : total_balance_summary_delta_plus),
1632 : &delta);
1633 : }
1634 : else
1635 : {
1636 : /* balance < reserve.balance */
1637 0 : TALER_ARL_amount_subtract (&delta,
1638 : &reserve.balance,
1639 : &rs->curr_balance.reserve_balance);
1640 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1641 : total_balance_summary_delta_minus),
1642 : &TALER_ARL_USE_AB (
1643 : total_balance_summary_delta_minus),
1644 : &delta);
1645 : }
1646 :
1647 : {
1648 0 : struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiig =
1649 : {
1650 : .reserve_pub = rs->reserve_pub.eddsa_pub,
1651 : .inconsistency_amount = nbalance,
1652 : .inconsistency_gain = true
1653 : };
1654 :
1655 0 : qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency
1656 : (
1657 : TALER_ARL_adb,
1658 : &rbiig);
1659 : }
1660 0 : if (qs < 0)
1661 : {
1662 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1663 0 : rc->qs = qs;
1664 0 : return GNUNET_SYSERR;
1665 : }
1666 :
1667 : {
1668 0 : struct TALER_AUDITORDB_ReserveBalanceSummaryWrongInconsistency rbswi =
1669 : {
1670 : .exchange_amount = reserve.balance,
1671 : .auditor_amount = rs->curr_balance.reserve_balance,
1672 : .reserve_pub = rs->reserve_pub
1673 : };
1674 :
1675 : qs =
1676 0 : TALER_AUDITORDB_insert_reserve_balance_summary_wrong_inconsistency
1677 : (
1678 : TALER_ARL_adb,
1679 : &rbswi);
1680 : }
1681 0 : if (qs < 0)
1682 : {
1683 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1684 0 : rc->qs = qs;
1685 0 : return GNUNET_SYSERR;
1686 : }
1687 : }
1688 : }
1689 : } /* end of 'if (internal_checks)' */
1690 :
1691 : /* Check that reserve is being closed if it is past its expiration date
1692 : (and the closing fee would not exceed the remaining balance) */
1693 16 : if (GNUNET_TIME_relative_cmp (CLOSING_GRACE_PERIOD,
1694 : <,
1695 : GNUNET_TIME_absolute_get_duration (
1696 : rs->a_expiration_date.abs_time)))
1697 : {
1698 : /* Reserve is expired */
1699 : struct TALER_Amount cfee;
1700 :
1701 0 : if ( (NULL != rs->sender_account.full_payto) &&
1702 : (GNUNET_OK ==
1703 0 : get_closing_fee (rs->sender_account,
1704 : rs->a_expiration_date,
1705 : &cfee)) )
1706 : {
1707 : /* We got the closing fee */
1708 0 : if (1 == TALER_amount_cmp (&nbalance,
1709 : &cfee))
1710 : {
1711 0 : struct TALER_AUDITORDB_ReserveNotClosedInconsistency rnci = {
1712 : .reserve_pub = rs->reserve_pub,
1713 : .expiration_time = rs->a_expiration_date.abs_time,
1714 : .balance = nbalance,
1715 0 : .diagnostic = rs->sender_account.full_payto
1716 : };
1717 :
1718 : /* remaining balance (according to us) exceeds closing fee */
1719 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1720 : total_balance_reserve_not_closed),
1721 : &TALER_ARL_USE_AB (
1722 : total_balance_reserve_not_closed),
1723 : &rnci.balance);
1724 0 : qs = TALER_AUDITORDB_insert_reserve_not_closed_inconsistency (
1725 : TALER_ARL_adb,
1726 : &rnci);
1727 0 : if (qs < 0)
1728 : {
1729 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1730 0 : rc->qs = qs;
1731 0 : return GNUNET_SYSERR;
1732 : }
1733 : }
1734 : }
1735 : else
1736 : {
1737 : /* We failed to determine the closing fee, complain! */
1738 0 : struct TALER_AUDITORDB_ReserveNotClosedInconsistency rncid = {
1739 : .reserve_pub = rs->reserve_pub,
1740 : .balance = nbalance,
1741 : .expiration_time = rs->a_expiration_date.abs_time,
1742 : .diagnostic = (char *) "could not determine closing fee"
1743 : };
1744 :
1745 : /* Even if we don't know the closing fee, update the
1746 : total_balance_reserve_not_closed */
1747 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1748 : total_balance_reserve_not_closed),
1749 : &TALER_ARL_USE_AB (
1750 : total_balance_reserve_not_closed),
1751 : &nbalance);
1752 0 : qs = TALER_AUDITORDB_insert_reserve_not_closed_inconsistency (
1753 : TALER_ARL_adb,
1754 : &rncid);
1755 0 : if (qs < 0)
1756 : {
1757 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1758 0 : rc->qs = qs;
1759 0 : return GNUNET_SYSERR;
1760 : }
1761 : }
1762 : }
1763 : /* We already computed the 'new' balance in 'curr_balance'
1764 : to include the previous balance, so this one is just
1765 : an assignment, not adding up! */
1766 16 : rs->prev_balance.reserve_balance = rs->curr_balance.reserve_balance;
1767 :
1768 : /* Add up new totals to previous totals */
1769 16 : TALER_ARL_amount_add (&rs->prev_balance.reserve_loss,
1770 : &rs->prev_balance.reserve_loss,
1771 : &rs->curr_balance.reserve_loss);
1772 16 : TALER_ARL_amount_add (&rs->prev_balance.withdraw_fee_balance,
1773 : &rs->prev_balance.withdraw_fee_balance,
1774 : &rs->curr_balance.withdraw_fee_balance);
1775 16 : TALER_ARL_amount_add (&rs->prev_balance.close_fee_balance,
1776 : &rs->prev_balance.close_fee_balance,
1777 : &rs->curr_balance.close_fee_balance);
1778 16 : TALER_ARL_amount_add (&rs->prev_balance.purse_fee_balance,
1779 : &rs->prev_balance.purse_fee_balance,
1780 : &rs->curr_balance.purse_fee_balance);
1781 16 : TALER_ARL_amount_add (&rs->prev_balance.open_fee_balance,
1782 : &rs->prev_balance.open_fee_balance,
1783 : &rs->curr_balance.open_fee_balance);
1784 16 : TALER_ARL_amount_add (&rs->prev_balance.history_fee_balance,
1785 : &rs->prev_balance.history_fee_balance,
1786 : &rs->curr_balance.history_fee_balance);
1787 : /* Update global balance: add incoming first, then try
1788 : to subtract outgoing... */
1789 16 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_total_balance),
1790 : &TALER_ARL_USE_AB (reserves_reserve_total_balance),
1791 : &rs->total_in);
1792 : {
1793 : struct TALER_Amount r;
1794 :
1795 16 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
1796 16 : TALER_ARL_amount_subtract_neg (&r,
1797 : &TALER_ARL_USE_AB (
1798 : reserves_reserve_total_balance),
1799 : &rs->total_out))
1800 : {
1801 : /* We could not reduce our total balance, i.e. exchange allowed IN TOTAL (!)
1802 : to be withdrawn more than it was IN TOTAL ever given (exchange balance
1803 : went negative!). Woopsie. Calculate how badly it went and log. */
1804 0 : report_amount_arithmetic_inconsistency ("global escrow balance",
1805 : 0,
1806 : &TALER_ARL_USE_AB (
1807 : reserves_reserve_total_balance), /* what we had */
1808 0 : &rs->total_out, /* what we needed */
1809 : 0 /* specific profit/loss does not apply to the total summary */
1810 : );
1811 0 : if (global_qs < 0)
1812 0 : return GNUNET_SYSERR;
1813 : /* We unexpectedly went negative, so a sane value to continue from
1814 : would be zero. */
1815 0 : GNUNET_assert (GNUNET_OK ==
1816 : TALER_amount_set_zero (TALER_ARL_currency,
1817 : &TALER_ARL_USE_AB (
1818 : reserves_reserve_total_balance)));
1819 : }
1820 : else
1821 : {
1822 16 : TALER_ARL_USE_AB (reserves_reserve_total_balance) = r;
1823 : }
1824 : }
1825 16 : if (TALER_amount_is_zero (&rs->prev_balance.reserve_balance))
1826 : {
1827 : /* balance is zero, drop reserve details (and then do not update/insert) */
1828 12 : if (rs->had_ri)
1829 : {
1830 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1831 : "Final balance of reserve `%s' is zero, dropping it\n",
1832 : TALER_B2S (&rs->reserve_pub));
1833 0 : qs = TALER_AUDITORDB_delete_reserve_info (TALER_ARL_adb,
1834 0 : &rs->reserve_pub);
1835 0 : if (0 >= qs)
1836 : {
1837 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1838 0 : ret = GNUNET_SYSERR;
1839 0 : rc->qs = qs;
1840 : }
1841 : }
1842 : else
1843 : {
1844 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1845 : "Final balance of reserve `%s' is zero, no need to remember it\n",
1846 : TALER_B2S (&rs->reserve_pub));
1847 : }
1848 : }
1849 : else
1850 : {
1851 : /* balance is non-zero, persist for future audits */
1852 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1853 : "Remembering final balance of reserve `%s' as %s\n",
1854 : TALER_B2S (&rs->reserve_pub),
1855 : TALER_amount2s (&rs->prev_balance.reserve_balance));
1856 4 : if (rs->had_ri)
1857 0 : qs = TALER_AUDITORDB_update_reserve_info (TALER_ARL_adb,
1858 0 : &rs->reserve_pub,
1859 0 : &rs->prev_balance,
1860 : rs->a_expiration_date);
1861 : else
1862 4 : qs = TALER_AUDITORDB_insert_reserve_info (TALER_ARL_adb,
1863 4 : &rs->reserve_pub,
1864 4 : &rs->prev_balance,
1865 : rs->a_expiration_date,
1866 : rs->sender_account);
1867 4 : if (0 >= qs)
1868 : {
1869 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1870 0 : ret = GNUNET_SYSERR;
1871 0 : rc->qs = qs;
1872 : }
1873 : }
1874 : /* now we can discard the cached entry */
1875 16 : GNUNET_assert (GNUNET_YES ==
1876 : GNUNET_CONTAINER_multihashmap_remove (rc->reserves,
1877 : key,
1878 : rs));
1879 16 : GNUNET_free (rs->sender_account.full_payto);
1880 16 : GNUNET_free (rs);
1881 16 : return ret;
1882 : }
1883 :
1884 :
1885 : #define CHECK_DB() do { \
1886 : if (qs < 0) { \
1887 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); \
1888 : goto cleanup; \
1889 : } \
1890 : if (global_qs < 0) { \
1891 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == global_qs); \
1892 : qs = global_qs; \
1893 : goto cleanup; \
1894 : } \
1895 : if (rc.qs < 0) { \
1896 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == rc.qs); \
1897 : qs = rc.qs; \
1898 : goto cleanup; \
1899 : } \
1900 : } while (0)
1901 :
1902 :
1903 : /**
1904 : * Analyze reserves for being well-formed.
1905 : *
1906 : * @param cls NULL
1907 : * @return transaction status code
1908 : */
1909 : static enum GNUNET_DB_QueryStatus
1910 14 : analyze_reserves (void *cls)
1911 : {
1912 14 : struct ReserveContext rc = {
1913 : .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
1914 : };
1915 : enum GNUNET_DB_QueryStatus qs;
1916 :
1917 : (void) cls;
1918 14 : global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
1919 14 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1920 : "Analyzing reserves\n");
1921 14 : qs = TALER_AUDITORDB_get_auditor_progress (
1922 : TALER_ARL_adb,
1923 : TALER_ARL_GET_PP (reserves_reserve_in_serial_id),
1924 : TALER_ARL_GET_PP (reserves_withdraw_serial_id),
1925 : TALER_ARL_GET_PP (reserves_reserve_recoup_serial_id),
1926 : TALER_ARL_GET_PP (reserves_reserve_open_serial_id),
1927 : TALER_ARL_GET_PP (reserves_reserve_close_serial_id),
1928 : TALER_ARL_GET_PP (reserves_purse_decisions_serial_id),
1929 : TALER_ARL_GET_PP (reserves_account_merges_serial_id),
1930 : TALER_ARL_GET_PP (reserves_history_requests_serial_id),
1931 : NULL);
1932 14 : if (0 > qs)
1933 : {
1934 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1935 0 : return qs;
1936 : }
1937 14 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1938 : {
1939 0 : GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
1940 : "First analysis using this auditor, starting audit from scratch\n");
1941 : }
1942 : else
1943 : {
1944 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1945 : "Resuming reserve audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
1946 : (unsigned long long) TALER_ARL_USE_PP (
1947 : reserves_reserve_in_serial_id),
1948 : (unsigned long long) TALER_ARL_USE_PP (
1949 : reserves_withdraw_serial_id),
1950 : (unsigned long long) TALER_ARL_USE_PP (
1951 : reserves_reserve_recoup_serial_id),
1952 : (unsigned long long) TALER_ARL_USE_PP (
1953 : reserves_reserve_open_serial_id),
1954 : (unsigned long long) TALER_ARL_USE_PP (
1955 : reserves_reserve_close_serial_id),
1956 : (unsigned long long) TALER_ARL_USE_PP (
1957 : reserves_purse_decisions_serial_id),
1958 : (unsigned long long) TALER_ARL_USE_PP (
1959 : reserves_account_merges_serial_id),
1960 : (unsigned long long) TALER_ARL_USE_PP (
1961 : reserves_history_requests_serial_id));
1962 : }
1963 14 : qs = TALER_AUDITORDB_get_balance (
1964 : TALER_ARL_adb,
1965 : TALER_ARL_GET_AB (reserves_reserve_total_balance),
1966 : TALER_ARL_GET_AB (reserves_reserve_loss),
1967 : TALER_ARL_GET_AB (reserves_withdraw_fee_revenue),
1968 : TALER_ARL_GET_AB (reserves_close_fee_revenue),
1969 : TALER_ARL_GET_AB (reserves_purse_fee_revenue),
1970 : TALER_ARL_GET_AB (reserves_open_fee_revenue),
1971 : TALER_ARL_GET_AB (reserves_history_fee_revenue),
1972 : TALER_ARL_GET_AB (reserves_total_bad_sig_loss),
1973 : TALER_ARL_GET_AB (total_balance_reserve_not_closed),
1974 : TALER_ARL_GET_AB (reserves_total_arithmetic_delta_plus),
1975 : TALER_ARL_GET_AB (reserves_total_arithmetic_delta_minus),
1976 : TALER_ARL_GET_AB (total_balance_summary_delta_plus),
1977 : TALER_ARL_GET_AB (total_balance_summary_delta_minus),
1978 : NULL);
1979 14 : if (qs < 0)
1980 : {
1981 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1982 0 : return qs;
1983 : }
1984 14 : rc.reserves = GNUNET_CONTAINER_multihashmap_create (512,
1985 : GNUNET_NO);
1986 14 : rc.revoked = GNUNET_CONTAINER_multihashmap_create (4,
1987 : GNUNET_NO);
1988 :
1989 14 : qs = TALER_EXCHANGEDB_iterate_reserves_in_above_serial_id (
1990 : TALER_ARL_edb,
1991 : TALER_ARL_USE_PP (reserves_reserve_in_serial_id),
1992 : &handle_reserve_in,
1993 : &rc);
1994 14 : CHECK_DB ();
1995 14 : qs = TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (
1996 : TALER_ARL_edb,
1997 : TALER_ARL_USE_PP (reserves_withdraw_serial_id),
1998 : &handle_withdrawals,
1999 : &rc);
2000 14 : CHECK_DB ();
2001 14 : qs = TALER_EXCHANGEDB_iterate_recoups_above_serial_id (
2002 : TALER_ARL_edb,
2003 : TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id),
2004 : &handle_recoup_by_reserve,
2005 : &rc);
2006 14 : CHECK_DB ();
2007 14 : qs = TALER_EXCHANGEDB_iterate_reserve_open_above_serial_id (
2008 : TALER_ARL_edb,
2009 : TALER_ARL_USE_PP (reserves_reserve_open_serial_id),
2010 : &handle_reserve_open,
2011 : &rc);
2012 14 : CHECK_DB ();
2013 14 : qs = TALER_EXCHANGEDB_iterate_reserve_closed_above_serial_id (
2014 : TALER_ARL_edb,
2015 : TALER_ARL_USE_PP (reserves_reserve_close_serial_id),
2016 : &handle_reserve_closed,
2017 : &rc);
2018 14 : CHECK_DB ();
2019 : /* process purse_decisions (to credit reserve) */
2020 14 : qs = TALER_EXCHANGEDB_iterate_purse_decisions_above_serial_id (
2021 : TALER_ARL_edb,
2022 : TALER_ARL_USE_PP (reserves_purse_decisions_serial_id),
2023 : false, /* only go for merged purses! */
2024 : &purse_decision_cb,
2025 : &rc);
2026 14 : CHECK_DB ();
2027 : /* Charge purse fee! */
2028 :
2029 14 : qs = TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
2030 : TALER_ARL_edb,
2031 : TALER_ARL_USE_PP (reserves_account_merges_serial_id),
2032 : &handle_account_merged,
2033 : &rc);
2034 14 : CHECK_DB ();
2035 14 : GNUNET_CONTAINER_multihashmap_iterate (rc.reserves,
2036 : &verify_reserve_balance,
2037 : &rc);
2038 14 : CHECK_DB ();
2039 14 : GNUNET_break (0 ==
2040 : GNUNET_CONTAINER_multihashmap_size (rc.reserves));
2041 :
2042 14 : qs = TALER_AUDITORDB_insert_balance (
2043 : TALER_ARL_adb,
2044 : TALER_ARL_SET_AB (reserves_reserve_total_balance),
2045 : TALER_ARL_SET_AB (reserves_reserve_loss),
2046 : TALER_ARL_SET_AB (reserves_withdraw_fee_revenue),
2047 : TALER_ARL_SET_AB (reserves_close_fee_revenue),
2048 : TALER_ARL_SET_AB (reserves_purse_fee_revenue),
2049 : TALER_ARL_SET_AB (reserves_open_fee_revenue),
2050 : TALER_ARL_SET_AB (reserves_history_fee_revenue),
2051 : TALER_ARL_SET_AB (reserves_total_bad_sig_loss),
2052 : TALER_ARL_SET_AB (total_balance_reserve_not_closed),
2053 : TALER_ARL_SET_AB (reserves_total_arithmetic_delta_plus),
2054 : TALER_ARL_SET_AB (reserves_total_arithmetic_delta_minus),
2055 : TALER_ARL_SET_AB (total_balance_summary_delta_plus),
2056 : TALER_ARL_SET_AB (total_balance_summary_delta_minus),
2057 : NULL);
2058 14 : if (0 > qs)
2059 : {
2060 3 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
2061 3 : goto cleanup;
2062 : }
2063 :
2064 11 : qs = TALER_AUDITORDB_insert_auditor_progress (
2065 : TALER_ARL_adb,
2066 : TALER_ARL_SET_PP (reserves_reserve_in_serial_id),
2067 : TALER_ARL_SET_PP (reserves_withdraw_serial_id),
2068 : TALER_ARL_SET_PP (reserves_reserve_recoup_serial_id),
2069 : TALER_ARL_SET_PP (reserves_reserve_open_serial_id),
2070 : TALER_ARL_SET_PP (reserves_reserve_close_serial_id),
2071 : TALER_ARL_SET_PP (reserves_purse_decisions_serial_id),
2072 : TALER_ARL_SET_PP (reserves_account_merges_serial_id),
2073 : TALER_ARL_SET_PP (reserves_history_requests_serial_id),
2074 : NULL);
2075 11 : if (0 > qs)
2076 : {
2077 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2078 : "Failed to update auditor DB, not recording progress\n");
2079 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
2080 0 : goto cleanup;
2081 : }
2082 :
2083 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2084 : "Concluded reserve audit step at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
2085 : (unsigned long long) TALER_ARL_USE_PP (
2086 : reserves_reserve_in_serial_id),
2087 : (unsigned long long) TALER_ARL_USE_PP (
2088 : reserves_withdraw_serial_id),
2089 : (unsigned long long) TALER_ARL_USE_PP (
2090 : reserves_reserve_recoup_serial_id),
2091 : (unsigned long long) TALER_ARL_USE_PP (
2092 : reserves_reserve_open_serial_id),
2093 : (unsigned long long) TALER_ARL_USE_PP (
2094 : reserves_reserve_close_serial_id),
2095 : (unsigned long long) TALER_ARL_USE_PP (
2096 : reserves_purse_decisions_serial_id),
2097 : (unsigned long long) TALER_ARL_USE_PP (
2098 : reserves_account_merges_serial_id),
2099 : (unsigned long long) TALER_ARL_USE_PP (
2100 : reserves_history_requests_serial_id));
2101 11 : qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
2102 14 : cleanup:
2103 14 : GNUNET_CONTAINER_multihashmap_destroy (rc.reserves);
2104 14 : GNUNET_CONTAINER_multihashmap_destroy (rc.revoked);
2105 14 : return qs;
2106 : }
2107 :
2108 :
2109 : #undef CHECK_DB
2110 :
2111 :
2112 : /**
2113 : * Function called on events received from Postgres.
2114 : *
2115 : * @param cls closure, NULL
2116 : * @param extra additional event data provided
2117 : * @param extra_size number of bytes in @a extra
2118 : */
2119 : static void
2120 0 : db_notify (void *cls,
2121 : const void *extra,
2122 : size_t extra_size)
2123 : {
2124 : (void) cls;
2125 : (void) extra;
2126 : (void) extra_size;
2127 :
2128 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2129 : "Received notification to wake reserves helper\n");
2130 0 : if (GNUNET_OK !=
2131 0 : TALER_ARL_setup_sessions_and_run (&analyze_reserves,
2132 : NULL))
2133 : {
2134 0 : GNUNET_SCHEDULER_shutdown ();
2135 0 : global_ret = EXIT_FAILURE;
2136 0 : return;
2137 : }
2138 : }
2139 :
2140 :
2141 : /**
2142 : * Function called on shutdown.
2143 : */
2144 : static void
2145 10 : do_shutdown (void *cls)
2146 : {
2147 : (void) cls;
2148 10 : if (NULL != eh)
2149 : {
2150 4 : TALER_AUDITORDB_event_listen_cancel (eh);
2151 4 : eh = NULL;
2152 : }
2153 10 : TALER_ARL_done ();
2154 10 : }
2155 :
2156 :
2157 : /**
2158 : * Main function that will be run.
2159 : *
2160 : * @param cls closure
2161 : * @param args remaining command-line arguments
2162 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
2163 : * @param c configuration
2164 : */
2165 : static void
2166 10 : run (void *cls,
2167 : char *const *args,
2168 : const char *cfgfile,
2169 : const struct GNUNET_CONFIGURATION_Handle *c)
2170 : {
2171 : (void) cls;
2172 : (void) args;
2173 : (void) cfgfile;
2174 :
2175 10 : cfg = c;
2176 10 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
2177 : NULL);
2178 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2179 : "Launching reserves auditor\n");
2180 10 : if (EXIT_SUCCESS !=
2181 10 : (global_ret = TALER_ARL_init (c)))
2182 : {
2183 0 : return;
2184 : }
2185 10 : if (GNUNET_OK !=
2186 10 : GNUNET_CONFIGURATION_get_value_time (TALER_ARL_cfg,
2187 : "exchangedb",
2188 : "IDLE_RESERVE_EXPIRATION_TIME",
2189 : &idle_reserve_expiration_time))
2190 : {
2191 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
2192 : "exchangedb",
2193 : "IDLE_RESERVE_EXPIRATION_TIME");
2194 0 : GNUNET_SCHEDULER_shutdown ();
2195 0 : global_ret = EXIT_FAILURE;
2196 0 : return;
2197 : }
2198 10 : if (test_mode != 1)
2199 : {
2200 4 : struct GNUNET_DB_EventHeaderP es = {
2201 4 : .size = htons (sizeof (es)),
2202 4 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_RESERVES)
2203 : };
2204 :
2205 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2206 : "Running helper indefinitely\n");
2207 4 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
2208 : &es,
2209 4 : GNUNET_TIME_UNIT_FOREVER_REL,
2210 : &db_notify,
2211 : NULL);
2212 : }
2213 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2214 : "Starting audit\n");
2215 10 : if (GNUNET_OK !=
2216 10 : TALER_ARL_setup_sessions_and_run (&analyze_reserves,
2217 : NULL))
2218 : {
2219 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2220 : "Audit failed\n");
2221 0 : GNUNET_SCHEDULER_shutdown ();
2222 0 : global_ret = EXIT_FAILURE;
2223 0 : return;
2224 : }
2225 : }
2226 :
2227 :
2228 : /**
2229 : * The main function to check the database's handling of reserves.
2230 : *
2231 : * @param argc number of arguments from the command line
2232 : * @param argv command line arguments
2233 : * @return 0 ok, 1 on error
2234 : */
2235 : int
2236 10 : main (int argc,
2237 : char *const *argv)
2238 : {
2239 10 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
2240 10 : GNUNET_GETOPT_option_flag ('i',
2241 : "internal",
2242 : "perform checks only applicable for exchange-internal audits",
2243 : &internal_checks),
2244 10 : GNUNET_GETOPT_option_flag ('t',
2245 : "test",
2246 : "run in test mode and exit when idle",
2247 : &test_mode),
2248 10 : GNUNET_GETOPT_option_timetravel ('T',
2249 : "timetravel"),
2250 : GNUNET_GETOPT_OPTION_END
2251 : };
2252 : enum GNUNET_GenericReturnValue ret;
2253 :
2254 10 : ret = GNUNET_PROGRAM_run (
2255 : TALER_AUDITOR_project_data (),
2256 : argc,
2257 : argv,
2258 : "taler-helper-auditor-reserves",
2259 : gettext_noop ("Audit Taler exchange reserve handling"),
2260 : options,
2261 : &run,
2262 : NULL);
2263 10 : if (GNUNET_SYSERR == ret)
2264 0 : return EXIT_NOTCONFIGURED;
2265 10 : if (GNUNET_NO == ret)
2266 0 : return EXIT_SUCCESS;
2267 10 : return global_ret;
2268 : }
2269 :
2270 :
2271 : /* end of taler-helper-auditor-reserves.c */
|