diff --git a/src/exchange/.gitignore b/src/exchange/.gitignore
index b0fe0a0f6..09cf60a8a 100644
--- a/src/exchange/.gitignore
+++ b/src/exchange/.gitignore
@@ -7,3 +7,4 @@ taler-exchange-httpd
taler-exchange-wirewatch
test_taler_exchange_wirewatch-postgres
test_taler_exchange_httpd_home/.config/taler/account-1.json
+taler-exchange-closer
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index 61d3341cc..227224d30 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -18,6 +18,7 @@ pkgcfg_DATA = \
bin_PROGRAMS = \
taler-exchange-aggregator \
+ taler-exchange-closer \
taler-exchange-httpd \
taler-exchange-wirewatch
@@ -33,6 +34,19 @@ taler_exchange_aggregator_LDADD = \
-lgnunetcurl \
-lgnunetutil
+
+taler_exchange_closer_SOURCES = \
+ taler-exchange-closer.c
+taler_exchange_closer_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ -ljansson \
+ -lgnunetcurl \
+ -lgnunetutil
+
taler_exchange_wirewatch_SOURCES = \
taler-exchange-wirewatch.c
taler_exchange_wirewatch_LDADD = \
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
index 5f99a472b..431abea4d 100644
--- a/src/exchange/taler-exchange-aggregator.c
+++ b/src/exchange/taler-exchange-aggregator.c
@@ -42,44 +42,6 @@
#include "taler_bank_service.h"
-/**
- * Information we keep for each supported account of the exchange.
- */
-struct WireAccount
-{
- /**
- * Accounts are kept in a DLL.
- */
- struct WireAccount *next;
-
- /**
- * Plugins are kept in a DLL.
- */
- struct WireAccount *prev;
-
- /**
- * Authentication data.
- */
- struct TALER_BANK_AuthenticationData auth;
-
- /**
- * Wire transfer fee structure.
- */
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- /**
- * Name of the section that configures this account.
- */
- char *section_name;
-
- /**
- * Name of the wire method underlying the account.
- */
- char *method;
-
-};
-
-
/**
* Data we keep to #run_transfers(). There is at most
* one of these around at any given point in time.
@@ -102,7 +64,7 @@ struct WirePrepareData
/**
* Wire account used for this preparation.
*/
- struct WireAccount *wa;
+ struct TALER_EXCHANGEDB_WireAccount *wa;
/**
* Row ID of the transfer.
@@ -170,7 +132,7 @@ struct AggregationUnit
* Exchange wire account to be used for the preparation and
* eventual execution of the aggregate wire transfer.
*/
- struct WireAccount *wa;
+ struct TALER_EXCHANGEDB_WireAccount *wa;
/**
* Database session for all of our transactions.
@@ -206,35 +168,6 @@ struct AggregationUnit
};
-/**
- * Context we use while closing a reserve.
- */
-struct CloseTransferContext
-{
-
- /**
- * Our database session.
- */
- struct TALER_EXCHANGEDB_Session *session;
-
- /**
- * Wire transfer method.
- */
- char *method;
-
- /**
- * Wire account used for closing the reserve.
- */
- struct WireAccount *wa;
-};
-
-
-/**
- * Active context while processing reserve closing,
- * or NULL.
- */
-static struct CloseTransferContext *ctc;
-
/**
* Which currency is used by this exchange?
*/
@@ -263,16 +196,6 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg;
*/
static struct TALER_EXCHANGEDB_Plugin *db_plugin;
-/**
- * Head of list of wire accounts of the exchange.
- */
-static struct WireAccount *wa_head;
-
-/**
- * Tail of list of wire accounts of the exchange.
- */
-static struct WireAccount *wa_tail;
-
/**
* Next task to run, if any.
*/
@@ -310,23 +233,6 @@ static int global_ret;
*/
static int test_mode;
-/**
- * Did #run_reserve_closures() have any work during its last run?
- * Used to detect when we should go to sleep for a while to avoid
- * busy waiting.
- */
-static int reserves_idle;
-
-
-/**
- * Main work function that finds and triggers transfers for reserves
- * closures.
- *
- * @param cls closure
- */
-static void
-run_reserve_closures (void *cls);
-
/**
* Main work function that queries the DB and aggregates transactions
@@ -348,191 +254,6 @@ static void
run_transfers (void *cls);
-/**
- * Find the record valid at time @a now in the fee structure.
- *
- * @param wa wire transfer fee data structure to update
- * @param now timestamp to update fees to
- * @return fee valid at @a now, or NULL if unknown
- */
-static struct TALER_EXCHANGEDB_AggregateFees *
-advance_fees (struct WireAccount *wa,
- struct GNUNET_TIME_Absolute now)
-{
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = wa->af;
- while ( (NULL != af) &&
- (af->end_date.abs_value_us < now.abs_value_us) )
- af = af->next;
- return af;
-}
-
-
-/**
- * Update wire transfer fee data structure in @a wa.
- *
- * @param wa wire account data structure to update
- * @param now timestamp to update fees to
- * @param session DB session to use
- * @return fee valid at @a now, or NULL if unknown
- */
-static struct TALER_EXCHANGEDB_AggregateFees *
-update_fees (struct WireAccount *wa,
- struct GNUNET_TIME_Absolute now,
- struct TALER_EXCHANGEDB_Session *session)
-{
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = advance_fees (wa,
- now);
- if (NULL != af)
- return af;
- /* Let's try to load it from disk... */
- wa->af = TALER_EXCHANGEDB_fees_read (cfg,
- wa->method);
- for (struct TALER_EXCHANGEDB_AggregateFees *p = wa->af;
- NULL != p;
- p = p->next)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Persisting fees starting at %s in database\n",
- GNUNET_STRINGS_absolute_time_to_string (p->start_date));
- qs = db_plugin->insert_wire_fee (db_plugin->cls,
- session,
- wa->method,
- p->start_date,
- p->end_date,
- &p->wire_fee,
- &p->closing_fee,
- &p->master_sig);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- TALER_EXCHANGEDB_fees_free (wa->af);
- wa->af = NULL;
- return NULL;
- }
- }
- af = advance_fees (wa,
- now);
- if (NULL != af)
- return af;
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to find current wire transfer fees for `%s' at %s\n",
- wa->method,
- GNUNET_STRINGS_absolute_time_to_string (now));
- return NULL;
-}
-
-
-/**
- * Find the wire plugin for the given payto:// URL
- *
- * @param method wire method we need an account for
- * @return NULL on error
- */
-static struct WireAccount *
-find_account_by_method (const char *method)
-{
- for (struct WireAccount *wa = wa_head; NULL != wa; wa = wa->next)
- if (0 == strcmp (method,
- wa->method))
- return wa;
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No wire account known for method `%s'\n",
- method);
- return NULL;
-}
-
-
-/**
- * Find the wire plugin for the given payto:// URL
- *
- * @param url wire address we need an account for
- * @return NULL on error
- */
-static struct WireAccount *
-find_account_by_payto_uri (const char *url)
-{
- char *method;
- struct WireAccount *wa;
-
- method = TALER_payto_get_method (url);
- if (NULL == method)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid payto:// URL `%s'\n",
- url);
- return NULL;
- }
- wa = find_account_by_method (method);
- GNUNET_free (method);
- return wa;
-}
-
-
-/**
- * Function called with information about a wire account. Adds
- * the account to our list.
- *
- * @param cls closure, NULL
- * @param ai account information
- */
-static void
-add_account_cb (void *cls,
- const struct TALER_EXCHANGEDB_AccountInfo *ai)
-{
- struct WireAccount *wa;
- char *payto_uri;
-
- (void) cls;
- if (GNUNET_YES != ai->debit_enabled)
- return; /* not enabled for us, skip */
- wa = GNUNET_new (struct WireAccount);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- ai->section_name,
- "PAYTO_URI",
- &payto_uri))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ai->section_name,
- "PAYTO_URI");
- GNUNET_free (wa);
- return;
- }
- wa->method = TALER_payto_get_method (payto_uri);
- if (NULL == wa->method)
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- ai->section_name,
- "PAYTO_URI",
- "could not obtain wire method from URI");
- GNUNET_free (wa);
- return;
- }
- GNUNET_free (payto_uri);
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- ai->section_name,
- &wa->auth))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
- "Failed to load exchange account `%s'\n",
- ai->section_name);
- GNUNET_free (wa->method);
- GNUNET_free (wa);
- return;
- }
- wa->section_name = GNUNET_strdup (ai->section_name);
- GNUNET_CONTAINER_DLL_insert (wa_head,
- wa_tail,
- wa);
-}
-
-
/**
* Free data stored in @a au, but not @a au itself (stack allocated).
*
@@ -589,32 +310,9 @@ shutdown_task (void *cls)
GNUNET_free (wpd);
wpd = NULL;
}
- if (NULL != ctc)
- {
- db_plugin->rollback (db_plugin->cls,
- ctc->session);
- GNUNET_free (ctc->method);
- GNUNET_free (ctc);
- ctc = NULL;
- }
TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL;
-
- {
- struct WireAccount *wa;
-
- while (NULL != (wa = wa_head))
- {
- GNUNET_CONTAINER_DLL_remove (wa_head,
- wa_tail,
- wa);
- TALER_BANK_auth_free (&wa->auth);
- TALER_EXCHANGEDB_fees_free (wa->af);
- GNUNET_free (wa->section_name);
- GNUNET_free (wa->method);
- GNUNET_free (wa);
- }
- }
+ TALER_EXCHANGEDB_unload_accounts ();
cfg = NULL;
}
@@ -691,10 +389,8 @@ parse_wirewatch_config ()
"Failed to initialize DB subsystem\n");
return GNUNET_SYSERR;
}
- TALER_EXCHANGEDB_find_accounts (cfg,
- &add_account_cb,
- NULL);
- if (NULL == wa_head)
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_load_accounts (cfg))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for debit!\n");
@@ -836,7 +532,7 @@ deposit_cb (void *cls,
char *url;
url = TALER_JSON_wire_to_payto (au->wire);
- au->wa = find_account_by_payto_uri (url);
+ au->wa = TALER_EXCHANGEDB_find_account_by_payto_uri (url);
GNUNET_free (url);
}
if (NULL == au->wa)
@@ -851,9 +547,11 @@ deposit_cb (void *cls,
{
struct TALER_EXCHANGEDB_AggregateFees *af;
- af = update_fees (au->wa,
- au->execution_time,
- au->session);
+ af = TALER_EXCHANGEDB_update_fees (cfg,
+ db_plugin,
+ au->wa,
+ au->execution_time,
+ au->session);
if (NULL == af)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -1042,311 +740,6 @@ commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
}
-/**
- * Closure for #expired_reserve_cb().
- */
-struct ExpiredReserveContext
-{
-
- /**
- * Database session we are using.
- */
- struct TALER_EXCHANGEDB_Session *session;
-
- /**
- * Set to #GNUNET_YES if the transaction continues
- * asynchronously.
- */
- int async_cont;
-};
-
-
-/**
- * Function called with details about expired reserves.
- * We trigger the reserve closure by inserting the respective
- * closing record and prewire instructions into the respective
- * tables.
- *
- * @param cls a `struct ExpiredReserveContext *`
- * @param reserve_pub public key of the reserve
- * @param left amount left in the reserve
- * @param account_payto_uri information about the bank account that initially
- * caused the reserve to be created
- * @param expiration_date when did the reserve expire
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-expired_reserve_cb (void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *left,
- const char *account_payto_uri,
- struct GNUNET_TIME_Absolute expiration_date)
-{
- struct ExpiredReserveContext *erc = cls;
- struct TALER_EXCHANGEDB_Session *session = erc->session;
- struct GNUNET_TIME_Absolute now;
- struct TALER_WireTransferIdentifierRawP wtid;
- struct TALER_Amount amount_without_fee;
- const struct TALER_Amount *closing_fee;
- int ret;
- enum GNUNET_DB_QueryStatus qs;
- struct WireAccount *wa;
- void *buf;
- size_t buf_size;
-
- /* NOTE: potential optimization: use custom SQL API to not
- fetch this: */
- GNUNET_assert (NULL == ctc);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Processing reserve closure at %s\n",
- GNUNET_STRINGS_absolute_time_to_string (expiration_date));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
-
- /* lookup account we should use */
- wa = find_account_by_payto_uri (account_payto_uri);
- if (NULL == wa)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No wire account configured to deal with target URI `%s'\n",
- account_payto_uri);
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* lookup `closing_fee` from time of actual reserve expiration
- (we may be lagging behind!) */
- {
- struct TALER_EXCHANGEDB_AggregateFees *af;
-
- af = update_fees (wa,
- expiration_date,
- session);
- if (NULL == af)
- {
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- closing_fee = &af->closing_fee;
- }
-
- /* calculate transfer amount */
- ret = TALER_amount_subtract (&amount_without_fee,
- left,
- closing_fee);
- if ( (GNUNET_SYSERR == ret) ||
- (GNUNET_NO == ret) )
- {
- /* Closing fee higher than or equal to remaining balance, close
- without wire transfer. */
- closing_fee = left;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (left->currency,
- &amount_without_fee));
- }
- /* round down to enable transfer */
- if (GNUNET_SYSERR ==
- TALER_amount_round_down (&amount_without_fee,
- ¤cy_round_unit))
- {
- GNUNET_break (0);
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (0 == amount_without_fee.value) &&
- (0 == amount_without_fee.fraction) )
- ret = GNUNET_NO;
-
- /* NOTE: sizeof (*reserve_pub) == sizeof (wtid) right now, but to
- be future-compatible, we use the memset + min construction */
- memset (&wtid,
- 0,
- sizeof (wtid));
- memcpy (&wtid,
- reserve_pub,
- GNUNET_MIN (sizeof (wtid),
- sizeof (*reserve_pub)));
- if (GNUNET_SYSERR != ret)
- qs = db_plugin->insert_reserve_closed (db_plugin->cls,
- session,
- reserve_pub,
- now,
- account_payto_uri,
- &wtid,
- left,
- closing_fee);
- else
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Closing reserve %s over %s (%d, %d)\n",
- TALER_B2S (reserve_pub),
- TALER_amount2s (left),
- ret,
- qs);
- /* Check for hard failure */
- if ( (GNUNET_SYSERR == ret) ||
- (GNUNET_DB_STATUS_HARD_ERROR == qs) )
- {
- GNUNET_break (0);
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (GNUNET_OK != ret) ||
- (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) )
- {
- /* Reserve balance was almost zero OR soft error */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reserve was virtually empty, moving on\n");
- (void) commit_or_warn (session);
- erc->async_cont = GNUNET_YES;
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_transfers,
- NULL);
- return qs;
- }
-
- /* success, perform wire transfer */
- ctc = GNUNET_new (struct CloseTransferContext);
- ctc->wa = wa;
- ctc->session = session;
- ctc->method = TALER_payto_get_method (account_payto_uri);
- TALER_BANK_prepare_transfer (account_payto_uri,
- &amount_without_fee,
- exchange_base_url,
- &wtid,
- &buf,
- &buf_size);
- /* Commit our intention to execute the wire transfer! */
- qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
- ctc->session,
- ctc->method,
- buf,
- buf_size);
- GNUNET_free (buf);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- GNUNET_free (ctc->method);
- GNUNET_free (ctc);
- ctc = NULL;
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- /* start again */
- GNUNET_free (ctc->method);
- GNUNET_free (ctc);
- ctc = NULL;
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- erc->async_cont = GNUNET_YES;
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_transfers,
- NULL);
- GNUNET_free (ctc->method);
- GNUNET_free (ctc);
- ctc = NULL;
-
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Main work function that finds and triggers transfers for reserves
- * closures.
- *
- * @param cls closure
- */
-static void
-run_reserve_closures (void *cls)
-{
- struct TALER_EXCHANGEDB_Session *session;
- enum GNUNET_DB_QueryStatus qs;
- const struct GNUNET_SCHEDULER_TaskContext *tc;
- struct ExpiredReserveContext erc;
- struct GNUNET_TIME_Absolute now;
-
- (void) cls;
- task = NULL;
- reserves_idle = GNUNET_NO;
- tc = GNUNET_SCHEDULER_get_task_context ();
- if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
- return;
- if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to obtain database session!\n");
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
-
- if (GNUNET_OK !=
- db_plugin->start (db_plugin->cls,
- session,
- "aggregator reserve closures"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction!\n");
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- erc.session = session;
- erc.async_cont = GNUNET_NO;
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking for reserves to close by date %s\n",
- GNUNET_STRINGS_absolute_time_to_string (now));
- qs = db_plugin->get_expired_reserves (db_plugin->cls,
- session,
- now,
- &expired_reserve_cb,
- &erc);
- GNUNET_assert (1 >= qs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls,
- session);
- global_ret = GNUNET_SYSERR;
- GNUNET_SCHEDULER_shutdown ();
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- db_plugin->rollback (db_plugin->cls,
- session);
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
- NULL);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No more idle reserves, going back to aggregation\n");
- reserves_idle = GNUNET_YES;
- db_plugin->rollback (db_plugin->cls,
- session);
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- NULL);
- return;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- (void) commit_or_warn (session);
- if (GNUNET_YES == erc.async_cont)
- break;
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
- NULL);
- return;
- }
-}
-
-
/**
* Main work function that queries the DB and aggregates transactions
* into larger wire transfers.
@@ -1356,7 +749,6 @@ run_reserve_closures (void *cls)
static void
run_aggregation (void *cls)
{
- static unsigned int swap;
struct AggregationUnit au_active;
struct TALER_EXCHANGEDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
@@ -1369,13 +761,6 @@ run_aggregation (void *cls)
tc = GNUNET_SCHEDULER_get_task_context ();
if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
return;
- if (0 == (++swap % 2))
- {
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
- NULL);
- return;
- }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Checking for ready deposits to aggregate\n");
if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
@@ -1420,7 +805,6 @@ run_aggregation (void *cls)
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
/* should re-try immediately */
- swap--; /* do not count failed attempts */
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
NULL);
@@ -1428,30 +812,18 @@ run_aggregation (void *cls)
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No more ready deposits, going to sleep\n");
- if ( (GNUNET_YES == test_mode) &&
- (swap >= 2) )
+ if (GNUNET_YES == test_mode)
{
/* in test mode, shutdown if we end up being idle */
GNUNET_SCHEDULER_shutdown ();
}
else
{
- if ( (GNUNET_NO == reserves_idle) ||
- (GNUNET_YES == test_mode) )
- {
- /* Possibly more to on reserves, go for it immediately */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
- NULL);
- }
- else
- {
- /* nothing to do, sleep for a minute and try again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
- &run_aggregation,
- NULL);
- }
+ /* nothing to do, sleep for a minute and try again */
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
+ &run_aggregation,
+ NULL);
}
return;
}
@@ -1791,14 +1163,14 @@ wire_prepare_cb (void *cls,
const char *buf,
size_t buf_size)
{
- struct WireAccount *wa;
+ struct TALER_EXCHANGEDB_WireAccount *wa;
(void) cls;
wpd->row_id = rowid;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting wire transfer %llu\n",
(unsigned long long) rowid);
- wpd->wa = find_account_by_method (wire_method);
+ wpd->wa = TALER_EXCHANGEDB_find_account_by_method (wire_method);
if (NULL == wpd->wa)
{
/* Should really never happen here, as when we get
diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c
new file mode 100644
index 000000000..181b231ba
--- /dev/null
+++ b/src/exchange/taler-exchange-closer.c
@@ -0,0 +1,601 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2016-2020 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-closer.c
+ * @brief Process that closes expired reserves
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include
+#include
+#include
+#include "taler_exchangedb_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+
+
+/**
+ * Which currency is used by this exchange?
+ */
+static char *exchange_currency_string;
+
+/**
+ * What is the smallest unit we support for wire transfers?
+ * We will need to round down to a multiple of this amount.
+ */
+static struct TALER_Amount currency_round_unit;
+
+/**
+ * What is the base URL of this exchange? Used in the
+ * wire transfer subjects to that merchants and governments
+ * can ask for the list of aggregated deposits.
+ */
+static char *exchange_base_url;
+
+/**
+ * The exchange's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_EXCHANGEDB_Plugin *db_plugin;
+
+/**
+ * Next task to run, if any.
+ */
+static struct GNUNET_SCHEDULER_Task *task;
+
+/**
+ * How long should we sleep when idle before trying to find more work?
+ */
+static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
+
+/**
+ * Value to return from main(). #GNUNET_OK on success, #GNUNET_SYSERR
+ * on serious errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+
+/**
+ * Main work function that finds and triggers transfers for reserves
+ * closures.
+ *
+ * @param cls closure
+ */
+static void
+run_reserve_closures (void *cls);
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ if (NULL != task)
+ {
+ GNUNET_SCHEDULER_cancel (task);
+ task = NULL;
+ }
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ TALER_EXCHANGEDB_unload_accounts ();
+ cfg = NULL;
+}
+
+
+/**
+ * Parse the configuration for wirewatch.
+ *
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_wirewatch_config ()
+{
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchange",
+ "AGGREGATOR_IDLE_SLEEP_INTERVAL",
+ &aggregator_idle_sleep_interval))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "AGGREGATOR_IDLE_SLEEP_INTERVAL");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &exchange_currency_string))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ return GNUNET_SYSERR;
+ }
+ if (strlen (exchange_currency_string) >= TALER_CURRENCY_LEN)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency `%s' longer than the allowed limit of %u characters.",
+ exchange_currency_string,
+ (unsigned int) TALER_CURRENCY_LEN);
+ return GNUNET_SYSERR;
+ }
+
+ if ( (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ "taler",
+ "CURRENCY_ROUND_UNIT",
+ ¤cy_round_unit)) ||
+ (0 != strcasecmp (exchange_currency_string,
+ currency_round_unit.currency)) ||
+ ( (0 != currency_round_unit.fraction) &&
+ (0 != currency_round_unit.value) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid value specified in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
+ 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))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire accounts configured for debit!\n");
+ TALER_EXCHANGEDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Perform a database commit. If it fails, print a warning.
+ *
+ * @param session session to perform the commit for.
+ * @return status of commit
+ */
+static enum GNUNET_DB_QueryStatus
+commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->commit (db_plugin->cls,
+ session);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? GNUNET_ERROR_TYPE_INFO
+ : GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit database transaction!\n");
+ return qs;
+}
+
+
+/**
+ * Closure for #expired_reserve_cb().
+ */
+struct ExpiredReserveContext
+{
+
+ /**
+ * Database session we are using.
+ */
+ struct TALER_EXCHANGEDB_Session *session;
+
+ /**
+ * Set to #GNUNET_YES if the transaction continues
+ * asynchronously.
+ */
+ int async_cont;
+};
+
+
+/**
+ * Function called with details about expired reserves.
+ * We trigger the reserve closure by inserting the respective
+ * closing record and prewire instructions into the respective
+ * tables.
+ *
+ * @param cls a `struct ExpiredReserveContext *`
+ * @param reserve_pub public key of the reserve
+ * @param left amount left in the reserve
+ * @param account_payto_uri information about the bank account that initially
+ * caused the reserve to be created
+ * @param expiration_date when did the reserve expire
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+expired_reserve_cb (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *left,
+ const char *account_payto_uri,
+ struct GNUNET_TIME_Absolute expiration_date)
+{
+ struct ExpiredReserveContext *erc = cls;
+ struct TALER_EXCHANGEDB_Session *session = erc->session;
+ struct GNUNET_TIME_Absolute now;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount amount_without_fee;
+ const struct TALER_Amount *closing_fee;
+ int ret;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_WireAccount *wa;
+
+ /* NOTE: potential optimization: use custom SQL API to not
+ fetch this: */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing reserve closure at %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (expiration_date));
+ now = GNUNET_TIME_absolute_get ();
+ (void) GNUNET_TIME_round_abs (&now);
+
+ /* lookup account we should use */
+ wa = TALER_EXCHANGEDB_find_account_by_payto_uri (account_payto_uri);
+ if (NULL == wa)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire account configured to deal with target URI `%s'\n",
+ account_payto_uri);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* lookup `closing_fee` from time of actual reserve expiration
+ (we may be lagging behind!) */
+ {
+ struct TALER_EXCHANGEDB_AggregateFees *af;
+
+ af = TALER_EXCHANGEDB_update_fees (cfg,
+ db_plugin,
+ wa,
+ expiration_date,
+ session);
+ if (NULL == af)
+ {
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ closing_fee = &af->closing_fee;
+ }
+
+ /* calculate transfer amount */
+ ret = TALER_amount_subtract (&amount_without_fee,
+ left,
+ closing_fee);
+ if ( (GNUNET_SYSERR == ret) ||
+ (GNUNET_NO == ret) )
+ {
+ /* Closing fee higher than or equal to remaining balance, close
+ without wire transfer. */
+ closing_fee = left;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (left->currency,
+ &amount_without_fee));
+ }
+ /* round down to enable transfer */
+ if (GNUNET_SYSERR ==
+ TALER_amount_round_down (&amount_without_fee,
+ ¤cy_round_unit))
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (0 == amount_without_fee.value) &&
+ (0 == amount_without_fee.fraction) )
+ ret = GNUNET_NO;
+
+ /* NOTE: sizeof (*reserve_pub) == sizeof (wtid) right now, but to
+ be future-compatible, we use the memset + min construction */
+ memset (&wtid,
+ 0,
+ sizeof (wtid));
+ memcpy (&wtid,
+ reserve_pub,
+ GNUNET_MIN (sizeof (wtid),
+ sizeof (*reserve_pub)));
+ if (GNUNET_SYSERR != ret)
+ qs = db_plugin->insert_reserve_closed (db_plugin->cls,
+ session,
+ reserve_pub,
+ now,
+ account_payto_uri,
+ &wtid,
+ left,
+ closing_fee);
+ else
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Closing reserve %s over %s (%d, %d)\n",
+ TALER_B2S (reserve_pub),
+ TALER_amount2s (left),
+ ret,
+ qs);
+ /* Check for hard failure */
+ if ( (GNUNET_SYSERR == ret) ||
+ (GNUNET_DB_STATUS_HARD_ERROR == qs) )
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_OK != ret) ||
+ (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) )
+ {
+ /* Reserve balance was almost zero OR soft error */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reserve was virtually empty, moving on\n");
+ (void) commit_or_warn (session);
+ erc->async_cont = GNUNET_YES;
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
+ NULL);
+ return qs;
+ }
+
+ /* success, perform wire transfer */
+ {
+ char *method;
+ void *buf;
+ size_t buf_size;
+
+ method = TALER_payto_get_method (account_payto_uri);
+ TALER_BANK_prepare_transfer (account_payto_uri,
+ &amount_without_fee,
+ exchange_base_url,
+ &wtid,
+ &buf,
+ &buf_size);
+ /* Commit our intention to execute the wire transfer! */
+ qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
+ session,
+ method,
+ buf,
+ buf_size);
+ GNUNET_free (buf);
+ GNUNET_free (method);
+ }
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ /* start again */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ erc->async_cont = GNUNET_YES;
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
+ NULL);
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Main work function that finds and triggers transfers for reserves
+ * closures.
+ *
+ * @param cls closure
+ */
+static void
+run_reserve_closures (void *cls)
+{
+ struct TALER_EXCHANGEDB_Session *session;
+ enum GNUNET_DB_QueryStatus qs;
+ const struct GNUNET_SCHEDULER_TaskContext *tc;
+ struct ExpiredReserveContext erc;
+ struct GNUNET_TIME_Absolute now;
+
+ (void) cls;
+ task = NULL;
+ tc = GNUNET_SCHEDULER_get_task_context ();
+ if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
+ return;
+ if (NULL == (session = db_plugin->get_session (db_plugin->cls)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain database session!\n");
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if (GNUNET_OK !=
+ db_plugin->start (db_plugin->cls,
+ session,
+ "aggregator reserve closures"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start database transaction!\n");
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ erc.session = session;
+ erc.async_cont = GNUNET_NO;
+ now = GNUNET_TIME_absolute_get ();
+ (void) GNUNET_TIME_round_abs (&now);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for reserves to close by date %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (now));
+ qs = db_plugin->get_expired_reserves (db_plugin->cls,
+ session,
+ now,
+ &expired_reserve_cb,
+ &erc);
+ GNUNET_assert (1 >= qs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ db_plugin->rollback (db_plugin->cls,
+ session);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ db_plugin->rollback (db_plugin->cls,
+ session);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No more idle reserves, going back to aggregation\n");
+ db_plugin->rollback (db_plugin->cls,
+ session);
+ GNUNET_assert (NULL == task);
+ if (GNUNET_YES == test_mode)
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ }
+ else
+ {
+ task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
+ &run_reserve_closures,
+ NULL);
+ }
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ (void) commit_or_warn (session);
+ if (GNUNET_YES == erc.async_cont)
+ break;
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
+ NULL);
+ return;
+ }
+}
+
+
+/**
+ * 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;
+ if (GNUNET_OK != parse_wirewatch_config ())
+ {
+ cfg = NULL;
+ global_ret = 1;
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
+ NULL);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ cls);
+}
+
+
+/**
+ * The main function of the taler-exchange-closer.
+ *
+ * @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_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
+ };
+
+ if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return 2;
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc, argv,
+ "taler-exchange-closer",
+ gettext_noop (
+ "background process that closes expired reserves"),
+ options,
+ &run, NULL))
+ {
+ GNUNET_free ((void *) argv);
+ return 1;
+ }
+ GNUNET_free ((void *) argv);
+ return global_ret;
+}
+
+
+/* end of taler-exchange-closer.c */
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index aa681918b..c3d0b4302 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -56,11 +56,10 @@ libtalerexchangedb_la_SOURCES = \
exchangedb_plugin.c \
exchangedb_signkeys.c \
exchangedb_transactions.c
-
libtalerexchangedb_la_LIBADD = \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil $(XLIB)
-
libtalerexchangedb_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
-version-info 1:0:0 \
diff --git a/src/exchangedb/exchangedb_accounts.c b/src/exchangedb/exchangedb_accounts.c
index 2943adb24..db23eafca 100644
--- a/src/exchangedb/exchangedb_accounts.c
+++ b/src/exchangedb/exchangedb_accounts.c
@@ -22,6 +22,17 @@
#include "taler_exchangedb_lib.h"
+/**
+ * Head of list of wire accounts of the exchange.
+ */
+static struct TALER_EXCHANGEDB_WireAccount *wa_head;
+
+/**
+ * Tail of list of wire accounts of the exchange.
+ */
+static struct TALER_EXCHANGEDB_WireAccount *wa_tail;
+
+
/**
* Closure of #check_for_account.
*/
@@ -141,4 +152,154 @@ TALER_EXCHANGEDB_find_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
+/**
+ * Find the wire plugin for the given payto:// URL
+ *
+ * @param method wire method we need an account for
+ * @return NULL on error
+ */
+struct TALER_EXCHANGEDB_WireAccount *
+TALER_EXCHANGEDB_find_account_by_method (const char *method)
+{
+ for (struct TALER_EXCHANGEDB_WireAccount *wa = wa_head; NULL != wa; wa =
+ wa->next)
+ if (0 == strcmp (method,
+ wa->method))
+ return wa;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire account known for method `%s'\n",
+ method);
+ return NULL;
+}
+
+
+/**
+ * Find the wire plugin for the given payto:// URL
+ *
+ * @param url wire address we need an account for
+ * @return NULL on error
+ */
+struct TALER_EXCHANGEDB_WireAccount *
+TALER_EXCHANGEDB_find_account_by_payto_uri (const char *url)
+{
+ char *method;
+ struct TALER_EXCHANGEDB_WireAccount *wa;
+
+ method = TALER_payto_get_method (url);
+ if (NULL == method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid payto:// URL `%s'\n",
+ url);
+ return NULL;
+ }
+ wa = TALER_EXCHANGEDB_find_account_by_method (method);
+ GNUNET_free (method);
+ return wa;
+}
+
+
+/**
+ * Function called with information about a wire account. Adds
+ * the account to our list.
+ *
+ * @param cls closure, a `struct GNUNET_CONFIGURATION_Handle`
+ * @param ai account information
+ */
+static void
+add_account_cb (void *cls,
+ const struct TALER_EXCHANGEDB_AccountInfo *ai)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_EXCHANGEDB_WireAccount *wa;
+ char *payto_uri;
+
+ (void) cls;
+ if (GNUNET_YES != ai->debit_enabled)
+ return; /* not enabled for us, skip */
+ wa = GNUNET_new (struct TALER_EXCHANGEDB_WireAccount);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ ai->section_name,
+ "PAYTO_URI",
+ &payto_uri))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ai->section_name,
+ "PAYTO_URI");
+ GNUNET_free (wa);
+ return;
+ }
+ wa->method = TALER_payto_get_method (payto_uri);
+ if (NULL == wa->method)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ ai->section_name,
+ "PAYTO_URI",
+ "could not obtain wire method from URI");
+ GNUNET_free (wa);
+ return;
+ }
+ GNUNET_free (payto_uri);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (cfg,
+ ai->section_name,
+ &wa->auth))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to load exchange account `%s'\n",
+ ai->section_name);
+ GNUNET_free (wa->method);
+ GNUNET_free (wa);
+ return;
+ }
+ wa->section_name = GNUNET_strdup (ai->section_name);
+ GNUNET_CONTAINER_DLL_insert (wa_head,
+ wa_tail,
+ wa);
+}
+
+
+/**
+ * Load account information opf the exchange from
+ * @a cfg.
+ *
+ * @param cfg configuration to load from
+ * @return #GNUNET_OK on success, #GNUNET_NO if no accounts are configured
+ */
+int
+TALER_EXCHANGEDB_load_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ TALER_EXCHANGEDB_find_accounts (cfg,
+ &add_account_cb,
+ (void *) cfg);
+ if (NULL == wa_head)
+ return GNUNET_NO;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free resources allocated by
+ * #TALER_EXCHANGEDB_load_accounts().
+ */
+void
+TALER_EXCHANGEDB_unload_accounts (void)
+{
+ struct TALER_EXCHANGEDB_WireAccount *wa;
+
+ while (NULL != (wa = wa_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wa_head,
+ wa_tail,
+ wa);
+ TALER_BANK_auth_free (&wa->auth);
+ TALER_EXCHANGEDB_fees_free (wa->af);
+ GNUNET_free (wa->section_name);
+ GNUNET_free (wa->method);
+ GNUNET_free (wa);
+ }
+}
+
+
/* end of exchangedb_accounts.c */
diff --git a/src/exchangedb/exchangedb_fees.c b/src/exchangedb/exchangedb_fees.c
index 75bb13cef..070f16eee 100644
--- a/src/exchangedb/exchangedb_fees.c
+++ b/src/exchangedb/exchangedb_fees.c
@@ -326,4 +326,87 @@ TALER_EXCHANGEDB_fees_free (struct TALER_EXCHANGEDB_AggregateFees *af)
}
+/**
+ * Find the record valid at time @a now in the fee structure.
+ *
+ * @param wa wire transfer fee data structure to update
+ * @param now timestamp to update fees to
+ * @return fee valid at @a now, or NULL if unknown
+ */
+static struct TALER_EXCHANGEDB_AggregateFees *
+advance_fees (struct TALER_EXCHANGEDB_WireAccount *wa,
+ struct GNUNET_TIME_Absolute now)
+{
+ struct TALER_EXCHANGEDB_AggregateFees *af;
+
+ af = wa->af;
+ while ( (NULL != af) &&
+ (af->end_date.abs_value_us < now.abs_value_us) )
+ af = af->next;
+ return af;
+}
+
+
+/**
+ * Update wire transfer fee data structure in @a wa.
+ *
+ * @param cfg configuration to use
+ * @param db_plugin database plugin to use
+ * @param wa wire account data structure to update
+ * @param now timestamp to update fees to
+ * @param session DB session to use
+ * @return fee valid at @a now, or NULL if unknown
+ */
+struct TALER_EXCHANGEDB_AggregateFees *
+TALER_EXCHANGEDB_update_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ struct TALER_EXCHANGEDB_Plugin *db_plugin,
+ struct TALER_EXCHANGEDB_WireAccount *wa,
+ struct GNUNET_TIME_Absolute now,
+ struct TALER_EXCHANGEDB_Session *session)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_AggregateFees *af;
+
+ af = advance_fees (wa,
+ now);
+ if (NULL != af)
+ return af;
+ /* Let's try to load it from disk... */
+ wa->af = TALER_EXCHANGEDB_fees_read (cfg,
+ wa->method);
+ for (struct TALER_EXCHANGEDB_AggregateFees *p = wa->af;
+ NULL != p;
+ p = p->next)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Persisting fees starting at %s in database\n",
+ GNUNET_STRINGS_absolute_time_to_string (p->start_date));
+ qs = db_plugin->insert_wire_fee (db_plugin->cls,
+ session,
+ wa->method,
+ p->start_date,
+ p->end_date,
+ &p->wire_fee,
+ &p->closing_fee,
+ &p->master_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ TALER_EXCHANGEDB_fees_free (wa->af);
+ wa->af = NULL;
+ return NULL;
+ }
+ }
+ af = advance_fees (wa,
+ now);
+ if (NULL != af)
+ return af;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to find current wire transfer fees for `%s' at %s\n",
+ wa->method,
+ GNUNET_STRINGS_absolute_time_to_string (now));
+ return NULL;
+}
+
+
/* end of exchangedb_fees.c */
diff --git a/src/include/taler_exchangedb_lib.h b/src/include/taler_exchangedb_lib.h
index 7139335cc..d8ce0e16a 100644
--- a/src/include/taler_exchangedb_lib.h
+++ b/src/include/taler_exchangedb_lib.h
@@ -25,6 +25,7 @@
#include "taler_signatures.h"
#include "taler_exchangedb_plugin.h"
+#include "taler_bank_service.h"
/**
* Subdirectroy under the exchange's base directory which contains
@@ -460,4 +461,104 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
struct TALER_Amount *ret);
+/* ***************** convenience functions ******** */
+
+/**
+ * Information we keep for each supported account of the exchange.
+ */
+struct TALER_EXCHANGEDB_WireAccount
+{
+ /**
+ * Accounts are kept in a DLL.
+ */
+ struct TALER_EXCHANGEDB_WireAccount *next;
+
+ /**
+ * Plugins are kept in a DLL.
+ */
+ struct TALER_EXCHANGEDB_WireAccount *prev;
+
+ /**
+ * Authentication data.
+ */
+ struct TALER_BANK_AuthenticationData auth;
+
+ /**
+ * Wire transfer fee structure.
+ */
+ struct TALER_EXCHANGEDB_AggregateFees *af;
+
+ /**
+ * Name of the section that configures this account.
+ */
+ char *section_name;
+
+ /**
+ * Name of the wire method underlying the account.
+ */
+ char *method;
+
+};
+
+
+/**
+ * Update wire transfer fee data structure in @a wa.
+ *
+ * @param cfg configuration to use
+ * @param db_plugin database plugin to use
+ * @param wa wire account data structure to update
+ * @param now timestamp to update fees to
+ * @param session DB session to use
+ * @return fee valid at @a now, or NULL if unknown
+ */
+struct TALER_EXCHANGEDB_AggregateFees *
+TALER_EXCHANGEDB_update_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ struct TALER_EXCHANGEDB_Plugin *db_plugin,
+ struct TALER_EXCHANGEDB_WireAccount *wa,
+ struct GNUNET_TIME_Absolute now,
+ struct TALER_EXCHANGEDB_Session *session);
+
+
+/**
+ * Find the wire plugin for the given payto:// URL.
+ * Only useful after the accounts have been loaded
+ * using #TALER_EXCHANGEDB_load_accounts().
+ *
+ * @param method wire method we need an account for
+ * @return NULL on error
+ */
+struct TALER_EXCHANGEDB_WireAccount *
+TALER_EXCHANGEDB_find_account_by_method (const char *method);
+
+
+/**
+ * Find the wire plugin for the given payto:// URL
+ * Only useful after the accounts have been loaded
+ * using #TALER_EXCHANGEDB_load_accounts().
+ *
+ * @param url wire address we need an account for
+ * @return NULL on error
+ */
+struct TALER_EXCHANGEDB_WireAccount *
+TALER_EXCHANGEDB_find_account_by_payto_uri (const char *url);
+
+
+/**
+ * Load account information opf the exchange from
+ * @a cfg.
+ *
+ * @param cfg configuration to load from
+ * @return #GNUNET_OK on success, #GNUNET_NO if no accounts are configured
+ */
+int
+TALER_EXCHANGEDB_load_accounts (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Free resources allocated by
+ * #TALER_EXCHANGEDB_load_accounts().
+ */
+void
+TALER_EXCHANGEDB_unload_accounts (void);
+
#endif