taler-exchange-wirewatch.c (33764B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2016--2023 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-exchange-wirewatch.c 18 * @brief Process that watches for wire transfers to the exchange's bank account 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <jansson.h> 24 #include <pthread.h> 25 #include <microhttpd.h> 26 #include "exchangedb_lib.h" 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_bank_service.h" 29 #include "exchange-database/begin_shard.h" 30 #include "exchange-database/abort_shard.h" 31 #include "exchange-database/preflight.h" 32 #include "exchange-database/do_import_credits.h" 33 #include "exchange-database/event_listen.h" 34 #include "exchange-database/event_listen_cancel.h" 35 36 /** 37 * How long to wait for an HTTP reply if there 38 * are no transactions pending at the server? 39 */ 40 #define LONGPOLL_TIMEOUT GNUNET_TIME_UNIT_MINUTES 41 42 /** 43 * What is the maximum batch size we use for credit history 44 * requests with the bank. See `batch_size` below. 45 */ 46 #define MAXIMUM_BATCH_SIZE 1024 47 48 /** 49 * Information about our account. 50 */ 51 static const struct TALER_EXCHANGEDB_AccountInfo *ai; 52 53 /** 54 * Active request for history. 55 */ 56 static struct TALER_BANK_CreditHistoryHandle *hh; 57 58 /** 59 * Set to true if the request for history did actually 60 * return transaction items. 61 */ 62 static bool hh_returned_data; 63 64 /** 65 * Set to true if the request for history did not 66 * succeed because the account was unknown. 67 */ 68 static bool hh_account_404; 69 70 /** 71 * Set to true if the request for history did not 72 * succeed because of some unexpected HTTP request error. 73 */ 74 static bool hh_error; 75 76 /** 77 * When did we start the last @e hh request? 78 */ 79 static struct GNUNET_TIME_Absolute hh_start_time; 80 81 /** 82 * Until when is processing this wire plugin delayed? 83 */ 84 static struct GNUNET_TIME_Absolute delayed_until; 85 86 /** 87 * Encoded offset in the wire transfer list from where 88 * to start the next query with the bank. 89 */ 90 static uint64_t batch_start; 91 92 /** 93 * Latest row offset we have imported and committed. Mirrors the 94 * @c progress_row of our shard in the database. 95 */ 96 static uint64_t latest_row_off; 97 98 /** 99 * Offset where our current shard begins (inclusive). 100 */ 101 static uint64_t shard_start; 102 103 /** 104 * Offset where our current shard ends (exclusive). 105 */ 106 static uint64_t shard_end; 107 108 /** 109 * For how long do we hold the shard? Renewed on every batch we import, so 110 * that a worker whose account trickles in slowly does not keep losing its 111 * shard to a second worker that then has nothing to do either. 112 */ 113 static struct GNUNET_TIME_Relative shard_lease; 114 115 /** 116 * When did we start with the shard? 117 */ 118 static struct GNUNET_TIME_Absolute shard_start_time; 119 120 /** 121 * For how long did we lock the shard? 122 */ 123 static struct GNUNET_TIME_Absolute shard_end_time; 124 125 /** 126 * How long did we take to finish the last shard 127 * for this account? 128 */ 129 static struct GNUNET_TIME_Relative shard_delay; 130 131 /** 132 * How long did we take to finish the last shard 133 * for this account? 134 */ 135 static struct GNUNET_TIME_Relative longpoll_timeout; 136 137 /** 138 * How long do we wait on 404. 139 */ 140 static struct GNUNET_TIME_Relative h404_backoff; 141 142 /** 143 * How long do we wait on HTTP history request errors. 144 */ 145 static struct GNUNET_TIME_Relative hh_error_backoff; 146 147 /** 148 * Name of our job in the shard table. 149 */ 150 static char *job_name; 151 152 /** 153 * How many transactions do we retrieve per batch? 154 */ 155 static unsigned int batch_size; 156 157 /** 158 * How much do we increment @e batch_size on success? 159 */ 160 static unsigned int batch_thresh; 161 162 /** 163 * Did work remain in the transaction queue? Set to true 164 * if we did some work and thus there might be more. 165 */ 166 static bool progress; 167 168 /** 169 * Is this shard still open for processing. 170 */ 171 static bool shard_open; 172 173 /** 174 * Handle to the context for interacting with the bank. 175 */ 176 static struct GNUNET_CURL_Context *ctx; 177 178 /** 179 * Scheduler context for running the @e ctx. 180 */ 181 static struct GNUNET_CURL_RescheduleContext *rc; 182 183 /** 184 * The exchange's configuration (global) 185 */ 186 static const struct GNUNET_CONFIGURATION_Handle *cfg; 187 188 /** 189 * Our DB plugin. 190 */ 191 static struct TALER_EXCHANGEDB_PostgresContext *pg; 192 193 /** 194 * How long should we sleep when idle before trying to find more work? 195 * Also used for how long we wait to grab a shard before trying it again. 196 * The value should be set to a bit above the average time it takes to 197 * process a shard. 198 */ 199 static struct GNUNET_TIME_Relative wirewatch_idle_sleep_interval; 200 201 /** 202 * How long do we sleep on serialization conflicts? 203 */ 204 static struct GNUNET_TIME_Relative wirewatch_conflict_sleep_interval; 205 206 /** 207 * Modulus to apply to group shards. The shard size must ultimately be a 208 * multiple of the batch size. Thus, if this is not a multiple of the 209 * #MAXIMUM_BATCH_SIZE, the batch size will be set to the #shard_size. 210 */ 211 static unsigned int shard_size = MAXIMUM_BATCH_SIZE; 212 213 /** 214 * How many workers should we plan our scheduling with? 215 */ 216 static unsigned int max_workers = 16; 217 218 /** 219 * -e command-line option: exit on errors talking to the bank? 220 */ 221 static int exit_on_error; 222 223 /** 224 * Value to return from main(). 0 on success, non-zero on 225 * on serious errors. 226 */ 227 static int global_ret; 228 229 /** 230 * Are we run in testing mode and should only do one pass? 231 */ 232 static int test_mode; 233 234 /** 235 * Should we ignore if the bank does not know our bank 236 * account? 237 */ 238 static int ignore_account_404; 239 240 /** 241 * Current task waiting for execution, if any. 242 */ 243 static struct GNUNET_SCHEDULER_Task *task; 244 245 /** 246 * Name of the configuration section with the account we should watch. 247 */ 248 static char *account_section; 249 250 /** 251 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 252 * 253 * @param cls closure 254 */ 255 static void 256 shutdown_task (void *cls) 257 { 258 enum GNUNET_DB_QueryStatus qs; 259 (void) cls; 260 261 if (NULL != hh) 262 { 263 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 264 "History request cancelled on shutdown\n"); 265 TALER_BANK_credit_history_cancel (hh); 266 hh = NULL; 267 } 268 if (shard_open) 269 { 270 /* Everything we imported is committed, and #latest_row_off is recorded in 271 the shard, so releasing the lease loses no work: whoever picks the 272 shard up next resumes where we stopped. */ 273 qs = TALER_EXCHANGEDB_abort_shard (pg, 274 job_name, 275 shard_start, 276 shard_end); 277 if (qs <= 0) 278 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 279 "Failed to abort work shard on shutdown\n"); 280 } 281 GNUNET_free (job_name); 282 if (NULL != ctx) 283 { 284 GNUNET_CURL_fini (ctx); 285 ctx = NULL; 286 } 287 if (NULL != rc) 288 { 289 GNUNET_CURL_gnunet_rc_destroy (rc); 290 rc = NULL; 291 } 292 if (NULL != task) 293 { 294 GNUNET_SCHEDULER_cancel (task); 295 task = NULL; 296 } 297 TALER_EXCHANGEDB_disconnect (pg); 298 pg = NULL; 299 TALER_EXCHANGEDB_unload_accounts (); 300 cfg = NULL; 301 } 302 303 304 /** 305 * Function called with information about a wire account. Adds the 306 * account to our list (if it is enabled and we can load the plugin). 307 * 308 * @param cls closure, NULL 309 * @param in_ai account information 310 */ 311 static void 312 add_account_cb (void *cls, 313 const struct TALER_EXCHANGEDB_AccountInfo *in_ai) 314 { 315 (void) cls; 316 if (! in_ai->credit_enabled) 317 return; /* not enabled for us, skip */ 318 if ( (NULL != account_section) && 319 (0 != strcasecmp (in_ai->section_name, 320 account_section)) ) 321 return; /* not enabled for us, skip */ 322 if (NULL != ai) 323 { 324 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 325 "Multiple accounts enabled (%s and %s), use '-a' command-line option to select one!\n", 326 ai->section_name, 327 in_ai->section_name); 328 GNUNET_SCHEDULER_shutdown (); 329 global_ret = EXIT_INVALIDARGUMENT; 330 return; 331 } 332 ai = in_ai; 333 GNUNET_asprintf (&job_name, 334 "wirewatch-%s", 335 ai->section_name); 336 batch_size = MAXIMUM_BATCH_SIZE; 337 if (0 != shard_size % batch_size) 338 batch_size = shard_size; 339 } 340 341 342 /** 343 * Parse configuration parameters for the exchange server into the 344 * corresponding global variables. 345 * 346 * @return #GNUNET_OK on success 347 */ 348 static enum GNUNET_GenericReturnValue 349 exchange_serve_process_config (void) 350 { 351 if (GNUNET_OK != 352 GNUNET_CONFIGURATION_get_value_time (cfg, 353 "exchange", 354 "WIREWATCH_IDLE_SLEEP_INTERVAL", 355 &wirewatch_idle_sleep_interval)) 356 { 357 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 358 "exchange", 359 "WIREWATCH_IDLE_SLEEP_INTERVAL"); 360 return GNUNET_SYSERR; 361 } 362 if (NULL == 363 (pg = TALER_EXCHANGEDB_connect (cfg))) 364 { 365 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 366 "Failed to initialize DB subsystem\n"); 367 return GNUNET_SYSERR; 368 } 369 if (GNUNET_OK != 370 TALER_EXCHANGEDB_load_accounts (cfg, 371 TALER_EXCHANGEDB_ALO_CREDIT 372 | TALER_EXCHANGEDB_ALO_AUTHDATA)) 373 { 374 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 375 "No wire accounts configured for credit!\n"); 376 return GNUNET_SYSERR; 377 } 378 TALER_EXCHANGEDB_find_accounts (&add_account_cb, 379 NULL); 380 if (NULL == ai) 381 { 382 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 383 "No accounts enabled for credit!\n"); 384 GNUNET_SCHEDULER_shutdown (); 385 return GNUNET_SYSERR; 386 } 387 return GNUNET_OK; 388 } 389 390 391 /** 392 * Lock a shard and then begin to query for incoming wire transfers. 393 * 394 * @param cls NULL 395 */ 396 static void 397 lock_shard (void *cls); 398 399 400 /** 401 * Continue with the credit history of the shard. 402 * 403 * @param cls NULL 404 */ 405 static void 406 continue_with_shard (void *cls); 407 408 409 /** 410 * We encountered a serialization error. The batch that hit it was a single 411 * statement, so the database has already discarded all of it; there is 412 * nothing to roll back. Shrink the batch and ask the bank again from the 413 * last point we committed. 414 */ 415 static void 416 handle_soft_error (void) 417 { 418 if (1 < batch_size) 419 { 420 batch_thresh = batch_size; 421 batch_size /= 2; 422 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 423 "Reduced batch size to %llu due to serialization issue\n", 424 (unsigned long long) batch_size); 425 } 426 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 427 "Encountered soft error, resetting start point to batch start\n"); 428 latest_row_off = batch_start; 429 GNUNET_assert (NULL == task); 430 task = GNUNET_SCHEDULER_add_now (&continue_with_shard, 431 NULL); 432 } 433 434 435 /** 436 * Schedule the #lock_shard() operation. 437 */ 438 static void 439 schedule_transfers (void) 440 { 441 if (shard_open) 442 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 443 "Will retry my shard (%llu,%llu] of %s in %s\n", 444 (unsigned long long) shard_start, 445 (unsigned long long) shard_end, 446 job_name, 447 GNUNET_STRINGS_relative_time_to_string ( 448 GNUNET_TIME_absolute_get_remaining (delayed_until), 449 true)); 450 else 451 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 452 "Will try to lock next shard of %s in %s\n", 453 job_name, 454 GNUNET_STRINGS_relative_time_to_string ( 455 GNUNET_TIME_absolute_get_remaining (delayed_until), 456 true)); 457 GNUNET_assert (NULL == task); 458 task = GNUNET_SCHEDULER_add_at (delayed_until, 459 &lock_shard, 460 NULL); 461 } 462 463 464 /** 465 * We are done with the work that is possible right now (and the transaction 466 * was committed, if there was one to commit). Move on to the next shard. 467 */ 468 static void 469 transaction_completed (void) 470 { 471 if ( (batch_start + batch_size == 472 latest_row_off) && 473 (batch_size < MAXIMUM_BATCH_SIZE) ) 474 { 475 /* The current batch size worked without serialization 476 issues, and we are allowed to grow. Do so slowly. */ 477 int delta; 478 479 delta = ((int) batch_thresh - (int) batch_size) / 4; 480 if (delta < 0) 481 delta = -delta; 482 batch_size = GNUNET_MIN (MAXIMUM_BATCH_SIZE, 483 batch_size + delta + 1); 484 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 485 "Increasing batch size to %llu\n", 486 (unsigned long long) batch_size); 487 } 488 489 if ( (! progress) && test_mode) 490 { 491 /* Transaction list was drained and we are in 492 test mode. So we are done. */ 493 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 494 "Transaction list drained and in test mode. Exiting\n"); 495 GNUNET_SCHEDULER_shutdown (); 496 return; 497 } 498 if (! (hh_returned_data || hh_account_404 || hh_error) ) 499 { 500 /* Enforce long-polling delay even if the server ignored it 501 and returned earlier */ 502 struct GNUNET_TIME_Relative latency; 503 struct GNUNET_TIME_Relative left; 504 505 latency = GNUNET_TIME_absolute_get_duration (hh_start_time); 506 left = GNUNET_TIME_relative_subtract (longpoll_timeout, 507 latency); 508 if (! (test_mode || 509 GNUNET_TIME_relative_is_zero (left)) ) 510 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 511 "Server did not respect long-polling, enforcing client-side by sleeping for %s\n", 512 GNUNET_TIME_relative2s (left, 513 true)); 514 delayed_until = GNUNET_TIME_relative_to_absolute (left); 515 } 516 if (hh_account_404) 517 { 518 h404_backoff = GNUNET_TIME_STD_BACKOFF (h404_backoff); 519 delayed_until = GNUNET_TIME_relative_to_absolute ( 520 h404_backoff); 521 } 522 else 523 { 524 h404_backoff = GNUNET_TIME_UNIT_ZERO; 525 } 526 if (hh_error) 527 { 528 hh_error_backoff = GNUNET_TIME_STD_BACKOFF (hh_error_backoff); 529 delayed_until = GNUNET_TIME_relative_to_absolute ( 530 hh_error_backoff); 531 } 532 else 533 { 534 hh_error_backoff = GNUNET_TIME_UNIT_ZERO; 535 } 536 if (test_mode) 537 delayed_until = GNUNET_TIME_UNIT_ZERO_ABS; 538 GNUNET_assert (NULL == task); 539 schedule_transfers (); 540 } 541 542 543 /** 544 * We got incoming transaction details from the bank. Add them 545 * to the database. 546 * 547 * @param details array of transaction details 548 * @param details_length length of the @a details array 549 */ 550 static void 551 process_reply (const struct TALER_BANK_CreditDetails *details, 552 unsigned int details_length) 553 { 554 enum GNUNET_DB_QueryStatus qs; 555 bool shard_done; 556 uint64_t lroff = latest_row_off; 557 558 if (0 == details_length) 559 { 560 /* Server should have used 204, not 200! */ 561 GNUNET_break_op (0); 562 transaction_completed (); 563 return; 564 } 565 hh_returned_data = true; 566 /* check serial IDs for range constraints */ 567 for (unsigned int i = 0; i<details_length; i++) 568 { 569 const struct TALER_BANK_CreditDetails *cd = &details[i]; 570 571 if (cd->serial_id < lroff) 572 { 573 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 574 "Serial ID %llu not monotonic (got %llu before). Failing!\n", 575 (unsigned long long) cd->serial_id, 576 (unsigned long long) lroff); 577 GNUNET_SCHEDULER_shutdown (); 578 return; 579 } 580 if (cd->serial_id > shard_end) 581 { 582 /* we are *past* the current shard (likely because the serial_id of the 583 shard_end happens to not exist in the DB). So commit and stop this 584 iteration! */ 585 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 586 "Serial ID %llu past shard end at %llu, ending iteration early!\n", 587 (unsigned long long) cd->serial_id, 588 (unsigned long long) shard_end); 589 details_length = i; 590 progress = true; 591 lroff = cd->serial_id - 1; 592 break; 593 } 594 lroff = cd->serial_id; 595 } 596 { 597 enum GNUNET_DB_QueryStatus qss[GNUNET_NZL (details_length)]; 598 struct TALER_EXCHANGEDB_ReserveInInfo reserves[GNUNET_NZL (details_length)]; 599 struct TALER_EXCHANGEDB_KycauthInInfo kycauths[GNUNET_NZL (details_length)]; 600 struct TALER_EXCHANGEDB_WadInInfo wads[GNUNET_NZL (details_length)]; 601 struct TALER_EXCHANGEDB_CreditBatch batch = { 602 .exchange_account_name = ai->section_name, 603 .reserves = reserves, 604 .kycauths = kycauths, 605 .wads = wads, 606 .job_name = job_name, 607 .shard_start = shard_start, 608 .shard_end = shard_end, 609 .progress_row = lroff, 610 .lease = shard_lease 611 }; 612 unsigned int j; 613 614 /* make compiler happy */ 615 memset (qss, 616 0, 617 sizeof (qss)); 618 if (0 != details_length) 619 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 620 "Importing %u transactions\n", 621 details_length); 622 for (unsigned int i = 0; i<details_length; i++) 623 { 624 const struct TALER_BANK_CreditDetails *cd = &details[i]; 625 626 switch (cd->type) 627 { 628 case TALER_BANK_CT_RESERVE: 629 { 630 struct TALER_EXCHANGEDB_ReserveInInfo *res 631 = &reserves[batch.reserves_length++]; 632 633 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 634 "Importing reserve transfer over %s\n", 635 TALER_amount2s (&cd->amount)); 636 res->reserve_pub = &cd->details.reserve.reserve_pub; 637 res->balance = &cd->amount; 638 res->execution_time = cd->execution_date; 639 res->sender_account_details = cd->debit_account_uri; 640 res->wire_reference = cd->serial_id; 641 } 642 break; 643 case TALER_BANK_CT_KYCAUTH: 644 { 645 struct TALER_EXCHANGEDB_KycauthInInfo *ka 646 = &kycauths[batch.kycauths_length++]; 647 648 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 649 "Importing KYC auth transfer over %s\n", 650 TALER_amount2s (&cd->amount)); 651 ka->account_pub = &cd->details.kycauth.account_pub; 652 ka->balance = &cd->amount; 653 ka->execution_time = cd->execution_date; 654 ka->sender_account_details = cd->debit_account_uri; 655 ka->wire_reference = cd->serial_id; 656 } 657 break; 658 case TALER_BANK_CT_WAD: 659 { 660 struct TALER_EXCHANGEDB_WadInInfo *wad 661 = &wads[batch.wads_length++]; 662 663 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 664 "Importing WAD transfer over %s\n", 665 TALER_amount2s (&cd->amount)); 666 wad->wad_id = &cd->details.wad.wad_id; 667 wad->origin_exchange_url = cd->details.wad.origin_exchange_url; 668 wad->balance = &cd->amount; 669 wad->execution_time = cd->execution_date; 670 } 671 break; 672 } 673 } 674 /* One statement, hence one transaction: these transfers and the record of 675 how far this shard has come become visible together. That is what lets 676 us hand the money to the wallets now instead of at the end of the 677 shard, without risking a crash that leaves the shard claiming work it 678 never did. */ 679 qs = TALER_EXCHANGEDB_do_import_credits (pg, 680 &batch, 681 qss); 682 switch (qs) 683 { 684 case GNUNET_DB_STATUS_HARD_ERROR: 685 GNUNET_break (0); 686 GNUNET_SCHEDULER_shutdown (); 687 return; 688 case GNUNET_DB_STATUS_SOFT_ERROR: 689 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 690 "Got DB soft error importing %u transactions. Retrying.\n", 691 details_length); 692 handle_soft_error (); 693 return; 694 default: 695 break; 696 } 697 j = 0; 698 for (unsigned int i = 0; i<details_length; i++) 699 { 700 const struct TALER_BANK_CreditDetails *cd = &details[i]; 701 702 if (TALER_BANK_CT_RESERVE != cd->type) 703 continue; /* only reserve transfers report a per-row status */ 704 switch (qss[j++]) 705 { 706 case GNUNET_DB_STATUS_HARD_ERROR: 707 case GNUNET_DB_STATUS_SOFT_ERROR: 708 GNUNET_break (0); /* handled above, for the batch as a whole */ 709 GNUNET_SCHEDULER_shutdown (); 710 return; 711 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 712 /* Either wirewatch was freshly started after the system was 713 shutdown and we're going over an incomplete shard again 714 after being restarted, or the shard lock period was too 715 short (number of workers set incorrectly?) and a 2nd 716 wirewatcher has been stealing our work while we are still 717 at it. */ 718 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 719 "Attempted to import transaction %llu (%s) twice. " 720 "This should happen rarely (if not, ask for support).\n", 721 (unsigned long long) cd->serial_id, 722 job_name); 723 break; 724 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 725 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 726 "Imported transaction %llu.\n", 727 (unsigned long long) cd->serial_id); 728 /* normal case */ 729 progress = true; 730 break; 731 } 732 } 733 } 734 735 latest_row_off = lroff; 736 /* The same statement renewed our lease in the database, so keep the local 737 deadline in step or we would go re-acquire a shard we still hold. */ 738 shard_end_time = GNUNET_TIME_relative_to_absolute (shard_lease); 739 shard_done = (shard_end <= latest_row_off); 740 if (shard_done) 741 { 742 /* The shard was marked completed by the very statement that imported the 743 last of its transfers; there is nothing left to write. */ 744 progress = true; 745 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 746 "Completed shard %s (%llu,%llu] after %s\n", 747 job_name, 748 (unsigned long long) shard_start, 749 (unsigned long long) shard_end, 750 GNUNET_STRINGS_relative_time_to_string ( 751 GNUNET_TIME_absolute_get_duration (shard_start_time), 752 true)); 753 shard_delay = GNUNET_TIME_absolute_get_duration (shard_start_time); 754 shard_open = false; 755 transaction_completed (); 756 return; 757 } 758 GNUNET_assert (NULL == task); 759 task = GNUNET_SCHEDULER_add_now (&continue_with_shard, 760 NULL); 761 } 762 763 764 /** 765 * Callbacks of this type are used to serve the result of asking 766 * the bank for the transaction history. 767 * 768 * @param cls NULL 769 * @param reply response we got from the bank 770 */ 771 static void 772 history_cb (void *cls, 773 const struct TALER_BANK_CreditHistoryResponse *reply) 774 { 775 (void) cls; 776 GNUNET_assert (NULL == task); 777 hh = NULL; 778 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 779 "History request returned with HTTP status %u\n", 780 reply->http_status); 781 switch (reply->http_status) 782 { 783 case MHD_HTTP_OK: 784 process_reply (reply->details.ok.details, 785 reply->details.ok.details_length); 786 return; 787 case MHD_HTTP_NO_CONTENT: 788 transaction_completed (); 789 return; 790 case MHD_HTTP_NOT_FOUND: 791 hh_account_404 = true; 792 if (ignore_account_404) 793 { 794 transaction_completed (); 795 return; 796 } 797 break; 798 default: 799 hh_error = true; 800 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 801 "Error fetching history: %s (%u)\n", 802 TALER_ErrorCode_get_hint (reply->ec), 803 reply->http_status); 804 break; 805 } 806 if (! exit_on_error) 807 { 808 transaction_completed (); 809 return; 810 } 811 GNUNET_SCHEDULER_shutdown (); 812 } 813 814 815 static void 816 continue_with_shard (void *cls) 817 { 818 unsigned int limit; 819 820 (void) cls; 821 task = NULL; 822 GNUNET_assert (shard_end > latest_row_off); 823 limit = GNUNET_MIN (batch_size, 824 shard_end - latest_row_off); 825 /* Where this batch starts out: the point we last committed, and thus where 826 #handle_soft_error() rewinds to and what #transaction_completed() measures 827 the batch against. */ 828 batch_start = latest_row_off; 829 GNUNET_assert (NULL == hh); 830 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 831 "Requesting credit history starting from %llu\n", 832 (unsigned long long) latest_row_off); 833 hh_start_time = GNUNET_TIME_absolute_get (); 834 hh_returned_data = false; 835 hh_account_404 = false; 836 hh_error = false; 837 hh = TALER_BANK_credit_history (ctx, 838 ai->auth, 839 latest_row_off, 840 limit, 841 test_mode 842 ? GNUNET_TIME_UNIT_ZERO 843 : longpoll_timeout, 844 &history_cb, 845 NULL); 846 if (NULL == hh) 847 { 848 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 849 "Failed to start request for account history!\n"); 850 global_ret = EXIT_FAILURE; 851 GNUNET_SCHEDULER_shutdown (); 852 return; 853 } 854 } 855 856 857 /** 858 * Reserve a shard for us to work on. 859 * 860 * @param cls NULL 861 */ 862 static void 863 lock_shard (void *cls) 864 { 865 enum GNUNET_DB_QueryStatus qs; 866 struct GNUNET_TIME_Relative delay; 867 uint64_t progress_row; 868 869 (void) cls; 870 task = NULL; 871 if (GNUNET_SYSERR == 872 TALER_EXCHANGEDB_preflight (pg)) 873 { 874 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 875 "Failed to obtain database connection!\n"); 876 global_ret = EXIT_FAILURE; 877 GNUNET_SCHEDULER_shutdown (); 878 return; 879 } 880 if ( (shard_open) && 881 (GNUNET_TIME_absolute_is_future (shard_end_time)) ) 882 { 883 progress = false; 884 task = GNUNET_SCHEDULER_add_now (&continue_with_shard, 885 NULL); 886 return; 887 } 888 if (shard_open) 889 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 890 "Shard not completed in time, will try to re-acquire\n"); 891 /* How long we lock a shard depends on the number of 892 workers expected, and how long we usually took to 893 process a shard. */ 894 if (0 == max_workers) 895 delay = GNUNET_TIME_UNIT_ZERO; 896 else 897 delay.rel_value_us = GNUNET_CRYPTO_random_u64 ( 898 4 * GNUNET_TIME_relative_max ( 899 wirewatch_idle_sleep_interval, 900 GNUNET_TIME_relative_multiply (shard_delay, 901 max_workers)).rel_value_us); 902 shard_start_time = GNUNET_TIME_absolute_get (); 903 shard_lease = delay; 904 qs = TALER_EXCHANGEDB_begin_shard (pg, 905 job_name, 906 delay, 907 shard_size, 908 &shard_start, 909 &shard_end, 910 &progress_row); 911 switch (qs) 912 { 913 case GNUNET_DB_STATUS_HARD_ERROR: 914 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 915 "Failed to obtain starting point for monitoring from database!\n"); 916 global_ret = EXIT_FAILURE; 917 GNUNET_SCHEDULER_shutdown (); 918 return; 919 case GNUNET_DB_STATUS_SOFT_ERROR: 920 /* try again */ 921 { 922 struct GNUNET_TIME_Relative rdelay; 923 924 wirewatch_conflict_sleep_interval 925 = GNUNET_TIME_STD_BACKOFF (wirewatch_conflict_sleep_interval); 926 rdelay = GNUNET_TIME_randomize (wirewatch_conflict_sleep_interval); 927 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 928 "Serialization error tying to obtain shard %s, will try again in %s!\n", 929 job_name, 930 GNUNET_STRINGS_relative_time_to_string (rdelay, 931 true)); 932 #if 1 933 if (GNUNET_TIME_relative_cmp (rdelay, 934 >, 935 GNUNET_TIME_UNIT_SECONDS)) 936 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 937 "Delay would have been for %s\n", 938 GNUNET_TIME_relative2s (rdelay, 939 true)); 940 rdelay = GNUNET_TIME_relative_min (rdelay, 941 GNUNET_TIME_UNIT_SECONDS); 942 #endif 943 delayed_until = GNUNET_TIME_relative_to_absolute (rdelay); 944 } 945 GNUNET_assert (NULL == task); 946 schedule_transfers (); 947 return; 948 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 949 GNUNET_break (0); 950 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 951 "No shard available, will try again for %s in %s!\n", 952 job_name, 953 GNUNET_STRINGS_relative_time_to_string ( 954 wirewatch_idle_sleep_interval, 955 true)); 956 delayed_until = GNUNET_TIME_relative_to_absolute ( 957 wirewatch_idle_sleep_interval); 958 shard_open = false; 959 GNUNET_assert (NULL == task); 960 schedule_transfers (); 961 return; 962 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 963 /* continued below */ 964 wirewatch_conflict_sleep_interval = GNUNET_TIME_UNIT_ZERO; 965 break; 966 } 967 shard_end_time = GNUNET_TIME_relative_to_absolute (delay); 968 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 969 "Starting with shard %s at (%llu,%llu] locked for %s\n", 970 job_name, 971 (unsigned long long) shard_start, 972 (unsigned long long) shard_end, 973 GNUNET_STRINGS_relative_time_to_string (delay, 974 true)); 975 progress = false; 976 /* The shard itself says where to resume. Whether this is a shard we had 977 before, one abandoned by another worker, or a brand new one no longer 978 matters: everything below progress_row is imported and committed, and 979 nothing above it is. */ 980 if (progress_row != shard_start) 981 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 982 "Continuing from %llu\n", 983 (unsigned long long) progress_row); 984 latest_row_off = progress_row; 985 batch_start = progress_row; 986 shard_open = true; 987 task = GNUNET_SCHEDULER_add_now (&continue_with_shard, 988 NULL); 989 } 990 991 992 /** 993 * First task. 994 * 995 * @param cls closure, NULL 996 * @param args remaining command-line arguments 997 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 998 * @param c configuration 999 */ 1000 static void 1001 run (void *cls, 1002 char *const *args, 1003 const char *cfgfile, 1004 const struct GNUNET_CONFIGURATION_Handle *c) 1005 { 1006 (void) cls; 1007 (void) args; 1008 (void) cfgfile; 1009 1010 cfg = c; 1011 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1012 cls); 1013 if (GNUNET_OK != 1014 exchange_serve_process_config ()) 1015 { 1016 global_ret = EXIT_NOTCONFIGURED; 1017 GNUNET_SCHEDULER_shutdown (); 1018 return; 1019 } 1020 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1021 &rc); 1022 if (NULL == ctx) 1023 { 1024 GNUNET_break (0); 1025 GNUNET_SCHEDULER_shutdown (); 1026 global_ret = EXIT_NO_RESTART; 1027 return; 1028 } 1029 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1030 schedule_transfers (); 1031 } 1032 1033 1034 /** 1035 * The main function of taler-exchange-wirewatch 1036 * 1037 * @param argc number of arguments from the command line 1038 * @param argv command line arguments 1039 * @return 0 ok, non-zero on error 1040 */ 1041 int 1042 main (int argc, 1043 char *const *argv) 1044 { 1045 struct GNUNET_GETOPT_CommandLineOption options[] = { 1046 GNUNET_GETOPT_option_string ('a', 1047 "account", 1048 "SECTION_NAME", 1049 "name of the configuration section with the account we should watch (needed if more than one is enabled for crediting)", 1050 &account_section), 1051 GNUNET_GETOPT_option_flag ('e', 1052 "exit-on-error", 1053 "terminate wirewatch if we failed to download information from the bank", 1054 &exit_on_error), 1055 GNUNET_GETOPT_option_relative_time ('f', 1056 "longpoll-timeout", 1057 "DELAY", 1058 "what is the timeout when asking the bank about new transactions, specify with unit (e.g. --longpoll-timeout=30s)", 1059 &longpoll_timeout), 1060 GNUNET_GETOPT_option_flag ('I', 1061 "ignore-not-found", 1062 "continue, even if the bank account of the exchange was not found", 1063 &ignore_account_404), 1064 GNUNET_GETOPT_option_uint ('S', 1065 "size", 1066 "SIZE", 1067 "Size to process per shard (default: 1024)", 1068 &shard_size), 1069 GNUNET_GETOPT_option_timetravel ('T', 1070 "timetravel"), 1071 GNUNET_GETOPT_option_flag ('t', 1072 "test", 1073 "run in test mode and exit when idle", 1074 &test_mode), 1075 GNUNET_GETOPT_option_uint ('w', 1076 "workers", 1077 "COUNT", 1078 "Plan work load with up to COUNT worker processes (default: 16)", 1079 &max_workers), 1080 GNUNET_GETOPT_option_version (VERSION), 1081 GNUNET_GETOPT_OPTION_END 1082 }; 1083 enum GNUNET_GenericReturnValue ret; 1084 1085 longpoll_timeout = LONGPOLL_TIMEOUT; 1086 ret = GNUNET_PROGRAM_run ( 1087 TALER_EXCHANGE_project_data (), 1088 argc, argv, 1089 "taler-exchange-wirewatch", 1090 gettext_noop ( 1091 "background process that watches for incoming wire transfers from customers"), 1092 options, 1093 &run, NULL); 1094 if (GNUNET_SYSERR == ret) 1095 return EXIT_INVALIDARGUMENT; 1096 if (GNUNET_NO == ret) 1097 return EXIT_SUCCESS; 1098 return global_ret; 1099 } 1100 1101 1102 /* end of taler-exchange-wirewatch.c */