taler-helper-auditor-deposits.c (16153B)
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 qs = TALER_AUDITORDB_get_auditor_progress ( 275 TALER_ARL_adb, 276 TALER_ARL_GET_PP (deposit_confirmation_serial_id), 277 NULL); 278 if (0 > qs) 279 { 280 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 281 return qs; 282 } 283 had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); 284 if (had_pp) 285 { 286 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 pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id); 291 } 292 else 293 { 294 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 295 "First analysis using deposit auditor, starting audit from scratch\n"); 296 } 297 qs = TALER_AUDITORDB_get_balance ( 298 TALER_ARL_adb, 299 TALER_ARL_GET_AB (total_missed_deposit_confirmations), 300 NULL); 301 if (0 > qs) 302 { 303 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 304 return qs; 305 } 306 had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 307 had_missing = ! TALER_amount_is_zero ( 308 &TALER_ARL_USE_AB (total_missed_deposit_confirmations)); 309 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 if (0 > qs) 317 { 318 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 319 return qs; 320 } 321 if (0 > eqs) 322 { 323 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 324 return eqs; 325 } 326 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 327 "Analyzed %d deposit confirmations\n", 328 (int) qs); 329 if (had_pp) 330 qs = TALER_AUDITORDB_update_auditor_progress ( 331 TALER_ARL_adb, 332 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 333 NULL); 334 else 335 qs = TALER_AUDITORDB_insert_auditor_progress ( 336 TALER_ARL_adb, 337 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 338 NULL); 339 if (0 > qs) 340 { 341 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 342 "Failed to update auditor DB, not recording progress\n"); 343 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 344 return qs; 345 } 346 if (had_bal && had_pp && had_missing) 347 { 348 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 if (0 > qs) 356 { 357 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 358 return qs; 359 } 360 if (0 > eqs) 361 { 362 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 363 return eqs; 364 } 365 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 366 "Re-analyzed %d deposit confirmations\n", 367 (int) qs); 368 } 369 if (had_bal) 370 qs = TALER_AUDITORDB_update_balance ( 371 TALER_ARL_adb, 372 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 373 NULL); 374 else 375 qs = TALER_AUDITORDB_insert_balance ( 376 TALER_ARL_adb, 377 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 378 NULL); 379 if (0 > qs) 380 { 381 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 382 "Failed to update auditor DB, not recording progress\n"); 383 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 384 return qs; 385 } 386 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 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 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 406 "Received notification for new deposit_confirmation\n"); 407 if (GNUNET_OK != 408 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 409 NULL)) 410 { 411 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 412 "Audit failed\n"); 413 GNUNET_SCHEDULER_shutdown (); 414 global_ret = EXIT_FAILURE; 415 return; 416 } 417 } 418 419 420 /** 421 * Function called on shutdown. 422 */ 423 static void 424 do_shutdown (void *cls) 425 { 426 (void) cls; 427 if (NULL != eh) 428 { 429 TALER_AUDITORDB_event_listen_cancel (eh); 430 eh = NULL; 431 } 432 TALER_ARL_done (); 433 } 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 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 cfg = c; 454 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 455 NULL); 456 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 457 "Launching deposit auditor\n"); 458 if (GNUNET_OK != 459 TALER_ARL_init (c)) 460 { 461 global_ret = EXIT_FAILURE; 462 return; 463 } 464 465 if (test_mode != 1) 466 { 467 struct GNUNET_DB_EventHeaderP es = { 468 .size = htons (sizeof (es)), 469 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS) 470 }; 471 472 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 473 "Running helper indefinitely\n"); 474 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 475 &es, 476 GNUNET_TIME_UNIT_FOREVER_REL, 477 &db_notify, 478 NULL); 479 } 480 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 481 "Starting audit\n"); 482 if (GNUNET_OK != 483 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 484 NULL)) 485 { 486 GNUNET_SCHEDULER_shutdown (); 487 global_ret = EXIT_FAILURE; 488 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 main (int argc, 502 char *const *argv) 503 { 504 const struct GNUNET_GETOPT_CommandLineOption options[] = { 505 GNUNET_GETOPT_option_flag ('i', 506 "internal", 507 "perform checks only applicable for exchange-internal audits", 508 &internal_checks), 509 GNUNET_GETOPT_option_flag ('t', 510 "test", 511 "run in test mode and exit when idle", 512 &test_mode), 513 GNUNET_GETOPT_option_timetravel ('T', 514 "timetravel"), 515 GNUNET_GETOPT_OPTION_END 516 }; 517 enum GNUNET_GenericReturnValue ret; 518 519 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 if (GNUNET_SYSERR == ret) 530 return EXIT_INVALIDARGUMENT; 531 if (GNUNET_NO == ret) 532 return EXIT_SUCCESS; 533 return global_ret; 534 } 535 536 537 /* end of taler-helper-auditor-deposits.c */