taler-helper-auditor-deposits.c (16489B)
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/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 test_dc (void *cls, 123 const struct TALER_AUDITORDB_DepositConfirmation *dc) 124 { 125 bool missing = false; 126 enum GNUNET_DB_QueryStatus qs; 127 128 (void) cls; 129 TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id; 130 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 qs = TALER_EXCHANGEDB_have_deposit2 (TALER_ARL_edb, 136 &dc->h_contract_terms, 137 &dc->h_wire, 138 &dc->coin_pubs[i], 139 &dc->merchant, 140 dc->refund_deadline, 141 &deposit_fee, 142 &exchange_timestamp); 143 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 missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 149 if (qs < 0) 150 { 151 GNUNET_break (0); /* DB error, complain */ 152 eqs = qs; 153 return GNUNET_SYSERR; 154 } 155 } 156 if (! missing) 157 { 158 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 159 "Deleting matching deposit confirmation %llu\n", 160 (unsigned long long) dc->row_id); 161 qs = TALER_AUDITORDB_delete_generic ( 162 TALER_ARL_adb, 163 TALER_AUDITORDB_DEPOSIT_CONFIRMATION, 164 dc->row_id); 165 if (qs < 0) 166 { 167 GNUNET_break (0); /* DB error, complain */ 168 eqs = qs; 169 return GNUNET_SYSERR; 170 } 171 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 172 "Found deposit %s in exchange database\n", 173 GNUNET_h2s (&dc->h_contract_terms.hash)); 174 return GNUNET_OK; /* all coins found, all good */ 175 } 176 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 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 recheck_dc (void *cls, 194 const struct TALER_AUDITORDB_DepositConfirmation *dc) 195 { 196 bool missing = false; 197 enum GNUNET_DB_QueryStatus qs; 198 199 (void) cls; 200 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 qs = TALER_EXCHANGEDB_have_deposit2 (TALER_ARL_edb, 206 &dc->h_contract_terms, 207 &dc->h_wire, 208 &dc->coin_pubs[i], 209 &dc->merchant, 210 dc->refund_deadline, 211 &deposit_fee, 212 &exchange_timestamp); 213 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 missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 219 if (qs < 0) 220 { 221 GNUNET_break (0); /* DB error, complain */ 222 eqs = qs; 223 return GNUNET_SYSERR; 224 } 225 } 226 if (! missing) 227 { 228 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 229 "Deleting matching deposit confirmation %llu\n", 230 (unsigned long long) dc->row_id); 231 qs = TALER_AUDITORDB_delete_generic ( 232 TALER_ARL_adb, 233 TALER_AUDITORDB_DEPOSIT_CONFIRMATION, 234 dc->row_id); 235 if (qs < 0) 236 { 237 GNUNET_break (0); /* DB error, complain */ 238 eqs = qs; 239 return GNUNET_SYSERR; 240 } 241 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 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 return GNUNET_OK; /* all coins found, all good */ 251 } 252 /* still missing, no change to totalmissing balance */ 253 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 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 /* Reset the shared exchange-DB status accumulator: it is a file-scope 275 static written by the test_dc/recheck_dc callbacks; if a previous 276 (possibly retried) run left it negative, failing to reset it here would 277 permanently wedge this helper via the `0 > eqs` checks below. */ 278 eqs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 279 qs = TALER_AUDITORDB_get_auditor_progress ( 280 TALER_ARL_adb, 281 TALER_ARL_GET_PP (deposit_confirmation_serial_id), 282 NULL); 283 if (0 > qs) 284 { 285 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 286 return qs; 287 } 288 had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); 289 if (had_pp) 290 { 291 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 292 "Resuming deposit confirmation audit at %llu\n", 293 (unsigned long long) TALER_ARL_USE_PP ( 294 deposit_confirmation_serial_id)); 295 pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id); 296 } 297 else 298 { 299 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 300 "First analysis using deposit auditor, starting audit from scratch\n"); 301 } 302 qs = TALER_AUDITORDB_get_balance ( 303 TALER_ARL_adb, 304 TALER_ARL_GET_AB (total_missed_deposit_confirmations), 305 NULL); 306 if (0 > qs) 307 { 308 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 309 return qs; 310 } 311 had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 312 had_missing = ! TALER_amount_is_zero ( 313 &TALER_ARL_USE_AB (total_missed_deposit_confirmations)); 314 qs = TALER_AUDITORDB_get_deposit_confirmations ( 315 TALER_ARL_adb, 316 INT64_MAX, 317 TALER_ARL_USE_PP (deposit_confirmation_serial_id), 318 true, /* return suppressed */ 319 &test_dc, 320 NULL); 321 if (0 > qs) 322 { 323 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 324 return qs; 325 } 326 if (0 > eqs) 327 { 328 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 329 return eqs; 330 } 331 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 332 "Analyzed %d deposit confirmations\n", 333 (int) qs); 334 if (had_pp) 335 qs = TALER_AUDITORDB_update_auditor_progress ( 336 TALER_ARL_adb, 337 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 338 NULL); 339 else 340 qs = TALER_AUDITORDB_insert_auditor_progress ( 341 TALER_ARL_adb, 342 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 343 NULL); 344 if (0 > qs) 345 { 346 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 347 "Failed to update auditor DB, not recording progress\n"); 348 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 349 return qs; 350 } 351 if (had_bal && had_pp && had_missing) 352 { 353 qs = TALER_AUDITORDB_get_deposit_confirmations ( 354 TALER_ARL_adb, 355 -INT64_MAX, 356 pp + 1, /* previous iteration went up to 'pp', try missing again */ 357 true, /* return suppressed */ 358 &recheck_dc, 359 NULL); 360 if (0 > qs) 361 { 362 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 363 return qs; 364 } 365 if (0 > eqs) 366 { 367 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 368 return eqs; 369 } 370 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 371 "Re-analyzed %d deposit confirmations\n", 372 (int) qs); 373 } 374 if (had_bal) 375 qs = TALER_AUDITORDB_update_balance ( 376 TALER_ARL_adb, 377 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 378 NULL); 379 else 380 qs = TALER_AUDITORDB_insert_balance ( 381 TALER_ARL_adb, 382 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 383 NULL); 384 if (0 > qs) 385 { 386 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 387 "Failed to update auditor DB, not recording progress\n"); 388 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 389 return qs; 390 } 391 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 392 } 393 394 395 /** 396 * Function called on events received from Postgres. 397 * 398 * @param cls closure, NULL 399 * @param extra additional event data provided 400 * @param extra_size number of bytes in @a extra 401 */ 402 static void 403 db_notify (void *cls, 404 const void *extra, 405 size_t extra_size) 406 { 407 (void) cls; 408 (void) extra; 409 (void) extra_size; 410 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 411 "Received notification for new deposit_confirmation\n"); 412 if (GNUNET_OK != 413 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 414 NULL)) 415 { 416 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 417 "Audit failed\n"); 418 GNUNET_SCHEDULER_shutdown (); 419 global_ret = EXIT_FAILURE; 420 return; 421 } 422 } 423 424 425 /** 426 * Function called on shutdown. 427 */ 428 static void 429 do_shutdown (void *cls) 430 { 431 (void) cls; 432 if (NULL != eh) 433 { 434 TALER_AUDITORDB_event_listen_cancel (eh); 435 eh = NULL; 436 } 437 TALER_ARL_done (); 438 } 439 440 441 /** 442 * Main function that will be run. 443 * 444 * @param cls closure 445 * @param args remaining command-line arguments 446 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 447 * @param c configuration 448 */ 449 static void 450 run (void *cls, 451 char *const *args, 452 const char *cfgfile, 453 const struct GNUNET_CONFIGURATION_Handle *c) 454 { 455 (void) cls; 456 (void) args; 457 (void) cfgfile; 458 cfg = c; 459 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 460 NULL); 461 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 462 "Launching deposit auditor\n"); 463 if (GNUNET_OK != 464 TALER_ARL_init (c)) 465 { 466 global_ret = EXIT_FAILURE; 467 return; 468 } 469 470 if (test_mode != 1) 471 { 472 struct GNUNET_DB_EventHeaderP es = { 473 .size = htons (sizeof (es)), 474 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS) 475 }; 476 477 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 478 "Running helper indefinitely\n"); 479 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 480 &es, 481 GNUNET_TIME_UNIT_FOREVER_REL, 482 &db_notify, 483 NULL); 484 } 485 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 486 "Starting audit\n"); 487 if (GNUNET_OK != 488 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 489 NULL)) 490 { 491 GNUNET_SCHEDULER_shutdown (); 492 global_ret = EXIT_FAILURE; 493 return; 494 } 495 } 496 497 498 /** 499 * The main function of the deposit auditing helper tool. 500 * 501 * @param argc number of arguments from the command line 502 * @param argv command line arguments 503 * @return 0 ok, 1 on error 504 */ 505 int 506 main (int argc, 507 char *const *argv) 508 { 509 const struct GNUNET_GETOPT_CommandLineOption options[] = { 510 GNUNET_GETOPT_option_flag ('i', 511 "internal", 512 "perform checks only applicable for exchange-internal audits", 513 &internal_checks), 514 GNUNET_GETOPT_option_flag ('t', 515 "test", 516 "run in test mode and exit when idle", 517 &test_mode), 518 GNUNET_GETOPT_option_timetravel ('T', 519 "timetravel"), 520 GNUNET_GETOPT_OPTION_END 521 }; 522 enum GNUNET_GenericReturnValue ret; 523 524 ret = GNUNET_PROGRAM_run ( 525 TALER_AUDITOR_project_data (), 526 argc, 527 argv, 528 "taler-helper-auditor-deposits", 529 gettext_noop ( 530 "Audit Taler exchange database for deposit confirmation consistency"), 531 options, 532 &run, 533 NULL); 534 if (GNUNET_SYSERR == ret) 535 return EXIT_INVALIDARGUMENT; 536 if (GNUNET_NO == ret) 537 return EXIT_SUCCESS; 538 return global_ret; 539 } 540 541 542 /* end of taler-helper-auditor-deposits.c */