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