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