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