taler-merchant-depositcheck.c (29516B)
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 "platform.h" 22 #include "microhttpd.h" 23 #include <gnunet/gnunet_util_lib.h> 24 #include <jansson.h> 25 #include <pthread.h> 26 #include "taler_merchant_util.h" 27 #include "taler_merchantdb_lib.h" 28 #include "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_OS_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_DepositGetHandle *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_deposits_get_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 (0 == 323 GNUNET_OS_process_kill (c->process, 324 SIGTERM)); 325 GNUNET_break (GNUNET_OK == 326 GNUNET_OS_process_wait_status (c->process, 327 &type, 328 &code)); 329 if ( (GNUNET_OS_PROCESS_EXITED != type) || 330 (0 != code) ) 331 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 332 "Process for exchange %s had trouble (%d/%d)\n", 333 c->base_url, 334 (int) type, 335 (int) code); 336 GNUNET_OS_process_destroy (c->process); 337 } 338 GNUNET_free (c->base_url); 339 GNUNET_free (c); 340 } 341 if (NULL != db_plugin) 342 { 343 db_plugin->rollback (db_plugin->cls); /* just in case */ 344 TALER_MERCHANTDB_plugin_unload (db_plugin); 345 db_plugin = NULL; 346 } 347 cfg = NULL; 348 if (NULL != ctx) 349 { 350 GNUNET_CURL_fini (ctx); 351 ctx = NULL; 352 } 353 if (NULL != rc) 354 { 355 GNUNET_CURL_gnunet_rc_destroy (rc); 356 rc = NULL; 357 } 358 } 359 360 361 /** 362 * Task to get more deposits to work on from the database. 363 * 364 * @param cls NULL 365 */ 366 static void 367 select_work (void *cls); 368 369 370 /** 371 * Make sure to run the select_work() task at 372 * the @a next_deadline. 373 * 374 * @param deadline time when work becomes ready 375 */ 376 static void 377 run_at (struct GNUNET_TIME_Absolute deadline) 378 { 379 if ( (NULL != task) && 380 (GNUNET_TIME_absolute_cmp (deadline, 381 >, 382 next_deadline)) ) 383 { 384 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 385 "Not scheduling for %s yet, already have earlier task pending\n", 386 GNUNET_TIME_absolute2s (deadline)); 387 return; 388 } 389 if (NULL == keys) 390 { 391 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 392 "Not scheduling for %s yet, no /keys available\n", 393 GNUNET_TIME_absolute2s (deadline)); 394 return; /* too early */ 395 } 396 next_deadline = deadline; 397 if (NULL != task) 398 GNUNET_SCHEDULER_cancel (task); 399 task = GNUNET_SCHEDULER_add_at (deadline, 400 &select_work, 401 NULL); 402 } 403 404 405 /** 406 * Function called with detailed wire transfer data. 407 * 408 * @param cls closure with a `struct ExchangeInteraction *` 409 * @param dr HTTP response data 410 */ 411 static void 412 deposit_get_cb ( 413 void *cls, 414 const struct TALER_EXCHANGE_GetDepositResponse *dr) 415 { 416 struct ExchangeInteraction *w = cls; 417 struct GNUNET_TIME_Absolute future_retry; 418 419 w->dgh = NULL; 420 future_retry 421 = GNUNET_TIME_relative_to_absolute (w->retry_backoff); 422 switch (dr->hr.http_status) 423 { 424 case MHD_HTTP_OK: 425 { 426 enum GNUNET_DB_QueryStatus qs; 427 428 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 429 "Exchange returned wire transfer over %s for deposited coin %s\n", 430 TALER_amount2s (&dr->details.ok.coin_contribution), 431 TALER_B2S (&w->coin_pub)); 432 qs = db_plugin->insert_deposit_to_transfer ( 433 db_plugin->cls, 434 w->deposit_serial, 435 &w->h_wire, 436 exchange_url, 437 &dr->details.ok); 438 if (qs <= 0) 439 { 440 GNUNET_break (0); 441 GNUNET_SCHEDULER_shutdown (); 442 return; 443 } 444 break; 445 } 446 case MHD_HTTP_ACCEPTED: 447 { 448 /* got a 'preliminary' reply from the exchange, 449 remember our target UUID */ 450 enum GNUNET_DB_QueryStatus qs; 451 struct GNUNET_TIME_Timestamp now; 452 453 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 454 "Exchange returned KYC requirement (%d) for deposited coin %s\n", 455 dr->details.accepted.kyc_ok, 456 TALER_B2S (&w->coin_pub)); 457 now = GNUNET_TIME_timestamp_get (); 458 qs = db_plugin->account_kyc_set_failed ( 459 db_plugin->cls, 460 w->instance_id, 461 &w->h_wire, 462 exchange_url, 463 now, 464 MHD_HTTP_ACCEPTED, 465 dr->details.accepted.kyc_ok); 466 if (qs < 0) 467 { 468 GNUNET_break (0); 469 GNUNET_SCHEDULER_shutdown (); 470 return; 471 } 472 if (dr->details.accepted.kyc_ok) 473 { 474 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 475 "Bumping wire transfer deadline in DB to %s as that is when we will retry\n", 476 GNUNET_TIME_absolute2s (future_retry)); 477 qs = db_plugin->update_deposit_confirmation_status ( 478 db_plugin->cls, 479 w->deposit_serial, 480 true, /* need to try again in the future! */ 481 GNUNET_TIME_absolute_to_timestamp (future_retry), 482 MHD_HTTP_ACCEPTED, 483 TALER_EC_NONE, 484 "Exchange reported 202 Accepted but no KYC block"); 485 if (qs < 0) 486 { 487 GNUNET_break (0); 488 GNUNET_SCHEDULER_shutdown (); 489 return; 490 } 491 } 492 else 493 { 494 future_retry 495 = GNUNET_TIME_absolute_max ( 496 future_retry, 497 GNUNET_TIME_relative_to_absolute ( 498 KYC_RETRY_DELAY)); 499 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 500 "Bumping wire transfer deadline in DB to %s as that is when we will retry\n", 501 GNUNET_TIME_absolute2s (future_retry)); 502 qs = db_plugin->update_deposit_confirmation_status ( 503 db_plugin->cls, 504 w->deposit_serial, 505 true /* need to try again in the future */, 506 GNUNET_TIME_absolute_to_timestamp (future_retry), 507 MHD_HTTP_ACCEPTED, 508 TALER_EC_NONE, 509 "Exchange reported 202 Accepted due to KYC/AML block"); 510 if (qs < 0) 511 { 512 GNUNET_break (0); 513 GNUNET_SCHEDULER_shutdown (); 514 return; 515 } 516 } 517 break; 518 } 519 default: 520 { 521 enum GNUNET_DB_QueryStatus qs; 522 bool retry_needed = false; 523 524 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 525 "Exchange %s returned tracking failure for deposited coin %s: %u\n", 526 exchange_url, 527 TALER_B2S (&w->coin_pub), 528 dr->hr.http_status); 529 /* rough classification by HTTP status group */ 530 switch (dr->hr.http_status / 100) 531 { 532 case 0: 533 /* timeout */ 534 retry_needed = true; 535 break; 536 case 1: 537 case 2: 538 case 3: 539 /* very strange */ 540 retry_needed = false; 541 break; 542 case 4: 543 /* likely fatal */ 544 retry_needed = false; 545 break; 546 case 5: 547 /* likely transient */ 548 retry_needed = true; 549 break; 550 } 551 qs = db_plugin->update_deposit_confirmation_status ( 552 db_plugin->cls, 553 w->deposit_serial, 554 retry_needed, 555 GNUNET_TIME_absolute_to_timestamp (future_retry), 556 (uint32_t) dr->hr.http_status, 557 dr->hr.ec, 558 dr->hr.hint); 559 if (qs < 0) 560 { 561 GNUNET_break (0); 562 GNUNET_SCHEDULER_shutdown (); 563 return; 564 } 565 break; 566 } 567 } /* end switch */ 568 569 GNUNET_CONTAINER_DLL_remove (w_head, 570 w_tail, 571 w); 572 w_count--; 573 GNUNET_free (w->instance_id); 574 GNUNET_free (w); 575 GNUNET_assert (NULL != keys); 576 if (0 == w_count) 577 { 578 /* We only SELECT() again after having finished 579 all requests, as otherwise we'll most like 580 just SELECT() those again that are already 581 being requested; alternatively, we could 582 update the retry_time already on SELECT(), 583 but this should be easier on the DB. */ 584 if (NULL != task) 585 GNUNET_SCHEDULER_cancel (task); 586 task = GNUNET_SCHEDULER_add_now (&select_work, 587 NULL); 588 } 589 } 590 591 592 /** 593 * Typically called by `select_work`. 594 * 595 * @param cls NULL 596 * @param deposit_serial identifies the deposit operation 597 * @param wire_deadline when is the wire due 598 * @param retry_time current value for the retry backoff 599 * @param h_contract_terms hash of the contract terms 600 * @param merchant_priv private key of the merchant 601 * @param instance_id row ID of the instance 602 * @param h_wire hash of the merchant's wire account into 603 * @param amount_with_fee amount the exchange will deposit for this coin 604 * @param deposit_fee fee the exchange will charge for this coin which the deposit was made 605 * @param coin_pub public key of the deposited coin 606 */ 607 static void 608 pending_deposits_cb ( 609 void *cls, 610 uint64_t deposit_serial, 611 struct GNUNET_TIME_Absolute wire_deadline, 612 struct GNUNET_TIME_Absolute retry_time, 613 const struct TALER_PrivateContractHashP *h_contract_terms, 614 const struct TALER_MerchantPrivateKeyP *merchant_priv, 615 const char *instance_id, 616 const struct TALER_MerchantWireHashP *h_wire, 617 const struct TALER_Amount *amount_with_fee, 618 const struct TALER_Amount *deposit_fee, 619 const struct TALER_CoinSpendPublicKeyP *coin_pub) 620 { 621 struct ExchangeInteraction *w; 622 struct GNUNET_TIME_Absolute mx 623 = GNUNET_TIME_absolute_max (wire_deadline, 624 retry_time); 625 struct GNUNET_TIME_Relative retry_backoff; 626 627 (void) cls; 628 if (GNUNET_TIME_absolute_is_future (mx)) 629 { 630 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 631 "Pending deposit should be checked next at %s\n", 632 GNUNET_TIME_absolute2s (mx)); 633 run_at (mx); 634 return; 635 } 636 if (GNUNET_TIME_absolute_is_zero (retry_time)) 637 retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline); 638 else 639 retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline, 640 retry_time); 641 w = GNUNET_new (struct ExchangeInteraction); 642 w->deposit_serial = deposit_serial; 643 w->wire_deadline = wire_deadline; 644 w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff, 645 GNUNET_TIME_UNIT_DAYS); 646 w->h_contract_terms = *h_contract_terms; 647 w->merchant_priv = *merchant_priv; 648 w->h_wire = *h_wire; 649 w->amount_with_fee = *amount_with_fee; 650 w->deposit_fee = *deposit_fee; 651 w->coin_pub = *coin_pub; 652 w->instance_id = GNUNET_strdup (instance_id); 653 GNUNET_CONTAINER_DLL_insert (w_head, 654 w_tail, 655 w); 656 w_count++; 657 GNUNET_assert (NULL != keys); 658 if (GNUNET_TIME_absolute_is_past ( 659 keys->key_data_expiration.abs_time)) 660 { 661 /* Parent should re-start us, then we will re-fetch /keys */ 662 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 663 "/keys expired, shutting down\n"); 664 GNUNET_SCHEDULER_shutdown (); 665 return; 666 } 667 GNUNET_assert (NULL == w->dgh); 668 w->dgh = TALER_EXCHANGE_deposits_get ( 669 ctx, 670 exchange_url, 671 keys, 672 &w->merchant_priv, 673 &w->h_wire, 674 &w->h_contract_terms, 675 &w->coin_pub, 676 GNUNET_TIME_UNIT_ZERO, 677 &deposit_get_cb, 678 w); 679 } 680 681 682 /** 683 * Function called on events received from Postgres. 684 * 685 * @param cls closure, NULL 686 * @param extra additional event data provided, timestamp with wire deadline 687 * @param extra_size number of bytes in @a extra 688 */ 689 static void 690 db_notify (void *cls, 691 const void *extra, 692 size_t extra_size) 693 { 694 struct GNUNET_TIME_Absolute deadline; 695 struct GNUNET_TIME_AbsoluteNBO nbo_deadline; 696 697 (void) cls; 698 if (sizeof (nbo_deadline) != extra_size) 699 { 700 GNUNET_break (0); 701 return; 702 } 703 if (0 != w_count) 704 return; /* already at work! */ 705 memcpy (&nbo_deadline, 706 extra, 707 extra_size); 708 deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline); 709 run_at (deadline); 710 } 711 712 713 static void 714 select_work (void *cls) 715 { 716 bool retry = false; 717 uint64_t limit = CONCURRENCY_LIMIT - w_count; 718 719 (void) cls; 720 task = NULL; 721 GNUNET_assert (w_count <= CONCURRENCY_LIMIT); 722 GNUNET_assert (NULL != keys); 723 if (0 == limit) 724 { 725 GNUNET_break (0); 726 return; 727 } 728 if (GNUNET_TIME_absolute_is_past ( 729 keys->key_data_expiration.abs_time)) 730 { 731 /* Parent should re-start us, then we will re-fetch /keys */ 732 GNUNET_SCHEDULER_shutdown (); 733 return; 734 } 735 while (1) 736 { 737 enum GNUNET_DB_QueryStatus qs; 738 739 db_plugin->preflight (db_plugin->cls); 740 if (retry) 741 limit = 1; 742 qs = db_plugin->lookup_pending_deposits ( 743 db_plugin->cls, 744 exchange_url, 745 limit, 746 retry, 747 &pending_deposits_cb, 748 NULL); 749 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 750 "Looking up pending deposits query status was %d\n", 751 (int) qs); 752 switch (qs) 753 { 754 case GNUNET_DB_STATUS_HARD_ERROR: 755 case GNUNET_DB_STATUS_SOFT_ERROR: 756 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 757 "Transaction failed!\n"); 758 global_ret = EXIT_FAILURE; 759 GNUNET_SCHEDULER_shutdown (); 760 return; 761 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 762 if (test_mode) 763 { 764 GNUNET_SCHEDULER_shutdown (); 765 return; 766 } 767 if (retry) 768 return; /* nothing left */ 769 retry = true; 770 continue; 771 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 772 default: 773 /* wait for async completion, then select more work. */ 774 return; 775 } 776 } 777 } 778 779 780 /** 781 * Start a copy of this process with the exchange URL 782 * set to the given @a base_url 783 * 784 * @param base_url base URL to run with 785 */ 786 static struct GNUNET_OS_Process * 787 start_worker (const char *base_url) 788 { 789 char toff[30]; 790 long long zo; 791 792 zo = GNUNET_TIME_get_offset (); 793 GNUNET_snprintf (toff, 794 sizeof (toff), 795 "%lld", 796 zo); 797 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 798 "Launching worker for exchange `%s' using `%s`\n", 799 base_url, 800 NULL == cfg_filename 801 ? "<default>" 802 : cfg_filename); 803 if (NULL == cfg_filename) 804 return GNUNET_OS_start_process ( 805 GNUNET_OS_INHERIT_STD_ALL, 806 NULL, 807 NULL, 808 NULL, 809 "taler-merchant-depositcheck", 810 "taler-merchant-depositcheck", 811 "-e", base_url, 812 "-L", "INFO", 813 "-T", toff, 814 test_mode ? "-t" : NULL, 815 NULL); 816 return GNUNET_OS_start_process ( 817 GNUNET_OS_INHERIT_STD_ALL, 818 NULL, 819 NULL, 820 NULL, 821 "taler-merchant-depositcheck", 822 "taler-merchant-depositcheck", 823 "-c", cfg_filename, 824 "-e", base_url, 825 "-L", "INFO", 826 "-T", toff, 827 test_mode ? "-t" : NULL, 828 NULL); 829 } 830 831 832 /** 833 * Restart worker process for the given child. 834 * 835 * @param cls a `struct Child *` that needs a worker. 836 */ 837 static void 838 restart_child (void *cls); 839 840 841 /** 842 * Function called upon death or completion of a child process. 843 * 844 * @param cls a `struct Child *` 845 * @param type type of the process 846 * @param exit_code status code of the process 847 */ 848 static void 849 child_done_cb (void *cls, 850 enum GNUNET_OS_ProcessStatusType type, 851 long unsigned int exit_code) 852 { 853 struct Child *c = cls; 854 855 c->cwh = NULL; 856 GNUNET_OS_process_destroy (c->process); 857 c->process = NULL; 858 if ( (GNUNET_OS_PROCESS_EXITED != type) || 859 (0 != exit_code) ) 860 { 861 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 862 "Process for exchange %s had trouble (%d/%d)\n", 863 c->base_url, 864 (int) type, 865 (int) exit_code); 866 GNUNET_SCHEDULER_shutdown (); 867 global_ret = EXIT_NOTINSTALLED; 868 return; 869 } 870 if (test_mode && 871 (! GNUNET_TIME_relative_is_zero (c->rd)) ) 872 { 873 return; 874 } 875 if (GNUNET_TIME_absolute_is_future (c->next_start)) 876 c->rd = GNUNET_TIME_STD_BACKOFF (c->rd); 877 else 878 c->rd = GNUNET_TIME_UNIT_SECONDS; 879 c->rt = GNUNET_SCHEDULER_add_at (c->next_start, 880 &restart_child, 881 c); 882 } 883 884 885 static void 886 restart_child (void *cls) 887 { 888 struct Child *c = cls; 889 890 c->rt = NULL; 891 c->next_start = GNUNET_TIME_relative_to_absolute (c->rd); 892 c->process = start_worker (c->base_url); 893 if (NULL == c->process) 894 { 895 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 896 "exec"); 897 global_ret = EXIT_NO_RESTART; 898 GNUNET_SCHEDULER_shutdown (); 899 return; 900 } 901 c->cwh = GNUNET_wait_child (c->process, 902 &child_done_cb, 903 c); 904 } 905 906 907 /** 908 * Function to iterate over section. 909 * 910 * @param cls closure 911 * @param section name of the section 912 */ 913 static void 914 cfg_iter_cb (void *cls, 915 const char *section) 916 { 917 char *base_url; 918 struct Child *c; 919 920 if (0 != 921 strncasecmp (section, 922 "merchant-exchange-", 923 strlen ("merchant-exchange-"))) 924 return; 925 if (GNUNET_YES == 926 GNUNET_CONFIGURATION_get_value_yesno (cfg, 927 section, 928 "DISABLED")) 929 return; 930 if (GNUNET_OK != 931 GNUNET_CONFIGURATION_get_value_string (cfg, 932 section, 933 "EXCHANGE_BASE_URL", 934 &base_url)) 935 { 936 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 937 section, 938 "EXCHANGE_BASE_URL"); 939 return; 940 } 941 c = GNUNET_new (struct Child); 942 c->rd = GNUNET_TIME_UNIT_SECONDS; 943 c->base_url = base_url; 944 GNUNET_CONTAINER_DLL_insert (c_head, 945 c_tail, 946 c); 947 c->rt = GNUNET_SCHEDULER_add_now (&restart_child, 948 c); 949 } 950 951 952 /** 953 * Trigger (re)loading of keys from DB. 954 * 955 * @param cls NULL 956 * @param extra base URL of the exchange that changed 957 * @param extra_len number of bytes in @a extra 958 */ 959 static void 960 update_exchange_keys (void *cls, 961 const void *extra, 962 size_t extra_len) 963 { 964 const char *url = extra; 965 966 if ( (NULL == extra) || 967 (0 == extra_len) ) 968 { 969 GNUNET_break (0); 970 return; 971 } 972 if ('\0' != url[extra_len - 1]) 973 { 974 GNUNET_break (0); 975 return; 976 } 977 if (0 != strcmp (url, 978 exchange_url)) 979 return; /* not relevant for us */ 980 981 { 982 enum GNUNET_DB_QueryStatus qs; 983 struct GNUNET_TIME_Absolute earliest_retry; 984 985 if (NULL != keys) 986 { 987 TALER_EXCHANGE_keys_decref (keys); 988 keys = NULL; 989 } 990 qs = db_plugin->select_exchange_keys (db_plugin->cls, 991 exchange_url, 992 &earliest_retry, 993 &keys); 994 if (qs < 0) 995 { 996 GNUNET_break (0); 997 GNUNET_SCHEDULER_shutdown (); 998 return; 999 } 1000 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1001 { 1002 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1003 "No keys yet for `%s'\n", 1004 exchange_url); 1005 } 1006 } 1007 if (NULL == keys) 1008 { 1009 if (NULL != task) 1010 { 1011 GNUNET_SCHEDULER_cancel (task); 1012 task = NULL; 1013 } 1014 } 1015 else 1016 { 1017 if (NULL == task) 1018 task = GNUNET_SCHEDULER_add_now (&select_work, 1019 NULL); 1020 } 1021 } 1022 1023 1024 /** 1025 * First task. 1026 * 1027 * @param cls closure, NULL 1028 * @param args remaining command-line arguments 1029 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1030 * @param c configuration 1031 */ 1032 static void 1033 run (void *cls, 1034 char *const *args, 1035 const char *cfgfile, 1036 const struct GNUNET_CONFIGURATION_Handle *c) 1037 { 1038 (void) args; 1039 1040 cfg = c; 1041 if (NULL != cfgfile) 1042 cfg_filename = GNUNET_strdup (cfgfile); 1043 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1044 "Running with configuration %s\n", 1045 cfgfile); 1046 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1047 NULL); 1048 if (NULL == exchange_url) 1049 { 1050 GNUNET_CONFIGURATION_iterate_sections (c, 1051 &cfg_iter_cb, 1052 NULL); 1053 if (NULL == c_head) 1054 { 1055 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1056 "No exchanges found in configuration\n"); 1057 global_ret = EXIT_NOTCONFIGURED; 1058 GNUNET_SCHEDULER_shutdown (); 1059 return; 1060 } 1061 return; 1062 } 1063 1064 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1065 &rc); 1066 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1067 if (NULL == ctx) 1068 { 1069 GNUNET_break (0); 1070 GNUNET_SCHEDULER_shutdown (); 1071 global_ret = EXIT_NO_RESTART; 1072 return; 1073 } 1074 if (NULL == 1075 (db_plugin = TALER_MERCHANTDB_plugin_load (cfg))) 1076 { 1077 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1078 "Failed to initialize DB subsystem\n"); 1079 GNUNET_SCHEDULER_shutdown (); 1080 global_ret = EXIT_NOTCONFIGURED; 1081 return; 1082 } 1083 if (GNUNET_OK != 1084 db_plugin->connect (db_plugin->cls)) 1085 { 1086 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1087 "Failed to connect to database. Consider running taler-merchant-dbinit!\n"); 1088 GNUNET_SCHEDULER_shutdown (); 1089 global_ret = EXIT_NO_RESTART; 1090 return; 1091 } 1092 { 1093 struct GNUNET_DB_EventHeaderP es = { 1094 .size = htons (sizeof (es)), 1095 .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE) 1096 }; 1097 1098 eh = db_plugin->event_listen (db_plugin->cls, 1099 &es, 1100 GNUNET_TIME_UNIT_FOREVER_REL, 1101 &db_notify, 1102 NULL); 1103 } 1104 { 1105 struct GNUNET_DB_EventHeaderP es = { 1106 .size = ntohs (sizeof (es)), 1107 .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) 1108 }; 1109 1110 keys_eh = db_plugin->event_listen (db_plugin->cls, 1111 &es, 1112 GNUNET_TIME_UNIT_FOREVER_REL, 1113 &update_exchange_keys, 1114 NULL); 1115 } 1116 1117 update_exchange_keys (NULL, 1118 exchange_url, 1119 strlen (exchange_url) + 1); 1120 } 1121 1122 1123 /** 1124 * The main function of the taler-merchant-depositcheck 1125 * 1126 * @param argc number of arguments from the command line 1127 * @param argv command line arguments 1128 * @return 0 ok, 1 on error 1129 */ 1130 int 1131 main (int argc, 1132 char *const *argv) 1133 { 1134 struct GNUNET_GETOPT_CommandLineOption options[] = { 1135 GNUNET_GETOPT_option_string ('e', 1136 "exchange", 1137 "BASE_URL", 1138 "limit us to checking deposits of this exchange", 1139 &exchange_url), 1140 GNUNET_GETOPT_option_timetravel ('T', 1141 "timetravel"), 1142 GNUNET_GETOPT_option_flag ('t', 1143 "test", 1144 "run in test mode and exit when idle", 1145 &test_mode), 1146 GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), 1147 GNUNET_GETOPT_OPTION_END 1148 }; 1149 enum GNUNET_GenericReturnValue ret; 1150 1151 ret = GNUNET_PROGRAM_run ( 1152 TALER_MERCHANT_project_data (), 1153 argc, argv, 1154 "taler-merchant-depositcheck", 1155 gettext_noop ( 1156 "background process that checks with the exchange on deposits that are past the wire deadline"), 1157 options, 1158 &run, NULL); 1159 if (GNUNET_SYSERR == ret) 1160 return EXIT_INVALIDARGUMENT; 1161 if (GNUNET_NO == ret) 1162 return EXIT_SUCCESS; 1163 return global_ret; 1164 } 1165 1166 1167 /* end of taler-merchant-depositcheck.c */