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