Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2016-2025 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-deposits.c
18 : * @brief audits an exchange database for deposit confirmation consistency
19 : * @author Christian Grothoff
20 : * @author Nic Eigel
21 : *
22 : * We simply check that all of the deposit confirmations reported to us
23 : * by merchants were also reported to us by the exchange.
24 : */
25 : #include "taler/platform.h"
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include "taler/taler_auditordb_lib.h"
28 : #include "taler/taler_exchangedb_lib.h"
29 : #include "taler/taler_bank_service.h"
30 : #include "report-lib.h"
31 : #include "taler/taler_dbevents.h"
32 : #include <jansson.h>
33 : #include <inttypes.h>
34 : #include "auditor-database/delete_generic.h"
35 : #include "auditor-database/event_listen.h"
36 : #include "auditor-database/get_auditor_progress.h"
37 : #include "auditor-database/get_balance.h"
38 : #include "auditor-database/get_deposit_confirmations.h"
39 : #include "auditor-database/insert_auditor_progress.h"
40 : #include "auditor-database/insert_balance.h"
41 : #include "auditor-database/update_auditor_progress.h"
42 : #include "auditor-database/update_balance.h"
43 : #include "exchange-database/have_deposit2.h"
44 :
45 : /*
46 : --
47 : -- SELECT serial_id,h_contract_terms,h_wire,merchant_pub ...
48 : -- FROM auditor.auditor_deposit_confirmations
49 : -- WHERE NOT ancient
50 : -- ORDER BY exchange_timestamp ASC;
51 : -- SELECT 1
52 : - FROM exchange.deposits dep
53 : WHERE ($RESULT.contract_terms = dep.h_contract_terms) AND ($RESULT.h_wire = dep.h_wire) AND ...);
54 : -- IF FOUND
55 : -- DELETE FROM auditor.auditor_deposit_confirmations
56 : -- WHERE serial_id = $RESULT.serial_id;
57 : -- SELECT exchange_timestamp AS latest
58 : -- FROM exchange.deposits ORDER BY exchange_timestamp DESC;
59 : -- latest -= 1 hour; // time is not exactly monotonic...
60 : -- UPDATE auditor.deposit_confirmations
61 : -- SET ancient=TRUE
62 : -- WHERE exchange_timestamp < latest
63 : -- AND NOT ancient;
64 : */
65 :
66 : /**
67 : * Return value from main().
68 : */
69 : static int global_ret;
70 :
71 : /**
72 : * Row ID until which we have added up missing deposit confirmations
73 : * in the total_missed_deposit_confirmations amount. Missing deposit
74 : * confirmations above this value need to be added, and if any appear
75 : * below this value we should subtract them from the reported amount.
76 : */
77 : static TALER_ARL_DEF_PP (deposit_confirmation_serial_id);
78 :
79 : /**
80 : * Run in test mode. Exit when idle instead of
81 : * going to sleep and waiting for more work.
82 : */
83 : static int test_mode;
84 :
85 : /**
86 : * Total amount involved in deposit confirmations that we did not get.
87 : */
88 : static TALER_ARL_DEF_AB (total_missed_deposit_confirmations);
89 :
90 : /**
91 : * Should we run checks that only work for exchange-internal audits?
92 : * Does nothing for this helper (present only for uniformity).
93 : */
94 : static int internal_checks;
95 :
96 : /**
97 : * Handler to wake us up on new deposit confirmations.
98 : */
99 : static struct GNUNET_DB_EventHandler *eh;
100 :
101 : /**
102 : * The auditors's configuration.
103 : */
104 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
105 :
106 : /**
107 : * Success or failure of (exchange) database operations within
108 : * #test_dc and #recheck_dc.
109 : */
110 : static enum GNUNET_DB_QueryStatus eqs;
111 :
112 :
113 : /**
114 : * Given a deposit confirmation from #TALER_ARL_adb, check that it is also
115 : * in #TALER_ARL_edb. Update the deposit confirmation context accordingly.
116 : *
117 : * @param cls NULL
118 : * @param dc the deposit confirmation we know
119 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
120 : */
121 : static enum GNUNET_GenericReturnValue
122 0 : test_dc (void *cls,
123 : const struct TALER_AUDITORDB_DepositConfirmation *dc)
124 : {
125 0 : bool missing = false;
126 : enum GNUNET_DB_QueryStatus qs;
127 :
128 : (void) cls;
129 0 : TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id;
130 0 : for (unsigned int i = 0; i < dc->num_coins; i++)
131 : {
132 : struct GNUNET_TIME_Timestamp exchange_timestamp;
133 : struct TALER_Amount deposit_fee;
134 :
135 0 : qs = TALER_EXCHANGEDB_have_deposit2 (TALER_ARL_edb,
136 : &dc->h_contract_terms,
137 : &dc->h_wire,
138 0 : &dc->coin_pubs[i],
139 : &dc->merchant,
140 : dc->refund_deadline,
141 : &deposit_fee,
142 : &exchange_timestamp);
143 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
144 : "Status for deposit confirmation %llu-%u is %d\n",
145 : (unsigned long long) dc->row_id,
146 : i,
147 : qs);
148 0 : missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
149 0 : if (qs < 0)
150 : {
151 0 : GNUNET_break (0); /* DB error, complain */
152 0 : eqs = qs;
153 0 : return GNUNET_SYSERR;
154 : }
155 : }
156 0 : if (! missing)
157 : {
158 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
159 : "Deleting matching deposit confirmation %llu\n",
160 : (unsigned long long) dc->row_id);
161 0 : qs = TALER_AUDITORDB_delete_generic (
162 : TALER_ARL_adb,
163 : TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
164 0 : dc->row_id);
165 0 : if (qs < 0)
166 : {
167 0 : GNUNET_break (0); /* DB error, complain */
168 0 : eqs = qs;
169 0 : return GNUNET_SYSERR;
170 : }
171 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
172 : "Found deposit %s in exchange database\n",
173 : GNUNET_h2s (&dc->h_contract_terms.hash));
174 0 : return GNUNET_OK; /* all coins found, all good */
175 : }
176 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_missed_deposit_confirmations),
177 : &TALER_ARL_USE_AB (total_missed_deposit_confirmations),
178 : &dc->total_without_fee);
179 0 : return GNUNET_OK;
180 : }
181 :
182 :
183 : /**
184 : * Given a previously missing deposit confirmation from #TALER_ARL_adb, check
185 : * *again* whether it is now in #TALER_ARL_edb. Update the deposit
186 : * confirmation context accordingly.
187 : *
188 : * @param cls NULL
189 : * @param dc the deposit confirmation we know
190 : * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
191 : */
192 : static enum GNUNET_GenericReturnValue
193 0 : recheck_dc (void *cls,
194 : const struct TALER_AUDITORDB_DepositConfirmation *dc)
195 : {
196 0 : bool missing = false;
197 : enum GNUNET_DB_QueryStatus qs;
198 :
199 : (void) cls;
200 0 : for (unsigned int i = 0; i < dc->num_coins; i++)
201 : {
202 : struct GNUNET_TIME_Timestamp exchange_timestamp;
203 : struct TALER_Amount deposit_fee;
204 :
205 0 : qs = TALER_EXCHANGEDB_have_deposit2 (TALER_ARL_edb,
206 : &dc->h_contract_terms,
207 : &dc->h_wire,
208 0 : &dc->coin_pubs[i],
209 : &dc->merchant,
210 : dc->refund_deadline,
211 : &deposit_fee,
212 : &exchange_timestamp);
213 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
214 : "Status for deposit confirmation %llu-%u is %d on re-check\n",
215 : (unsigned long long) dc->row_id,
216 : i,
217 : qs);
218 0 : missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
219 0 : if (qs < 0)
220 : {
221 0 : GNUNET_break (0); /* DB error, complain */
222 0 : eqs = qs;
223 0 : return GNUNET_SYSERR;
224 : }
225 : }
226 0 : if (! missing)
227 : {
228 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
229 : "Deleting matching deposit confirmation %llu\n",
230 : (unsigned long long) dc->row_id);
231 0 : qs = TALER_AUDITORDB_delete_generic (
232 : TALER_ARL_adb,
233 : TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
234 0 : dc->row_id);
235 0 : if (qs < 0)
236 : {
237 0 : GNUNET_break (0); /* DB error, complain */
238 0 : eqs = qs;
239 0 : return GNUNET_SYSERR;
240 : }
241 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
242 : "Previously missing deposit %s appeared in exchange database\n",
243 : GNUNET_h2s (&dc->h_contract_terms.hash));
244 : /* It appeared, so *reduce* total missing balance */
245 0 : TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (
246 : total_missed_deposit_confirmations),
247 : &TALER_ARL_USE_AB (
248 : total_missed_deposit_confirmations),
249 : &dc->total_without_fee);
250 0 : return GNUNET_OK; /* all coins found, all good */
251 : }
252 : /* still missing, no change to totalmissing balance */
253 0 : return GNUNET_OK;
254 : }
255 :
256 :
257 : /**
258 : * Check that the deposit-confirmations that were reported to
259 : * us by merchants are also in the exchange's database.
260 : *
261 : * @param cls closure
262 : * @return transaction status code
263 : */
264 : static enum GNUNET_DB_QueryStatus
265 4 : analyze_deposit_confirmations (void *cls)
266 : {
267 : enum GNUNET_DB_QueryStatus qs;
268 : bool had_pp;
269 : bool had_bal;
270 : bool had_missing;
271 : uint64_t pp;
272 :
273 : (void) cls;
274 4 : qs = TALER_AUDITORDB_get_auditor_progress (
275 : TALER_ARL_adb,
276 : TALER_ARL_GET_PP (deposit_confirmation_serial_id),
277 : NULL);
278 4 : if (0 > qs)
279 : {
280 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
281 0 : return qs;
282 : }
283 4 : had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
284 4 : if (had_pp)
285 : {
286 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
287 : "Resuming deposit confirmation audit at %llu\n",
288 : (unsigned long long) TALER_ARL_USE_PP (
289 : deposit_confirmation_serial_id));
290 4 : pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id);
291 : }
292 : else
293 : {
294 0 : GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
295 : "First analysis using deposit auditor, starting audit from scratch\n");
296 : }
297 4 : qs = TALER_AUDITORDB_get_balance (
298 : TALER_ARL_adb,
299 : TALER_ARL_GET_AB (total_missed_deposit_confirmations),
300 : NULL);
301 4 : if (0 > qs)
302 : {
303 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
304 0 : return qs;
305 : }
306 4 : had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
307 4 : had_missing = ! TALER_amount_is_zero (
308 4 : &TALER_ARL_USE_AB (total_missed_deposit_confirmations));
309 4 : qs = TALER_AUDITORDB_get_deposit_confirmations (
310 : TALER_ARL_adb,
311 : INT64_MAX,
312 : TALER_ARL_USE_PP (deposit_confirmation_serial_id),
313 : true, /* return suppressed */
314 : &test_dc,
315 : NULL);
316 4 : if (0 > qs)
317 : {
318 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
319 0 : return qs;
320 : }
321 4 : if (0 > eqs)
322 : {
323 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
324 0 : return eqs;
325 : }
326 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
327 : "Analyzed %d deposit confirmations\n",
328 : (int) qs);
329 4 : if (had_pp)
330 4 : qs = TALER_AUDITORDB_update_auditor_progress (
331 : TALER_ARL_adb,
332 : TALER_ARL_SET_PP (deposit_confirmation_serial_id),
333 : NULL);
334 : else
335 0 : qs = TALER_AUDITORDB_insert_auditor_progress (
336 : TALER_ARL_adb,
337 : TALER_ARL_SET_PP (deposit_confirmation_serial_id),
338 : NULL);
339 4 : if (0 > qs)
340 : {
341 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
342 : "Failed to update auditor DB, not recording progress\n");
343 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
344 0 : return qs;
345 : }
346 4 : if (had_bal && had_pp && had_missing)
347 : {
348 0 : qs = TALER_AUDITORDB_get_deposit_confirmations (
349 : TALER_ARL_adb,
350 : -INT64_MAX,
351 : pp + 1, /* previous iteration went up to 'pp', try missing again */
352 : true, /* return suppressed */
353 : &recheck_dc,
354 : NULL);
355 0 : if (0 > qs)
356 : {
357 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
358 0 : return qs;
359 : }
360 0 : if (0 > eqs)
361 : {
362 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
363 0 : return eqs;
364 : }
365 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
366 : "Re-analyzed %d deposit confirmations\n",
367 : (int) qs);
368 : }
369 4 : if (had_bal)
370 4 : qs = TALER_AUDITORDB_update_balance (
371 : TALER_ARL_adb,
372 : TALER_ARL_SET_AB (total_missed_deposit_confirmations),
373 : NULL);
374 : else
375 0 : qs = TALER_AUDITORDB_insert_balance (
376 : TALER_ARL_adb,
377 : TALER_ARL_SET_AB (total_missed_deposit_confirmations),
378 : NULL);
379 4 : if (0 > qs)
380 : {
381 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
382 : "Failed to update auditor DB, not recording progress\n");
383 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
384 0 : return qs;
385 : }
386 4 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
387 : }
388 :
389 :
390 : /**
391 : * Function called on events received from Postgres.
392 : *
393 : * @param cls closure, NULL
394 : * @param extra additional event data provided
395 : * @param extra_size number of bytes in @a extra
396 : */
397 : static void
398 0 : db_notify (void *cls,
399 : const void *extra,
400 : size_t extra_size)
401 : {
402 : (void) cls;
403 : (void) extra;
404 : (void) extra_size;
405 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
406 : "Received notification for new deposit_confirmation\n");
407 0 : if (GNUNET_OK !=
408 0 : TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
409 : NULL))
410 : {
411 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
412 : "Audit failed\n");
413 0 : GNUNET_SCHEDULER_shutdown ();
414 0 : global_ret = EXIT_FAILURE;
415 0 : return;
416 : }
417 : }
418 :
419 :
420 : /**
421 : * Function called on shutdown.
422 : */
423 : static void
424 4 : do_shutdown (void *cls)
425 : {
426 : (void) cls;
427 4 : if (NULL != eh)
428 : {
429 4 : TALER_AUDITORDB_event_listen_cancel (eh);
430 4 : eh = NULL;
431 : }
432 4 : TALER_ARL_done ();
433 4 : }
434 :
435 :
436 : /**
437 : * Main function that will be run.
438 : *
439 : * @param cls closure
440 : * @param args remaining command-line arguments
441 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
442 : * @param c configuration
443 : */
444 : static void
445 4 : run (void *cls,
446 : char *const *args,
447 : const char *cfgfile,
448 : const struct GNUNET_CONFIGURATION_Handle *c)
449 : {
450 : (void) cls;
451 : (void) args;
452 : (void) cfgfile;
453 4 : cfg = c;
454 4 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
455 : NULL);
456 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
457 : "Launching deposit auditor\n");
458 4 : if (GNUNET_OK !=
459 4 : TALER_ARL_init (c))
460 : {
461 0 : global_ret = EXIT_FAILURE;
462 0 : return;
463 : }
464 :
465 4 : if (test_mode != 1)
466 : {
467 4 : struct GNUNET_DB_EventHeaderP es = {
468 4 : .size = htons (sizeof (es)),
469 4 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS)
470 : };
471 :
472 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
473 : "Running helper indefinitely\n");
474 4 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
475 : &es,
476 4 : GNUNET_TIME_UNIT_FOREVER_REL,
477 : &db_notify,
478 : NULL);
479 : }
480 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
481 : "Starting audit\n");
482 4 : if (GNUNET_OK !=
483 4 : TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
484 : NULL))
485 : {
486 0 : GNUNET_SCHEDULER_shutdown ();
487 0 : global_ret = EXIT_FAILURE;
488 0 : return;
489 : }
490 : }
491 :
492 :
493 : /**
494 : * The main function of the deposit auditing helper tool.
495 : *
496 : * @param argc number of arguments from the command line
497 : * @param argv command line arguments
498 : * @return 0 ok, 1 on error
499 : */
500 : int
501 4 : main (int argc,
502 : char *const *argv)
503 : {
504 4 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
505 4 : GNUNET_GETOPT_option_flag ('i',
506 : "internal",
507 : "perform checks only applicable for exchange-internal audits",
508 : &internal_checks),
509 4 : GNUNET_GETOPT_option_flag ('t',
510 : "test",
511 : "run in test mode and exit when idle",
512 : &test_mode),
513 4 : GNUNET_GETOPT_option_timetravel ('T',
514 : "timetravel"),
515 : GNUNET_GETOPT_OPTION_END
516 : };
517 : enum GNUNET_GenericReturnValue ret;
518 :
519 4 : ret = GNUNET_PROGRAM_run (
520 : TALER_AUDITOR_project_data (),
521 : argc,
522 : argv,
523 : "taler-helper-auditor-deposits",
524 : gettext_noop (
525 : "Audit Taler exchange database for deposit confirmation consistency"),
526 : options,
527 : &run,
528 : NULL);
529 4 : if (GNUNET_SYSERR == ret)
530 0 : return EXIT_INVALIDARGUMENT;
531 4 : if (GNUNET_NO == ret)
532 0 : return EXIT_SUCCESS;
533 4 : return global_ret;
534 : }
535 :
536 :
537 : /* end of taler-helper-auditor-deposits.c */
|