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