exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */