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