taler-merchant-donaukeyupdate.c (29749B)
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 d->keys = keys; 489 /* Reset back-off */ 490 d->retry_delay = DONAU_MAXFREQ; 491 /* limit retry */ 492 d->first_retry = first_retry; 493 494 /* FIXME: Might be good to reference some key_data_expiration and not first sign_key*/ 495 n = GNUNET_TIME_absolute_max (d->first_retry, 496 keys->sign_keys[0].expire_sign.abs_time); 497 if (NULL != d->retry_task) 498 GNUNET_SCHEDULER_cancel (d->retry_task); 499 d->retry_task = GNUNET_SCHEDULER_add_at (n, 500 &download_keys, 501 d); 502 end_inquiry (); 503 return; 504 default: 505 GNUNET_break (NULL == keys); 506 break; 507 } 508 509 d->retry_delay 510 = GNUNET_TIME_STD_BACKOFF (d->retry_delay); 511 n = GNUNET_TIME_absolute_max ( 512 d->first_retry, 513 GNUNET_TIME_relative_to_absolute (d->retry_delay)); 514 515 if (NULL != d->retry_task) 516 GNUNET_SCHEDULER_cancel (d->retry_task); 517 d->retry_task 518 = GNUNET_SCHEDULER_add_at (n, 519 &download_keys, 520 d); 521 end_inquiry (); 522 } 523 524 525 /** 526 * Initiate the download of Donau keys. 527 * 528 * @param cls closure with a `struct Donau *` 529 */ 530 static void 531 download_keys (void *cls) 532 { 533 struct Donau *d = cls; 534 535 d->retry_task = NULL; 536 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 537 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 538 { 539 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 540 "Cannot start more donaukeys inquiries, already at limit\n"); 541 d->limited = true; 542 at_limit = true; 543 return; 544 } 545 d->retry_delay 546 = GNUNET_TIME_STD_BACKOFF (d->retry_delay); 547 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 548 "Downloading keys from %s (%s)\n", 549 d->donau_url, 550 d->force_retry ? "forced" : "regular"); 551 d->conn = DONAU_get_keys (ctx, 552 d->donau_url, 553 &donau_cert_cb, 554 d); 555 d->force_retry = false; 556 if (NULL != d->conn) 557 { 558 active_inquiries++; 559 } 560 else 561 { 562 struct GNUNET_TIME_Relative n; 563 564 n = GNUNET_TIME_relative_max (d->retry_delay, 565 DONAU_MAXFREQ); 566 567 d->retry_task 568 = GNUNET_SCHEDULER_add_delayed (n, 569 &download_keys, 570 d); 571 } 572 } 573 574 575 /** 576 * Callback for DONAU_charity_get() that stores the charity 577 * information in the DB and finishes the inquiry. 578 * 579 * @param cls closure with `struct ForceCharityCtx *` 580 * @param gcr response from DONAU 581 */ 582 static void 583 donau_charity_cb (void *cls, 584 const struct DONAU_GetCharityResponse *gcr) 585 { 586 struct ForceCharityCtx *fcc = cls; 587 fcc->h = NULL; 588 589 switch (gcr->hr.http_status) 590 { 591 case MHD_HTTP_OK: 592 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 593 "Got charity_id `%llu' details for donau `%s', updating DB\n", 594 (unsigned long long) fcc->charity_id, 595 fcc->donau_url); 596 597 if (! store_donau_charity (fcc->charity_id, 598 fcc->donau_url, 599 &gcr->details.ok.charity)) 600 { 601 GNUNET_break (0); 602 } 603 break; 604 605 default: 606 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 607 "DONAU charity_get for `%s' failed with HTTP %u / ec %u\n", 608 fcc->donau_url, 609 gcr->hr.http_status, 610 gcr->hr.ec); 611 break; 612 } 613 614 end_inquiry (); 615 } 616 617 618 /** 619 * Download the charity_id for a Donau instance. 620 * 621 * @param cls closure with a `struct Donau *` 622 */ 623 static void 624 download_charity_id (void *cls) 625 { 626 struct ForceCharityCtx *fcc = cls; 627 628 /* nothing to do if a request is already outstanding */ 629 if (NULL != fcc->h) 630 return; 631 632 /* respect global inquiry limit */ 633 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 634 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 635 { 636 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 637 "Cannot start more charity inquiries, already at limit\n"); 638 return; 639 } 640 641 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 642 "Downloading charity `%llu' from `%s'\n", 643 (unsigned long long) fcc->charity_id, 644 fcc->donau_url); 645 646 fcc->h = DONAU_charity_get (ctx, 647 fcc->donau_url, 648 fcc->charity_id, 649 NULL, /* bearer token -- not needed */ 650 &donau_charity_cb, 651 fcc); 652 653 if (NULL != fcc->h) 654 { 655 active_inquiries++; 656 } 657 else 658 { 659 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 660 "Failed to initiate DONAU_charity_get() for `%s'\n", 661 fcc->donau_url); 662 /* we do NOT retry here – simply finish the (failed) inquiry */ 663 end_inquiry (); 664 } 665 } 666 667 668 /** 669 * Lookup donau by @a donau_url. Create one 670 * if it does not exist. 671 * 672 * @param donau_url base URL to match against 673 * @return NULL if not found 674 */ 675 static struct Donau * 676 lookup_donau (const char *donau_url) 677 { 678 for (struct Donau *d = d_head; 679 NULL != d; 680 d = d->next) 681 if (0 == strcmp (d->donau_url, 682 donau_url)) 683 return d; 684 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 685 "Got notification about unknown Donau `%s'\n", 686 donau_url); 687 return NULL; 688 } 689 690 691 /** 692 * Lookup a ForceCharityCtx by donau-instance serial. 693 * 694 * @param di_serial serial to search for 695 * @return matching context or NULL 696 */ 697 static struct ForceCharityCtx * 698 lookup_donau_charity (uint64_t di_serial) 699 { 700 for (struct ForceCharityCtx *fcc = fcc_head; 701 NULL != fcc; 702 fcc = fcc->next) 703 if (fcc->di_serial == di_serial) 704 return fcc; 705 return NULL; 706 } 707 708 709 /** 710 * Force immediate (re)loading of /charity_id for an donau. 711 * 712 * @param cls NULL 713 * @param extra base URL of the donau that changed 714 * @param extra_len number of bytes in @a extra 715 */ 716 static void 717 force_donau_charity_id (void *cls, 718 const void *extra, 719 size_t extra_len) 720 { 721 uint64_t di_serial; 722 char *donau_url = NULL; 723 uint64_t charity_id = -1; 724 enum GNUNET_DB_QueryStatus qs; 725 struct ForceCharityCtx *fcc; 726 727 if ( (sizeof(uint64_t) != extra_len) || 728 (NULL == extra) ) 729 { 730 GNUNET_break (0); 731 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 732 "Incorrect extra for the force_donau_charity_id"); 733 return; 734 } 735 GNUNET_memcpy (&di_serial, 736 extra, 737 sizeof(uint64_t)); 738 di_serial = GNUNET_ntohll (di_serial); 739 qs = TALER_MERCHANTDB_select_donau_instance_by_serial (pg, 740 di_serial, 741 &donau_url, 742 &charity_id); 743 744 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 745 { 746 GNUNET_break (0); 747 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 748 "force_donau_charity_id: instance serial %llu not found (status %d)\n", 749 (unsigned long long) di_serial, 750 qs); 751 return; 752 } 753 754 fcc = lookup_donau_charity (di_serial); 755 if (NULL == fcc) 756 { 757 fcc = GNUNET_new (struct ForceCharityCtx); 758 fcc->di_serial = di_serial; 759 fcc->donau_url = donau_url; /* take ownership */ 760 fcc->charity_id = charity_id; 761 GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc); 762 763 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 764 "Created new ForceCharityCtx for donau `%s' " 765 "(serial %llu, charity %llu)\n", 766 donau_url, 767 (unsigned long long) di_serial, 768 (unsigned long long) charity_id); 769 } 770 else 771 { 772 GNUNET_free (donau_url); 773 if (NULL != fcc->h) 774 { 775 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 776 "Already downloading charity_id for donau `%s'\n", 777 fcc->donau_url); 778 return; 779 } 780 } 781 download_charity_id (fcc); 782 } 783 784 785 /** 786 * Force immediate (re)loading of /keys for an donau. 787 * 788 * @param cls NULL 789 * @param extra base URL of the donau that changed 790 * @param extra_len number of bytes in @a extra 791 */ 792 static void 793 force_donau_keys (void *cls, 794 const void *extra, 795 size_t extra_len) 796 { 797 const char *url = extra; 798 struct Donau *d; 799 800 if ( (NULL == extra) || 801 (0 == extra_len) ) 802 { 803 GNUNET_break (0); 804 return; 805 } 806 if ('\0' != url[extra_len - 1]) 807 { 808 GNUNET_break (0); 809 return; 810 } 811 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 812 "Received keys update notification: reload `%s'\n", 813 url); 814 815 d = lookup_donau (url); 816 if (NULL == d) 817 { 818 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 819 "Donau instance `%s' not found. Creating new instance.\n", 820 url); 821 822 d = GNUNET_new (struct Donau); 823 d->donau_url = GNUNET_strdup (url); 824 d->retry_delay = DONAU_MAXFREQ; 825 d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO); 826 827 GNUNET_CONTAINER_DLL_insert (d_head, 828 d_tail, 829 d); 830 download_keys (d); 831 } 832 833 if (NULL != d->conn) 834 { 835 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 836 "Already downloading %skeys\n", 837 url); 838 return; 839 } 840 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 841 "Will download %skeys in %s\n", 842 url, 843 GNUNET_TIME_relative2s ( 844 GNUNET_TIME_absolute_get_remaining ( 845 d->first_retry), 846 true)); 847 if (NULL != d->retry_task) 848 GNUNET_SCHEDULER_cancel (d->retry_task); 849 d->force_retry = true; 850 d->retry_task 851 = GNUNET_SCHEDULER_add_at (d->first_retry, 852 &download_keys, 853 d); 854 } 855 856 857 /** 858 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 859 * 860 * @param cls closure (NULL) 861 */ 862 static void 863 shutdown_task (void *cls) 864 { 865 (void) cls; 866 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 867 "Running shutdown\n"); 868 while (NULL != d_head) 869 { 870 struct Donau *d = d_head; 871 872 GNUNET_free (d->donau_url); 873 GNUNET_free (d->currency); 874 if (NULL != d->conn) 875 { 876 DONAU_get_keys_cancel (d->conn); 877 d->conn = NULL; 878 } 879 if (NULL != d->keys) 880 { 881 DONAU_keys_decref (d->keys); 882 d->keys = NULL; 883 } 884 if (NULL != d->retry_task) 885 { 886 GNUNET_SCHEDULER_cancel (d->retry_task); 887 d->retry_task = NULL; 888 } 889 GNUNET_CONTAINER_DLL_remove (d_head, 890 d_tail, 891 d); 892 GNUNET_free (d); 893 } 894 if (NULL != eh) 895 { 896 TALER_MERCHANTDB_event_listen_cancel (eh); 897 eh = NULL; 898 } 899 if (NULL != eh_charity) 900 { 901 TALER_MERCHANTDB_event_listen_cancel (eh_charity); 902 eh_charity = NULL; 903 } 904 if (NULL != pg) 905 { 906 TALER_MERCHANTDB_disconnect (pg); 907 pg = NULL; 908 } 909 cfg = NULL; 910 if (NULL != ctx) 911 { 912 GNUNET_CURL_fini (ctx); 913 ctx = NULL; 914 } 915 if (NULL != rc) 916 { 917 GNUNET_CURL_gnunet_rc_destroy (rc); 918 rc = NULL; 919 } 920 } 921 922 923 /** 924 * Callback function typically used by `select_donau_instances` to handle 925 * the details of each Donau instance retrieved from the database. 926 * 927 * @param cls Closure to pass additional context or data to the callback function. 928 * @param donau_instance_serial Serial number of the Donau instance in the merchant database. 929 * @param donau_url The URL of the Donau instance. 930 * @param charity_name The name of the charity associated with the Donau instance. 931 * @param charity_pub_key Pointer to the charity's public key used for cryptographic operations. 932 * @param charity_id The unique identifier for the charity within the Donau instance. 933 * @param charity_max_per_year Maximum allowed donations to the charity for the current year. 934 * @param charity_receipts_to_date Total donations received by the charity so far in the current year. 935 * @param current_year The year for which the donation data is being tracked. 936 * @param donau_keys_json JSON object containing additional key-related information for the Donau instance. 937 */ 938 static void 939 accept_donau ( 940 void *cls, 941 uint64_t donau_instance_serial, 942 const char *donau_url, 943 const char *charity_name, 944 const struct DONAU_CharityPublicKeyP *charity_pub_key, 945 uint64_t charity_id, 946 const struct TALER_Amount *charity_max_per_year, 947 const struct TALER_Amount *charity_receipts_to_date, 948 int64_t current_year, 949 const json_t *donau_keys_json 950 ) 951 { 952 struct Donau *d; 953 954 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 955 "Donau instance `%s' not found. Creating new instance.\n", 956 donau_url); 957 d = GNUNET_new (struct Donau); 958 d->donau_url = GNUNET_strdup (donau_url); 959 d->retry_delay = DONAU_MAXFREQ; 960 d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO); 961 GNUNET_CONTAINER_DLL_insert (d_head, 962 d_tail, 963 d); 964 if (NULL == donau_keys_json) 965 { 966 download_keys (d); 967 return; 968 } 969 d->keys = DONAU_keys_from_json (donau_keys_json); 970 if (NULL == d->keys) 971 { 972 GNUNET_break (0); 973 download_keys (d); 974 return; 975 } 976 d->retry_delay = DONAU_MAXFREQ; 977 d->first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ); 978 979 { 980 struct GNUNET_TIME_Absolute n; 981 982 n = GNUNET_TIME_absolute_min ( 983 d->first_retry, 984 d->keys->sign_keys[0].expire_sign.abs_time); 985 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 986 "Will download %skeys in %s\n", 987 donau_url, 988 GNUNET_TIME_relative2s ( 989 GNUNET_TIME_absolute_get_remaining (n), 990 true)); 991 d->retry_task = GNUNET_SCHEDULER_add_at (n, 992 &download_keys, 993 d); 994 } 995 } 996 997 998 /** 999 * First task. 1000 * 1001 * @param cls closure, NULL 1002 * @param args remaining command-line arguments 1003 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1004 * @param c configuration 1005 */ 1006 static void 1007 run (void *cls, 1008 char *const *args, 1009 const char *cfgfile, 1010 const struct GNUNET_CONFIGURATION_Handle *c) 1011 { 1012 (void) args; 1013 (void) cfgfile; 1014 1015 cfg = c; 1016 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1017 NULL); 1018 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1019 &rc); 1020 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1021 if (NULL == ctx) 1022 { 1023 GNUNET_break (0); 1024 GNUNET_SCHEDULER_shutdown (); 1025 global_ret = EXIT_FAILURE; 1026 return; 1027 } 1028 if (NULL == ctx) 1029 { 1030 GNUNET_break (0); 1031 GNUNET_SCHEDULER_shutdown (); 1032 global_ret = EXIT_FAILURE; 1033 return; 1034 } 1035 if (NULL == 1036 (pg = TALER_MERCHANTDB_connect (cfg)) ) 1037 { 1038 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1039 "Failed to initialize DB subsystem\n"); 1040 GNUNET_SCHEDULER_shutdown (); 1041 global_ret = EXIT_FAILURE; 1042 return; 1043 } 1044 { 1045 struct GNUNET_DB_EventHeaderP es = { 1046 .size = htons (sizeof(es)), 1047 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS) 1048 }; 1049 1050 eh = TALER_MERCHANTDB_event_listen (pg, 1051 &es, 1052 GNUNET_TIME_UNIT_FOREVER_REL, 1053 &force_donau_keys, 1054 NULL); 1055 } 1056 { 1057 struct GNUNET_DB_EventHeaderP es = { 1058 .size = htons (sizeof(es)), 1059 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID) 1060 }; 1061 1062 eh_charity = TALER_MERCHANTDB_event_listen ( 1063 pg, 1064 &es, 1065 GNUNET_TIME_UNIT_FOREVER_REL, 1066 &force_donau_charity_id, 1067 NULL); 1068 } 1069 1070 { 1071 enum GNUNET_DB_QueryStatus qs; 1072 1073 qs = TALER_MERCHANTDB_select_all_donau_instances (pg, 1074 &accept_donau, 1075 NULL); 1076 if (qs < 0) 1077 { 1078 GNUNET_break (0); 1079 GNUNET_SCHEDULER_shutdown (); 1080 return; 1081 } 1082 } 1083 if ( (0 == active_inquiries) && 1084 (test_mode) ) 1085 { 1086 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1087 "No donau keys inquiries to start, exiting.\n"); 1088 GNUNET_SCHEDULER_shutdown (); 1089 return; 1090 } 1091 } 1092 1093 1094 /** 1095 * The main function of taler-merchant-donaukeyupdate 1096 * 1097 * @param argc number of arguments from the command line 1098 * @param argv command line arguments 1099 * @return 0 ok, 1 on error 1100 */ 1101 int 1102 main (int argc, 1103 char *const *argv) 1104 { 1105 struct GNUNET_GETOPT_CommandLineOption options[] = { 1106 GNUNET_GETOPT_option_timetravel ('T', 1107 "timetravel"), 1108 GNUNET_GETOPT_option_flag ('t', 1109 "test", 1110 "run in test mode and exit when idle", 1111 &test_mode), 1112 GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), 1113 GNUNET_GETOPT_OPTION_END 1114 }; 1115 enum GNUNET_GenericReturnValue ret; 1116 1117 ret = GNUNET_PROGRAM_run ( 1118 TALER_MERCHANT_project_data (), 1119 argc, argv, 1120 "taler-merchant-donaukeyupdate", 1121 gettext_noop ( 1122 "background process that ensures our key and configuration data on Donau is up-to-date"), 1123 options, 1124 &run, NULL); 1125 if (GNUNET_SYSERR == ret) 1126 return EXIT_INVALIDARGUMENT; 1127 if (GNUNET_NO == ret) 1128 return EXIT_SUCCESS; 1129 return global_ret; 1130 } 1131 1132 1133 /* end of taler-merchant-donaukeyupdate.c */