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