summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-depositcheck.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-depositcheck.c')
-rw-r--r--src/backend/taler-merchant-depositcheck.c1071
1 files changed, 1071 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c
new file mode 100644
index 00000000..9245e1fb
--- /dev/null
+++ b/src/backend/taler-merchant-depositcheck.c
@@ -0,0 +1,1071 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-depositcheck.c
+ * @brief Process that inquires with the exchange for deposits that should have been wired
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_plugin.h"
+#include <taler/taler_dbevents.h>
+
+/**
+ * How many requests do we make at most in parallel to the same exchange?
+ */
+#define CONCURRENCY_LIMIT 32
+
+/**
+ * How long do we not try a deposit check if the deposit
+ * was put on hold due to a KYC/AML block?
+ */
+#define KYC_RETRY_DELAY GNUNET_TIME_UNIT_HOURS
+
+/**
+ * Information we keep per exchange.
+ */
+struct Child
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Child *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Child *prev;
+
+ /**
+ * The child process.
+ */
+ struct GNUNET_OS_Process *process;
+
+ /**
+ * Wait handle.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Which exchange is this state for?
+ */
+ char *base_url;
+
+ /**
+ * Task to restart the child.
+ */
+ struct GNUNET_SCHEDULER_Task *rt;
+
+ /**
+ * When should the child be restarted at the earliest?
+ */
+ struct GNUNET_TIME_Absolute next_start;
+
+ /**
+ * Current minimum delay between restarts, grows
+ * exponentially if child exits before this time.
+ */
+ struct GNUNET_TIME_Relative rd;
+
+};
+
+
+/**
+ * Information we keep per exchange interaction.
+ */
+struct ExchangeInteraction
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeInteraction *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeInteraction *prev;
+
+ /**
+ * Handle for exchange interaction.
+ */
+ struct TALER_EXCHANGE_DepositGetHandle *dgh;
+
+ /**
+ * Wire deadline for the deposit.
+ */
+ struct GNUNET_TIME_Absolute wire_deadline;
+
+ /**
+ * Current value for the retry backoff
+ */
+ struct GNUNET_TIME_Relative retry_backoff;
+
+ /**
+ * Target account hash of the deposit.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Deposited amount.
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Deposit fee paid.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Public key of the deposited coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Hash over the @e contract_terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merchant instance's private key.
+ */
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+
+ /**
+ * Serial number of the row in the deposits table
+ * that we are processing.
+ */
+ uint64_t deposit_serial;
+
+ /**
+ * The instance the deposit belongs to.
+ */
+ char *instance_id;
+
+};
+
+
+/**
+ * Head of list of children we forked.
+ */
+static struct Child *c_head;
+
+/**
+ * Tail of list of children we forked.
+ */
+static struct Child *c_tail;
+
+/**
+ * Key material of the exchange.
+ */
+static struct TALER_EXCHANGE_Keys *keys;
+
+/**
+ * Handle for active /keys request.
+ */
+static struct TALER_EXCHANGE_GetKeysHandle *gkh;
+
+/**
+ * Head of list of active exchange interactions.
+ */
+static struct ExchangeInteraction *w_head;
+
+/**
+ * Tail of list of active exchange interactions.
+ */
+static struct ExchangeInteraction *w_tail;
+
+/**
+ * Number of active entries in the @e w_head list.
+ */
+static uint64_t w_count;
+
+/**
+ * Notification handler from database on new work.
+ */
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Name of the configuration file we use.
+ */
+static char *cfg_filename;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Next wire deadline that @e task is scheduled for.
+ */
+static struct GNUNET_TIME_Absolute next_deadline;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * Handle to the context for interacting with the exchange.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Which exchange are we monitoring? NULL if we
+ * are the parent of the workers.
+ */
+static char *exchange_url;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ struct Child *c;
+ struct ExchangeInteraction *w;
+
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ if (NULL != gkh)
+ {
+ TALER_EXCHANGE_get_keys_cancel (gkh);
+ gkh = NULL;
+ }
+ while (NULL != (w = w_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ if (NULL != w->dgh)
+ {
+ TALER_EXCHANGE_deposits_get_cancel (w->dgh);
+ w->dgh = NULL;
+ }
+ w_count--;
+ GNUNET_free (w->instance_id);
+ GNUNET_free (w);
+ }
+ while (NULL != (c = c_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (c_head,
+ c_tail,
+ c);
+ if (NULL != c->rt)
+ {
+ GNUNET_SCHEDULER_cancel (c->rt);
+ c->rt = NULL;
+ }
+ if (NULL != c->cwh)
+ {
+ GNUNET_wait_child_cancel (c->cwh);
+ c->cwh = NULL;
+ }
+ if (NULL != c->process)
+ {
+ enum GNUNET_OS_ProcessStatusType type
+ = GNUNET_OS_PROCESS_UNKNOWN;
+ unsigned long code = 0;
+
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (c->process,
+ SIGTERM));
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait_status (c->process,
+ &type,
+ &code));
+ if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != code) )
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Process for exchange %s had trouble (%d/%d)\n",
+ c->base_url,
+ (int) type,
+ (int) code);
+ GNUNET_OS_process_destroy (c->process);
+ }
+ GNUNET_free (c->base_url);
+ GNUNET_free (c);
+ }
+ if (NULL != db_plugin)
+ {
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ TALER_MERCHANTDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ }
+ cfg = NULL;
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * Task to get more deposits to work on from the database.
+ *
+ * @param cls NULL
+ */
+static void
+select_work (void *cls);
+
+
+/**
+ * Make sure to run the select_work() task at
+ * the @a next_deadline.
+ *
+ * @param deadline time when work becomes ready
+ */
+static void
+run_at (struct GNUNET_TIME_Absolute deadline)
+{
+ if ( (NULL != task) &&
+ (GNUNET_TIME_absolute_cmp (deadline,
+ >,
+ next_deadline)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not scheduling for %s yet, already have earlier task pending\n",
+ GNUNET_TIME_absolute2s (deadline));
+ return;
+ }
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not scheduling for %s yet, no /keys available\n",
+ GNUNET_TIME_absolute2s (deadline));
+ return; /* too early */
+ }
+ next_deadline = deadline;
+ if (NULL != task)
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_at (deadline,
+ &select_work,
+ NULL);
+}
+
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure with a `struct ExchangeInteraction *`
+ * @param dr HTTP response data
+ */
+static void
+deposit_get_cb (void *cls,
+ const struct TALER_EXCHANGE_GetDepositResponse *dr)
+{
+ struct ExchangeInteraction *w = cls;
+ struct GNUNET_TIME_Absolute future_retry;
+
+ future_retry
+ = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
+ switch (dr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange returned wire transfer over %s for deposited coin %s\n",
+ TALER_amount2s (&dr->details.ok.coin_contribution),
+ TALER_B2S (&w->coin_pub));
+ qs = db_plugin->insert_deposit_to_transfer (db_plugin->cls,
+ w->deposit_serial,
+ &dr->details.ok);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ false, /* we are done, wire_pending is now false */
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ NULL);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ break;
+ }
+ case MHD_HTTP_ACCEPTED:
+ {
+ /* got a 'preliminary' reply from the exchange,
+ remember our target UUID */
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp now;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange returned KYC requirement (%d/%d) for deposited coin %s\n",
+ dr->details.accepted.kyc_ok,
+ dr->details.accepted.aml_decision,
+ TALER_B2S (&w->coin_pub));
+ now = GNUNET_TIME_timestamp_get ();
+ qs = db_plugin->account_kyc_set_status (
+ db_plugin->cls,
+ w->instance_id,
+ &w->h_wire,
+ exchange_url,
+ dr->details.accepted.requirement_row,
+ NULL,
+ NULL,
+ now,
+ dr->details.accepted.kyc_ok,
+ dr->details.accepted.aml_decision);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (dr->details.accepted.kyc_ok &&
+ (TALER_AML_NORMAL ==
+ dr->details.accepted.aml_decision))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
+ GNUNET_TIME_absolute2s (future_retry));
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ true, /* wire_pending is still true! */
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ "Exchange reported 202 Accepted but no KYC block");
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ else
+ {
+ future_retry
+ = GNUNET_TIME_absolute_max (
+ future_retry,
+ GNUNET_TIME_relative_to_absolute (
+ KYC_RETRY_DELAY));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
+ GNUNET_TIME_absolute2s (future_retry));
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ true,
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ "Exchange reported 202 Accepted due to KYC/AML block");
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ char *msg;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s returned tracking failure for deposited coin %s\n",
+ exchange_url,
+ TALER_B2S (&w->coin_pub));
+ GNUNET_asprintf (&msg,
+ "Unexpected exchange status %u (#%d, %s)\n",
+ dr->hr.http_status,
+ (int) dr->hr.ec,
+ dr->hr.hint);
+ qs = db_plugin->update_deposit_confirmation_status (
+ db_plugin->cls,
+ w->deposit_serial,
+ true, /* this failed, wire_pending remains true */
+ GNUNET_TIME_absolute_to_timestamp (future_retry),
+ w->retry_backoff,
+ msg);
+ GNUNET_free (msg);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ return;
+ }
+ } /* end switch */
+
+ GNUNET_CONTAINER_DLL_remove (w_head,
+ w_tail,
+ w);
+ w_count--;
+ GNUNET_free (w->instance_id);
+ GNUNET_free (w);
+ GNUNET_assert (NULL != keys);
+ if ( (w_count < CONCURRENCY_LIMIT / 2) ||
+ (0 == w_count) )
+ {
+ if (NULL != task)
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_now (&select_work,
+ NULL);
+ }
+}
+
+
+/**
+ * Typically called by `select_work`.
+ *
+ * @param cls NULL
+ * @param deposit_serial identifies the deposit operation
+ * @param wire_deadline when is the wire due
+ * @param retry_backoff current value for the retry backoff
+ * @param h_contract_terms hash of the contract terms
+ * @param merchant_priv private key of the merchant
+ * @param instance_id row ID of the instance
+ * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
+ * @param coin_pub public key of the deposited coin
+ */
+static void
+pending_deposits_cb (
+ void *cls,
+ uint64_t deposit_serial,
+ struct GNUNET_TIME_Absolute wire_deadline,
+ struct GNUNET_TIME_Relative retry_backoff,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const char *instance_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct ExchangeInteraction *w = GNUNET_new (struct ExchangeInteraction);
+
+ (void) cls;
+ if (GNUNET_TIME_absolute_is_future (wire_deadline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Pending deposit has deadline in the future at %s\n",
+ GNUNET_TIME_absolute2s (wire_deadline));
+ run_at (wire_deadline);
+ return;
+ }
+ w->deposit_serial = deposit_serial;
+ w->wire_deadline = wire_deadline;
+ w->retry_backoff = GNUNET_TIME_STD_BACKOFF (retry_backoff);
+ w->h_contract_terms = *h_contract_terms;
+ w->merchant_priv = *merchant_priv;
+ w->h_wire = *h_wire;
+ w->amount_with_fee = *amount_with_fee;
+ w->deposit_fee = *deposit_fee;
+ w->coin_pub = *coin_pub;
+ w->instance_id = GNUNET_strdup (instance_id);
+ GNUNET_CONTAINER_DLL_insert (w_head,
+ w_tail,
+ w);
+ w_count++;
+ GNUNET_assert (NULL != keys);
+ if (GNUNET_TIME_absolute_is_past (keys->key_data_expiration.abs_time))
+ {
+ /* Parent should re-start us, then we will re-fetch /keys */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "/keys expired, shutting down\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == w->dgh);
+ w->dgh = TALER_EXCHANGE_deposits_get (
+ ctx,
+ exchange_url,
+ keys,
+ &w->merchant_priv,
+ &w->h_wire,
+ &w->h_contract_terms,
+ &w->coin_pub,
+ GNUNET_TIME_UNIT_ZERO,
+ &deposit_get_cb,
+ w);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ *
+ * @param cls closure, NULL
+ * @param extra additional event data provided, timestamp with wire deadline
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_notify (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct GNUNET_TIME_Absolute deadline;
+ struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
+
+ (void) cls;
+ if (sizeof (nbo_deadline) != extra_size)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (0 != w_count)
+ return; /* already at work! */
+ memcpy (&nbo_deadline,
+ extra,
+ extra_size);
+ deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
+ run_at (deadline);
+}
+
+
+static void
+select_work (void *cls)
+{
+ bool retry = false;
+ uint64_t limit = CONCURRENCY_LIMIT - w_count;
+
+ (void) cls;
+ task = NULL;
+ GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
+ GNUNET_assert (NULL != keys);
+ if (0 == limit)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_past (keys->key_data_expiration.abs_time))
+ {
+ /* Parent should re-start us, then we will re-fetch /keys */
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ while (1)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ db_plugin->preflight (db_plugin->cls);
+ if (retry)
+ limit = 1;
+ qs = db_plugin->lookup_pending_deposits (db_plugin->cls,
+ exchange_url,
+ limit,
+ retry,
+ &pending_deposits_cb,
+ NULL);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Looking up pending deposits query status was %d\n",
+ (int) qs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Transaction failed!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (test_mode)
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (retry)
+ return; /* nothing left */
+ retry = true;
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ default:
+ /* wait for async completion, then select more work. */
+ return;
+ }
+ }
+}
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ * The ownership over the @a keys object is passed to
+ * the callee, thus it is given explicitly and not
+ * (only) via @a kr.
+ *
+ * @param cls closure, NULL
+ * @param kr response from /keys
+ * @param[in] in_keys keys object passed to callback with
+ * reference counter of 1. Must be freed by callee
+ * using TALER_EXCHANGE_keys_decref(). NULL on failure.
+ */
+static void
+keys_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *in_keys)
+{
+ gkh = NULL;
+ if (NULL == in_keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to download %skeys\n",
+ exchange_url);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ keys = TALER_EXCHANGE_keys_incref (in_keys);
+ if (NULL != task)
+ GNUNET_SCHEDULER_cancel (task);
+ task = GNUNET_SCHEDULER_add_now (&select_work,
+ NULL);
+}
+
+
+/**
+ * Start a copy of this process with the exchange URL
+ * set to the given @a base_url
+ *
+ * @param base_url base URL to run with
+ */
+static struct GNUNET_OS_Process *
+start_worker (const char *base_url)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Launching worker for exchange `%s' using `%s`\n",
+ base_url,
+ cfg_filename);
+ return GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL,
+ NULL,
+ NULL,
+ "taler-merchant-depositcheck",
+ "taler-merchant-depositcheck",
+ "-c", cfg_filename,
+ "-e", base_url,
+ "-L", "INFO",
+ test_mode ? "-t" : NULL,
+ NULL);
+}
+
+
+/**
+ * Restart worker process for the given child.
+ *
+ * @param cls a `struct Child *` that needs a worker.
+ */
+static void
+restart_child (void *cls);
+
+
+/**
+ * Function called upon death or completion of a child process.
+ *
+ * @param cls a `struct Child *`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+child_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct Child *c = cls;
+
+ c->cwh = NULL;
+ if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != exit_code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Process for exchange %s had trouble (%d/%d)\n",
+ c->base_url,
+ (int) type,
+ (int) exit_code);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTINSTALLED;
+ return;
+ }
+ GNUNET_OS_process_destroy (c->process);
+ c->process = NULL;
+ if (test_mode &&
+ (! GNUNET_TIME_relative_is_zero (c->rd)) )
+ {
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_future (c->next_start))
+ c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
+ else
+ c->rd = GNUNET_TIME_UNIT_SECONDS;
+ c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
+ &restart_child,
+ c);
+}
+
+
+static void
+restart_child (void *cls)
+{
+ struct Child *c = cls;
+
+ c->rt = NULL;
+ c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
+ c->process = start_worker (c->base_url);
+ if (NULL == c->process)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "exec");
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ c->cwh = GNUNET_wait_child (c->process,
+ &child_done_cb,
+ c);
+}
+
+
+/**
+ * Function to iterate over section.
+ *
+ * @param cls closure
+ * @param section name of the section
+ */
+static void
+cfg_iter_cb (void *cls,
+ const char *section)
+{
+ char *base_url;
+ struct Child *c;
+
+ if (0 !=
+ strncasecmp (section,
+ "merchant-exchange-",
+ strlen ("merchant-exchange-")))
+ return;
+ if (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno (cfg,
+ section,
+ "DISABLED"))
+ return;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "EXCHANGE_BASE_URL",
+ &base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ section,
+ "EXCHANGE_BASE_URL");
+ return;
+ }
+ c = GNUNET_new (struct Child);
+ c->rd = GNUNET_TIME_UNIT_SECONDS;
+ c->base_url = base_url;
+ GNUNET_CONTAINER_DLL_insert (c_head,
+ c_tail,
+ c);
+ c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
+ c);
+}
+
+
+/**
+ * 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) args;
+
+ cfg = c;
+ cfg_filename = GNUNET_strdup (cfgfile);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running with configuration %s\n",
+ cfgfile);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+ if (NULL == exchange_url)
+ {
+ GNUNET_CONFIGURATION_iterate_sections (c,
+ &cfg_iter_cb,
+ NULL);
+ if (NULL == c_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No exchanges found in configuration\n");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ return;
+ }
+
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ if (NULL ==
+ (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->connect (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
+ return;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &db_notify,
+ NULL);
+ }
+ gkh = TALER_EXCHANGE_get_keys (ctx,
+ exchange_url,
+ NULL,
+ &keys_cb,
+ NULL);
+}
+
+
+/**
+ * The main function of the taler-merchant-depositcheck
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string ('e',
+ "exchange",
+ "BASE_URL",
+ "limit us to checking deposits of this exchange",
+ &exchange_url),
+ 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_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ 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-merchant-depositcheck",
+ gettext_noop (
+ "background process that checks with the exchange on deposits that are past the wire deadline"),
+ 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-merchant-depositcheck.c */