taler-merchant-depositcheck.c (30089B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024, 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 General 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 General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-merchant-depositcheck.c 18 * @brief Process that inquires with the exchange for deposits that should have been wired 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include "microhttpd.h" 23 #include <gnunet/gnunet_util_lib.h> 24 #include <jansson.h> 25 #include <pthread.h> 26 #include "taler/taler_merchant_util.h" 27 #include "taler/taler_merchantdb_lib.h" 28 #include "taler/taler_merchantdb_plugin.h" 29 #include <taler/taler_dbevents.h> 30 31 /** 32 * How many requests do we make at most in parallel to the same exchange? 33 */ 34 #define CONCURRENCY_LIMIT 32 35 36 /** 37 * How long do we not try a deposit check if the deposit 38 * was put on hold due to a KYC/AML block? 39 */ 40 #define KYC_RETRY_DELAY GNUNET_TIME_UNIT_HOURS 41 42 /** 43 * Information we keep per exchange. 44 */ 45 struct Child 46 { 47 48 /** 49 * Kept in a DLL. 50 */ 51 struct Child *next; 52 53 /** 54 * Kept in a DLL. 55 */ 56 struct Child *prev; 57 58 /** 59 * The child process. 60 */ 61 struct GNUNET_Process *process; 62 63 /** 64 * Wait handle. 65 */ 66 struct GNUNET_ChildWaitHandle *cwh; 67 68 /** 69 * Which exchange is this state for? 70 */ 71 char *base_url; 72 73 /** 74 * Task to restart the child. 75 */ 76 struct GNUNET_SCHEDULER_Task *rt; 77 78 /** 79 * When should the child be restarted at the earliest? 80 */ 81 struct GNUNET_TIME_Absolute next_start; 82 83 /** 84 * Current minimum delay between restarts, grows 85 * exponentially if child exits before this time. 86 */ 87 struct GNUNET_TIME_Relative rd; 88 89 }; 90 91 92 /** 93 * Information we keep per exchange interaction. 94 */ 95 struct ExchangeInteraction 96 { 97 /** 98 * Kept in a DLL. 99 */ 100 struct ExchangeInteraction *next; 101 102 /** 103 * Kept in a DLL. 104 */ 105 struct ExchangeInteraction *prev; 106 107 /** 108 * Handle for exchange interaction. 109 */ 110 struct TALER_EXCHANGE_GetDepositsHandle *dgh; 111 112 /** 113 * Wire deadline for the deposit. 114 */ 115 struct GNUNET_TIME_Absolute wire_deadline; 116 117 /** 118 * Current value for the retry backoff 119 */ 120 struct GNUNET_TIME_Relative retry_backoff; 121 122 /** 123 * Target account hash of the deposit. 124 */ 125 struct TALER_MerchantWireHashP h_wire; 126 127 /** 128 * Deposited amount. 129 */ 130 struct TALER_Amount amount_with_fee; 131 132 /** 133 * Deposit fee paid. 134 */ 135 struct TALER_Amount deposit_fee; 136 137 /** 138 * Public key of the deposited coin. 139 */ 140 struct TALER_CoinSpendPublicKeyP coin_pub; 141 142 /** 143 * Hash over the @e contract_terms. 144 */ 145 struct TALER_PrivateContractHashP h_contract_terms; 146 147 /** 148 * Merchant instance's private key. 149 */ 150 struct TALER_MerchantPrivateKeyP merchant_priv; 151 152 /** 153 * Serial number of the row in the deposits table 154 * that we are processing. 155 */ 156 uint64_t deposit_serial; 157 158 /** 159 * The instance the deposit belongs to. 160 */ 161 char *instance_id; 162 163 }; 164 165 166 /** 167 * Head of list of children we forked. 168 */ 169 static struct Child *c_head; 170 171 /** 172 * Tail of list of children we forked. 173 */ 174 static struct Child *c_tail; 175 176 /** 177 * Key material of the exchange. 178 */ 179 static struct TALER_EXCHANGE_Keys *keys; 180 181 /** 182 * Head of list of active exchange interactions. 183 */ 184 static struct ExchangeInteraction *w_head; 185 186 /** 187 * Tail of list of active exchange interactions. 188 */ 189 static struct ExchangeInteraction *w_tail; 190 191 /** 192 * Number of active entries in the @e w_head list. 193 */ 194 static uint64_t w_count; 195 196 /** 197 * Notification handler from database on new work. 198 */ 199 static struct GNUNET_DB_EventHandler *eh; 200 201 /** 202 * Notification handler from database on new keys. 203 */ 204 static struct GNUNET_DB_EventHandler *keys_eh; 205 206 /** 207 * The merchant's configuration. 208 */ 209 static const struct GNUNET_CONFIGURATION_Handle *cfg; 210 211 /** 212 * Name of the configuration file we use. 213 */ 214 static char *cfg_filename; 215 216 /** 217 * Our database plugin. 218 */ 219 static struct TALER_MERCHANTDB_Plugin *db_plugin; 220 221 /** 222 * Next wire deadline that @e task is scheduled for. 223 */ 224 static struct GNUNET_TIME_Absolute next_deadline; 225 226 /** 227 * Next task to run, if any. 228 */ 229 static struct GNUNET_SCHEDULER_Task *task; 230 231 /** 232 * Handle to the context for interacting with the exchange. 233 */ 234 static struct GNUNET_CURL_Context *ctx; 235 236 /** 237 * Scheduler context for running the @e ctx. 238 */ 239 static struct GNUNET_CURL_RescheduleContext *rc; 240 241 /** 242 * Which exchange are we monitoring? NULL if we 243 * are the parent of the workers. 244 */ 245 static char *exchange_url; 246 247 /** 248 * Value to return from main(). 0 on success, non-zero on errors. 249 */ 250 static int global_ret; 251 252 /** 253 * #GNUNET_YES if we are in test mode and should exit when idle. 254 */ 255 static int test_mode; 256 257 258 /** 259 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 260 * 261 * @param cls closure 262 */ 263 static void 264 shutdown_task (void *cls) 265 { 266 struct Child *c; 267 struct ExchangeInteraction *w; 268 269 (void) cls; 270 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 271 "Running shutdown\n"); 272 if (NULL != eh) 273 { 274 db_plugin->event_listen_cancel (eh); 275 eh = NULL; 276 } 277 if (NULL != keys_eh) 278 { 279 db_plugin->event_listen_cancel (keys_eh); 280 keys_eh = NULL; 281 } 282 if (NULL != task) 283 { 284 GNUNET_SCHEDULER_cancel (task); 285 task = NULL; 286 } 287 while (NULL != (w = w_head)) 288 { 289 GNUNET_CONTAINER_DLL_remove (w_head, 290 w_tail, 291 w); 292 if (NULL != w->dgh) 293 { 294 TALER_EXCHANGE_get_deposits_cancel (w->dgh); 295 w->dgh = NULL; 296 } 297 w_count--; 298 GNUNET_free (w->instance_id); 299 GNUNET_free (w); 300 } 301 while (NULL != (c = c_head)) 302 { 303 GNUNET_CONTAINER_DLL_remove (c_head, 304 c_tail, 305 c); 306 if (NULL != c->rt) 307 { 308 GNUNET_SCHEDULER_cancel (c->rt); 309 c->rt = NULL; 310 } 311 if (NULL != c->cwh) 312 { 313 GNUNET_wait_child_cancel (c->cwh); 314 c->cwh = NULL; 315 } 316 if (NULL != c->process) 317 { 318 enum GNUNET_OS_ProcessStatusType type 319 = GNUNET_OS_PROCESS_UNKNOWN; 320 unsigned long code = 0; 321 322 GNUNET_break (GNUNET_OK == 323 GNUNET_process_kill (c->process, 324 SIGTERM)); 325 GNUNET_break (GNUNET_OK == 326 GNUNET_process_wait (c->process, 327 true, 328 &type, 329 &code)); 330 if ( (GNUNET_OS_PROCESS_EXITED != type) || 331 (0 != code) ) 332 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 333 "Process for exchange %s had trouble (%d/%d)\n", 334 c->base_url, 335 (int) type, 336 (int) code); 337 GNUNET_process_destroy (c->process); 338 } 339 GNUNET_free (c->base_url); 340 GNUNET_free (c); 341 } 342 if (NULL != db_plugin) 343 { 344 db_plugin->rollback (db_plugin->cls); /* just in case */ 345 TALER_MERCHANTDB_plugin_unload (db_plugin); 346 db_plugin = NULL; 347 } 348 cfg = NULL; 349 if (NULL != ctx) 350 { 351 GNUNET_CURL_fini (ctx); 352 ctx = NULL; 353 } 354 if (NULL != rc) 355 { 356 GNUNET_CURL_gnunet_rc_destroy (rc); 357 rc = NULL; 358 } 359 } 360 361 362 /** 363 * Task to get more deposits to work on from the database. 364 * 365 * @param cls NULL 366 */ 367 static void 368 select_work (void *cls); 369 370 371 /** 372 * Make sure to run the select_work() task at 373 * the @a next_deadline. 374 * 375 * @param deadline time when work becomes ready 376 */ 377 static void 378 run_at (struct GNUNET_TIME_Absolute deadline) 379 { 380 if ( (NULL != task) && 381 (GNUNET_TIME_absolute_cmp (deadline, 382 >, 383 next_deadline)) ) 384 { 385 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 386 "Not scheduling for %s yet, already have earlier task pending\n", 387 GNUNET_TIME_absolute2s (deadline)); 388 return; 389 } 390 if (NULL == keys) 391 { 392 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 393 "Not scheduling for %s yet, no /keys available\n", 394 GNUNET_TIME_absolute2s (deadline)); 395 return; /* too early */ 396 } 397 next_deadline = deadline; 398 if (NULL != task) 399 GNUNET_SCHEDULER_cancel (task); 400 task = GNUNET_SCHEDULER_add_at (deadline, 401 &select_work, 402 NULL); 403 } 404 405 406 /** 407 * Function called with detailed wire transfer data. 408 * 409 * @param cls closure with a `struct ExchangeInteraction *` 410 * @param dr HTTP response data 411 */ 412 static void 413 deposit_get_cb ( 414 void *cls, 415 const struct TALER_EXCHANGE_GetDepositsResponse *dr) 416 { 417 struct ExchangeInteraction *w = cls; 418 struct GNUNET_TIME_Absolute future_retry; 419 420 w->dgh = NULL; 421 future_retry 422 = GNUNET_TIME_relative_to_absolute (w->retry_backoff); 423 switch (dr->hr.http_status) 424 { 425 case MHD_HTTP_OK: 426 { 427 enum GNUNET_DB_QueryStatus qs; 428 429 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 430 "Exchange returned wire transfer over %s for deposited coin %s\n", 431 TALER_amount2s (&dr->details.ok.coin_contribution), 432 TALER_B2S (&w->coin_pub)); 433 qs = db_plugin->insert_deposit_to_transfer ( 434 db_plugin->cls, 435 w->deposit_serial, 436 &w->h_wire, 437 exchange_url, 438 &dr->details.ok); 439 if (qs <= 0) 440 { 441 GNUNET_break (0); 442 GNUNET_SCHEDULER_shutdown (); 443 return; 444 } 445 break; 446 } 447 case MHD_HTTP_ACCEPTED: 448 { 449 /* got a 'preliminary' reply from the exchange, 450 remember our target UUID */ 451 enum GNUNET_DB_QueryStatus qs; 452 struct GNUNET_TIME_Timestamp now; 453 454 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 455 "Exchange returned KYC requirement (%d) for deposited coin %s\n", 456 dr->details.accepted.kyc_ok, 457 TALER_B2S (&w->coin_pub)); 458 now = GNUNET_TIME_timestamp_get (); 459 qs = db_plugin->account_kyc_set_failed ( 460 db_plugin->cls, 461 w->instance_id, 462 &w->h_wire, 463 exchange_url, 464 now, 465 MHD_HTTP_ACCEPTED, 466 dr->details.accepted.kyc_ok); 467 if (qs < 0) 468 { 469 GNUNET_break (0); 470 GNUNET_SCHEDULER_shutdown (); 471 return; 472 } 473 if (dr->details.accepted.kyc_ok) 474 { 475 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 476 "Bumping wire transfer deadline in DB to %s as that is when we will retry\n", 477 GNUNET_TIME_absolute2s (future_retry)); 478 qs = db_plugin->update_deposit_confirmation_status ( 479 db_plugin->cls, 480 w->deposit_serial, 481 true, /* need to try again in the future! */ 482 GNUNET_TIME_absolute_to_timestamp (future_retry), 483 MHD_HTTP_ACCEPTED, 484 TALER_EC_NONE, 485 "Exchange reported 202 Accepted but no KYC block"); 486 if (qs < 0) 487 { 488 GNUNET_break (0); 489 GNUNET_SCHEDULER_shutdown (); 490 return; 491 } 492 } 493 else 494 { 495 future_retry 496 = GNUNET_TIME_absolute_max ( 497 future_retry, 498 GNUNET_TIME_relative_to_absolute ( 499 KYC_RETRY_DELAY)); 500 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 501 "Bumping wire transfer deadline in DB to %s as that is when we will retry\n", 502 GNUNET_TIME_absolute2s (future_retry)); 503 qs = db_plugin->update_deposit_confirmation_status ( 504 db_plugin->cls, 505 w->deposit_serial, 506 true /* need to try again in the future */, 507 GNUNET_TIME_absolute_to_timestamp (future_retry), 508 MHD_HTTP_ACCEPTED, 509 TALER_EC_NONE, 510 "Exchange reported 202 Accepted due to KYC/AML block"); 511 if (qs < 0) 512 { 513 GNUNET_break (0); 514 GNUNET_SCHEDULER_shutdown (); 515 return; 516 } 517 } 518 break; 519 } 520 default: 521 { 522 enum GNUNET_DB_QueryStatus qs; 523 bool retry_needed = false; 524 525 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 526 "Exchange %s returned tracking failure for deposited coin %s: %u\n", 527 exchange_url, 528 TALER_B2S (&w->coin_pub), 529 dr->hr.http_status); 530 /* rough classification by HTTP status group */ 531 switch (dr->hr.http_status / 100) 532 { 533 case 0: 534 /* timeout */ 535 retry_needed = true; 536 break; 537 case 1: 538 case 2: 539 case 3: 540 /* very strange */ 541 retry_needed = false; 542 break; 543 case 4: 544 /* likely fatal */ 545 retry_needed = false; 546 break; 547 case 5: 548 /* likely transient */ 549 retry_needed = true; 550 break; 551 } 552 qs = db_plugin->update_deposit_confirmation_status ( 553 db_plugin->cls, 554 w->deposit_serial, 555 retry_needed, 556 GNUNET_TIME_absolute_to_timestamp (future_retry), 557 (uint32_t) dr->hr.http_status, 558 dr->hr.ec, 559 dr->hr.hint); 560 if (qs < 0) 561 { 562 GNUNET_break (0); 563 GNUNET_SCHEDULER_shutdown (); 564 return; 565 } 566 break; 567 } 568 } /* end switch */ 569 570 GNUNET_CONTAINER_DLL_remove (w_head, 571 w_tail, 572 w); 573 w_count--; 574 GNUNET_free (w->instance_id); 575 GNUNET_free (w); 576 GNUNET_assert (NULL != keys); 577 if (0 == w_count) 578 { 579 /* We only SELECT() again after having finished 580 all requests, as otherwise we'll most like 581 just SELECT() those again that are already 582 being requested; alternatively, we could 583 update the retry_time already on SELECT(), 584 but this should be easier on the DB. */ 585 if (NULL != task) 586 GNUNET_SCHEDULER_cancel (task); 587 task = GNUNET_SCHEDULER_add_now (&select_work, 588 NULL); 589 } 590 } 591 592 593 /** 594 * Typically called by `select_work`. 595 * 596 * @param cls NULL 597 * @param deposit_serial identifies the deposit operation 598 * @param wire_deadline when is the wire due 599 * @param retry_time current value for the retry backoff 600 * @param h_contract_terms hash of the contract terms 601 * @param merchant_priv private key of the merchant 602 * @param instance_id row ID of the instance 603 * @param h_wire hash of the merchant's wire account into 604 * @param amount_with_fee amount the exchange will deposit for this coin 605 * @param deposit_fee fee the exchange will charge for this coin which the deposit was made 606 * @param coin_pub public key of the deposited coin 607 */ 608 static void 609 pending_deposits_cb ( 610 void *cls, 611 uint64_t deposit_serial, 612 struct GNUNET_TIME_Absolute wire_deadline, 613 struct GNUNET_TIME_Absolute retry_time, 614 const struct TALER_PrivateContractHashP *h_contract_terms, 615 const struct TALER_MerchantPrivateKeyP *merchant_priv, 616 const char *instance_id, 617 const struct TALER_MerchantWireHashP *h_wire, 618 const struct TALER_Amount *amount_with_fee, 619 const struct TALER_Amount *deposit_fee, 620 const struct TALER_CoinSpendPublicKeyP *coin_pub) 621 { 622 struct ExchangeInteraction *w; 623 struct GNUNET_TIME_Absolute mx 624 = GNUNET_TIME_absolute_max (wire_deadline, 625 retry_time); 626 struct GNUNET_TIME_Relative retry_backoff; 627 628 (void) cls; 629 if (GNUNET_TIME_absolute_is_future (mx)) 630 { 631 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 632 "Pending deposit should be checked next at %s\n", 633 GNUNET_TIME_absolute2s (mx)); 634 run_at (mx); 635 return; 636 } 637 if (GNUNET_TIME_absolute_is_zero (retry_time)) 638 retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline); 639 else 640 retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline, 641 retry_time); 642 w = GNUNET_new (struct ExchangeInteraction); 643 w->deposit_serial = deposit_serial; 644 w->wire_deadline = wire_deadline; 645 w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff, 646 GNUNET_TIME_UNIT_DAYS); 647 w->h_contract_terms = *h_contract_terms; 648 w->merchant_priv = *merchant_priv; 649 w->h_wire = *h_wire; 650 w->amount_with_fee = *amount_with_fee; 651 w->deposit_fee = *deposit_fee; 652 w->coin_pub = *coin_pub; 653 w->instance_id = GNUNET_strdup (instance_id); 654 GNUNET_CONTAINER_DLL_insert (w_head, 655 w_tail, 656 w); 657 w_count++; 658 GNUNET_assert (NULL != keys); 659 if (GNUNET_TIME_absolute_is_past ( 660 keys->key_data_expiration.abs_time)) 661 { 662 /* Parent should re-start us, then we will re-fetch /keys */ 663 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 664 "/keys expired, shutting down\n"); 665 GNUNET_SCHEDULER_shutdown (); 666 return; 667 } 668 GNUNET_assert (NULL == w->dgh); 669 w->dgh = TALER_EXCHANGE_get_deposits_create ( 670 ctx, 671 exchange_url, 672 keys, 673 &w->merchant_priv, 674 &w->h_wire, 675 &w->h_contract_terms, 676 &w->coin_pub); 677 if (NULL == w->dgh) 678 { 679 GNUNET_break (0); 680 GNUNET_SCHEDULER_shutdown (); 681 return; 682 } 683 if (TALER_EC_NONE != 684 TALER_EXCHANGE_get_deposits_start (w->dgh, 685 &deposit_get_cb, 686 w)) 687 { 688 GNUNET_break (0); 689 TALER_EXCHANGE_get_deposits_cancel (w->dgh); 690 w->dgh = NULL; 691 GNUNET_SCHEDULER_shutdown (); 692 return; 693 } 694 } 695 696 697 /** 698 * Function called on events received from Postgres. 699 * 700 * @param cls closure, NULL 701 * @param extra additional event data provided, timestamp with wire deadline 702 * @param extra_size number of bytes in @a extra 703 */ 704 static void 705 db_notify (void *cls, 706 const void *extra, 707 size_t extra_size) 708 { 709 struct GNUNET_TIME_Absolute deadline; 710 struct GNUNET_TIME_AbsoluteNBO nbo_deadline; 711 712 (void) cls; 713 if (sizeof (nbo_deadline) != extra_size) 714 { 715 GNUNET_break (0); 716 return; 717 } 718 if (0 != w_count) 719 return; /* already at work! */ 720 memcpy (&nbo_deadline, 721 extra, 722 extra_size); 723 deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline); 724 run_at (deadline); 725 } 726 727 728 static void 729 select_work (void *cls) 730 { 731 bool retry = false; 732 uint64_t limit = CONCURRENCY_LIMIT - w_count; 733 734 (void) cls; 735 task = NULL; 736 GNUNET_assert (w_count <= CONCURRENCY_LIMIT); 737 GNUNET_assert (NULL != keys); 738 if (0 == limit) 739 { 740 GNUNET_break (0); 741 return; 742 } 743 if (GNUNET_TIME_absolute_is_past ( 744 keys->key_data_expiration.abs_time)) 745 { 746 /* Parent should re-start us, then we will re-fetch /keys */ 747 GNUNET_SCHEDULER_shutdown (); 748 return; 749 } 750 while (1) 751 { 752 enum GNUNET_DB_QueryStatus qs; 753 754 db_plugin->preflight (db_plugin->cls); 755 if (retry) 756 limit = 1; 757 qs = db_plugin->lookup_pending_deposits ( 758 db_plugin->cls, 759 exchange_url, 760 limit, 761 retry, 762 &pending_deposits_cb, 763 NULL); 764 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 765 "Looking up pending deposits query status was %d\n", 766 (int) qs); 767 switch (qs) 768 { 769 case GNUNET_DB_STATUS_HARD_ERROR: 770 case GNUNET_DB_STATUS_SOFT_ERROR: 771 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 772 "Transaction failed!\n"); 773 global_ret = EXIT_FAILURE; 774 GNUNET_SCHEDULER_shutdown (); 775 return; 776 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 777 if (test_mode) 778 { 779 GNUNET_SCHEDULER_shutdown (); 780 return; 781 } 782 if (retry) 783 return; /* nothing left */ 784 retry = true; 785 continue; 786 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 787 default: 788 /* wait for async completion, then select more work. */ 789 return; 790 } 791 } 792 } 793 794 795 /** 796 * Start a copy of this process with the exchange URL 797 * set to the given @a base_url 798 * 799 * @param base_url base URL to run with 800 */ 801 static struct GNUNET_Process * 802 start_worker (const char *base_url) 803 { 804 struct GNUNET_Process *p; 805 char toff[30]; 806 long long zo; 807 enum GNUNET_GenericReturnValue ret; 808 809 zo = GNUNET_TIME_get_offset (); 810 GNUNET_snprintf (toff, 811 sizeof (toff), 812 "%lld", 813 zo); 814 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 815 "Launching worker for exchange `%s' using `%s`\n", 816 base_url, 817 NULL == cfg_filename 818 ? "<default>" 819 : cfg_filename); 820 p = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 821 822 if (NULL == cfg_filename) 823 ret = GNUNET_process_run_command_va ( 824 p, 825 "taler-merchant-depositcheck", 826 "taler-merchant-depositcheck", 827 "-e", base_url, 828 "-L", "INFO", 829 "-T", toff, 830 test_mode ? "-t" : NULL, 831 NULL); 832 else 833 ret = GNUNET_process_run_command_va ( 834 p, 835 "taler-merchant-depositcheck", 836 "taler-merchant-depositcheck", 837 "-c", cfg_filename, 838 "-e", base_url, 839 "-L", "INFO", 840 "-T", toff, 841 test_mode ? "-t" : NULL, 842 NULL); 843 if (GNUNET_OK != ret) 844 { 845 GNUNET_process_destroy (p); 846 return NULL; 847 } 848 return p; 849 } 850 851 852 /** 853 * Restart worker process for the given child. 854 * 855 * @param cls a `struct Child *` that needs a worker. 856 */ 857 static void 858 restart_child (void *cls); 859 860 861 /** 862 * Function called upon death or completion of a child process. 863 * 864 * @param cls a `struct Child *` 865 * @param type type of the process 866 * @param exit_code status code of the process 867 */ 868 static void 869 child_done_cb (void *cls, 870 enum GNUNET_OS_ProcessStatusType type, 871 long unsigned int exit_code) 872 { 873 struct Child *c = cls; 874 875 c->cwh = NULL; 876 GNUNET_process_destroy (c->process); 877 c->process = NULL; 878 if ( (GNUNET_OS_PROCESS_EXITED != type) || 879 (0 != exit_code) ) 880 { 881 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 882 "Process for exchange %s had trouble (%d/%d)\n", 883 c->base_url, 884 (int) type, 885 (int) exit_code); 886 GNUNET_SCHEDULER_shutdown (); 887 global_ret = EXIT_NOTINSTALLED; 888 return; 889 } 890 if (test_mode && 891 (! GNUNET_TIME_relative_is_zero (c->rd)) ) 892 { 893 return; 894 } 895 if (GNUNET_TIME_absolute_is_future (c->next_start)) 896 c->rd = GNUNET_TIME_STD_BACKOFF (c->rd); 897 else 898 c->rd = GNUNET_TIME_UNIT_SECONDS; 899 c->rt = GNUNET_SCHEDULER_add_at (c->next_start, 900 &restart_child, 901 c); 902 } 903 904 905 static void 906 restart_child (void *cls) 907 { 908 struct Child *c = cls; 909 910 c->rt = NULL; 911 c->next_start = GNUNET_TIME_relative_to_absolute (c->rd); 912 c->process = start_worker (c->base_url); 913 if (NULL == c->process) 914 { 915 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 916 "exec"); 917 global_ret = EXIT_NO_RESTART; 918 GNUNET_SCHEDULER_shutdown (); 919 return; 920 } 921 c->cwh = GNUNET_wait_child (c->process, 922 &child_done_cb, 923 c); 924 } 925 926 927 /** 928 * Function to iterate over section. 929 * 930 * @param cls closure 931 * @param section name of the section 932 */ 933 static void 934 cfg_iter_cb (void *cls, 935 const char *section) 936 { 937 char *base_url; 938 struct Child *c; 939 940 if (0 != 941 strncasecmp (section, 942 "merchant-exchange-", 943 strlen ("merchant-exchange-"))) 944 return; 945 if (GNUNET_YES == 946 GNUNET_CONFIGURATION_get_value_yesno (cfg, 947 section, 948 "DISABLED")) 949 return; 950 if (GNUNET_OK != 951 GNUNET_CONFIGURATION_get_value_string (cfg, 952 section, 953 "EXCHANGE_BASE_URL", 954 &base_url)) 955 { 956 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 957 section, 958 "EXCHANGE_BASE_URL"); 959 return; 960 } 961 c = GNUNET_new (struct Child); 962 c->rd = GNUNET_TIME_UNIT_SECONDS; 963 c->base_url = base_url; 964 GNUNET_CONTAINER_DLL_insert (c_head, 965 c_tail, 966 c); 967 c->rt = GNUNET_SCHEDULER_add_now (&restart_child, 968 c); 969 } 970 971 972 /** 973 * Trigger (re)loading of keys from DB. 974 * 975 * @param cls NULL 976 * @param extra base URL of the exchange that changed 977 * @param extra_len number of bytes in @a extra 978 */ 979 static void 980 update_exchange_keys (void *cls, 981 const void *extra, 982 size_t extra_len) 983 { 984 const char *url = extra; 985 986 if ( (NULL == extra) || 987 (0 == extra_len) ) 988 { 989 GNUNET_break (0); 990 return; 991 } 992 if ('\0' != url[extra_len - 1]) 993 { 994 GNUNET_break (0); 995 return; 996 } 997 if (0 != strcmp (url, 998 exchange_url)) 999 return; /* not relevant for us */ 1000 1001 { 1002 enum GNUNET_DB_QueryStatus qs; 1003 struct GNUNET_TIME_Absolute earliest_retry; 1004 1005 if (NULL != keys) 1006 { 1007 TALER_EXCHANGE_keys_decref (keys); 1008 keys = NULL; 1009 } 1010 qs = db_plugin->select_exchange_keys (db_plugin->cls, 1011 exchange_url, 1012 &earliest_retry, 1013 &keys); 1014 if (qs < 0) 1015 { 1016 GNUNET_break (0); 1017 GNUNET_SCHEDULER_shutdown (); 1018 return; 1019 } 1020 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || 1021 (NULL == keys) ) 1022 { 1023 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1024 "No keys yet for `%s'\n", 1025 exchange_url); 1026 } 1027 } 1028 if (NULL == keys) 1029 { 1030 if (NULL != task) 1031 { 1032 GNUNET_SCHEDULER_cancel (task); 1033 task = NULL; 1034 } 1035 } 1036 else 1037 { 1038 if (NULL == task) 1039 task = GNUNET_SCHEDULER_add_now (&select_work, 1040 NULL); 1041 } 1042 } 1043 1044 1045 /** 1046 * First task. 1047 * 1048 * @param cls closure, NULL 1049 * @param args remaining command-line arguments 1050 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1051 * @param c configuration 1052 */ 1053 static void 1054 run (void *cls, 1055 char *const *args, 1056 const char *cfgfile, 1057 const struct GNUNET_CONFIGURATION_Handle *c) 1058 { 1059 (void) args; 1060 1061 cfg = c; 1062 if (NULL != cfgfile) 1063 cfg_filename = GNUNET_strdup (cfgfile); 1064 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1065 "Running with configuration %s\n", 1066 cfgfile); 1067 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1068 NULL); 1069 if (NULL == exchange_url) 1070 { 1071 GNUNET_CONFIGURATION_iterate_sections (c, 1072 &cfg_iter_cb, 1073 NULL); 1074 if (NULL == c_head) 1075 { 1076 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1077 "No exchanges found in configuration\n"); 1078 global_ret = EXIT_NOTCONFIGURED; 1079 GNUNET_SCHEDULER_shutdown (); 1080 return; 1081 } 1082 return; 1083 } 1084 1085 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1086 &rc); 1087 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1088 if (NULL == ctx) 1089 { 1090 GNUNET_break (0); 1091 GNUNET_SCHEDULER_shutdown (); 1092 global_ret = EXIT_NO_RESTART; 1093 return; 1094 } 1095 if (NULL == 1096 (db_plugin = TALER_MERCHANTDB_plugin_load (cfg))) 1097 { 1098 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1099 "Failed to initialize DB subsystem\n"); 1100 GNUNET_SCHEDULER_shutdown (); 1101 global_ret = EXIT_NOTCONFIGURED; 1102 return; 1103 } 1104 if (GNUNET_OK != 1105 db_plugin->connect (db_plugin->cls)) 1106 { 1107 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1108 "Failed to connect to database. Consider running taler-merchant-dbinit!\n"); 1109 GNUNET_SCHEDULER_shutdown (); 1110 global_ret = EXIT_NO_RESTART; 1111 return; 1112 } 1113 { 1114 struct GNUNET_DB_EventHeaderP es = { 1115 .size = htons (sizeof (es)), 1116 .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE) 1117 }; 1118 1119 eh = db_plugin->event_listen (db_plugin->cls, 1120 &es, 1121 GNUNET_TIME_UNIT_FOREVER_REL, 1122 &db_notify, 1123 NULL); 1124 } 1125 { 1126 struct GNUNET_DB_EventHeaderP es = { 1127 .size = htons (sizeof (es)), 1128 .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) 1129 }; 1130 1131 keys_eh = db_plugin->event_listen (db_plugin->cls, 1132 &es, 1133 GNUNET_TIME_UNIT_FOREVER_REL, 1134 &update_exchange_keys, 1135 NULL); 1136 } 1137 1138 update_exchange_keys (NULL, 1139 exchange_url, 1140 strlen (exchange_url) + 1); 1141 } 1142 1143 1144 /** 1145 * The main function of the taler-merchant-depositcheck 1146 * 1147 * @param argc number of arguments from the command line 1148 * @param argv command line arguments 1149 * @return 0 ok, 1 on error 1150 */ 1151 int 1152 main (int argc, 1153 char *const *argv) 1154 { 1155 struct GNUNET_GETOPT_CommandLineOption options[] = { 1156 GNUNET_GETOPT_option_string ('e', 1157 "exchange", 1158 "BASE_URL", 1159 "limit us to checking deposits of this exchange", 1160 &exchange_url), 1161 GNUNET_GETOPT_option_timetravel ('T', 1162 "timetravel"), 1163 GNUNET_GETOPT_option_flag ('t', 1164 "test", 1165 "run in test mode and exit when idle", 1166 &test_mode), 1167 GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), 1168 GNUNET_GETOPT_OPTION_END 1169 }; 1170 enum GNUNET_GenericReturnValue ret; 1171 1172 ret = GNUNET_PROGRAM_run ( 1173 TALER_MERCHANT_project_data (), 1174 argc, argv, 1175 "taler-merchant-depositcheck", 1176 gettext_noop ( 1177 "background process that checks with the exchange on deposits that are past the wire deadline"), 1178 options, 1179 &run, NULL); 1180 if (GNUNET_SYSERR == ret) 1181 return EXIT_INVALIDARGUMENT; 1182 if (GNUNET_NO == ret) 1183 return EXIT_SUCCESS; 1184 return global_ret; 1185 } 1186 1187 1188 /* end of taler-merchant-depositcheck.c */