/*
This file is part of TALER
Copyright (C) 2016--2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file taler-exchange-wirewatch.c
* @brief Process that watches for wire transfers to the exchange's bank account
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include
#include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h"
#include "taler_json_lib.h"
#include "taler_bank_service.h"
/**
* How long to wait for an HTTP reply if there
* are no transactions pending at the server?
*/
#define LONGPOLL_TIMEOUT GNUNET_TIME_UNIT_MINUTES
/**
* What is the maximum batch size we use for credit history
* requests with the bank. See `batch_size` below.
*/
#define MAXIMUM_BATCH_SIZE 1024
/**
* Information about our account.
*/
static const struct TALER_EXCHANGEDB_AccountInfo *ai;
/**
* Active request for history.
*/
static struct TALER_BANK_CreditHistoryHandle *hh;
/**
* Set to true if the request for history did actually
* return transaction items.
*/
static bool hh_returned_data;
/**
* Set to true if the request for history did not
* succeed because the account was unknown.
*/
static bool hh_account_404;
/**
* When did we start the last @e hh request?
*/
static struct GNUNET_TIME_Absolute hh_start_time;
/**
* Until when is processing this wire plugin delayed?
*/
static struct GNUNET_TIME_Absolute delayed_until;
/**
* Encoded offset in the wire transfer list from where
* to start the next query with the bank.
*/
static uint64_t batch_start;
/**
* Latest row offset seen in this transaction, becomes
* the new #batch_start upon commit.
*/
static uint64_t latest_row_off;
/**
* Offset where our current shard begins (inclusive).
*/
static uint64_t shard_start;
/**
* Offset where our current shard ends (exclusive).
*/
static uint64_t shard_end;
/**
* When did we start with the shard?
*/
static struct GNUNET_TIME_Absolute shard_start_time;
/**
* For how long did we lock the shard?
*/
static struct GNUNET_TIME_Absolute shard_end_time;
/**
* How long did we take to finish the last shard
* for this account?
*/
static struct GNUNET_TIME_Relative shard_delay;
/**
* How long did we take to finish the last shard
* for this account?
*/
static struct GNUNET_TIME_Relative longpoll_timeout;
/**
* Name of our job in the shard table.
*/
static char *job_name;
/**
* How many transactions do we retrieve per batch?
*/
static unsigned int batch_size;
/**
* How much do we increment @e batch_size on success?
*/
static unsigned int batch_thresh;
/**
* Did work remain in the transaction queue? Set to true
* if we did some work and thus there might be more.
*/
static bool progress;
/**
* Did we start a transaction yet?
*/
static bool started_transaction;
/**
* Is this shard still open for processing.
*/
static bool shard_open;
/**
* Handle to the context for interacting with the bank.
*/
static struct GNUNET_CURL_Context *ctx;
/**
* Scheduler context for running the @e ctx.
*/
static struct GNUNET_CURL_RescheduleContext *rc;
/**
* The exchange's configuration (global)
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Our DB plugin.
*/
static struct TALER_EXCHANGEDB_Plugin *db_plugin;
/**
* How long should we sleep when idle before trying to find more work?
* Also used for how long we wait to grab a shard before trying it again.
* The value should be set to a bit above the average time it takes to
* process a shard.
*/
static struct GNUNET_TIME_Relative wirewatch_idle_sleep_interval;
/**
* How long do we sleep on serialization conflicts?
*/
static struct GNUNET_TIME_Relative wirewatch_conflict_sleep_interval;
/**
* Modulus to apply to group shards. The shard size must ultimately be a
* multiple of the batch size. Thus, if this is not a multiple of the
* #MAXIMUM_BATCH_SIZE, the batch size will be set to the #shard_size.
*/
static unsigned int shard_size = MAXIMUM_BATCH_SIZE;
/**
* How many workers should we plan our scheduling with?
*/
static unsigned int max_workers = 16;
/**
* -e command-line option: exit on errors talking to the bank?
*/
static int exit_on_error;
/**
* Value to return from main(). 0 on success, non-zero on
* on serious errors.
*/
static int global_ret;
/**
* Are we run in testing mode and should only do one pass?
*/
static int test_mode;
/**
* Should we ignore if the bank does not know our bank
* account?
*/
static int ignore_account_404;
/**
* Current task waiting for execution, if any.
*/
static struct GNUNET_SCHEDULER_Task *task;
/**
* Name of the configuration section with the account we should watch.
*/
static char *account_section;
/**
* We're being aborted with CTRL-C (or SIGTERM). Shut down.
*
* @param cls closure
*/
static void
shutdown_task (void *cls)
{
enum GNUNET_DB_QueryStatus qs;
(void) cls;
if (NULL != hh)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"History request cancelled on shutdown\n");
TALER_BANK_credit_history_cancel (hh);
hh = NULL;
}
if (started_transaction)
{
db_plugin->rollback (db_plugin->cls);
started_transaction = false;
}
if (shard_open)
{
qs = db_plugin->abort_shard (db_plugin->cls,
job_name,
shard_start,
shard_end);
if (qs <= 0)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to abort work shard on shutdown\n");
}
GNUNET_free (job_name);
if (NULL != ctx)
{
GNUNET_CURL_fini (ctx);
ctx = NULL;
}
if (NULL != rc)
{
GNUNET_CURL_gnunet_rc_destroy (rc);
rc = NULL;
}
if (NULL != task)
{
GNUNET_SCHEDULER_cancel (task);
task = NULL;
}
TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL;
TALER_EXCHANGEDB_unload_accounts ();
cfg = NULL;
}
/**
* Function called with information about a wire account. Adds the
* account to our list (if it is enabled and we can load the plugin).
*
* @param cls closure, NULL
* @param in_ai account information
*/
static void
add_account_cb (void *cls,
const struct TALER_EXCHANGEDB_AccountInfo *in_ai)
{
(void) cls;
if (! in_ai->credit_enabled)
return; /* not enabled for us, skip */
if ( (NULL != account_section) &&
(0 != strcasecmp (in_ai->section_name,
account_section)) )
return; /* not enabled for us, skip */
if (NULL != ai)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Multiple accounts enabled (%s and %s), use '-a' command-line option to select one!\n",
ai->section_name,
in_ai->section_name);
GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
ai = in_ai;
GNUNET_asprintf (&job_name,
"wirewatch-%s",
ai->section_name);
batch_size = MAXIMUM_BATCH_SIZE;
if (0 != shard_size % batch_size)
batch_size = shard_size;
}
/**
* Parse configuration parameters for the exchange server into the
* corresponding global variables.
*
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
exchange_serve_process_config (void)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"exchange",
"WIREWATCH_IDLE_SLEEP_INTERVAL",
&wirewatch_idle_sleep_interval))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
"WIREWATCH_IDLE_SLEEP_INTERVAL");
return GNUNET_SYSERR;
}
if (NULL ==
(db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize DB subsystem\n");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_EXCHANGEDB_load_accounts (cfg,
TALER_EXCHANGEDB_ALO_CREDIT
| TALER_EXCHANGEDB_ALO_AUTHDATA))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for credit!\n");
return GNUNET_SYSERR;
}
TALER_EXCHANGEDB_find_accounts (&add_account_cb,
NULL);
if (NULL == ai)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No accounts enabled for credit!\n");
GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Lock a shard and then begin to query for incoming wire transfers.
*
* @param cls NULL
*/
static void
lock_shard (void *cls);
/**
* Continue with the credit history of the shard.
*
* @param cls NULL
*/
static void
continue_with_shard (void *cls);
/**
* We encountered a serialization error. Rollback the transaction and try
* again.
*/
static void
handle_soft_error (void)
{
db_plugin->rollback (db_plugin->cls);
started_transaction = false;
if (1 < batch_size)
{
batch_thresh = batch_size;
batch_size /= 2;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Reduced batch size to %llu due to serialization issue\n",
(unsigned long long) batch_size);
}
/* Reset to beginning of transaction, and go again
from there. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Encountered soft error, resetting start point to batch start\n");
latest_row_off = batch_start;
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
}
/**
* Schedule the #lock_shard() operation.
*/
static void
schedule_transfers (void)
{
if (shard_open)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Will retry my shard (%llu,%llu] of %s in %s\n",
(unsigned long long) shard_start,
(unsigned long long) shard_end,
job_name,
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_remaining (delayed_until),
true));
else
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Will try to lock next shard of %s in %s\n",
job_name,
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_remaining (delayed_until),
true));
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_at (delayed_until,
&lock_shard,
NULL);
}
/**
* We are done with the work that is possible right now (and the transaction
* was committed, if there was one to commit). Move on to the next shard.
*/
static void
transaction_completed (void)
{
if ( (batch_start + batch_size ==
latest_row_off) &&
(batch_size < MAXIMUM_BATCH_SIZE) )
{
/* The current batch size worked without serialization
issues, and we are allowed to grow. Do so slowly. */
int delta;
delta = ((int) batch_thresh - (int) batch_size) / 4;
if (delta < 0)
delta = -delta;
batch_size = GNUNET_MIN (MAXIMUM_BATCH_SIZE,
batch_size + delta + 1);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Increasing batch size to %llu\n",
(unsigned long long) batch_size);
}
if ( (! progress) && test_mode)
{
/* Transaction list was drained and we are in
test mode. So we are done. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Transaction list drained and in test mode. Exiting\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (! (hh_returned_data || hh_account_404) )
{
/* Enforce long-polling delay even if the server ignored it
and returned earlier */
struct GNUNET_TIME_Relative latency;
struct GNUNET_TIME_Relative left;
latency = GNUNET_TIME_absolute_get_duration (hh_start_time);
left = GNUNET_TIME_relative_subtract (longpoll_timeout,
latency);
if (! (test_mode ||
GNUNET_TIME_relative_is_zero (left)) )
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, // WARNING,
"Server did not respect long-polling, enforcing client-side by sleeping for %s\n",
GNUNET_TIME_relative2s (left,
true));
delayed_until = GNUNET_TIME_relative_to_absolute (left);
}
if (hh_account_404)
delayed_until = GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_MILLISECONDS);
if (test_mode)
delayed_until = GNUNET_TIME_UNIT_ZERO_ABS;
GNUNET_assert (NULL == task);
schedule_transfers ();
}
/**
* We got incoming transaction details from the bank. Add them
* to the database.
*
* @param details array of transaction details
* @param details_length length of the @a details array
*/
static void
process_reply (const struct TALER_BANK_CreditDetails *details,
unsigned int details_length)
{
enum GNUNET_DB_QueryStatus qs;
bool shard_done;
uint64_t lroff = latest_row_off;
if (0 == details_length)
{
/* Server should have used 204, not 200! */
GNUNET_break_op (0);
transaction_completed ();
return;
}
hh_returned_data = true;
/* check serial IDs for range constraints */
for (unsigned int i = 0; iserial_id < lroff)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Serial ID %llu not monotonic (got %llu before). Failing!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) lroff);
db_plugin->rollback (db_plugin->cls);
GNUNET_SCHEDULER_shutdown ();
return;
}
if (cd->serial_id > shard_end)
{
/* we are *past* the current shard (likely because the serial_id of the
shard_end happens to not exist in the DB). So commit and stop this
iteration! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serial ID %llu past shard end at %llu, ending iteration early!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) shard_end);
details_length = i;
progress = true;
lroff = cd->serial_id - 1;
break;
}
lroff = cd->serial_id;
}
if (0 != details_length)
{
enum GNUNET_DB_QueryStatus qss[details_length];
struct TALER_EXCHANGEDB_ReserveInInfo reserves[details_length];
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Importing %u transactions\n",
details_length);
for (unsigned int i = 0; ireserve_pub = &cd->reserve_pub;
res->balance = &cd->amount;
res->execution_time = cd->execution_date;
res->sender_account_details = cd->debit_account_uri;
res->exchange_account_name = ai->section_name;
res->wire_reference = cd->serial_id;
}
qs = db_plugin->reserves_in_insert (db_plugin->cls,
reserves,
details_length,
qss);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for reserves_in_insert (%u). Rolling back.\n",
details_length);
handle_soft_error ();
return;
default:
break;
}
for (unsigned int i = 0; iserial_id,
job_name);
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Imported transaction %llu.",
(unsigned long long) cd->serial_id);
/* normal case */
progress = true;
break;
}
}
}
latest_row_off = lroff;
shard_done = (shard_end <= latest_row_off);
if (shard_done)
{
/* shard is complete, mark this as well */
qs = db_plugin->complete_shard (db_plugin->cls,
job_name,
shard_start,
shard_end);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for complete_shard. Rolling back.\n");
handle_soft_error ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break (0);
/* Not expected, but let's just continue */
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* normal case */
progress = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Completed shard %s (%llu,%llu] after %s\n",
job_name,
(unsigned long long) shard_start,
(unsigned long long) shard_end,
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_duration (shard_start_time),
true));
break;
}
shard_delay = GNUNET_TIME_absolute_get_duration (shard_start_time);
shard_open = false;
transaction_completed ();
return;
}
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
}
/**
* Callbacks of this type are used to serve the result of asking
* the bank for the transaction history.
*
* @param cls NULL
* @param reply response we got from the bank
*/
static void
history_cb (void *cls,
const struct TALER_BANK_CreditHistoryResponse *reply)
{
(void) cls;
GNUNET_assert (NULL == task);
hh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"History request returned with HTTP status %u\n",
reply->http_status);
switch (reply->http_status)
{
case MHD_HTTP_OK:
process_reply (reply->details.ok.details,
reply->details.ok.details_length);
return;
case MHD_HTTP_NO_CONTENT:
transaction_completed ();
return;
case MHD_HTTP_NOT_FOUND:
hh_account_404 = true;
if (ignore_account_404)
{
transaction_completed ();
return;
}
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Error fetching history: %s (%u)\n",
TALER_ErrorCode_get_hint (reply->ec),
reply->http_status);
break;
}
if (! exit_on_error)
{
transaction_completed ();
return;
}
GNUNET_SCHEDULER_shutdown ();
}
static void
continue_with_shard (void *cls)
{
unsigned int limit;
(void) cls;
task = NULL;
GNUNET_assert (shard_end > latest_row_off);
limit = GNUNET_MIN (batch_size,
shard_end - latest_row_off);
GNUNET_assert (NULL == hh);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting credit history starting from %llu\n",
(unsigned long long) latest_row_off);
hh_start_time = GNUNET_TIME_absolute_get ();
hh_returned_data = false;
hh_account_404 = false;
hh = TALER_BANK_credit_history (ctx,
ai->auth,
latest_row_off,
limit,
test_mode
? GNUNET_TIME_UNIT_ZERO
: longpoll_timeout,
&history_cb,
NULL);
if (NULL == hh)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start request for account history!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
}
/**
* Reserve a shard for us to work on.
*
* @param cls NULL
*/
static void
lock_shard (void *cls)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Relative delay;
uint64_t last_shard_start = shard_start;
uint64_t last_shard_end = shard_end;
(void) cls;
task = NULL;
if (GNUNET_SYSERR ==
db_plugin->preflight (db_plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain database connection!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
if ( (shard_open) &&
(GNUNET_TIME_absolute_is_future (shard_end_time)) )
{
progress = false;
batch_start = latest_row_off;
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
return;
}
if (shard_open)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Shard not completed in time, will try to re-acquire\n");
/* How long we lock a shard depends on the number of
workers expected, and how long we usually took to
process a shard. */
if (0 == max_workers)
delay = GNUNET_TIME_UNIT_ZERO;
else
delay.rel_value_us = GNUNET_CRYPTO_random_u64 (
GNUNET_CRYPTO_QUALITY_WEAK,
4 * GNUNET_TIME_relative_max (
wirewatch_idle_sleep_interval,
GNUNET_TIME_relative_multiply (shard_delay,
max_workers)).rel_value_us);
shard_start_time = GNUNET_TIME_absolute_get ();
qs = db_plugin->begin_shard (db_plugin->cls,
job_name,
delay,
shard_size,
&shard_start,
&shard_end);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain starting point for montoring from database!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
/* try again */
{
struct GNUNET_TIME_Relative rdelay;
wirewatch_conflict_sleep_interval
= GNUNET_TIME_STD_BACKOFF (wirewatch_conflict_sleep_interval);
rdelay = GNUNET_TIME_randomize (wirewatch_conflict_sleep_interval);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Serialization error tying to obtain shard %s, will try again in %s!\n",
job_name,
GNUNET_STRINGS_relative_time_to_string (rdelay,
true));
#if 1
if (GNUNET_TIME_relative_cmp (rdelay,
>,
GNUNET_TIME_UNIT_SECONDS))
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Delay would have been for %s\n",
GNUNET_TIME_relative2s (rdelay,
true));
rdelay = GNUNET_TIME_relative_min (rdelay,
GNUNET_TIME_UNIT_SECONDS);
#endif
delayed_until = GNUNET_TIME_relative_to_absolute (rdelay);
}
GNUNET_assert (NULL == task);
schedule_transfers ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No shard available, will try again for %s in %s!\n",
job_name,
GNUNET_STRINGS_relative_time_to_string (
wirewatch_idle_sleep_interval,
true));
delayed_until = GNUNET_TIME_relative_to_absolute (
wirewatch_idle_sleep_interval);
shard_open = false;
GNUNET_assert (NULL == task);
schedule_transfers ();
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* continued below */
wirewatch_conflict_sleep_interval = GNUNET_TIME_UNIT_ZERO;
break;
}
shard_end_time = GNUNET_TIME_relative_to_absolute (delay);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting with shard %s at (%llu,%llu] locked for %s\n",
job_name,
(unsigned long long) shard_start,
(unsigned long long) shard_end,
GNUNET_STRINGS_relative_time_to_string (delay,
true));
progress = false;
batch_start = shard_start;
if ( (shard_open) &&
(shard_start == last_shard_start) &&
(shard_end == last_shard_end) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Continuing from %llu\n",
(unsigned long long) latest_row_off);
GNUNET_break (latest_row_off >= batch_start); /* resume where we left things */
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resetting shard start to original start point (%d)\n",
shard_open ? 1 : 0);
latest_row_off = batch_start;
}
shard_open = true;
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
}
/**
* First task.
*
* @param cls closure, NULL
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param c configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
(void) cls;
(void) args;
(void) cfgfile;
cfg = c;
GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
cls);
if (GNUNET_OK !=
exchange_serve_process_config ())
{
global_ret = EXIT_NOTCONFIGURED;
GNUNET_SCHEDULER_shutdown ();
return;
}
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
&rc);
if (NULL == ctx)
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
}
rc = GNUNET_CURL_gnunet_rc_create (ctx);
schedule_transfers ();
}
/**
* The main function of taler-exchange-wirewatch
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, non-zero on error
*/
int
main (int argc,
char *const *argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_string ('a',
"account",
"SECTION_NAME",
"name of the configuration section with the account we should watch (needed if more than one is enabled for crediting)",
&account_section),
GNUNET_GETOPT_option_flag ('e',
"exit-on-error",
"terminate wirewatch if we failed to download information from the bank",
&exit_on_error),
GNUNET_GETOPT_option_relative_time ('f',
"longpoll-timeout",
"DELAY",
"what is the timeout when asking the bank about new transactions, specify with unit (e.g. --longpoll-timeout=30s)",
&longpoll_timeout),
GNUNET_GETOPT_option_flag ('I',
"ignore-not-found",
"continue, even if the bank account of the exchange was not found",
&ignore_account_404),
GNUNET_GETOPT_option_uint ('S',
"size",
"SIZE",
"Size to process per shard (default: 1024)",
&shard_size),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_option_flag ('t',
"test",
"run in test mode and exit when idle",
&test_mode),
GNUNET_GETOPT_option_uint ('w',
"workers",
"COUNT",
"Plan work load with up to COUNT worker processes (default: 16)",
&max_workers),
GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
enum GNUNET_GenericReturnValue ret;
longpoll_timeout = LONGPOLL_TIMEOUT;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
return EXIT_INVALIDARGUMENT;
TALER_OS_init ();
ret = GNUNET_PROGRAM_run (
argc, argv,
"taler-exchange-wirewatch",
gettext_noop (
"background process that watches for incoming wire transfers from customers"),
options,
&run, NULL);
GNUNET_free_nz ((void *) argv);
if (GNUNET_SYSERR == ret)
return EXIT_INVALIDARGUMENT;
if (GNUNET_NO == ret)
return EXIT_SUCCESS;
return global_ret;
}
/* end of taler-exchange-wirewatch.c */