taler-merchant-donaukeyupdate.c (32736B)
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-donaukeyupdate.c 18 * @brief Process that ensures our /keys data for all Donau instances is current 19 * @author Bohdan Potuzhnyi 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include "microhttpd.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <jansson.h> 26 #include <pthread.h> 27 #include <taler/taler_dbevents.h> 28 #include "donau/donau_service.h" 29 #include "taler/taler_merchant_util.h" 30 #include "merchantdb_lib.h" 31 #include "merchantdb_lib.h" 32 #include "taler/taler_merchant_bank_lib.h" 33 #include "merchant-database/iterate_all_donau_instances.h" 34 #include "merchant-database/get_donau_instance_by_serial.h" 35 #include "merchant-database/update_donau_instance.h" 36 #include "merchant-database/insert_donau_keys.h" 37 #include "merchant-database/event_listen.h" 38 #include "merchant-database/preflight.h" 39 #include "merchant-database/start.h" 40 41 /** 42 * Maximum frequency for the Donau interaction. 43 */ 44 #define DONAU_MAXFREQ GNUNET_TIME_relative_multiply ( \ 45 GNUNET_TIME_UNIT_MINUTES, \ 46 5) 47 48 /** 49 * How many inquiries do we process concurrently at most. 50 */ 51 #define OPEN_INQUIRY_LIMIT 1024 52 53 /** 54 * How often do we retry after DB serialization errors (at most)? 55 */ 56 #define MAX_RETRIES 3 57 58 /** 59 * Information about a Donau instance. 60 */ 61 struct Donau 62 { 63 /** 64 * Pointer to the next Donau instance in the doubly linked list. 65 */ 66 struct Donau *next; 67 68 /** 69 * Pointer to the previous Donau instance in the doubly linked list. 70 */ 71 struct Donau *prev; 72 73 /** 74 * Base URL of the Donau instance being tracked. 75 * This URL is used to query the Donau service for keys and other resources. 76 */ 77 char *donau_url; 78 79 /** 80 * Expected currency of the donau. 81 */ 82 char *currency; 83 84 /** 85 * Pointer to the keys obtained from the Donau instance. 86 * This structure holds the cryptographic keys for the Donau instance. 87 */ 88 struct DONAU_Keys *keys; 89 90 /** 91 * A handle for an ongoing /keys request to the Donau instance. 92 * This is NULL when there is no active request. 93 */ 94 struct DONAU_GetKeysHandle *conn; 95 96 /** 97 * Scheduler task for retrying a failed /keys request. 98 * This task will trigger the next attempt to download the Donau keys if the previous request failed or needs to be retried. 99 */ 100 struct GNUNET_SCHEDULER_Task *retry_task; 101 102 /** 103 * The earliest time at which the Donau instance can attempt another /keys request. 104 * This is used to manage the timing between requests and ensure compliance with rate-limiting rules. 105 */ 106 struct GNUNET_TIME_Absolute first_retry; 107 108 /** 109 * The delay between the next retry for fetching /keys. 110 * Used to implement exponential backoff strategies for retries in case of failures. 111 */ 112 struct GNUNET_TIME_Relative retry_delay; 113 114 /** 115 * A flag indicating whether this Donau instance is currently rate-limited. 116 * If true, the instance is temporarily paused from making further requests due to reaching a limit. 117 */ 118 bool limited; 119 120 /** 121 * Are we force-retrying a /keys download because some keys 122 * were missing? 123 */ 124 bool force_retry; 125 }; 126 127 128 /** 129 * Head of known Donau instances. 130 */ 131 static struct Donau *d_head; 132 133 /** 134 * Tail of known Donau instances. 135 */ 136 static struct Donau *d_tail; 137 138 /** 139 * Context for the charity force download. 140 */ 141 struct ForceCharityCtx 142 { 143 /** 144 * Pointer to the next ForceCharityCtx in the doubly linked list. 145 */ 146 struct ForceCharityCtx *next; 147 148 /** 149 * Pointer to the previous ForceCharityCtx in the doubly linked list. 150 */ 151 struct ForceCharityCtx *prev; 152 153 /** 154 * Serial of the Donau instance in our DB for which we running the force update. 155 */ 156 uint64_t di_serial; 157 158 /** 159 * Base URL of the Donau instance for which we are running the force update. 160 */ 161 char *donau_url; 162 163 /** 164 * ID of the charity for which we are running the force update. 165 */ 166 uint64_t charity_id; 167 168 /** 169 * Handle to the charity update request. 170 */ 171 struct DONAU_CharityGetHandle *h; 172 173 /** 174 * Scheduler task for retrying a failed /charity_id request. 175 * This task will trigger the next attempt to download the charity 176 * details if the previous request could not be started. 177 */ 178 struct GNUNET_SCHEDULER_Task *retry_task; 179 180 /** 181 * The delay before the next retry for fetching /charity_id. 182 * Used to implement exponential backoff in case of failures. 183 */ 184 struct GNUNET_TIME_Relative retry_delay; 185 186 /** 187 * A flag indicating whether this charity inquiry is currently 188 * rate-limited. If true, the inquiry was postponed because we 189 * were at the #OPEN_INQUIRY_LIMIT and must be re-driven from 190 * end_inquiry() once a slot frees up. 191 */ 192 bool limited; 193 }; 194 195 /** 196 * Head of the list of charity force updates. 197 */ 198 static struct ForceCharityCtx *fcc_head; 199 200 /** 201 * Tail of the list of charity force updates. 202 */ 203 static struct ForceCharityCtx *fcc_tail; 204 205 /** 206 * The merchant's configuration. 207 */ 208 static const struct GNUNET_CONFIGURATION_Handle *cfg; 209 210 /** 211 * Our database connection. 212 */ 213 static struct TALER_MERCHANTDB_PostgresContext *pg; 214 215 /** 216 * Our event handler listening for /keys forced downloads. 217 */ 218 static struct GNUNET_DB_EventHandler *eh; 219 220 /** 221 * Our event handler listening for /charity_id forced downloads. 222 */ 223 static struct GNUNET_DB_EventHandler *eh_charity; 224 225 /** 226 * Handle to the context for interacting with the Donau services. 227 */ 228 static struct GNUNET_CURL_Context *ctx; 229 230 /** 231 * Scheduler context for running the @e ctx. 232 */ 233 static struct GNUNET_CURL_RescheduleContext *rc; 234 235 /** 236 * How many active inquiries do we have right now. 237 */ 238 static unsigned int active_inquiries; 239 240 /** 241 * Value to return from main(). 0 on success, non-zero on errors. 242 */ 243 static int global_ret; 244 245 /** 246 * Should we enable HTTP/2 and HTTP/3 when talking to the Donau? 247 * Those are not expected to be terribly beneficial for a client with 248 * stable connections to a few servers, but they could cause stability 249 * issues with libcurl. Hence we *default* to HTTP/1.1-only, as that 250 * is the conservative and most tested code path. 251 */ 252 static int enable_h3; 253 254 /** 255 * #GNUNET_YES if we are in test mode and should exit when idle. 256 */ 257 static int test_mode; 258 259 /** 260 * True if the last DB query was limited by the 261 * #OPEN_INQUIRY_LIMIT and we thus should check again 262 * as soon as we are substantially below that limit, 263 * and not only when we get a DB notification. 264 */ 265 static bool at_limit; 266 267 268 /** 269 * Function that initiates a /keys download for a Donau instance. 270 * 271 * @param cls closure with a `struct Donau *` 272 */ 273 static void 274 download_keys (void *cls); 275 276 277 /** 278 * Function that initiates a /charity_id download for a Donau instance. 279 * 280 * @param cls closure with a `struct ForceCharityCtx *` 281 */ 282 static void 283 download_charity_id (void *cls); 284 285 286 /** 287 * An inquiry finished, check if we need to start more. 288 */ 289 static void 290 end_inquiry (void) 291 { 292 GNUNET_assert (active_inquiries > 0); 293 active_inquiries--; 294 if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) && 295 (at_limit) ) 296 { 297 at_limit = false; 298 for (struct Donau *d = d_head; 299 NULL != d; 300 d = d->next) 301 { 302 if (! d->limited) 303 continue; 304 d->limited = false; 305 /* done synchronously so that the active_inquiries 306 is updated immediately */ 307 download_keys (d); 308 if (at_limit) 309 break; 310 } 311 for (struct ForceCharityCtx *fcc = fcc_head; 312 NULL != fcc; 313 fcc = fcc->next) 314 { 315 if (at_limit) 316 break; 317 if (! fcc->limited) 318 continue; 319 fcc->limited = false; 320 /* done synchronously so that the active_inquiries 321 is updated immediately */ 322 download_charity_id (fcc); 323 } 324 } 325 if ( (! at_limit) && 326 (0 == active_inquiries) && 327 (test_mode) ) 328 { 329 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 330 "No more open inquiries and in test mode. Exiting.\n"); 331 GNUNET_SCHEDULER_shutdown (); 332 return; 333 } 334 } 335 336 337 /** 338 * Update Donau keys in the database. 339 * 340 * @param keys Donau keys to persist 341 * @param first_retry earliest we may retry fetching the keys 342 * @return transaction status 343 */ 344 static enum GNUNET_DB_QueryStatus 345 insert_donau_keys_data (const struct DONAU_Keys *keys, 346 struct GNUNET_TIME_Absolute first_retry) 347 { 348 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 349 "Inserting Donau keys into the database %s\n", 350 keys->donau_url); 351 return TALER_MERCHANTDB_insert_donau_keys (pg, 352 keys, 353 first_retry); 354 } 355 356 357 /** 358 * Store Donau keys in the database and handle retries. 359 * 360 * @param keys the keys to store 361 * @param first_retry earliest time we may retry fetching the keys 362 * @return true on success 363 */ 364 static bool 365 store_donau_keys (struct DONAU_Keys *keys, 366 struct GNUNET_TIME_Absolute first_retry) 367 { 368 enum GNUNET_DB_QueryStatus qs; 369 TALER_MERCHANTDB_preflight (pg); 370 for (unsigned int r = 0; r < MAX_RETRIES; r++) 371 { 372 if (GNUNET_OK != 373 TALER_MERCHANTDB_start (pg, 374 "update donau key data")) 375 { 376 TALER_MERCHANTDB_rollback (pg); 377 GNUNET_break (0); 378 return false; 379 } 380 381 qs = insert_donau_keys_data (keys, 382 first_retry); 383 if (0 > qs) 384 { 385 TALER_MERCHANTDB_rollback (pg); 386 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 387 "Error while inserting Donau keys into the database: status %d", 388 qs); 389 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 390 continue; 391 GNUNET_break (0); 392 return false; 393 } 394 395 qs = TALER_MERCHANTDB_commit (pg); 396 if (0 > qs) 397 { 398 TALER_MERCHANTDB_rollback (pg); 399 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 400 "Failed to commit Donau keys to the database: status %d", 401 qs); 402 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 403 continue; 404 GNUNET_break (0); 405 return false; 406 } 407 break; 408 } 409 if (qs < 0) 410 { 411 GNUNET_break (0); 412 return false; 413 } 414 return true; 415 } 416 417 418 /** 419 * Store Donau charity in the database and handle retries. 420 * 421 * @param charity_id the charity ID to store 422 * @param donau_url the base URL of the Donau instance 423 * @param charity the charity structure to store 424 */ 425 static bool 426 store_donau_charity (uint64_t charity_id, 427 const char *donau_url, 428 const struct DONAU_Charity *charity) 429 { 430 enum GNUNET_DB_QueryStatus qs; 431 432 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 433 "Inserting/updating charity %llu for Donau `%s'\n", 434 (unsigned long long) charity_id, 435 donau_url); 436 437 TALER_MERCHANTDB_preflight (pg); 438 439 for (unsigned int r = 0; r < MAX_RETRIES; r++) 440 { 441 if (GNUNET_OK != 442 TALER_MERCHANTDB_start (pg, 443 "update donau charity data")) 444 { 445 TALER_MERCHANTDB_rollback (pg); 446 GNUNET_break (0); 447 return false; 448 } 449 450 qs = TALER_MERCHANTDB_update_donau_instance (pg, 451 donau_url, 452 charity, 453 charity_id); 454 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 455 { 456 TALER_MERCHANTDB_rollback (pg); 457 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 458 "Error while updating charity into the database: status %d", 459 qs); 460 continue; 461 } 462 if (0 >= qs) 463 { 464 TALER_MERCHANTDB_rollback (pg); 465 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 466 "Error while updating charity into the database: status %d", 467 qs); 468 return false; 469 } 470 471 qs = TALER_MERCHANTDB_commit (pg); 472 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 473 { 474 TALER_MERCHANTDB_rollback (pg); 475 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 476 "Failed to commit charity data to the database: status %d", 477 qs); 478 continue; 479 } 480 if (0 > qs) 481 { 482 TALER_MERCHANTDB_rollback (pg); 483 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 484 "Failed to commit charity data to the database: status %d", 485 qs); 486 return false; 487 } 488 break; 489 } 490 if (0 >= qs) 491 { 492 GNUNET_break (0); 493 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 494 "Retries exhausted while inserting charity %llu for Donau `%s': last status %d", 495 (unsigned long long) charity_id, 496 donau_url, 497 qs); 498 return false; 499 } 500 return true; 501 } 502 503 504 /** 505 * Callback after Donau keys are fetched. 506 * 507 * @param cls closure with a `struct Donau *` 508 * @param kr response data 509 * @param keys the keys of the Donau instance 510 */ 511 static void 512 donau_cert_cb ( 513 void *cls, 514 const struct DONAU_KeysResponse *kr, 515 struct DONAU_Keys *keys) 516 { 517 struct Donau *d = cls; 518 struct GNUNET_TIME_Absolute n; 519 struct GNUNET_TIME_Absolute first_retry; 520 521 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 522 "Starting donau cert with object \n"); 523 524 d->conn = NULL; 525 switch (kr->hr.http_status) 526 { 527 case MHD_HTTP_OK: 528 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 529 "Got new keys for %s, updating database\n", 530 d->donau_url); 531 first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ); 532 if (! store_donau_keys (keys, 533 first_retry)) 534 { 535 GNUNET_break (0); 536 DONAU_keys_decref (keys); 537 break; 538 } 539 540 DONAU_keys_decref (d->keys); 541 d->keys = keys; 542 /* Reset back-off */ 543 d->retry_delay = DONAU_MAXFREQ; 544 /* limit retry */ 545 d->first_retry = first_retry; 546 547 if (0 < keys->num_sign_keys) 548 n = GNUNET_TIME_absolute_max (d->first_retry, 549 keys->sign_keys[0].expire_sign.abs_time); 550 else 551 n = d->first_retry; 552 if (NULL != d->retry_task) 553 GNUNET_SCHEDULER_cancel (d->retry_task); 554 d->retry_task = GNUNET_SCHEDULER_add_at (n, 555 &download_keys, 556 d); 557 end_inquiry (); 558 return; 559 default: 560 GNUNET_break (NULL == keys); 561 break; 562 } 563 564 d->retry_delay 565 = GNUNET_TIME_STD_BACKOFF (d->retry_delay); 566 n = GNUNET_TIME_absolute_max ( 567 d->first_retry, 568 GNUNET_TIME_relative_to_absolute (d->retry_delay)); 569 570 if (NULL != d->retry_task) 571 GNUNET_SCHEDULER_cancel (d->retry_task); 572 d->retry_task 573 = GNUNET_SCHEDULER_add_at (n, 574 &download_keys, 575 d); 576 end_inquiry (); 577 } 578 579 580 /** 581 * Initiate the download of Donau keys. 582 * 583 * @param cls closure with a `struct Donau *` 584 */ 585 static void 586 download_keys (void *cls) 587 { 588 struct Donau *d = cls; 589 590 d->retry_task = NULL; 591 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 592 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 593 { 594 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 595 "Cannot start more donaukeys inquiries, already at limit\n"); 596 d->limited = true; 597 at_limit = true; 598 return; 599 } 600 d->retry_delay 601 = GNUNET_TIME_STD_BACKOFF (d->retry_delay); 602 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 603 "Downloading keys from %s (%s)\n", 604 d->donau_url, 605 d->force_retry ? "forced" : "regular"); 606 d->conn = DONAU_get_keys (ctx, 607 d->donau_url, 608 &donau_cert_cb, 609 d); 610 d->force_retry = false; 611 if (NULL != d->conn) 612 { 613 active_inquiries++; 614 } 615 else 616 { 617 struct GNUNET_TIME_Relative n; 618 619 n = GNUNET_TIME_relative_max (d->retry_delay, 620 DONAU_MAXFREQ); 621 622 d->retry_task 623 = GNUNET_SCHEDULER_add_delayed (n, 624 &download_keys, 625 d); 626 } 627 } 628 629 630 /** 631 * Callback for DONAU_charity_get() that stores the charity 632 * information in the DB and finishes the inquiry. 633 * 634 * @param cls closure with `struct ForceCharityCtx *` 635 * @param gcr response from DONAU 636 */ 637 static void 638 donau_charity_cb (void *cls, 639 const struct DONAU_GetCharityResponse *gcr) 640 { 641 struct ForceCharityCtx *fcc = cls; 642 fcc->h = NULL; 643 644 switch (gcr->hr.http_status) 645 { 646 case MHD_HTTP_OK: 647 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 648 "Got charity_id `%llu' details for donau `%s', updating DB\n", 649 (unsigned long long) fcc->charity_id, 650 fcc->donau_url); 651 652 if (! store_donau_charity (fcc->charity_id, 653 fcc->donau_url, 654 &gcr->details.ok.charity)) 655 { 656 GNUNET_break (0); 657 } 658 break; 659 660 default: 661 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 662 "DONAU charity_get for `%s' failed with HTTP %u / ec %u\n", 663 fcc->donau_url, 664 gcr->hr.http_status, 665 gcr->hr.ec); 666 break; 667 } 668 669 end_inquiry (); 670 } 671 672 673 /** 674 * Download the charity_id for a Donau instance. 675 * 676 * @param cls closure with a `struct ForceCharityCtx *` 677 */ 678 static void 679 download_charity_id (void *cls) 680 { 681 struct ForceCharityCtx *fcc = cls; 682 683 fcc->retry_task = NULL; 684 /* nothing to do if a request is already outstanding */ 685 if (NULL != fcc->h) 686 return; 687 688 /* respect global inquiry limit */ 689 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 690 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 691 { 692 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 693 "Cannot start more charity inquiries, already at limit\n"); 694 /* remember to re-drive this inquiry from end_inquiry() 695 once we are substantially below the limit again */ 696 fcc->limited = true; 697 at_limit = true; 698 return; 699 } 700 701 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 702 "Downloading charity `%llu' from `%s'\n", 703 (unsigned long long) fcc->charity_id, 704 fcc->donau_url); 705 706 fcc->h = DONAU_charity_get (ctx, 707 fcc->donau_url, 708 fcc->charity_id, 709 NULL, /* bearer token -- not needed */ 710 &donau_charity_cb, 711 fcc); 712 713 if (NULL != fcc->h) 714 { 715 fcc->retry_delay = GNUNET_TIME_UNIT_ZERO; 716 active_inquiries++; 717 } 718 else 719 { 720 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 721 "Failed to initiate DONAU_charity_get() for `%s'\n", 722 fcc->donau_url); 723 /* no inquiry was started, so we must not call end_inquiry() here; 724 retry with exponential backoff instead */ 725 fcc->retry_delay 726 = GNUNET_TIME_STD_BACKOFF (fcc->retry_delay); 727 fcc->retry_task 728 = GNUNET_SCHEDULER_add_delayed (fcc->retry_delay, 729 &download_charity_id, 730 fcc); 731 } 732 } 733 734 735 /** 736 * Lookup donau by @a donau_url. Create one 737 * if it does not exist. 738 * 739 * @param donau_url base URL to match against 740 * @return NULL if not found 741 */ 742 static struct Donau * 743 lookup_donau (const char *donau_url) 744 { 745 for (struct Donau *d = d_head; 746 NULL != d; 747 d = d->next) 748 if (0 == strcmp (d->donau_url, 749 donau_url)) 750 return d; 751 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 752 "Got notification about unknown Donau `%s'\n", 753 donau_url); 754 return NULL; 755 } 756 757 758 /** 759 * Lookup a ForceCharityCtx by donau-instance serial. 760 * 761 * @param di_serial serial to search for 762 * @return matching context or NULL 763 */ 764 static struct ForceCharityCtx * 765 lookup_donau_charity (uint64_t di_serial) 766 { 767 for (struct ForceCharityCtx *fcc = fcc_head; 768 NULL != fcc; 769 fcc = fcc->next) 770 if (fcc->di_serial == di_serial) 771 return fcc; 772 return NULL; 773 } 774 775 776 /** 777 * Force immediate (re)loading of /charity_id for an donau. 778 * 779 * @param cls NULL 780 * @param extra base URL of the donau that changed 781 * @param extra_len number of bytes in @a extra 782 */ 783 static void 784 force_donau_charity_id (void *cls, 785 const void *extra, 786 size_t extra_len) 787 { 788 uint64_t di_serial; 789 char *donau_url = NULL; 790 uint64_t charity_id = -1; 791 enum GNUNET_DB_QueryStatus qs; 792 struct ForceCharityCtx *fcc; 793 794 if ( (sizeof(uint64_t) != extra_len) || 795 (NULL == extra) ) 796 { 797 GNUNET_break (0); 798 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 799 "Incorrect extra for the force_donau_charity_id"); 800 return; 801 } 802 GNUNET_memcpy (&di_serial, 803 extra, 804 sizeof(uint64_t)); 805 di_serial = GNUNET_ntohll (di_serial); 806 qs = TALER_MERCHANTDB_get_donau_instance_by_serial (pg, 807 di_serial, 808 &donau_url, 809 &charity_id); 810 811 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 812 { 813 GNUNET_break (0); 814 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 815 "force_donau_charity_id: instance serial %llu not found (status %d)\n", 816 (unsigned long long) di_serial, 817 qs); 818 return; 819 } 820 821 fcc = lookup_donau_charity (di_serial); 822 if (NULL == fcc) 823 { 824 fcc = GNUNET_new (struct ForceCharityCtx); 825 fcc->di_serial = di_serial; 826 fcc->donau_url = donau_url; /* take ownership */ 827 fcc->charity_id = charity_id; 828 GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc); 829 830 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 831 "Created new ForceCharityCtx for donau `%s' " 832 "(serial %llu, charity %llu)\n", 833 donau_url, 834 (unsigned long long) di_serial, 835 (unsigned long long) charity_id); 836 } 837 else 838 { 839 GNUNET_free (donau_url); 840 if (NULL != fcc->h) 841 { 842 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 843 "Already downloading charity_id for donau `%s'\n", 844 fcc->donau_url); 845 return; 846 } 847 if (NULL != fcc->retry_task) 848 { 849 GNUNET_SCHEDULER_cancel (fcc->retry_task); 850 fcc->retry_task = NULL; 851 } 852 fcc->charity_id = charity_id; 853 } 854 download_charity_id (fcc); 855 } 856 857 858 /** 859 * Force immediate (re)loading of /keys for an donau. 860 * 861 * @param cls NULL 862 * @param extra base URL of the donau that changed 863 * @param extra_len number of bytes in @a extra 864 */ 865 static void 866 force_donau_keys (void *cls, 867 const void *extra, 868 size_t extra_len) 869 { 870 const char *url = extra; 871 struct Donau *d; 872 873 if ( (NULL == extra) || 874 (0 == extra_len) ) 875 { 876 GNUNET_break (0); 877 return; 878 } 879 if ('\0' != url[extra_len - 1]) 880 { 881 GNUNET_break (0); 882 return; 883 } 884 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 885 "Received keys update notification: reload `%s'\n", 886 url); 887 888 d = lookup_donau (url); 889 if (NULL == d) 890 { 891 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 892 "Donau instance `%s' not found. Creating new instance.\n", 893 url); 894 895 d = GNUNET_new (struct Donau); 896 d->donau_url = GNUNET_strdup (url); 897 d->retry_delay = DONAU_MAXFREQ; 898 d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO); 899 900 GNUNET_CONTAINER_DLL_insert (d_head, 901 d_tail, 902 d); 903 download_keys (d); 904 } 905 906 if (NULL != d->conn) 907 { 908 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 909 "Already downloading %skeys\n", 910 url); 911 return; 912 } 913 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 914 "Will download %skeys in %s\n", 915 url, 916 GNUNET_TIME_relative2s ( 917 GNUNET_TIME_absolute_get_remaining ( 918 d->first_retry), 919 true)); 920 if (NULL != d->retry_task) 921 GNUNET_SCHEDULER_cancel (d->retry_task); 922 d->force_retry = true; 923 d->retry_task 924 = GNUNET_SCHEDULER_add_at (d->first_retry, 925 &download_keys, 926 d); 927 } 928 929 930 /** 931 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 932 * 933 * @param cls closure (NULL) 934 */ 935 static void 936 shutdown_task (void *cls) 937 { 938 (void) cls; 939 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 940 "Running shutdown\n"); 941 while (NULL != d_head) 942 { 943 struct Donau *d = d_head; 944 945 GNUNET_free (d->donau_url); 946 GNUNET_free (d->currency); 947 if (NULL != d->conn) 948 { 949 DONAU_get_keys_cancel (d->conn); 950 d->conn = NULL; 951 } 952 if (NULL != d->keys) 953 { 954 DONAU_keys_decref (d->keys); 955 d->keys = NULL; 956 } 957 if (NULL != d->retry_task) 958 { 959 GNUNET_SCHEDULER_cancel (d->retry_task); 960 d->retry_task = NULL; 961 } 962 GNUNET_CONTAINER_DLL_remove (d_head, 963 d_tail, 964 d); 965 GNUNET_free (d); 966 } 967 while (NULL != fcc_head) 968 { 969 struct ForceCharityCtx *fcc = fcc_head; 970 971 if (NULL != fcc->h) 972 { 973 DONAU_charity_get_cancel (fcc->h); 974 fcc->h = NULL; 975 } 976 if (NULL != fcc->retry_task) 977 { 978 GNUNET_SCHEDULER_cancel (fcc->retry_task); 979 fcc->retry_task = NULL; 980 } 981 GNUNET_CONTAINER_DLL_remove (fcc_head, 982 fcc_tail, 983 fcc); 984 GNUNET_free (fcc->donau_url); 985 GNUNET_free (fcc); 986 } 987 if (NULL != eh) 988 { 989 TALER_MERCHANTDB_event_listen_cancel (eh); 990 eh = NULL; 991 } 992 if (NULL != eh_charity) 993 { 994 TALER_MERCHANTDB_event_listen_cancel (eh_charity); 995 eh_charity = NULL; 996 } 997 if (NULL != pg) 998 { 999 TALER_MERCHANTDB_disconnect (pg); 1000 pg = NULL; 1001 } 1002 cfg = NULL; 1003 if (NULL != ctx) 1004 { 1005 GNUNET_CURL_fini (ctx); 1006 ctx = NULL; 1007 } 1008 if (NULL != rc) 1009 { 1010 GNUNET_CURL_gnunet_rc_destroy (rc); 1011 rc = NULL; 1012 } 1013 } 1014 1015 1016 /** 1017 * Callback function typically used by `iterate_donau_instances` to handle 1018 * the details of each Donau instance retrieved from the database. 1019 * 1020 * @param cls Closure to pass additional context or data to the callback function. 1021 * @param donau_instance_serial Serial number of the Donau instance in the merchant database. 1022 * @param donau_url The URL of the Donau instance. 1023 * @param charity_name The name of the charity associated with the Donau instance. 1024 * @param charity_pub_key Pointer to the charity's public key used for cryptographic operations. 1025 * @param charity_id The unique identifier for the charity within the Donau instance. 1026 * @param charity_max_per_year Maximum allowed donations to the charity for the current year. 1027 * @param charity_receipts_to_date Total donations received by the charity so far in the current year. 1028 * @param current_year The year for which the donation data is being tracked. 1029 * @param donau_keys_json JSON object containing additional key-related information for the Donau instance. 1030 */ 1031 static void 1032 accept_donau ( 1033 void *cls, 1034 uint64_t donau_instance_serial, 1035 const char *donau_url, 1036 const char *charity_name, 1037 const struct DONAU_CharityPublicKeyP *charity_pub_key, 1038 uint64_t charity_id, 1039 const struct TALER_Amount *charity_max_per_year, 1040 const struct TALER_Amount *charity_receipts_to_date, 1041 int64_t current_year, 1042 const json_t *donau_keys_json 1043 ) 1044 { 1045 struct Donau *d; 1046 1047 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1048 "Donau instance `%s' not found. Creating new instance.\n", 1049 donau_url); 1050 d = GNUNET_new (struct Donau); 1051 d->donau_url = GNUNET_strdup (donau_url); 1052 d->retry_delay = DONAU_MAXFREQ; 1053 d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO); 1054 GNUNET_CONTAINER_DLL_insert (d_head, 1055 d_tail, 1056 d); 1057 if (NULL == donau_keys_json) 1058 { 1059 download_keys (d); 1060 return; 1061 } 1062 d->keys = DONAU_keys_from_json (donau_keys_json); 1063 if (NULL == d->keys) 1064 { 1065 GNUNET_break (0); 1066 download_keys (d); 1067 return; 1068 } 1069 d->retry_delay = DONAU_MAXFREQ; 1070 d->first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ); 1071 1072 { 1073 struct GNUNET_TIME_Absolute n; 1074 1075 if (0 < d->keys->num_sign_keys) 1076 n = GNUNET_TIME_absolute_min ( 1077 d->first_retry, 1078 d->keys->sign_keys[0].expire_sign.abs_time); 1079 else 1080 n = d->first_retry; 1081 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1082 "Will download %skeys in %s\n", 1083 donau_url, 1084 GNUNET_TIME_relative2s ( 1085 GNUNET_TIME_absolute_get_remaining (n), 1086 true)); 1087 d->retry_task = GNUNET_SCHEDULER_add_at (n, 1088 &download_keys, 1089 d); 1090 } 1091 } 1092 1093 1094 /** 1095 * First task. 1096 * 1097 * @param cls closure, NULL 1098 * @param args remaining command-line arguments 1099 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1100 * @param c configuration 1101 */ 1102 static void 1103 run (void *cls, 1104 char *const *args, 1105 const char *cfgfile, 1106 const struct GNUNET_CONFIGURATION_Handle *c) 1107 { 1108 (void) args; 1109 (void) cfgfile; 1110 1111 cfg = c; 1112 DONAU_setup (enable_h3 1113 ? DONAU_GO_ENABLE_HTTP3 1114 : DONAU_GO_FORCE_HTTP1_1); 1115 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1116 NULL); 1117 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1118 &rc); 1119 if (NULL == ctx) 1120 { 1121 GNUNET_break (0); 1122 GNUNET_SCHEDULER_shutdown (); 1123 global_ret = EXIT_FAILURE; 1124 return; 1125 } 1126 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1127 if (NULL == 1128 (pg = TALER_MERCHANTDB_connect (cfg)) ) 1129 { 1130 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1131 "Failed to initialize DB subsystem\n"); 1132 GNUNET_SCHEDULER_shutdown (); 1133 global_ret = EXIT_FAILURE; 1134 return; 1135 } 1136 { 1137 struct GNUNET_DB_EventHeaderP es = { 1138 .size = htons (sizeof(es)), 1139 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS) 1140 }; 1141 1142 eh = TALER_MERCHANTDB_event_listen (pg, 1143 &es, 1144 GNUNET_TIME_UNIT_FOREVER_REL, 1145 &force_donau_keys, 1146 NULL); 1147 } 1148 { 1149 struct GNUNET_DB_EventHeaderP es = { 1150 .size = htons (sizeof(es)), 1151 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID) 1152 }; 1153 1154 eh_charity = TALER_MERCHANTDB_event_listen ( 1155 pg, 1156 &es, 1157 GNUNET_TIME_UNIT_FOREVER_REL, 1158 &force_donau_charity_id, 1159 NULL); 1160 } 1161 1162 { 1163 enum GNUNET_DB_QueryStatus qs; 1164 1165 qs = TALER_MERCHANTDB_iterate_all_donau_instances (pg, 1166 &accept_donau, 1167 NULL); 1168 if (qs < 0) 1169 { 1170 GNUNET_break (0); 1171 GNUNET_SCHEDULER_shutdown (); 1172 return; 1173 } 1174 } 1175 if ( (0 == active_inquiries) && 1176 (test_mode) ) 1177 { 1178 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1179 "No donau keys inquiries to start, exiting.\n"); 1180 GNUNET_SCHEDULER_shutdown (); 1181 return; 1182 } 1183 } 1184 1185 1186 /** 1187 * The main function of taler-merchant-donaukeyupdate 1188 * 1189 * @param argc number of arguments from the command line 1190 * @param argv command line arguments 1191 * @return 0 ok, 1 on error 1192 */ 1193 int 1194 main (int argc, 1195 char *const *argv) 1196 { 1197 struct GNUNET_GETOPT_CommandLineOption options[] = { 1198 GNUNET_GETOPT_option_flag ('3', 1199 "http3", 1200 "enable support for HTTP/2 and HTTP/3", 1201 &enable_h3), 1202 GNUNET_GETOPT_option_timetravel ('T', 1203 "timetravel"), 1204 GNUNET_GETOPT_option_flag ('t', 1205 "test", 1206 "run in test mode and exit when idle", 1207 &test_mode), 1208 GNUNET_GETOPT_option_version (VERSION), 1209 GNUNET_GETOPT_OPTION_END 1210 }; 1211 enum GNUNET_GenericReturnValue ret; 1212 1213 ret = GNUNET_PROGRAM_run ( 1214 TALER_MERCHANT_project_data (), 1215 argc, argv, 1216 "taler-merchant-donaukeyupdate", 1217 gettext_noop ( 1218 "background process that ensures our key and configuration data on Donau is up-to-date"), 1219 options, 1220 &run, NULL); 1221 if (GNUNET_SYSERR == ret) 1222 return EXIT_INVALIDARGUMENT; 1223 if (GNUNET_NO == ret) 1224 return EXIT_SUCCESS; 1225 return global_ret; 1226 } 1227 1228 1229 /* end of taler-merchant-donaukeyupdate.c */