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