taler-helper-auditor-deposits.c (15759B)
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 "taler/platform.h" 26 #include <gnunet/gnunet_util_lib.h> 27 #include "taler/taler_auditordb_plugin.h" 28 #include "taler/taler_exchangedb_lib.h" 29 #include "taler/taler_bank_service.h" 30 #include "taler/taler_signatures.h" 31 #include "report-lib.h" 32 #include "taler/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 test_dc (void *cls, 114 const struct TALER_AUDITORDB_DepositConfirmation *dc) 115 { 116 bool missing = false; 117 enum GNUNET_DB_QueryStatus qs; 118 119 (void) cls; 120 TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id; 121 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 qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls, 127 &dc->h_contract_terms, 128 &dc->h_wire, 129 &dc->coin_pubs[i], 130 &dc->merchant, 131 dc->refund_deadline, 132 &deposit_fee, 133 &exchange_timestamp); 134 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 missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 140 if (qs < 0) 141 { 142 GNUNET_break (0); /* DB error, complain */ 143 eqs = qs; 144 return GNUNET_SYSERR; 145 } 146 } 147 if (! missing) 148 { 149 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 150 "Deleting matching deposit confirmation %llu\n", 151 (unsigned long long) dc->row_id); 152 qs = TALER_ARL_adb->delete_generic ( 153 TALER_ARL_adb->cls, 154 TALER_AUDITORDB_DEPOSIT_CONFIRMATION, 155 dc->row_id); 156 if (qs < 0) 157 { 158 GNUNET_break (0); /* DB error, complain */ 159 eqs = qs; 160 return GNUNET_SYSERR; 161 } 162 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 163 "Found deposit %s in exchange database\n", 164 GNUNET_h2s (&dc->h_contract_terms.hash)); 165 return GNUNET_OK; /* all coins found, all good */ 166 } 167 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 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 recheck_dc (void *cls, 185 const struct TALER_AUDITORDB_DepositConfirmation *dc) 186 { 187 bool missing = false; 188 enum GNUNET_DB_QueryStatus qs; 189 190 (void) cls; 191 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 qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls, 197 &dc->h_contract_terms, 198 &dc->h_wire, 199 &dc->coin_pubs[i], 200 &dc->merchant, 201 dc->refund_deadline, 202 &deposit_fee, 203 &exchange_timestamp); 204 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 missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 210 if (qs < 0) 211 { 212 GNUNET_break (0); /* DB error, complain */ 213 eqs = qs; 214 return GNUNET_SYSERR; 215 } 216 } 217 if (! missing) 218 { 219 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 220 "Deleting matching deposit confirmation %llu\n", 221 (unsigned long long) dc->row_id); 222 qs = TALER_ARL_adb->delete_generic ( 223 TALER_ARL_adb->cls, 224 TALER_AUDITORDB_DEPOSIT_CONFIRMATION, 225 dc->row_id); 226 if (qs < 0) 227 { 228 GNUNET_break (0); /* DB error, complain */ 229 eqs = qs; 230 return GNUNET_SYSERR; 231 } 232 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 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 return GNUNET_OK; /* all coins found, all good */ 242 } 243 /* still missing, no change to totalmissing balance */ 244 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 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 qs = TALER_ARL_adb->get_auditor_progress ( 266 TALER_ARL_adb->cls, 267 TALER_ARL_GET_PP (deposit_confirmation_serial_id), 268 NULL); 269 if (0 > qs) 270 { 271 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 272 return qs; 273 } 274 had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); 275 if (had_pp) 276 { 277 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 pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id); 282 } 283 else 284 { 285 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 286 "First analysis using deposit auditor, starting audit from scratch\n"); 287 } 288 qs = TALER_ARL_adb->get_balance ( 289 TALER_ARL_adb->cls, 290 TALER_ARL_GET_AB (total_missed_deposit_confirmations), 291 NULL); 292 if (0 > qs) 293 { 294 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 295 return qs; 296 } 297 had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 298 had_missing = ! TALER_amount_is_zero ( 299 &TALER_ARL_USE_AB (total_missed_deposit_confirmations)); 300 qs = TALER_ARL_adb->get_deposit_confirmations ( 301 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 if (0 > qs) 308 { 309 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 310 return qs; 311 } 312 if (0 > eqs) 313 { 314 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 315 return eqs; 316 } 317 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 318 "Analyzed %d deposit confirmations\n", 319 (int) qs); 320 if (had_pp) 321 qs = TALER_ARL_adb->update_auditor_progress ( 322 TALER_ARL_adb->cls, 323 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 324 NULL); 325 else 326 qs = TALER_ARL_adb->insert_auditor_progress ( 327 TALER_ARL_adb->cls, 328 TALER_ARL_SET_PP (deposit_confirmation_serial_id), 329 NULL); 330 if (0 > qs) 331 { 332 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 333 "Failed to update auditor DB, not recording progress\n"); 334 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 335 return qs; 336 } 337 if (had_bal && had_pp && had_missing) 338 { 339 qs = TALER_ARL_adb->get_deposit_confirmations ( 340 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 if (0 > qs) 347 { 348 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 349 return qs; 350 } 351 if (0 > eqs) 352 { 353 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs); 354 return eqs; 355 } 356 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 357 "Re-analyzed %d deposit confirmations\n", 358 (int) qs); 359 } 360 if (had_bal) 361 qs = TALER_ARL_adb->update_balance ( 362 TALER_ARL_adb->cls, 363 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 364 NULL); 365 else 366 qs = TALER_ARL_adb->insert_balance ( 367 TALER_ARL_adb->cls, 368 TALER_ARL_SET_AB (total_missed_deposit_confirmations), 369 NULL); 370 if (0 > qs) 371 { 372 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 373 "Failed to update auditor DB, not recording progress\n"); 374 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 375 return qs; 376 } 377 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 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 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 397 "Received notification for new deposit_confirmation\n"); 398 if (GNUNET_OK != 399 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 400 NULL)) 401 { 402 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 403 "Audit failed\n"); 404 GNUNET_SCHEDULER_shutdown (); 405 global_ret = EXIT_FAILURE; 406 return; 407 } 408 } 409 410 411 /** 412 * Function called on shutdown. 413 */ 414 static void 415 do_shutdown (void *cls) 416 { 417 (void) cls; 418 if (NULL != eh) 419 { 420 TALER_ARL_adb->event_listen_cancel (eh); 421 eh = NULL; 422 } 423 TALER_ARL_done (); 424 } 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 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 cfg = c; 445 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 446 NULL); 447 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 448 "Launching deposit auditor\n"); 449 if (GNUNET_OK != 450 TALER_ARL_init (c)) 451 { 452 global_ret = EXIT_FAILURE; 453 return; 454 } 455 456 if (test_mode != 1) 457 { 458 struct GNUNET_DB_EventHeaderP es = { 459 .size = htons (sizeof (es)), 460 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS) 461 }; 462 463 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 464 "Running helper indefinitely\n"); 465 eh = TALER_ARL_adb->event_listen (TALER_ARL_adb->cls, 466 &es, 467 GNUNET_TIME_UNIT_FOREVER_REL, 468 &db_notify, 469 NULL); 470 } 471 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 472 "Starting audit\n"); 473 if (GNUNET_OK != 474 TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, 475 NULL)) 476 { 477 GNUNET_SCHEDULER_shutdown (); 478 global_ret = EXIT_FAILURE; 479 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 main (int argc, 493 char *const *argv) 494 { 495 const struct GNUNET_GETOPT_CommandLineOption options[] = { 496 GNUNET_GETOPT_option_flag ('i', 497 "internal", 498 "perform checks only applicable for exchange-internal audits", 499 &internal_checks), 500 GNUNET_GETOPT_option_flag ('t', 501 "test", 502 "run in test mode and exit when idle", 503 &test_mode), 504 GNUNET_GETOPT_option_timetravel ('T', 505 "timetravel"), 506 GNUNET_GETOPT_OPTION_END 507 }; 508 enum GNUNET_GenericReturnValue ret; 509 510 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 if (GNUNET_SYSERR == ret) 521 return EXIT_INVALIDARGUMENT; 522 if (GNUNET_NO == ret) 523 return EXIT_SUCCESS; 524 return global_ret; 525 } 526 527 528 /* end of taler-helper-auditor-deposits.c */