taler-helper-auditor-deposits.c (15971B)
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 test_dc (void *cls, 121 const struct TALER_AUDITORDB_DepositConfirmation *dc) 122 { 123 bool missing = false; 124 enum GNUNET_DB_QueryStatus qs; 125 126 (void) cls; 127 TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id; 128 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 qs = TALER_EXCHANGEDB_get_exists_deposit (TALER_ARL_edb, 134 &dc->h_contract_terms, 135 &dc->h_wire, 136 &dc->coin_pubs[i], 137 &dc->merchant, 138 dc->refund_deadline, 139 &deposit_fee, 140 &exchange_timestamp); 141 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 missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 147 if (qs < 0) 148 { 149 GNUNET_break (0); /* DB error, complain */ 150 eqs = qs; 151 return GNUNET_SYSERR; 152 } 153 } 154 if (! missing) 155 { 156 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 157 "Deleting matching deposit confirmation %llu\n", 158 (unsigned long long) dc->row_id); 159 qs = TALER_AUDITORDB_delete_generic ( 160 TALER_ARL_adb, 161 TALER_AUDITORDB_DEPOSIT_CONFIRMATION, 162 dc->row_id); 163 if (qs < 0) 164 { 165 GNUNET_break (0); /* DB error, complain */ 166 eqs = qs; 167 return GNUNET_SYSERR; 168 } 169 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 170 "Found deposit %s in exchange database\n", 171 GNUNET_h2s (&dc->h_contract_terms.hash)); 172 return GNUNET_OK; /* all coins found, all good */ 173 } 174 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 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 recheck_dc (void *cls, 192 const struct TALER_AUDITORDB_DepositConfirmation *dc) 193 { 194 bool missing = false; 195 enum GNUNET_DB_QueryStatus qs; 196 197 (void) cls; 198 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 qs = TALER_EXCHANGEDB_get_exists_deposit (TALER_ARL_edb, 204 &dc->h_contract_terms, 205 &dc->h_wire, 206 &dc->coin_pubs[i], 207 &dc->merchant, 208 dc->refund_deadline, 209 &deposit_fee, 210 &exchange_timestamp); 211 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 missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 217 if (qs < 0) 218 { 219 GNUNET_break (0); /* DB error, complain */ 220 eqs = qs; 221 return GNUNET_SYSERR; 222 } 223 } 224 if (! missing) 225 { 226 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 227 "Deleting matching deposit confirmation %llu\n", 228 (unsigned long long) dc->row_id); 229 qs = TALER_AUDITORDB_delete_generic ( 230 TALER_ARL_adb, 231 TALER_AUDITORDB_DEPOSIT_CONFIRMATION, 232 dc->row_id); 233 if (qs < 0) 234 { 235 GNUNET_break (0); /* DB error, complain */ 236 eqs = qs; 237 return GNUNET_SYSERR; 238 } 239 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 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 return GNUNET_OK; /* all coins found, all good */ 249 } 250 /* still missing, no change to totalmissing balance */ 251 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 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 eqs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 277 qs = TALER_AUDITORDB_get_auditor_progress ( 278 TALER_ARL_adb, 279 TALER_ARL_GET_PP (deposit_confirmation_serial_id), 280 NULL); 281 if (0 > qs) 282 { 283 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 284 return qs; 285 } 286 had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); 287 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 pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id); 292 qs = TALER_AUDITORDB_get_balance ( 293 TALER_ARL_adb, 294 TALER_ARL_GET_AB (total_missed_deposit_confirmations), 295 NULL); 296 if (0 > qs) 297 { 298 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 299 return qs; 300 } 301 had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 302 had_missing = ! TALER_amount_is_zero ( 303 &TALER_ARL_USE_AB (total_missed_deposit_confirmations)); 304 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 if (0 > qs) 312 { 313 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 314 return qs; 315 } 316 if (0 > eqs) 317 { 318 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 319 return eqs; 320 } 321 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 322 "Analyzed %d deposit confirmations\n", 323 (int) qs); 324 qs = TALER_AUDITORDB_insert_auditor_progress ( 325 TALER_ARL_adb, 326 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 327 NULL); 328 if (0 > qs) 329 { 330 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 331 "Failed to update auditor DB, not recording progress\n"); 332 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 333 return qs; 334 } 335 if (had_bal && had_pp && had_missing) 336 { 337 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 if (0 > qs) 345 { 346 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 347 return qs; 348 } 349 if (0 > eqs) 350 { 351 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 352 return eqs; 353 } 354 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 355 "Re-analyzed %d deposit confirmations\n", 356 (int) qs); 357 } 358 qs = TALER_AUDITORDB_insert_balance ( 359 TALER_ARL_adb, 360 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 361 NULL); 362 if (0 > qs) 363 { 364 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 365 "Failed to update auditor DB, not recording progress\n"); 366 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 367 return qs; 368 } 369 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 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 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 389 "Received notification for new deposit_confirmation\n"); 390 if (GNUNET_OK != 391 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 392 NULL)) 393 { 394 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 395 "Audit failed\n"); 396 GNUNET_SCHEDULER_shutdown (); 397 global_ret = EXIT_FAILURE; 398 return; 399 } 400 } 401 402 403 /** 404 * Function called on shutdown. 405 */ 406 static void 407 do_shutdown (void *cls) 408 { 409 (void) cls; 410 if (NULL != eh) 411 { 412 TALER_AUDITORDB_event_listen_cancel (eh); 413 eh = NULL; 414 } 415 TALER_ARL_done (); 416 } 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 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 cfg = c; 437 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 438 NULL); 439 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 440 "Launching deposit auditor\n"); 441 if (GNUNET_OK != 442 TALER_ARL_init (c)) 443 { 444 global_ret = EXIT_FAILURE; 445 return; 446 } 447 448 if (test_mode != 1) 449 { 450 struct GNUNET_DB_EventHeaderP es = { 451 .size = htons (sizeof (es)), 452 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS) 453 }; 454 455 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 456 "Running helper indefinitely\n"); 457 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 458 &es, 459 GNUNET_TIME_UNIT_FOREVER_REL, 460 &db_notify, 461 NULL); 462 } 463 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 464 "Starting audit\n"); 465 if (GNUNET_OK != 466 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 467 NULL)) 468 { 469 GNUNET_SCHEDULER_shutdown (); 470 global_ret = EXIT_FAILURE; 471 return; 472 } 473 } 474 475 476 /** 477 * The main function of the deposit auditing helper tool. 478 * 479 * @param argc number of arguments from the command line 480 * @param argv command line arguments 481 * @return 0 ok, 1 on error 482 */ 483 int 484 main (int argc, 485 char *const *argv) 486 { 487 const struct GNUNET_GETOPT_CommandLineOption options[] = { 488 GNUNET_GETOPT_option_flag ('i', 489 "internal", 490 "perform checks only applicable for exchange-internal audits", 491 &internal_checks), 492 GNUNET_GETOPT_option_flag ('t', 493 "test", 494 "run in test mode and exit when idle", 495 &test_mode), 496 GNUNET_GETOPT_option_timetravel ('T', 497 "timetravel"), 498 GNUNET_GETOPT_OPTION_END 499 }; 500 enum GNUNET_GenericReturnValue ret; 501 502 ret = GNUNET_PROGRAM_run ( 503 TALER_AUDITOR_project_data (), 504 argc, 505 argv, 506 "taler-helper-auditor-deposits", 507 gettext_noop ( 508 "Audit Taler exchange database for deposit confirmation consistency"), 509 options, 510 &run, 511 NULL); 512 if (GNUNET_SYSERR == ret) 513 return EXIT_INVALIDARGUMENT; 514 if (GNUNET_NO == ret) 515 return EXIT_SUCCESS; 516 return global_ret; 517 } 518 519 520 /* end of taler-helper-auditor-deposits.c */