taler-merchant-donaukeyupdate.c (32092B)
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/select_all_donau_instances.h" 34 #include "merchant-database/select_donau_instance_by_serial.h" 35 #include "merchant-database/update_donau_instance.h" 36 #include "merchant-database/upsert_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 * #GNUNET_YES if we are in test mode and should exit when idle. 247 */ 248 static int test_mode; 249 250 /** 251 * True if the last DB query was limited by the 252 * #OPEN_INQUIRY_LIMIT and we thus should check again 253 * as soon as we are substantially below that limit, 254 * and not only when we get a DB notification. 255 */ 256 static bool at_limit; 257 258 259 /** 260 * Function that initiates a /keys download for a Donau instance. 261 * 262 * @param cls closure with a `struct Donau *` 263 */ 264 static void 265 download_keys (void *cls); 266 267 268 /** 269 * Function that initiates a /charity_id download for a Donau instance. 270 * 271 * @param cls closure with a `struct ForceCharityCtx *` 272 */ 273 static void 274 download_charity_id (void *cls); 275 276 277 /** 278 * An inquiry finished, check if we need to start more. 279 */ 280 static void 281 end_inquiry (void) 282 { 283 GNUNET_assert (active_inquiries > 0); 284 active_inquiries--; 285 if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) && 286 (at_limit) ) 287 { 288 at_limit = false; 289 for (struct Donau *d = d_head; 290 NULL != d; 291 d = d->next) 292 { 293 if (! d->limited) 294 continue; 295 d->limited = false; 296 /* done synchronously so that the active_inquiries 297 is updated immediately */ 298 download_keys (d); 299 if (at_limit) 300 break; 301 } 302 for (struct ForceCharityCtx *fcc = fcc_head; 303 NULL != fcc; 304 fcc = fcc->next) 305 { 306 if (at_limit) 307 break; 308 if (! fcc->limited) 309 continue; 310 fcc->limited = false; 311 /* done synchronously so that the active_inquiries 312 is updated immediately */ 313 download_charity_id (fcc); 314 } 315 } 316 if ( (! at_limit) && 317 (0 == active_inquiries) && 318 (test_mode) ) 319 { 320 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 321 "No more open inquiries and in test mode. Exiting.\n"); 322 GNUNET_SCHEDULER_shutdown (); 323 return; 324 } 325 } 326 327 328 /** 329 * Update Donau keys in the database. 330 * 331 * @param keys Donau keys to persist 332 * @param first_retry earliest we may retry fetching the keys 333 * @return transaction status 334 */ 335 static enum GNUNET_DB_QueryStatus 336 insert_donau_keys_data (const struct DONAU_Keys *keys, 337 struct GNUNET_TIME_Absolute first_retry) 338 { 339 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 340 "Inserting Donau keys into the database %s\n", 341 keys->donau_url); 342 return TALER_MERCHANTDB_upsert_donau_keys (pg, 343 keys, 344 first_retry); 345 } 346 347 348 /** 349 * Store Donau keys in the database and handle retries. 350 * 351 * @param keys the keys to store 352 * @param first_retry earliest time we may retry fetching the keys 353 * @return true on success 354 */ 355 static bool 356 store_donau_keys (struct DONAU_Keys *keys, 357 struct GNUNET_TIME_Absolute first_retry) 358 { 359 enum GNUNET_DB_QueryStatus qs; 360 TALER_MERCHANTDB_preflight (pg); 361 for (unsigned int r = 0; r < MAX_RETRIES; r++) 362 { 363 if (GNUNET_OK != 364 TALER_MERCHANTDB_start (pg, 365 "update donau key data")) 366 { 367 TALER_MERCHANTDB_rollback (pg); 368 GNUNET_break (0); 369 return false; 370 } 371 372 qs = insert_donau_keys_data (keys, 373 first_retry); 374 if (0 > qs) 375 { 376 TALER_MERCHANTDB_rollback (pg); 377 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 378 "Error while inserting Donau keys into the database: status %d", 379 qs); 380 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 381 continue; 382 GNUNET_break (0); 383 return false; 384 } 385 386 qs = TALER_MERCHANTDB_commit (pg); 387 if (0 > qs) 388 { 389 TALER_MERCHANTDB_rollback (pg); 390 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 391 "Failed to commit Donau keys to the database: status %d", 392 qs); 393 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 394 continue; 395 GNUNET_break (0); 396 return false; 397 } 398 break; 399 } 400 if (qs < 0) 401 { 402 GNUNET_break (0); 403 return false; 404 } 405 return true; 406 } 407 408 409 /** 410 * Store Donau charity in the database and handle retries. 411 * 412 * @param charity_id the charity ID to store 413 * @param donau_url the base URL of the Donau instance 414 * @param charity the charity structure to store 415 */ 416 static bool 417 store_donau_charity (uint64_t charity_id, 418 const char *donau_url, 419 const struct DONAU_Charity *charity) 420 { 421 enum GNUNET_DB_QueryStatus qs; 422 423 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 424 "Inserting/updating charity %llu for Donau `%s'\n", 425 (unsigned long long) charity_id, 426 donau_url); 427 428 TALER_MERCHANTDB_preflight (pg); 429 430 for (unsigned int r = 0; r < MAX_RETRIES; r++) 431 { 432 if (GNUNET_OK != 433 TALER_MERCHANTDB_start (pg, 434 "update donau charity data")) 435 { 436 TALER_MERCHANTDB_rollback (pg); 437 GNUNET_break (0); 438 return false; 439 } 440 441 qs = TALER_MERCHANTDB_update_donau_instance (pg, 442 donau_url, 443 charity, 444 charity_id); 445 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 446 { 447 TALER_MERCHANTDB_rollback (pg); 448 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 449 "Error while updating charity into the database: status %d", 450 qs); 451 continue; 452 } 453 if (0 >= qs) 454 { 455 TALER_MERCHANTDB_rollback (pg); 456 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 457 "Error while updating charity into the database: status %d", 458 qs); 459 return false; 460 } 461 462 qs = TALER_MERCHANTDB_commit (pg); 463 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 464 { 465 TALER_MERCHANTDB_rollback (pg); 466 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 467 "Failed to commit charity data to the database: status %d", 468 qs); 469 continue; 470 } 471 if (0 > qs) 472 { 473 TALER_MERCHANTDB_rollback (pg); 474 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 475 "Failed to commit charity data to the database: status %d", 476 qs); 477 return false; 478 } 479 break; 480 } 481 if (0 >= qs) 482 { 483 GNUNET_break (0); 484 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 485 "Retries exhausted while inserting charity %llu for Donau `%s': last status %d", 486 (unsigned long long) charity_id, 487 donau_url, 488 qs); 489 return false; 490 } 491 return true; 492 } 493 494 495 /** 496 * Callback after Donau keys are fetched. 497 * 498 * @param cls closure with a `struct Donau *` 499 * @param kr response data 500 * @param keys the keys of the Donau instance 501 */ 502 static void 503 donau_cert_cb ( 504 void *cls, 505 const struct DONAU_KeysResponse *kr, 506 struct DONAU_Keys *keys) 507 { 508 struct Donau *d = cls; 509 struct GNUNET_TIME_Absolute n; 510 struct GNUNET_TIME_Absolute first_retry; 511 512 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 513 "Starting donau cert with object \n"); 514 515 d->conn = NULL; 516 switch (kr->hr.http_status) 517 { 518 case MHD_HTTP_OK: 519 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 520 "Got new keys for %s, updating database\n", 521 d->donau_url); 522 first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ); 523 if (! store_donau_keys (keys, 524 first_retry)) 525 { 526 GNUNET_break (0); 527 DONAU_keys_decref (keys); 528 break; 529 } 530 531 DONAU_keys_decref (d->keys); 532 d->keys = keys; 533 /* Reset back-off */ 534 d->retry_delay = DONAU_MAXFREQ; 535 /* limit retry */ 536 d->first_retry = first_retry; 537 538 if (0 < keys->num_sign_keys) 539 n = GNUNET_TIME_absolute_max (d->first_retry, 540 keys->sign_keys[0].expire_sign.abs_time); 541 else 542 n = d->first_retry; 543 if (NULL != d->retry_task) 544 GNUNET_SCHEDULER_cancel (d->retry_task); 545 d->retry_task = GNUNET_SCHEDULER_add_at (n, 546 &download_keys, 547 d); 548 end_inquiry (); 549 return; 550 default: 551 GNUNET_break (NULL == keys); 552 break; 553 } 554 555 d->retry_delay 556 = GNUNET_TIME_STD_BACKOFF (d->retry_delay); 557 n = GNUNET_TIME_absolute_max ( 558 d->first_retry, 559 GNUNET_TIME_relative_to_absolute (d->retry_delay)); 560 561 if (NULL != d->retry_task) 562 GNUNET_SCHEDULER_cancel (d->retry_task); 563 d->retry_task 564 = GNUNET_SCHEDULER_add_at (n, 565 &download_keys, 566 d); 567 end_inquiry (); 568 } 569 570 571 /** 572 * Initiate the download of Donau keys. 573 * 574 * @param cls closure with a `struct Donau *` 575 */ 576 static void 577 download_keys (void *cls) 578 { 579 struct Donau *d = cls; 580 581 d->retry_task = NULL; 582 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 583 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 584 { 585 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 586 "Cannot start more donaukeys inquiries, already at limit\n"); 587 d->limited = true; 588 at_limit = true; 589 return; 590 } 591 d->retry_delay 592 = GNUNET_TIME_STD_BACKOFF (d->retry_delay); 593 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 594 "Downloading keys from %s (%s)\n", 595 d->donau_url, 596 d->force_retry ? "forced" : "regular"); 597 d->conn = DONAU_get_keys (ctx, 598 d->donau_url, 599 &donau_cert_cb, 600 d); 601 d->force_retry = false; 602 if (NULL != d->conn) 603 { 604 active_inquiries++; 605 } 606 else 607 { 608 struct GNUNET_TIME_Relative n; 609 610 n = GNUNET_TIME_relative_max (d->retry_delay, 611 DONAU_MAXFREQ); 612 613 d->retry_task 614 = GNUNET_SCHEDULER_add_delayed (n, 615 &download_keys, 616 d); 617 } 618 } 619 620 621 /** 622 * Callback for DONAU_charity_get() that stores the charity 623 * information in the DB and finishes the inquiry. 624 * 625 * @param cls closure with `struct ForceCharityCtx *` 626 * @param gcr response from DONAU 627 */ 628 static void 629 donau_charity_cb (void *cls, 630 const struct DONAU_GetCharityResponse *gcr) 631 { 632 struct ForceCharityCtx *fcc = cls; 633 fcc->h = NULL; 634 635 switch (gcr->hr.http_status) 636 { 637 case MHD_HTTP_OK: 638 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 639 "Got charity_id `%llu' details for donau `%s', updating DB\n", 640 (unsigned long long) fcc->charity_id, 641 fcc->donau_url); 642 643 if (! store_donau_charity (fcc->charity_id, 644 fcc->donau_url, 645 &gcr->details.ok.charity)) 646 { 647 GNUNET_break (0); 648 } 649 break; 650 651 default: 652 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 653 "DONAU charity_get for `%s' failed with HTTP %u / ec %u\n", 654 fcc->donau_url, 655 gcr->hr.http_status, 656 gcr->hr.ec); 657 break; 658 } 659 660 end_inquiry (); 661 } 662 663 664 /** 665 * Download the charity_id for a Donau instance. 666 * 667 * @param cls closure with a `struct ForceCharityCtx *` 668 */ 669 static void 670 download_charity_id (void *cls) 671 { 672 struct ForceCharityCtx *fcc = cls; 673 674 fcc->retry_task = NULL; 675 /* nothing to do if a request is already outstanding */ 676 if (NULL != fcc->h) 677 return; 678 679 /* respect global inquiry limit */ 680 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 681 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 682 { 683 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 684 "Cannot start more charity inquiries, already at limit\n"); 685 /* remember to re-drive this inquiry from end_inquiry() 686 once we are substantially below the limit again */ 687 fcc->limited = true; 688 at_limit = true; 689 return; 690 } 691 692 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 693 "Downloading charity `%llu' from `%s'\n", 694 (unsigned long long) fcc->charity_id, 695 fcc->donau_url); 696 697 fcc->h = DONAU_charity_get (ctx, 698 fcc->donau_url, 699 fcc->charity_id, 700 NULL, /* bearer token -- not needed */ 701 &donau_charity_cb, 702 fcc); 703 704 if (NULL != fcc->h) 705 { 706 fcc->retry_delay = GNUNET_TIME_UNIT_ZERO; 707 active_inquiries++; 708 } 709 else 710 { 711 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 712 "Failed to initiate DONAU_charity_get() for `%s'\n", 713 fcc->donau_url); 714 /* no inquiry was started, so we must not call end_inquiry() here; 715 retry with exponential backoff instead */ 716 fcc->retry_delay 717 = GNUNET_TIME_STD_BACKOFF (fcc->retry_delay); 718 fcc->retry_task 719 = GNUNET_SCHEDULER_add_delayed (fcc->retry_delay, 720 &download_charity_id, 721 fcc); 722 } 723 } 724 725 726 /** 727 * Lookup donau by @a donau_url. Create one 728 * if it does not exist. 729 * 730 * @param donau_url base URL to match against 731 * @return NULL if not found 732 */ 733 static struct Donau * 734 lookup_donau (const char *donau_url) 735 { 736 for (struct Donau *d = d_head; 737 NULL != d; 738 d = d->next) 739 if (0 == strcmp (d->donau_url, 740 donau_url)) 741 return d; 742 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 743 "Got notification about unknown Donau `%s'\n", 744 donau_url); 745 return NULL; 746 } 747 748 749 /** 750 * Lookup a ForceCharityCtx by donau-instance serial. 751 * 752 * @param di_serial serial to search for 753 * @return matching context or NULL 754 */ 755 static struct ForceCharityCtx * 756 lookup_donau_charity (uint64_t di_serial) 757 { 758 for (struct ForceCharityCtx *fcc = fcc_head; 759 NULL != fcc; 760 fcc = fcc->next) 761 if (fcc->di_serial == di_serial) 762 return fcc; 763 return NULL; 764 } 765 766 767 /** 768 * Force immediate (re)loading of /charity_id for an donau. 769 * 770 * @param cls NULL 771 * @param extra base URL of the donau that changed 772 * @param extra_len number of bytes in @a extra 773 */ 774 static void 775 force_donau_charity_id (void *cls, 776 const void *extra, 777 size_t extra_len) 778 { 779 uint64_t di_serial; 780 char *donau_url = NULL; 781 uint64_t charity_id = -1; 782 enum GNUNET_DB_QueryStatus qs; 783 struct ForceCharityCtx *fcc; 784 785 if ( (sizeof(uint64_t) != extra_len) || 786 (NULL == extra) ) 787 { 788 GNUNET_break (0); 789 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 790 "Incorrect extra for the force_donau_charity_id"); 791 return; 792 } 793 GNUNET_memcpy (&di_serial, 794 extra, 795 sizeof(uint64_t)); 796 di_serial = GNUNET_ntohll (di_serial); 797 qs = TALER_MERCHANTDB_select_donau_instance_by_serial (pg, 798 di_serial, 799 &donau_url, 800 &charity_id); 801 802 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 803 { 804 GNUNET_break (0); 805 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 806 "force_donau_charity_id: instance serial %llu not found (status %d)\n", 807 (unsigned long long) di_serial, 808 qs); 809 return; 810 } 811 812 fcc = lookup_donau_charity (di_serial); 813 if (NULL == fcc) 814 { 815 fcc = GNUNET_new (struct ForceCharityCtx); 816 fcc->di_serial = di_serial; 817 fcc->donau_url = donau_url; /* take ownership */ 818 fcc->charity_id = charity_id; 819 GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc); 820 821 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 822 "Created new ForceCharityCtx for donau `%s' " 823 "(serial %llu, charity %llu)\n", 824 donau_url, 825 (unsigned long long) di_serial, 826 (unsigned long long) charity_id); 827 } 828 else 829 { 830 GNUNET_free (donau_url); 831 if (NULL != fcc->h) 832 { 833 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 834 "Already downloading charity_id for donau `%s'\n", 835 fcc->donau_url); 836 return; 837 } 838 if (NULL != fcc->retry_task) 839 { 840 GNUNET_SCHEDULER_cancel (fcc->retry_task); 841 fcc->retry_task = NULL; 842 } 843 fcc->charity_id = charity_id; 844 } 845 download_charity_id (fcc); 846 } 847 848 849 /** 850 * Force immediate (re)loading of /keys for an donau. 851 * 852 * @param cls NULL 853 * @param extra base URL of the donau that changed 854 * @param extra_len number of bytes in @a extra 855 */ 856 static void 857 force_donau_keys (void *cls, 858 const void *extra, 859 size_t extra_len) 860 { 861 const char *url = extra; 862 struct Donau *d; 863 864 if ( (NULL == extra) || 865 (0 == extra_len) ) 866 { 867 GNUNET_break (0); 868 return; 869 } 870 if ('\0' != url[extra_len - 1]) 871 { 872 GNUNET_break (0); 873 return; 874 } 875 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 876 "Received keys update notification: reload `%s'\n", 877 url); 878 879 d = lookup_donau (url); 880 if (NULL == d) 881 { 882 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 883 "Donau instance `%s' not found. Creating new instance.\n", 884 url); 885 886 d = GNUNET_new (struct Donau); 887 d->donau_url = GNUNET_strdup (url); 888 d->retry_delay = DONAU_MAXFREQ; 889 d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO); 890 891 GNUNET_CONTAINER_DLL_insert (d_head, 892 d_tail, 893 d); 894 download_keys (d); 895 } 896 897 if (NULL != d->conn) 898 { 899 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 900 "Already downloading %skeys\n", 901 url); 902 return; 903 } 904 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 905 "Will download %skeys in %s\n", 906 url, 907 GNUNET_TIME_relative2s ( 908 GNUNET_TIME_absolute_get_remaining ( 909 d->first_retry), 910 true)); 911 if (NULL != d->retry_task) 912 GNUNET_SCHEDULER_cancel (d->retry_task); 913 d->force_retry = true; 914 d->retry_task 915 = GNUNET_SCHEDULER_add_at (d->first_retry, 916 &download_keys, 917 d); 918 } 919 920 921 /** 922 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 923 * 924 * @param cls closure (NULL) 925 */ 926 static void 927 shutdown_task (void *cls) 928 { 929 (void) cls; 930 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 931 "Running shutdown\n"); 932 while (NULL != d_head) 933 { 934 struct Donau *d = d_head; 935 936 GNUNET_free (d->donau_url); 937 GNUNET_free (d->currency); 938 if (NULL != d->conn) 939 { 940 DONAU_get_keys_cancel (d->conn); 941 d->conn = NULL; 942 } 943 if (NULL != d->keys) 944 { 945 DONAU_keys_decref (d->keys); 946 d->keys = NULL; 947 } 948 if (NULL != d->retry_task) 949 { 950 GNUNET_SCHEDULER_cancel (d->retry_task); 951 d->retry_task = NULL; 952 } 953 GNUNET_CONTAINER_DLL_remove (d_head, 954 d_tail, 955 d); 956 GNUNET_free (d); 957 } 958 while (NULL != fcc_head) 959 { 960 struct ForceCharityCtx *fcc = fcc_head; 961 962 if (NULL != fcc->h) 963 { 964 DONAU_charity_get_cancel (fcc->h); 965 fcc->h = NULL; 966 } 967 if (NULL != fcc->retry_task) 968 { 969 GNUNET_SCHEDULER_cancel (fcc->retry_task); 970 fcc->retry_task = NULL; 971 } 972 GNUNET_CONTAINER_DLL_remove (fcc_head, 973 fcc_tail, 974 fcc); 975 GNUNET_free (fcc->donau_url); 976 GNUNET_free (fcc); 977 } 978 if (NULL != eh) 979 { 980 TALER_MERCHANTDB_event_listen_cancel (eh); 981 eh = NULL; 982 } 983 if (NULL != eh_charity) 984 { 985 TALER_MERCHANTDB_event_listen_cancel (eh_charity); 986 eh_charity = NULL; 987 } 988 if (NULL != pg) 989 { 990 TALER_MERCHANTDB_disconnect (pg); 991 pg = NULL; 992 } 993 cfg = NULL; 994 if (NULL != ctx) 995 { 996 GNUNET_CURL_fini (ctx); 997 ctx = NULL; 998 } 999 if (NULL != rc) 1000 { 1001 GNUNET_CURL_gnunet_rc_destroy (rc); 1002 rc = NULL; 1003 } 1004 } 1005 1006 1007 /** 1008 * Callback function typically used by `select_donau_instances` to handle 1009 * the details of each Donau instance retrieved from the database. 1010 * 1011 * @param cls Closure to pass additional context or data to the callback function. 1012 * @param donau_instance_serial Serial number of the Donau instance in the merchant database. 1013 * @param donau_url The URL of the Donau instance. 1014 * @param charity_name The name of the charity associated with the Donau instance. 1015 * @param charity_pub_key Pointer to the charity's public key used for cryptographic operations. 1016 * @param charity_id The unique identifier for the charity within the Donau instance. 1017 * @param charity_max_per_year Maximum allowed donations to the charity for the current year. 1018 * @param charity_receipts_to_date Total donations received by the charity so far in the current year. 1019 * @param current_year The year for which the donation data is being tracked. 1020 * @param donau_keys_json JSON object containing additional key-related information for the Donau instance. 1021 */ 1022 static void 1023 accept_donau ( 1024 void *cls, 1025 uint64_t donau_instance_serial, 1026 const char *donau_url, 1027 const char *charity_name, 1028 const struct DONAU_CharityPublicKeyP *charity_pub_key, 1029 uint64_t charity_id, 1030 const struct TALER_Amount *charity_max_per_year, 1031 const struct TALER_Amount *charity_receipts_to_date, 1032 int64_t current_year, 1033 const json_t *donau_keys_json 1034 ) 1035 { 1036 struct Donau *d; 1037 1038 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1039 "Donau instance `%s' not found. Creating new instance.\n", 1040 donau_url); 1041 d = GNUNET_new (struct Donau); 1042 d->donau_url = GNUNET_strdup (donau_url); 1043 d->retry_delay = DONAU_MAXFREQ; 1044 d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO); 1045 GNUNET_CONTAINER_DLL_insert (d_head, 1046 d_tail, 1047 d); 1048 if (NULL == donau_keys_json) 1049 { 1050 download_keys (d); 1051 return; 1052 } 1053 d->keys = DONAU_keys_from_json (donau_keys_json); 1054 if (NULL == d->keys) 1055 { 1056 GNUNET_break (0); 1057 download_keys (d); 1058 return; 1059 } 1060 d->retry_delay = DONAU_MAXFREQ; 1061 d->first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ); 1062 1063 { 1064 struct GNUNET_TIME_Absolute n; 1065 1066 if (0 < d->keys->num_sign_keys) 1067 n = GNUNET_TIME_absolute_min ( 1068 d->first_retry, 1069 d->keys->sign_keys[0].expire_sign.abs_time); 1070 else 1071 n = d->first_retry; 1072 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1073 "Will download %skeys in %s\n", 1074 donau_url, 1075 GNUNET_TIME_relative2s ( 1076 GNUNET_TIME_absolute_get_remaining (n), 1077 true)); 1078 d->retry_task = GNUNET_SCHEDULER_add_at (n, 1079 &download_keys, 1080 d); 1081 } 1082 } 1083 1084 1085 /** 1086 * First task. 1087 * 1088 * @param cls closure, NULL 1089 * @param args remaining command-line arguments 1090 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1091 * @param c configuration 1092 */ 1093 static void 1094 run (void *cls, 1095 char *const *args, 1096 const char *cfgfile, 1097 const struct GNUNET_CONFIGURATION_Handle *c) 1098 { 1099 (void) args; 1100 (void) cfgfile; 1101 1102 cfg = c; 1103 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1104 NULL); 1105 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1106 &rc); 1107 if (NULL == ctx) 1108 { 1109 GNUNET_break (0); 1110 GNUNET_SCHEDULER_shutdown (); 1111 global_ret = EXIT_FAILURE; 1112 return; 1113 } 1114 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1115 if (NULL == 1116 (pg = TALER_MERCHANTDB_connect (cfg)) ) 1117 { 1118 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1119 "Failed to initialize DB subsystem\n"); 1120 GNUNET_SCHEDULER_shutdown (); 1121 global_ret = EXIT_FAILURE; 1122 return; 1123 } 1124 { 1125 struct GNUNET_DB_EventHeaderP es = { 1126 .size = htons (sizeof(es)), 1127 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS) 1128 }; 1129 1130 eh = TALER_MERCHANTDB_event_listen (pg, 1131 &es, 1132 GNUNET_TIME_UNIT_FOREVER_REL, 1133 &force_donau_keys, 1134 NULL); 1135 } 1136 { 1137 struct GNUNET_DB_EventHeaderP es = { 1138 .size = htons (sizeof(es)), 1139 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID) 1140 }; 1141 1142 eh_charity = TALER_MERCHANTDB_event_listen ( 1143 pg, 1144 &es, 1145 GNUNET_TIME_UNIT_FOREVER_REL, 1146 &force_donau_charity_id, 1147 NULL); 1148 } 1149 1150 { 1151 enum GNUNET_DB_QueryStatus qs; 1152 1153 qs = TALER_MERCHANTDB_select_all_donau_instances (pg, 1154 &accept_donau, 1155 NULL); 1156 if (qs < 0) 1157 { 1158 GNUNET_break (0); 1159 GNUNET_SCHEDULER_shutdown (); 1160 return; 1161 } 1162 } 1163 if ( (0 == active_inquiries) && 1164 (test_mode) ) 1165 { 1166 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1167 "No donau keys inquiries to start, exiting.\n"); 1168 GNUNET_SCHEDULER_shutdown (); 1169 return; 1170 } 1171 } 1172 1173 1174 /** 1175 * The main function of taler-merchant-donaukeyupdate 1176 * 1177 * @param argc number of arguments from the command line 1178 * @param argv command line arguments 1179 * @return 0 ok, 1 on error 1180 */ 1181 int 1182 main (int argc, 1183 char *const *argv) 1184 { 1185 struct GNUNET_GETOPT_CommandLineOption options[] = { 1186 GNUNET_GETOPT_option_timetravel ('T', 1187 "timetravel"), 1188 GNUNET_GETOPT_option_flag ('t', 1189 "test", 1190 "run in test mode and exit when idle", 1191 &test_mode), 1192 GNUNET_GETOPT_option_version (VERSION), 1193 GNUNET_GETOPT_OPTION_END 1194 }; 1195 enum GNUNET_GenericReturnValue ret; 1196 1197 ret = GNUNET_PROGRAM_run ( 1198 TALER_MERCHANT_project_data (), 1199 argc, argv, 1200 "taler-merchant-donaukeyupdate", 1201 gettext_noop ( 1202 "background process that ensures our key and configuration data on Donau is up-to-date"), 1203 options, 1204 &run, NULL); 1205 if (GNUNET_SYSERR == ret) 1206 return EXIT_INVALIDARGUMENT; 1207 if (GNUNET_NO == ret) 1208 return EXIT_SUCCESS; 1209 return global_ret; 1210 } 1211 1212 1213 /* end of taler-merchant-donaukeyupdate.c */