taler-merchant-depositcheck.c (30706B)
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/iterate_pending_deposits.h" 34 #include "merchant-database/get_exchange_keys.h" 35 #include "merchant-database/preflight.h" 36 #include "merchant-database/insert_kyc_failure.h" 37 #include "merchant-database/set_instance.h" 38 #include "merchant-database/insert_deposit_to_transfer.h" 39 #include "merchant-database/update_deposit_settlement_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_insert_kyc_failure ( 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_settlement_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_settlement_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 bool retry_needed = false; 543 544 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 545 "Exchange %s returned tracking failure for deposited coin %s: %u\n", 546 exchange_url, 547 TALER_B2S (&w->coin_pub), 548 dr->hr.http_status); 549 /* rough classification by HTTP status group */ 550 switch (dr->hr.http_status / 100) 551 { 552 case 0: 553 /* timeout */ 554 retry_needed = true; 555 break; 556 case 1: 557 case 2: 558 case 3: 559 /* very strange */ 560 retry_needed = false; 561 break; 562 case 4: 563 /* likely fatal */ 564 retry_needed = false; 565 break; 566 case 5: 567 /* likely transient */ 568 retry_needed = true; 569 break; 570 } 571 qs = TALER_MERCHANTDB_update_deposit_settlement_status ( 572 pg, 573 w->deposit_serial, 574 retry_needed, 575 GNUNET_TIME_absolute_to_timestamp (future_retry), 576 (uint32_t) dr->hr.http_status, 577 dr->hr.ec, 578 dr->hr.hint); 579 if (qs < 0) 580 { 581 GNUNET_break (0); 582 global_ret = EXIT_FAILURE; 583 GNUNET_SCHEDULER_shutdown (); 584 return; 585 } 586 break; 587 } 588 } /* end switch */ 589 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 590 TALER_MERCHANTDB_set_instance (pg, 591 NULL)); 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_iterate_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_get_exchange_keys (pg, 1034 exchange_url, 1035 &earliest_retry, 1036 &keys); 1037 if (qs < 0) 1038 { 1039 GNUNET_break (0); 1040 global_ret = EXIT_FAILURE; 1041 GNUNET_SCHEDULER_shutdown (); 1042 return; 1043 } 1044 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || 1045 (NULL == keys) ) 1046 { 1047 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1048 "No keys yet for `%s'\n", 1049 exchange_url); 1050 } 1051 } 1052 if (NULL == keys) 1053 { 1054 if (NULL != task) 1055 { 1056 GNUNET_SCHEDULER_cancel (task); 1057 task = NULL; 1058 } 1059 } 1060 else 1061 { 1062 if (NULL == task) 1063 task = GNUNET_SCHEDULER_add_now (&select_work, 1064 NULL); 1065 } 1066 } 1067 1068 1069 /** 1070 * First task. 1071 * 1072 * @param cls closure, NULL 1073 * @param args remaining command-line arguments 1074 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1075 * @param c configuration 1076 */ 1077 static void 1078 run (void *cls, 1079 char *const *args, 1080 const char *cfgfile, 1081 const struct GNUNET_CONFIGURATION_Handle *c) 1082 { 1083 (void) args; 1084 1085 cfg = c; 1086 if (NULL != cfgfile) 1087 cfg_filename = GNUNET_strdup (cfgfile); 1088 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1089 "Running with configuration %s\n", 1090 cfgfile); 1091 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1092 NULL); 1093 if (NULL == exchange_url) 1094 { 1095 GNUNET_CONFIGURATION_iterate_sections (c, 1096 &cfg_iter_cb, 1097 NULL); 1098 if (NULL == c_head) 1099 { 1100 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1101 "No exchanges found in configuration\n"); 1102 global_ret = EXIT_NOTCONFIGURED; 1103 GNUNET_SCHEDULER_shutdown (); 1104 return; 1105 } 1106 return; 1107 } 1108 1109 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1110 &rc); 1111 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1112 if (NULL == ctx) 1113 { 1114 GNUNET_break (0); 1115 GNUNET_SCHEDULER_shutdown (); 1116 global_ret = EXIT_NO_RESTART; 1117 return; 1118 } 1119 if (NULL == 1120 (pg = TALER_MERCHANTDB_connect (cfg))) 1121 { 1122 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1123 "Failed to initialize DB subsystem\n"); 1124 GNUNET_SCHEDULER_shutdown (); 1125 global_ret = EXIT_NOTCONFIGURED; 1126 return; 1127 } 1128 { 1129 struct GNUNET_DB_EventHeaderP es = { 1130 .size = htons (sizeof (es)), 1131 .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE) 1132 }; 1133 1134 eh = TALER_MERCHANTDB_event_listen (pg, 1135 &es, 1136 GNUNET_TIME_UNIT_FOREVER_REL, 1137 &db_notify, 1138 NULL); 1139 } 1140 { 1141 struct GNUNET_DB_EventHeaderP es = { 1142 .size = htons (sizeof (es)), 1143 .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) 1144 }; 1145 1146 keys_eh = TALER_MERCHANTDB_event_listen (pg, 1147 &es, 1148 GNUNET_TIME_UNIT_FOREVER_REL, 1149 &update_exchange_keys, 1150 NULL); 1151 } 1152 1153 update_exchange_keys (NULL, 1154 exchange_url, 1155 strlen (exchange_url) + 1); 1156 } 1157 1158 1159 /** 1160 * The main function of the taler-merchant-depositcheck 1161 * 1162 * @param argc number of arguments from the command line 1163 * @param argv command line arguments 1164 * @return 0 ok, 1 on error 1165 */ 1166 int 1167 main (int argc, 1168 char *const *argv) 1169 { 1170 struct GNUNET_GETOPT_CommandLineOption options[] = { 1171 GNUNET_GETOPT_option_string ('e', 1172 "exchange", 1173 "BASE_URL", 1174 "limit us to checking deposits of this exchange", 1175 &exchange_url), 1176 GNUNET_GETOPT_option_timetravel ('T', 1177 "timetravel"), 1178 GNUNET_GETOPT_option_flag ('t', 1179 "test", 1180 "run in test mode and exit when idle", 1181 &test_mode), 1182 GNUNET_GETOPT_option_version (VERSION), 1183 GNUNET_GETOPT_OPTION_END 1184 }; 1185 enum GNUNET_GenericReturnValue ret; 1186 1187 ret = GNUNET_PROGRAM_run ( 1188 TALER_MERCHANT_project_data (), 1189 argc, argv, 1190 "taler-merchant-depositcheck", 1191 gettext_noop ( 1192 "background process that checks with the exchange on deposits that are past the wire deadline"), 1193 options, 1194 &run, NULL); 1195 if (GNUNET_SYSERR == ret) 1196 return EXIT_INVALIDARGUMENT; 1197 if (GNUNET_NO == ret) 1198 return EXIT_SUCCESS; 1199 return global_ret; 1200 } 1201 1202 1203 /* end of taler-merchant-depositcheck.c */