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