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