summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_exchanges.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_exchanges.c')
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.c2090
1 files changed, 1195 insertions, 895 deletions
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
index f0324c47..260a725a 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2021 Taler Systems SA
+ (C) 2014-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
@@ -21,21 +21,15 @@
*/
#include "platform.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd.h"
-
-
-/**
- * Delay after which we'll re-fetch key information from the exchange.
- */
-#define RELOAD_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
+#include <regex.h>
/**
- * Delay after which we'll allow clients to force us to re-fetch key
- * information from the exchange if we don't know the denomination key.
+ * How often do we retry DB transactions with soft errors?
*/
-#define FORCED_RELOAD_DELAY GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MINUTES, 15)
+#define MAX_RETRIES 3
/**
* Threshold after which exponential backoff should not increase.
@@ -43,46 +37,35 @@
#define RETRY_BACKOFF_THRESHOLD GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_SECONDS, 60)
-
-/**
- * Perform our exponential back-off calculation, starting at 1 ms
- * and then going by a factor of 2 up unto a maximum of RETRY_BACKOFF_THRESHOLD.
- *
- * @param r current backoff time, initially zero
- */
-#define RETRY_BACKOFF(r) GNUNET_TIME_relative_min (RETRY_BACKOFF_THRESHOLD, \
- GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_relative_max ( \
- GNUNET_TIME_UNIT_MILLISECONDS, \
- (r)), 2));
-
-
/**
- * Exchange
+ * This is how long /keys long-polls for, so we should
+ * allow this time between requests if there is no
+ * answer. See exchange_api_handle.c.
*/
-struct Exchange;
+#define LONG_POLL_THRESHOLD GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 120)
/**
- * Information we keep for a pending #MMH_EXCHANGES_find_exchange() operation.
+ * Information we keep for a pending #MMH_EXCHANGES_keys4exchange() operation.
*/
-struct TMH_EXCHANGES_FindOperation
+struct TMH_EXCHANGES_KeysOperation
{
/**
* Kept in a DLL.
*/
- struct TMH_EXCHANGES_FindOperation *next;
+ struct TMH_EXCHANGES_KeysOperation *next;
/**
* Kept in a DLL.
*/
- struct TMH_EXCHANGES_FindOperation *prev;
+ struct TMH_EXCHANGES_KeysOperation *prev;
/**
* Function to call with the result.
*/
- TMH_EXCHANGES_FindContinuation fc;
+ TMH_EXCHANGES_Find2Continuation fc;
/**
* Closure for @e fc.
@@ -92,12 +75,7 @@ struct TMH_EXCHANGES_FindOperation
/**
* Exchange we wait for the /keys for.
*/
- struct Exchange *my_exchange;
-
- /**
- * Wire method we care about for fees, NULL if we do not care about wire fees.
- */
- char *wire_method;
+ struct TMH_Exchange *my_exchange;
/**
* Task scheduled to asynchronously return the result to
@@ -130,43 +108,129 @@ struct FeesByWireMethod
char *wire_method;
/**
- * Full payto URI of the exchange.
+ * Applicable fees, NULL if unknown/error.
*/
- char *payto_uri;
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+};
+
+/**
+ * Restriction that applies to an exchange account.
+ */
+struct Restriction
+{
/**
- * Applicable fees, NULL if unknown/error.
+ * Kept in a DLL.
*/
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ struct Restriction *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Restriction *prev;
+ /**
+ * Type of restriction imposed on the account.
+ */
+ enum TALER_EXCHANGE_AccountRestrictionType type;
+
+ /**
+ * Details depending on @e type.
+ */
+ union
+ {
+
+ /**
+ * Accounts must match the given regular expression.
+ */
+ struct
+ {
+
+ /**
+ * Pre-compiled regex.
+ */
+ regex_t ex;
+
+ } regex;
+
+ } details;
};
/**
- * Exchange
+ * Information about a bank account of the exchange.
*/
-struct Exchange
+struct ExchangeAccount
{
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeAccount *next;
/**
* Kept in a DLL.
*/
- struct Exchange *next;
+ struct ExchangeAccount *prev;
+
+ /**
+ * Wire method of this exchange account.
+ */
+ char *wire_method;
+
+ /**
+ * Currency conversion that applies to this account,
+ * NULL if none.
+ */
+ char *conversion_url;
+
+ /**
+ * Head of DLL of debit restrictions of this account.
+ */
+ struct Restriction *d_head;
+
+ /**
+ * Tail of DLL of debit restrictions of this account.
+ */
+ struct Restriction *d_tail;
+};
+
+
+/**
+ * Internal representation for an exchange
+ */
+struct TMH_Exchange
+{
/**
* Kept in a DLL.
*/
- struct Exchange *prev;
+ struct TMH_Exchange *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TMH_Exchange *prev;
/**
* Head of FOs pending for this exchange.
*/
- struct TMH_EXCHANGES_FindOperation *fo_head;
+ struct TMH_EXCHANGES_KeysOperation *keys_head;
/**
* Tail of FOs pending for this exchange.
*/
- struct TMH_EXCHANGES_FindOperation *fo_tail;
+ struct TMH_EXCHANGES_KeysOperation *keys_tail;
+
+ /**
+ * Head of accounts of this exchange.
+ */
+ struct ExchangeAccount *acc_head;
+
+ /**
+ * Tail of accounts of this exchange.
+ */
+ struct ExchangeAccount *acc_tail;
/**
* (base) URL of the exchange.
@@ -174,19 +238,19 @@ struct Exchange
char *url;
/**
- * A connection to this exchange
+ * Currency offered by the exchange according to OUR configuration.
*/
- struct TALER_EXCHANGE_Handle *conn;
+ char *currency;
/**
- * Active /wire request to the exchange, or NULL.
+ * A connection to this exchange
*/
- struct TALER_EXCHANGE_WireHandle *wire_request;
+ struct TALER_EXCHANGE_GetKeysHandle *conn;
/**
- * Task to re-run /wire after some delay.
+ * The keys of this exchange
*/
- struct GNUNET_SCHEDULER_Task *wire_task;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* Head of wire fees from /wire request.
@@ -199,8 +263,7 @@ struct Exchange
struct FeesByWireMethod *wire_fees_tail;
/**
- * Master public key, guaranteed to be set ONLY for
- * trusted exchanges.
+ * Master public key of the exchange.
*/
struct TALER_MasterPublicKeyP master_pub;
@@ -225,11 +288,45 @@ struct Exchange
struct GNUNET_SCHEDULER_Task *retry_task;
/**
- * true to indicate that there is an ongoing
- * transfer we are waiting for,
- * false to indicate that key data is up-to-date.
+ * What state is this exchange in?
*/
- bool pending;
+ enum
+ {
+
+ /**
+ * Downloading /keys failed.
+ */
+ ESTATE_FAILED = -1,
+
+ /**
+ * Nothing was ever done.
+ */
+ ESTATE_INIT = 0,
+
+ /**
+ * We are actively downloading /keys for the first time.
+ */
+ ESTATE_DOWNLOADING_FIRST = 1,
+
+ /**
+ * We finished downloading /keys and the exchange is
+ * ready.
+ */
+ ESTATE_DOWNLOADED = 2,
+
+ /**
+ * We are downloading /keys again after a previous
+ * success.
+ */
+ ESTATE_REDOWNLOADING_SUCCESS = 3,
+
+ /**
+ * We are downloading /keys again after a previous
+ * failure.
+ */
+ ESTATE_REDOWNLOADING_FAILURE = 4
+
+ } state;
/**
* true if this exchange is from our configuration and
@@ -238,557 +335,633 @@ struct Exchange
*/
bool trusted;
- /**
- * true if this exchange did return to use the
- * response from /wire.
- */
- bool have_wire;
-
};
/**
- * Context for all exchange operations (useful to the event loop)
+ * Head of exchanges we know about.
*/
-static struct GNUNET_CURL_Context *merchant_curl_ctx;
+static struct TMH_Exchange *exchange_head;
/**
- * Context for integrating #merchant_curl_ctx with the
- * GNUnet event loop.
+ * Tail of exchanges we know about.
*/
-static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc;
+static struct TMH_Exchange *exchange_tail;
/**
- * Head of exchanges we know about.
+ * Our event handler listening for /keys downloads
+ * being put into the database.
*/
-static struct Exchange *exchange_head;
+static struct GNUNET_DB_EventHandler *keys_eh;
/**
- * Tail of exchanges we know about.
+ * How many exchanges do we trust (for our configured
+ * currency) as per our configuration? Used for a
+ * sanity-check on startup.
*/
-static struct Exchange *exchange_tail;
+static int trusted_exchange_count;
-/**
- * List of our trusted exchanges for inclusion in contracts.
- */
-json_t *TMH_trusted_exchanges;
+const struct TALER_MasterPublicKeyP *
+TMH_EXCHANGES_get_master_pub (
+ const struct TMH_Exchange *exchange)
+{
+ GNUNET_break ( (exchange->trusted) ||
+ (NULL != exchange->keys) );
+ return &exchange->master_pub;
+}
-/**
- * Function called with information about who is auditing
- * a particular exchange and what key the exchange is using.
- *
- * @param cls closure, will be `struct Exchange` so that
- * when this function gets called, it will change the flag 'pending'
- * to 'false'. Note: 'keys' is automatically saved inside the exchange's
- * handle, which is contained inside 'struct Exchange', when
- * this callback is called. Thus, once 'pending' turns 'false',
- * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle,
- * in order to get the "good" keys.
- * @param hr http response details
- * @param keys information about the various keys used
- * by the exchange
- * @param compat version compatibility data
- */
-static void
-keys_mgmt_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat);
+
+const char *
+TMH_EXCHANGES_get_currency (
+ const struct TMH_Exchange *exchange)
+{
+ return exchange->currency;
+}
/**
- * Retry getting information from the given exchange in
- * the closure.
+ * Free data structures within @a ea, but not @a ea
+ * pointer itself.
*
- * @param cls the exchange
+ * @param[in] ea data structure to free
*/
static void
-retry_exchange (void *cls)
+free_exchange_account (struct ExchangeAccount *ea)
{
- struct Exchange *exchange = cls;
+ struct Restriction *r;
- /* might be a scheduled reload and not our first attempt */
- exchange->retry_task = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Connecting to exchange %s in retry_exchange()\n",
- exchange->url);
- if (NULL == exchange->conn)
+ while (NULL != (r = ea->d_head))
{
- exchange->conn = TALER_EXCHANGE_connect (merchant_curl_ctx,
- exchange->url,
- &keys_mgmt_cb,
- exchange,
- TALER_EXCHANGE_OPTION_END);
- }
- else
- {
- struct GNUNET_TIME_Timestamp next;
-
- next = TALER_EXCHANGE_check_keys_current (exchange->conn,
- TALER_EXCHANGE_CKF_NONE);
- if (! GNUNET_TIME_absolute_is_zero (next.abs_time))
+ GNUNET_CONTAINER_DLL_remove (ea->d_head,
+ ea->d_tail,
+ r);
+ switch (r->type)
{
- exchange->retry_task = GNUNET_SCHEDULER_add_at (next.abs_time,
- &retry_exchange,
- exchange);
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ regfree (&r->details.regex.ex);
+ break;
}
+ GNUNET_free (r);
}
- /* Note: while the API spec says 'returns NULL on error', the implementation
- actually never returns NULL. */
- GNUNET_break (NULL != exchange->conn);
+ GNUNET_free (ea->wire_method);
+ GNUNET_free (ea->conversion_url);
}
/**
- * Function called with information about the wire fees
- * for each wire method. Stores the wire fees with the
- * exchange for later use.
+ * Free list of all accounts in @a exchange.
*
- * @param exchange connection to the exchange
- * @param master_pub public key of the exchange
- * @param wire_method name of the wire method (i.e. "iban")
- * @param payto_uri full payto URI of the exchange
- * @param fees fee structure for this method
- * @return #TALER_EC_NONE on success
+ * @param[in,out] exchange entry to free accounts for
*/
-static enum TALER_ErrorCode
-process_wire_fees (struct Exchange *exchange,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *wire_method,
- const char *payto_uri,
- const struct TALER_EXCHANGE_WireAggregateFees *fees)
+static void
+purge_exchange_accounts (struct TMH_Exchange *exchange)
{
- struct FeesByWireMethod *f;
- struct TALER_EXCHANGE_WireAggregateFees *endp;
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ struct ExchangeAccount *acc;
- for (f = exchange->wire_fees_head; NULL != f; f = f->next)
- if (0 == strcasecmp (wire_method,
- f->wire_method))
- break;
- if (NULL == f)
- {
- f = GNUNET_new (struct FeesByWireMethod);
- f->wire_method = GNUNET_strdup (wire_method);
- f->payto_uri = GNUNET_strdup (payto_uri);
- GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
- exchange->wire_fees_tail,
- f);
- }
- endp = f->af;
- while ( (NULL != endp) &&
- (NULL != endp->next) )
- endp = endp->next;
- while ( (NULL != endp) &&
- (NULL != fees) &&
- (GNUNET_TIME_timestamp_cmp (fees->start_date,
- <,
- endp->end_date)) )
- fees = fees->next;
- if ( (NULL != endp) &&
- (NULL != fees) &&
- (GNUNET_TIME_timestamp_cmp (fees->start_date,
- !=,
- endp->end_date)) )
- {
- /* Hole in the fee structure, not allowed! */
- GNUNET_break_op (0);
- return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE;
- }
- while (NULL != fees)
+ while (NULL != (acc = exchange->acc_head))
{
- struct GNUNET_HashCode h_wire_method;
- enum GNUNET_DB_QueryStatus qs;
-
- af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
- *af = *fees;
- GNUNET_CRYPTO_hash (wire_method,
- strlen (wire_method) + 1,
- &h_wire_method);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing wire fee for `%s' and method `%s' at %s in DB; the fee is %s\n",
- TALER_B2S (master_pub),
- wire_method,
- GNUNET_TIME_timestamp2s (af->start_date),
- TALER_amount2s (&af->fees.wire));
- TMH_db->preflight (TMH_db->cls);
- if (GNUNET_OK !=
- TMH_db->start (TMH_db->cls,
- "store wire fee"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction to store exchange wire fees (will try to continue anyway)!\n");
- GNUNET_free (af);
- fees = fees->next;
- continue;
- }
- qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
- master_pub,
- &h_wire_method,
- &af->fees,
- af->start_date,
- af->end_date,
- &af->master_sig);
- if (0 > qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n");
- GNUNET_free (af);
- fees = fees->next;
- TMH_db->rollback (TMH_db->cls);
- continue;
- }
- if (0 == qs)
- {
- /* Entry was already in DB, fine, continue as if we had succeeded */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Fees already in DB, rolling back transaction attempt!\n");
- TMH_db->rollback (TMH_db->cls);
- }
- if (0 < qs)
- {
- /* Inserted into DB, make sure transaction completes */
- qs = TMH_db->commit (TMH_db->cls);
- if (0 > qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n");
- GNUNET_free (af);
- fees = fees->next;
- continue;
- }
- }
- af->next = NULL;
- if (NULL == endp)
- f->af = af;
- else
- endp->next = af;
- endp = af;
- fees = fees->next;
+ GNUNET_CONTAINER_DLL_remove (exchange->acc_head,
+ exchange->acc_tail,
+ acc);
+ free_exchange_account (acc);
+ GNUNET_free (acc);
}
- return TALER_EC_NONE;
}
/**
- * Function called with information about the wire accounts
- * of the exchange. Stores the wire fees with the
- * exchange for laster use.
+ * Lookup exchange by @a exchange_url. Create one
+ * if it does not exist.
*
- * @param exchange the exchange
- * @param master_pub public key of the exchange
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange
- * @return #TALER_EC_NONE on success
+ * @param exchange_url base URL to match against
+ * @return fresh entry if exchange was not yet known
*/
-static enum TALER_ErrorCode
-process_wire_accounts (struct Exchange *exchange,
- const struct TALER_MasterPublicKeyP *master_pub,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts)
+static struct TMH_Exchange *
+lookup_exchange (const char *exchange_url)
{
- for (unsigned int i = 0; i<accounts_len; i++)
- {
- enum TALER_ErrorCode ec;
- char *method;
+ struct TMH_Exchange *exchange;
+ enum GNUNET_DB_QueryStatus qs;
- method = TALER_payto_get_method (accounts[i].payto_uri);
- if (NULL == method)
- {
- /* malformed payto:// URI returned by exchange */
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_PAYTO_URI_MALFORMED;
- }
- ec = process_wire_fees (exchange,
- master_pub,
- method,
- accounts[i].payto_uri,
- accounts[i].fees);
- GNUNET_free (method);
- if (TALER_EC_NONE != ec)
- return ec;
- }
- return TALER_EC_NONE;
+ for (exchange = exchange_head;
+ NULL != exchange;
+ exchange = exchange->next)
+ if (0 == strcmp (exchange->url,
+ exchange_url))
+ return exchange;
+ exchange = GNUNET_new (struct TMH_Exchange);
+ exchange->url = GNUNET_strdup (exchange_url);
+ GNUNET_CONTAINER_DLL_insert (exchange_head,
+ exchange_tail,
+ exchange);
+ qs = TMH_db->select_exchange_keys (TMH_db->cls,
+ exchange->url,
+ &exchange->keys);
+ GNUNET_break (qs >= 0);
+ if (qs > 0)
+ exchange->state = ESTATE_DOWNLOADED;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "The exchange `%s' is new (%d)\n",
+ exchange_url,
+ exchange->state);
+ return exchange;
}
/**
- * Obtain applicable fees for @a exchange and @a wire_method.
+ * Set the list of accounts of @a exchange.
*
- * @param exchange the exchange to query
- * @param now current time
- * @param wire_method the wire method we want the fees for
- * @return NULL if we do not have fees for this method yet
+ * @param[in,out] exchange exchange to initialize or update
+ * @param accounts_len length of @a accounts array
+ * @param accounts array of accounts to convert
+ * @return #GNUNET_OK on success
*/
-static const struct FeesByWireMethod *
-get_wire_fees (struct Exchange *exchange,
- struct GNUNET_TIME_Timestamp now,
- const char *wire_method)
+static enum GNUNET_GenericReturnValue
+set_exchange_accounts (
+ struct TMH_Exchange *exchange,
+ unsigned int accounts_len,
+ const struct TALER_EXCHANGE_WireAccount accounts[static accounts_len])
{
- for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
- NULL != fbw;
- fbw = fbw->next)
+ enum GNUNET_GenericReturnValue ret = GNUNET_OK;
+
+ purge_exchange_accounts (exchange);
+ for (unsigned int i = 0; i<accounts_len; i++)
{
- if (0 == strcasecmp (fbw->wire_method,
- wire_method) )
+ const struct TALER_EXCHANGE_WireAccount *account = &accounts[i];
+ struct ExchangeAccount *acc;
+
+ acc = GNUNET_new (struct ExchangeAccount);
+ acc->wire_method = TALER_payto_get_method (account->payto_uri);
+ if (NULL != account->conversion_url)
+ acc->conversion_url = GNUNET_strdup (account->conversion_url);
+ GNUNET_CONTAINER_DLL_insert (exchange->acc_head,
+ exchange->acc_tail,
+ acc);
+ for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
{
- struct TALER_EXCHANGE_WireAggregateFees *af;
+ const struct TALER_EXCHANGE_AccountRestriction *ar =
+ &account->debit_restrictions[j];
+ struct Restriction *r;
- /* Advance through list up to current time */
- while ( (NULL != (af = fbw->af)) &&
- (GNUNET_TIME_timestamp_cmp (now,
- >=,
- af->end_date)) )
+ r = GNUNET_new (struct Restriction);
+ r->type = ar->type;
+ switch (ar->type)
{
- fbw->af = af->next;
- GNUNET_free (af);
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ if (0 != regcomp (&r->details.regex.ex,
+ ar->details.regex.posix_egrep,
+ REG_NOSUB | REG_EXTENDED))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (r);
+ ret = GNUNET_SYSERR;
+ continue;
+ }
+ break;
}
- return fbw;
+ GNUNET_CONTAINER_DLL_insert (acc->d_head,
+ acc->d_tail,
+ r);
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange supports `%s' as a wire method (but we do not use that one)\n",
- fbw->wire_method);
}
- return NULL;
+ return ret;
}
/**
+ * Function called with information about who is auditing
+ * a particular exchange and what key the exchange is using.
+ *
+ * @param cls closure, will be `struct TMH_Exchange`
+ * @param kr response details
+ * @param[in] keys keys object returned
+ */
+static void
+keys_mgmt_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys);
+
+
+/**
* Check if we have any remaining pending requests for the
* given @a exchange, and if we have the required data, call
* the callback.
*
* @param exchange the exchange to check for pending find operations
- * @return true if we need /wire data from @a exchange
*/
-static bool
-process_find_operations (struct Exchange *exchange)
+static void
+process_find_operations (struct TMH_Exchange *exchange)
{
- struct TMH_EXCHANGES_FindOperation *fn;
struct GNUNET_TIME_Timestamp now;
- bool need_wire;
now = GNUNET_TIME_timestamp_get ();
- need_wire = false;
- for (struct TMH_EXCHANGES_FindOperation *fo = exchange->fo_head;
- NULL != fo;
- fo = fn)
+ for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
+ NULL != fbw;
+ fbw = fbw->next)
{
- const struct FeesByWireMethod *fbw;
-
- fn = fo->next;
- if (NULL != fo->wire_method)
+ while ( (NULL != fbw->af) &&
+ (GNUNET_TIME_timestamp_cmp (fbw->af->end_date,
+ <,
+ now)) )
{
- /* Find fee structure for our wire method */
- fbw = get_wire_fees (exchange,
- now,
- fo->wire_method);
- if (NULL == fbw)
- {
- need_wire = true;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Missing wire fees for exchange %s and method %s\n",
- exchange->url,
- fo->wire_method);
- /* Do not warn if this is before our first attempt */
- if (! GNUNET_TIME_relative_is_zero (exchange->wire_retry_delay))
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange does not support `%s' wire method (will retry later)\n",
- fo->wire_method);
- fbw = NULL;
- continue;
- }
- if (NULL == fbw->af)
- {
- /* Disagreement on the current time */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange has no wire fees configured for `%s' wire method (will retry later)\n",
- fo->wire_method);
- fbw = NULL;
- continue;
- }
- if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
- >,
- now))
- {
- /* Disagreement on the current time */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange's earliest fee is %s ahead of our time. Clock skew issue?\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- fbw->af->start_date.abs_time),
- true));
- fbw = NULL;
- continue;
- }
+ struct TALER_EXCHANGE_WireAggregateFees *af = fbw->af;
+
+ fbw->af = af->next;
+ GNUNET_free (af);
}
- else
+ if (NULL == fbw->af)
{
- /* no wire transfer method given, so we yield no fee */
- fbw = NULL;
+ /* Disagreement on the current time */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange has no wire fees configured for `%s' wire method\n",
+ fbw->wire_method);
}
+ else if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
+ >,
+ now))
{
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- };
+ /* Disagreement on the current time */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange's earliest fee is %s ahead of our time. Clock skew issue?\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ fbw->af->start_date.abs_time),
+ true));
+ }
+ } /* for all wire methods */
- if ( (NULL != fo->wire_method) &&
- (! exchange->have_wire) )
- {
- /* We needed /wire, but didn't get it. That's not "200 OK". */
- hr.http_status = MHD_HTTP_BAD_GATEWAY;
- hr.ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED;
- }
- fo->fc (fo->fc_cls,
- &hr,
- exchange->conn,
- (NULL != fbw) ? fbw->payto_uri : NULL,
- (NULL != fbw) ? &fbw->af->fees.wire : NULL,
- exchange->trusted);
+ {
+ struct TMH_EXCHANGES_KeysOperation *kon;
+
+ kon = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing find operations for `%s' (%d)\n",
+ exchange->url,
+ exchange->state);
+ for (struct TMH_EXCHANGES_KeysOperation *ko = exchange->keys_head;
+ NULL != ko;
+ ko = kon)
+ {
+ kon = ko->next;
+ ko->fc (ko->fc_cls,
+ exchange->keys,
+ exchange);
+ TMH_EXCHANGES_keys4exchange_cancel (ko);
}
- TMH_EXCHANGES_find_exchange_cancel (fo);
}
- return need_wire;
}
-static void
-wire_task_cb (void *cls);
+/**
+ * Function called with information about the wire fees for each wire method.
+ * Stores the wire fees within our internal data structures for later use.
+ *
+ * @param exchange connection to the exchange
+ * @param master_pub public key of the exchange
+ * @param num_methods number of wire methods supported
+ * @param fbm wire fees by method
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+process_wire_fees (
+ struct TMH_Exchange *exchange,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ unsigned int num_methods,
+ const struct TALER_EXCHANGE_WireFeesByMethod fbm[static num_methods])
+{
+ for (unsigned int i = 0; i<num_methods; i++)
+ {
+ const char *wire_method = fbm[i].method;
+ const struct TALER_EXCHANGE_WireAggregateFees *fees = fbm[i].fees_head;
+ struct FeesByWireMethod *f;
+ struct TALER_EXCHANGE_WireAggregateFees *endp;
+
+ for (f = exchange->wire_fees_head; NULL != f; f = f->next)
+ if (0 == strcasecmp (wire_method,
+ f->wire_method))
+ break;
+ if (NULL == f)
+ {
+ f = GNUNET_new (struct FeesByWireMethod);
+ f->wire_method = GNUNET_strdup (wire_method);
+ GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
+ exchange->wire_fees_tail,
+ f);
+ }
+ endp = f->af;
+ while ( (NULL != endp) &&
+ (NULL != endp->next) )
+ endp = endp->next;
+ while ( (NULL != endp) &&
+ (NULL != fees) &&
+ (GNUNET_TIME_timestamp_cmp (fees->start_date,
+ <,
+ endp->end_date)) )
+ fees = fees->next;
+ if ( (NULL != endp) &&
+ (NULL != fees) &&
+ (GNUNET_TIME_timestamp_cmp (fees->start_date,
+ !=,
+ endp->end_date)) )
+ {
+ /* Hole or overlap in the fee structure, not allowed! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while (NULL != fees)
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+ *af = *fees;
+ af->next = NULL;
+ if (NULL == endp)
+ f->af = af;
+ else
+ endp->next = af;
+ endp = af;
+ fees = fees->next;
+ } /* all fees for this method */
+ } /* for all methods (i) */
+ return GNUNET_OK;
+}
/**
- * Callbacks of this type are used to serve the result of submitting a
- * wire format inquiry request to a exchange.
- *
- * If the request fails to generate a valid response from the
- * exchange, @a http_status will also be zero.
+ * Add account restriction @a a to array of @a restrictions.
*
- * Must only be called if 'exchange->pending' is #GNUNET_NO,
- * that is #TALER_EXCHANGE_get_keys() will succeed.
+ * @param[in,out] restrictions JSON array to build
+ * @param r restriction to add to @a restrictions
+ * @return #GNUNET_SYSERR if @a r is malformed
+ */
+static enum GNUNET_GenericReturnValue
+add_restriction (json_t *restrictions,
+ const struct TALER_EXCHANGE_AccountRestriction *r)
+{
+ json_t *jr;
+
+ jr = NULL;
+ switch (r->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ case TALER_EXCHANGE_AR_DENY:
+ jr = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "deny")
+ );
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ jr = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "type",
+ "regex"),
+ GNUNET_JSON_pack_string (
+ "regex",
+ r->details.regex.posix_egrep),
+ GNUNET_JSON_pack_string (
+ "human_hint",
+ r->details.regex.human_hint),
+ GNUNET_JSON_pack_object_incref (
+ "human_hint_i18n",
+ (json_t *) r->details.regex.human_hint_i18n)
+ );
+ break;
+ }
+ if (NULL == jr)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (restrictions,
+ jr));
+ return GNUNET_OK;
+
+}
+
+
+/**
+ * Retry getting keys from the given exchange in the closure.
*
- * @param cls closure, a `struct Exchange`
- * @param hr HTTP response details
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange, NULL on error
+ * @param cls the `struct TMH_Exchange *`
*/
static void
-handle_wire_data (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts)
+retry_exchange (void *cls)
{
- struct Exchange *exchange = cls;
- const struct TALER_EXCHANGE_Keys *keys;
- enum TALER_ErrorCode ecx;
+ struct TMH_Exchange *exchange = cls;
- exchange->wire_request = NULL;
+ exchange->retry_task = NULL;
+ GNUNET_assert (NULL == exchange->conn);
+ exchange->retry_delay
+ = GNUNET_TIME_randomized_backoff (exchange->retry_delay,
+ RETRY_BACKOFF_THRESHOLD);
+ /* Block for the duration of the long-poller */
+ exchange->first_retry
+ = GNUNET_TIME_relative_to_absolute (LONG_POLL_THRESHOLD);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /wire response\n");
- if (MHD_HTTP_OK != hr->http_status)
+ "Fetching /keys from exchange %s in retry_exchange()\n",
+ exchange->url);
+ switch (exchange->state)
{
- struct TMH_EXCHANGES_FindOperation *fo;
-
- exchange->have_wire = false;
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to obtain /wire details from `%s': %u/%d\n",
- exchange->url,
- hr->http_status,
- hr->ec);
- while (NULL != (fo = exchange->fo_head))
- {
- fo->fc (fo->fc_cls,
- hr,
- exchange->conn,
- NULL,
- NULL,
- GNUNET_NO);
- TMH_EXCHANGES_find_exchange_cancel (fo);
- }
+ case ESTATE_FAILED:
+ exchange->state = ESTATE_REDOWNLOADING_FAILURE;
+ break;
+ case ESTATE_INIT:
+ exchange->state = ESTATE_DOWNLOADING_FIRST;
+ break;
+ case ESTATE_DOWNLOADING_FIRST:
+ GNUNET_break (0);
+ return;
+ case ESTATE_DOWNLOADED:
+ exchange->state = ESTATE_REDOWNLOADING_SUCCESS;
+ break;
+ case ESTATE_REDOWNLOADING_SUCCESS:
+ GNUNET_break (0);
+ return;
+ case ESTATE_REDOWNLOADING_FAILURE:
+ GNUNET_break (0);
return;
}
- keys = TALER_EXCHANGE_get_keys (exchange->conn);
- GNUNET_assert (NULL != keys);
- ecx = process_wire_accounts (exchange,
- &keys->master_pub,
- accounts_len,
- accounts);
- if (TALER_EC_NONE != ecx)
- {
- /* Report hard failure to all callbacks! */
- struct TMH_EXCHANGES_FindOperation *fo;
- struct TALER_EXCHANGE_HttpResponse hrx = {
- .ec = ecx,
- .http_status = 0,
- .reply = hr->reply
- };
+ exchange->conn
+ = TALER_EXCHANGE_get_keys (
+ TMH_curl_ctx,
+ exchange->url,
+ exchange->keys,
+ &keys_mgmt_cb,
+ exchange);
+ /* Note: while the API spec says 'returns NULL on error', the implementation
+ actually never returns NULL. */
+ GNUNET_break (NULL != exchange->conn);
+}
- GNUNET_break_op (0);
- exchange->have_wire = false;
- while (NULL != (fo = exchange->fo_head))
- {
- fo->fc (fo->fc_cls,
- &hrx,
- NULL,
- NULL,
- NULL,
- GNUNET_NO);
- TMH_EXCHANGES_find_exchange_cancel (fo);
- }
- return;
+
+/**
+ * Task to asynchronously return keys operation result to caller.
+ *
+ * @param cls a `struct TMH_EXCHANGES_KeysOperation`
+ */
+static void
+return_keys (void *cls)
+{
+ struct TMH_EXCHANGES_KeysOperation *fo = cls;
+ struct TMH_Exchange *exchange = fo->my_exchange;
+
+ fo->at = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning key data for %s instantly\n",
+ exchange->url);
+ process_find_operations (exchange);
+}
+
+
+struct TMH_EXCHANGES_KeysOperation *
+TMH_EXCHANGES_keys4exchange (
+ const char *chosen_exchange,
+ bool force_download,
+ TMH_EXCHANGES_Find2Continuation fc,
+ void *fc_cls)
+{
+ struct TMH_Exchange *exchange;
+ struct TMH_EXCHANGES_KeysOperation *fo;
+
+ if (NULL == TMH_curl_ctx)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to find chosen exchange `%s'\n",
+ chosen_exchange);
+ exchange = lookup_exchange (chosen_exchange);
+ fo = GNUNET_new (struct TMH_EXCHANGES_KeysOperation);
+ fo->fc = fc;
+ fo->fc_cls = fc_cls;
+ fo->my_exchange = exchange;
+ GNUNET_CONTAINER_DLL_insert (exchange->keys_head,
+ exchange->keys_tail,
+ fo);
+ if ( (NULL != exchange->keys) &&
+ (! force_download) &&
+ (GNUNET_TIME_absolute_is_future (
+ exchange->keys->key_data_expiration.abs_time)) )
+ {
+ /* We have a valid reply, immediately return result */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "The exchange `%s' is ready\n",
+ exchange->url);
+ GNUNET_assert (NULL == fo->at);
+ fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+ fo);
+ return fo;
}
- exchange->have_wire = true;
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_task) &&
- (NULL == exchange->wire_request) )
+ if ( (NULL == exchange->conn) &&
+ ( (ESTATE_FAILED == exchange->state) ||
+ (ESTATE_REDOWNLOADING_FAILURE == exchange->state) ) )
{
- /* need to run /wire again. But as we DID get a successful reply,
- and as the exchange is unlikely to offer new wire methods very
- frequently, start with some significant delay */
- exchange->wire_retry_delay
- = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_MINUTES,
- exchange->wire_retry_delay);
- exchange->wire_retry_delay = RETRY_BACKOFF (exchange->wire_retry_delay);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange does not support our wire method. Retrying in %s\n",
-
- GNUNET_STRINGS_relative_time_to_string (
- exchange->wire_retry_delay,
- GNUNET_YES));
- exchange->wire_task
- = GNUNET_SCHEDULER_add_delayed (exchange->wire_retry_delay,
- &wire_task_cb,
- exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Already waiting for `%skeys' for a while, failing query instantly\n",
+ exchange->url);
+ GNUNET_assert (NULL == fo->at);
+ fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+ fo);
+ return fo;
+ }
+ if ( (force_download) &&
+ (GNUNET_TIME_absolute_is_future (exchange->first_retry)) &&
+ (ESTATE_DOWNLOADED == exchange->state) )
+ {
+ /* Return results immediately. */
+ fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+ fo);
+ /* *no* return here, we MAY schedule a 'retry_task' in the
+ next block if there isn't one yet */
}
+ if ( (NULL == exchange->retry_task) &&
+ (NULL == exchange->conn) )
+ exchange->retry_task
+ = GNUNET_SCHEDULER_add_at (exchange->first_retry,
+ &retry_exchange,
+ exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Next %skeys (%d) request scheduled for %s\n",
+ exchange->url,
+ exchange->state,
+ GNUNET_TIME_absolute2s (
+ exchange->first_retry));
+ /* No activity to launch, we are already doing so. */
+ return fo;
+}
+
+
+void
+TMH_EXCHANGES_keys4exchange_cancel (
+ struct TMH_EXCHANGES_KeysOperation *fo)
+{
+ struct TMH_Exchange *exchange = fo->my_exchange;
+
+ if (NULL != fo->at)
+ {
+ GNUNET_SCHEDULER_cancel (fo->at);
+ fo->at = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (exchange->keys_head,
+ exchange->keys_tail,
+ fo);
+ GNUNET_free (fo);
}
/**
- * Check if we have any remaining pending requests for the
- * given @a exchange, and if we have the required data, call
- * the callback. If requests without /wire data remain,
- * retry the /wire request after some delay.
- *
- * Must only be called if 'exchange->pending' is #GNUNET_NO,
- * that is #TALER_EXCHANGE_get_keys() will succeed.
+ * Obtain applicable fees for @a exchange and @a wire_method.
*
- * @param cls a `struct Exchange` to check
+ * @param exchange the exchange to query
+ * @param now current time
+ * @param wire_method the wire method we want the fees for
+ * @return NULL if we do not have fees for this method yet
*/
-static void
-wire_task_cb (void *cls)
+static const struct FeesByWireMethod *
+get_wire_fees (const struct TMH_Exchange *exchange,
+ struct GNUNET_TIME_Timestamp now,
+ const char *wire_method)
{
- struct Exchange *exchange = cls;
+ for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
+ NULL != fbw;
+ fbw = fbw->next)
+ {
+ if (0 == strcasecmp (fbw->wire_method,
+ wire_method) )
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af;
- exchange->wire_task = NULL;
- GNUNET_assert (! exchange->pending);
- if (! process_find_operations (exchange))
- return; /* no more need */
- GNUNET_assert (NULL == exchange->wire_request);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Initiating /wire download\n");
- exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
- &handle_wire_data,
- exchange);
+ /* Advance through list up to current time */
+ while ( (NULL != (af = fbw->af)) &&
+ (GNUNET_TIME_timestamp_cmp (now,
+ >=,
+ af->end_date)) )
+ {
+ fbw->af = af->next;
+ GNUNET_free (af);
+ }
+ return fbw;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange supports `%s' as a wire method (but we do not use that one)\n",
+ fbw->wire_method);
+ }
+ return NULL;
}
@@ -798,13 +971,19 @@ wire_task_cb (void *cls)
* @param[in] exchange entry to free
*/
static void
-free_exchange_entry (struct Exchange *exchange)
+free_exchange_entry (struct TMH_Exchange *exchange)
{
struct FeesByWireMethod *f;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Releasing %s exchange %s (%d)\n",
+ exchange->trusted ? "trusted" : "untrusted",
+ exchange->url,
+ exchange->state);
GNUNET_CONTAINER_DLL_remove (exchange_head,
exchange_tail,
exchange);
+ purge_exchange_accounts (exchange);
while (NULL != (f = exchange->wire_fees_head))
{
struct TALER_EXCHANGE_WireAggregateFees *af;
@@ -818,31 +997,23 @@ free_exchange_entry (struct Exchange *exchange)
GNUNET_free (af);
}
GNUNET_free (f->wire_method);
- GNUNET_free (f->payto_uri);
GNUNET_free (f);
}
- if (NULL != exchange->wire_request)
- {
- TALER_EXCHANGE_wire_cancel (exchange->wire_request);
- exchange->wire_request = NULL;
- }
- if (NULL != exchange->wire_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->wire_task);
- exchange->wire_task = NULL;
- }
if (NULL != exchange->conn)
{
- TALER_EXCHANGE_disconnect (exchange->conn);
+ TALER_EXCHANGE_get_keys_cancel (exchange->conn);
exchange->conn = NULL;
}
+ TALER_EXCHANGE_keys_decref (exchange->keys);
+ exchange->keys = NULL;
if (NULL != exchange->retry_task)
{
GNUNET_SCHEDULER_cancel (exchange->retry_task);
exchange->retry_task = NULL;
}
- GNUNET_assert (NULL == exchange->fo_head);
- GNUNET_assert (NULL == exchange->fo_tail);
+ GNUNET_assert (NULL == exchange->keys_head);
+ GNUNET_assert (NULL == exchange->keys_tail);
+ GNUNET_free (exchange->currency);
GNUNET_free (exchange->url);
GNUNET_free (exchange);
}
@@ -853,148 +1024,53 @@ free_exchange_entry (struct Exchange *exchange)
* about our failure, abort pending operations and retry later.
*
* @param exchange exchange that failed
- * @param hr details about the HTTP reply
- * @param compat version compatibility data
*/
static void
-fail_and_retry (struct Exchange *exchange,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- enum TALER_EXCHANGE_VersionCompatibility compat)
+fail_and_retry (struct TMH_Exchange *exchange)
{
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *keys;
- exchange->pending = true;
- if (NULL != exchange->wire_request)
- {
- TALER_EXCHANGE_wire_cancel (exchange->wire_request);
- exchange->wire_request = NULL;
- }
- if (NULL != exchange->wire_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->wire_task);
- exchange->wire_task = NULL;
- }
- while (NULL != (fo = exchange->fo_head))
- {
- fo->fc (fo->fc_cls,
- hr,
- NULL,
- NULL,
- NULL,
- GNUNET_NO);
- TMH_EXCHANGES_find_exchange_cancel (fo);
- }
- if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat)
- {
- /* Log hard error: we likely need admin help! */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Exchange `%s' runs an incompatible more recent version of the Taler protocol. Will not retry. This client may need to be updated.\n",
- exchange->url);
- /* Theoretically, the exchange could downgrade,
- but let's not be too aggressive about retries
- on this one. */
- exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS,
- exchange->retry_delay);
- }
- if ( (NULL == exchange->fo_head) &&
- (TALER_EC_GENERIC_CONFIGURATION_INVALID == hr->ec) )
+ exchange->state = ESTATE_FAILED;
+ while (NULL != (keys = exchange->keys_head))
{
- /* This can NEVER work, so don't retry */
- free_exchange_entry (exchange);
- return;
+ keys->fc (keys->fc_cls,
+ NULL,
+ exchange);
+ TMH_EXCHANGES_keys4exchange_cancel (keys);
}
- exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
+ exchange->retry_delay
+ = GNUNET_TIME_randomized_backoff (exchange->retry_delay,
+ RETRY_BACKOFF_THRESHOLD);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to fetch /keys from `%s': %d/%u, retrying in %s\n",
+ "Failed to fetch /keys from `%s'; retrying in %s\n",
exchange->url,
- (int) hr->ec,
- hr->http_status,
GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay,
- GNUNET_YES));
+ true));
if (NULL != exchange->retry_task)
GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->first_retry = GNUNET_TIME_relative_to_absolute (
- exchange->retry_delay);
- exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
- &retry_exchange,
- exchange);
+ exchange->first_retry
+ = GNUNET_TIME_relative_to_absolute (
+ exchange->retry_delay);
+ exchange->retry_task
+ = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
+ &retry_exchange,
+ exchange);
}
/**
- * Function called with information about who is auditing
- * a particular exchange and what key the exchange is using.
+ * Update our information in the database about the
+ * /keys of an exchange. Run inside of a database
+ * transaction scope that will re-try and/or commit
+ * depending on the return value.
*
- * @param cls closure, will be `struct Exchange` so that
- * when this function gets called, it will change the flag 'pending'
- * to 'false'. Note: 'keys' is automatically saved inside the exchange's
- * handle, which is contained inside 'struct Exchange', when
- * this callback is called. Thus, once 'pending' turns 'false',
- * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle,
- * in order to get the "good" keys.
- * @param hr http response details
- * @param keys information about the various keys used
- * by the exchange
- * @param compat version compatibility data
+ * @param keys information to persist
+ * @return transaction status
*/
-static void
-keys_mgmt_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat)
+static enum GNUNET_DB_QueryStatus
+insert_keys_data (const struct TALER_EXCHANGE_Keys *keys)
{
- struct Exchange *exchange = cls;
- struct GNUNET_TIME_Timestamp expire;
- struct GNUNET_TIME_Relative delay;
-
- if ( (MHD_HTTP_OK != hr->http_status) ||
- (NULL == keys) )
- {
- fail_and_retry (exchange,
- hr,
- compat);
- return;
- }
- if ( (exchange->trusted) &&
- (0 != GNUNET_memcmp (&exchange->master_pub,
- &keys->master_pub)) )
- {
- /* master pub differs => do not trust the exchange (without auditor) */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Master public key of exchange `%s' differs from our configuration. Not trusting exchange.\n",
- exchange->url);
- exchange->trusted = false;
- }
- if (! exchange->trusted)
- {
- exchange->master_pub = keys->master_pub;
- for (struct Exchange *e = exchange_head;
- NULL != e;
- e = e->next)
- {
- if (e == exchange)
- continue;
- if (! e->trusted)
- continue;
- if (0 ==
- GNUNET_memcmp (&e->master_pub,
- &exchange->master_pub))
- exchange->trusted = true; /* same exchange, different URL => trust applies */
- }
- }
- if (0 != (TALER_EXCHANGE_VC_NEWER & compat))
- {
- /* Warn user exactly once about need to upgrade */
- static int once;
-
- if (0 == once)
- {
- once = 1;
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange `%s' runs a more recent version of the Taler protocol. You may want to update this client.\n",
- exchange->url);
- }
- }
+ enum GNUNET_DB_QueryStatus qs;
/* store exchange online signing keys in our DB */
for (unsigned int i = 0; i<keys->num_sign_keys; i++)
@@ -1002,237 +1078,363 @@ keys_mgmt_cb (void *cls,
struct TALER_EXCHANGE_SigningPublicKey *sign_key = &keys->sign_keys[i];
enum GNUNET_DB_QueryStatus qs;
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->insert_exchange_signkey (TMH_db->cls,
- &keys->master_pub,
- &sign_key->key,
- sign_key->valid_from,
- sign_key->valid_until,
- sign_key->valid_legal,
- &sign_key->master_sig);
+ qs = TMH_db->insert_exchange_signkey (
+ TMH_db->cls,
+ &keys->master_pub,
+ &sign_key->key,
+ sign_key->valid_from,
+ sign_key->valid_until,
+ sign_key->valid_legal,
+ &sign_key->master_sig);
/* 0 is OK, we may already have the key in the DB! */
if (0 > qs)
{
- GNUNET_break (0);
- fail_and_retry (exchange,
- hr,
- compat);
- return;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
}
- exchange->first_retry = GNUNET_TIME_relative_to_absolute (RELOAD_DELAY);
- expire = TALER_EXCHANGE_check_keys_current (exchange->conn,
- TALER_EXCHANGE_CKF_NONE);
- if (0 == GNUNET_TIME_absolute_is_zero (expire.abs_time))
- {
- delay = RELOAD_DELAY;
- }
- else
+ qs = TMH_db->insert_exchange_keys (TMH_db->cls,
+ keys);
+ if (0 > qs)
{
- delay = GNUNET_TIME_absolute_get_remaining (expire.abs_time);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/keys response from expires at %s! Retrying at that time!\n",
- GNUNET_TIME_absolute2s (expire.abs_time));
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
- if (GNUNET_TIME_relative_is_zero (delay))
+
+ qs = TMH_db->delete_exchange_accounts (TMH_db->cls,
+ &keys->master_pub);
+ if (0 > qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "/keys response from exchange expired immediately! Retrying in 1 minute.\n");
- delay = GNUNET_TIME_UNIT_MINUTES;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
}
- exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
- if (NULL != exchange->retry_task)
- GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->retry_task
- = GNUNET_SCHEDULER_add_delayed (delay,
- &retry_exchange,
- exchange);
- exchange->pending = false;
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_request) &&
- (NULL == exchange->wire_task) )
+
+ for (unsigned int i = 0; i<keys->accounts_len; i++)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got key data, but also need wire data. Will request /wire now\n");
- exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
- &handle_wire_data,
- exchange);
- }
-}
+ const struct TALER_EXCHANGE_WireAccount *account = &keys->accounts[i];
+ json_t *debit_restrictions;
+ json_t *credit_restrictions;
+
+ debit_restrictions = json_array ();
+ GNUNET_assert (NULL != debit_restrictions);
+ credit_restrictions = json_array ();
+ GNUNET_assert (NULL != credit_restrictions);
+ for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
+ {
+ if (GNUNET_OK !=
+ add_restriction (debit_restrictions,
+ &account->debit_restrictions[j]))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
+ {
+ if (GNUNET_OK !=
+ add_restriction (credit_restrictions,
+ &account->credit_restrictions[j]))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ qs = TMH_db->insert_exchange_account (
+ TMH_db->cls,
+ &keys->master_pub,
+ account->payto_uri,
+ account->conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &account->master_sig);
+ json_decref (debit_restrictions);
+ json_decref (credit_restrictions);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ } /* end 'for all accounts' */
+ for (unsigned int i = 0; i<keys->fees_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireFeesByMethod *fbm = &keys->fees[i];
+ const char *wire_method = fbm->method;
+ const struct TALER_EXCHANGE_WireAggregateFees *fees = fbm->fees_head;
-/**
- * Task to return find operation result asynchronously to caller.
- *
- * @param cls a `struct TMH_EXCHANGES_FindOperation`
- */
-static void
-return_result (void *cls)
-{
- struct TMH_EXCHANGES_FindOperation *fo = cls;
- struct Exchange *exchange = fo->my_exchange;
+ while (NULL != fees)
+ {
+ struct GNUNET_HashCode h_wire_method;
+
+ GNUNET_CRYPTO_hash (wire_method,
+ strlen (wire_method) + 1,
+ &h_wire_method);
+ qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
+ &keys->master_pub,
+ &h_wire_method,
+ &fees->fees,
+ fees->start_date,
+ fees->end_date,
+ &fees->master_sig);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ fees = fees->next;
+ } /* all fees for this method */
+ } /* for all methods (i) */
- fo->at = NULL;
- if ( (process_find_operations (exchange)) &&
- (NULL == exchange->wire_request) &&
- (! exchange->pending) &&
- (NULL != exchange->wire_task) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Do not have current wire data. Will re-request /wire in 1 minute\n");
- exchange->wire_task
- = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
- &wire_task_cb,
- exchange);
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof (es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
+ };
+
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ keys->exchange_url,
+ strlen (keys->exchange_url) + 1);
}
+ return qs;
}
-struct TMH_EXCHANGES_FindOperation *
-TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
- const char *wire_method,
- int force_reload,
- TMH_EXCHANGES_FindContinuation fc,
- void *fc_cls)
+static void
+keys_mgmt_cb (void *cls,
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys)
{
- struct Exchange *exchange;
- struct TMH_EXCHANGES_FindOperation *fo;
- struct GNUNET_TIME_Timestamp now;
+ struct TMH_Exchange *exchange = cls;
+ enum GNUNET_DB_QueryStatus qs;
- if (NULL == merchant_curl_ctx)
- {
- GNUNET_break (0);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Trying to find chosen exchange `%s'\n",
- chosen_exchange);
- /* Check if the exchange is known */
- for (exchange = exchange_head; NULL != exchange; exchange = exchange->next)
+ exchange->conn = NULL;
+ if (MHD_HTTP_OK != kr->hr.http_status)
{
- /* test it by checking URL */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Comparing chosen exchange url '%s' with known url '%s'.\n",
- chosen_exchange,
- exchange->url);
- if (0 == strcmp (exchange->url,
- chosen_exchange))
+ if (GNUNET_TIME_absolute_is_future (exchange->first_retry))
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "The exchange `%s' is already known (good)\n",
- chosen_exchange);
- break;
+ /* /keys failed *before* the long polling threshold.
+ We apply the exponential back-off from now. */
+ exchange->first_retry
+ = GNUNET_TIME_relative_to_absolute (
+ exchange->retry_delay);
}
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
}
- if (NULL == exchange)
+ if (NULL == exchange->currency)
+ exchange->currency = GNUNET_strdup (keys->currency);
+ if (0 != strcmp (exchange->currency,
+ keys->currency))
{
- /* This is a new exchange */
- exchange = GNUNET_new (struct Exchange);
- exchange->url = GNUNET_strdup (chosen_exchange);
- exchange->pending = true;
- GNUNET_CONTAINER_DLL_insert (exchange_head,
- exchange_tail,
- exchange);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "The exchange `%s' is new\n",
- chosen_exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/keys response from `%s' is for currency `%s', but we expected `%s'\n",
+ exchange->url,
+ keys->currency,
+ exchange->currency);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
}
-
- fo = GNUNET_new (struct TMH_EXCHANGES_FindOperation);
- fo->fc = fc;
- fo->fc_cls = fc_cls;
- fo->my_exchange = exchange;
- if (NULL != wire_method)
- fo->wire_method = GNUNET_strdup (wire_method);
- GNUNET_CONTAINER_DLL_insert (exchange->fo_head,
- exchange->fo_tail,
- fo);
- if ( (force_reload) &&
- (GNUNET_TIME_absolute_is_past (exchange->first_retry)) )
+ exchange->state = ESTATE_DOWNLOADED;
+ TMH_db->preflight (TMH_db->cls);
+ for (unsigned int r = 0; r<MAX_RETRIES; r++)
{
- /* increment exponential-backoff */
- exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
- /* do not allow forced check until both backoff and #FORCED_RELOAD_DELAY
- are satisfied again */
- exchange->first_retry
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_max (
- exchange->retry_delay,
- FORCED_RELOAD_DELAY));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/keys retry forced, waiting until %s\n",
- GNUNET_TIME_absolute2s (exchange->first_retry));
- /* NOTE: return value tells us how long /keys should still
- be valid. */
- (void) TALER_EXCHANGE_check_keys_current (exchange->conn,
- TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
- return fo;
- }
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "update exchange key data"))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
+ }
- now = GNUNET_TIME_timestamp_get ();
- if ( (! exchange->pending) &&
- ( (NULL == fo->wire_method) ||
- (NULL != get_wire_fees (exchange,
- now,
- fo->wire_method)) ) )
+ qs = insert_keys_data (keys);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
+ }
+
+ qs = TMH_db->commit (TMH_db->cls);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
+ }
+ } /* end of retry loop */
+ if (qs < 0)
{
- /* We are not currently waiting for a reply, immediately
- return result */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "The exchange `%s' is ready\n",
- chosen_exchange);
- GNUNET_assert (NULL == fo->at);
- fo->at = GNUNET_SCHEDULER_add_now (&return_result,
- fo);
- return fo;
+ GNUNET_break (0);
+ fail_and_retry (exchange);
+ TALER_EXCHANGE_keys_decref (keys);
+ return;
}
+ TALER_EXCHANGE_keys_decref (keys);
+ exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+}
- /* If new or resumed, (re)try fetching /keys */
- if ( (NULL == exchange->conn) &&
- (NULL == exchange->retry_task) &&
- (exchange->pending) )
+
+enum GNUNET_GenericReturnValue
+TMH_EXCHANGES_lookup_wire_fee (
+ const struct TMH_Exchange *exchange,
+ const char *wire_method,
+ struct TALER_Amount *wire_fee)
+{
+ const struct FeesByWireMethod *fbm;
+ const struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ fbm = get_wire_fees (exchange,
+ GNUNET_TIME_timestamp_get (),
+ wire_method);
+ if (NULL == fbm)
+ return GNUNET_NO;
+ af = fbm->af;
+ *wire_fee = af->fees.wire;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TMH_exchange_check_debit (
+ const struct TMH_Exchange *exchange,
+ const struct TMH_WireMethod *wm)
+{
+ if (NULL == exchange->acc_head)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Do not have current /keys data for `%s'. Will request /keys now\n",
- chosen_exchange);
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
- exchange);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No accounts available for %s\n",
+ exchange->url);
+ return GNUNET_SYSERR;
}
- else if ( (! exchange->pending) &&
- (NULL == exchange->wire_task) &&
- (NULL == exchange->wire_request) )
+ for (struct ExchangeAccount *acc = exchange->acc_head;
+ NULL != acc;
+ acc = acc->next)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Do not have required wire data. Will re-request /wire now\n");
- exchange->wire_task = GNUNET_SCHEDULER_add_now (&wire_task_cb,
- exchange);
+ bool ok = true;
+
+ if (0 != strcmp (acc->wire_method,
+ wm->wire_method))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s wire method %s != %s\n",
+ exchange->url,
+ acc->wire_method,
+ wm->wire_method);
+ continue;
+ }
+ if (NULL != acc->conversion_url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s account requires currency conversion (not supported)\n",
+ exchange->url);
+ continue; /* never use accounts with conversion */
+ }
+ for (struct Restriction *r = acc->d_head;
+ NULL != r;
+ r = r->next)
+ {
+ switch (r->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break (0);
+ ok = false;
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ ok = false;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s account is disabled\n",
+ exchange->url);
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ if (0 != regexec (&r->details.regex.ex,
+ wm->payto_uri,
+ 0, NULL, 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s account regex does not match %s\n",
+ exchange->url,
+ wm->payto_uri);
+ ok = false;
+ }
+ break;
+ }
+ if (! ok)
+ break;
+ }
+
+ if (ok)
+ return GNUNET_OK;
}
- return fo;
+ return GNUNET_NO;
}
void
-TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo)
+TMH_exchange_get_trusted (TMH_ExchangeCallback cb,
+ void *cb_cls)
{
- struct Exchange *exchange = fo->my_exchange;
+ for (const struct TMH_Exchange *exchange = exchange_head;
+ NULL != exchange;
+ exchange = exchange->next)
+ {
+ if (! exchange->trusted)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s not trusted, skipping!\n",
+ exchange->url);
+ continue;
+ }
+ cb (cb_cls,
+ exchange->url,
+ exchange);
+ }
+}
- if (NULL != fo->at)
+
+bool
+TMH_test_exchange_configured_for_currency (
+ const char *currency)
+{
+ for (const struct TMH_Exchange *exchange = exchange_head;
+ NULL != exchange;
+ exchange = exchange->next)
{
- GNUNET_SCHEDULER_cancel (fo->at);
- fo->at = NULL;
+ if (! exchange->trusted)
+ continue;
+ if (NULL == exchange->currency)
+ continue;
+ if (0 == strcmp (currency,
+ exchange->currency))
+ return true;
}
- GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
- exchange->fo_tail,
- fo);
- GNUNET_free (fo->wire_method);
- GNUNET_free (fo);
+ return false;
}
/**
* Function called on each configuration section. Finds sections
- * about exchanges, parses the entries and tries to connect to
- * it in order to fetch /keys.
+ * about exchanges, parses the entries.
*
* @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *`
* @param section name of the section
@@ -1244,47 +1446,49 @@ accept_exchanges (void *cls,
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
char *url;
char *mks;
- struct Exchange *exchange;
+ struct TMH_Exchange *exchange;
char *currency;
+ if (GNUNET_SYSERR == trusted_exchange_count)
+ return;
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,
- "CURRENCY",
- &currency))
+ "EXCHANGE_BASE_URL",
+ &url))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
- "CURRENCY");
- return;
- }
- if (0 != strcasecmp (currency,
- TMH_currency))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange given in section `%s' is for another currency. Skipping.\n",
- section);
- GNUNET_free (currency);
+ "EXCHANGE_BASE_URL");
return;
}
- GNUNET_free (currency);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
- "EXCHANGE_BASE_URL",
- &url))
+ "CURRENCY",
+ &currency))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
- "EXCHANGE_BASE_URL");
+ "CURRENCY");
+ GNUNET_free (url);
return;
}
- exchange = GNUNET_new (struct Exchange);
- exchange->url = url;
+ exchange = lookup_exchange (url);
+ GNUNET_free (url);
+ if (NULL == exchange->currency)
+ exchange->currency = currency;
+ else
+ GNUNET_free (currency);
if (GNUNET_OK ==
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
@@ -1292,19 +1496,20 @@ accept_exchanges (void *cls,
&mks))
{
if (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_public_key_from_string (mks,
- strlen (mks),
- &exchange->master_pub.
- eddsa_pub))
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ mks,
+ strlen (mks),
+ &exchange->master_pub.eddsa_pub))
{
exchange->trusted = true;
+ trusted_exchange_count++;
}
else
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
section,
"MASTER_KEY",
- _ ("ill-formed EdDSA key"));
+ "malformed EdDSA key");
}
GNUNET_free (mks);
}
@@ -1313,79 +1518,174 @@ accept_exchanges (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"MASTER_KEY missing in section '%s', not trusting exchange\n",
section);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup exchange %s as %s\n",
+ exchange->url,
+ exchange->trusted ? "trusted" : "untrusted");
+ if (NULL != exchange->retry_task)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange at `%s' configured in multiple configuration sections (see `%s')!\n",
+ exchange->url,
+ section);
+ trusted_exchange_count = GNUNET_SYSERR;
+ return;
+ }
+ exchange->retry_task
+ = GNUNET_SCHEDULER_add_now (&retry_exchange,
+ exchange);
+}
+
+
+/**
+ * Trigger (re)loading of keys from DB.
+ *
+ * @param cls NULL
+ * @param extra base URL of the exchange that changed
+ * @param extra_len number of bytes in @a extra
+ */
+static void
+update_exchange_keys (void *cls,
+ const void *extra,
+ size_t extra_len)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ const char *url = extra;
+ struct TMH_Exchange *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
+ if ( (NULL == extra) ||
+ (0 == extra_len) )
+ {
+ GNUNET_break (0);
+ return;
}
- GNUNET_CONTAINER_DLL_insert (exchange_head,
- exchange_tail,
- exchange);
- exchange->pending = true;
- GNUNET_assert (NULL == exchange->retry_task);
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
- exchange);
+ if ('\0' != url[extra_len - 1])
+ {
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received keys change notification: reload `%s'\n",
+ url);
+ exchange = lookup_exchange (url);
+ qs = TMH_db->select_exchange_keys (TMH_db->cls,
+ url,
+ &keys);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (NULL == exchange->currency)
+ exchange->currency = GNUNET_strdup (keys->currency);
+ if (0 != strcmp (keys->currency,
+ exchange->currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/keys cached in our database are for currency `%s', but we expected `%s'\n",
+ keys->currency,
+ exchange->currency);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loaded /keys from database with %u accounts\n",
+ keys->accounts_len);
+ if (GNUNET_OK !=
+ set_exchange_accounts (exchange,
+ keys->accounts_len,
+ keys->accounts))
+ {
+ /* invalid account specification given */
+ GNUNET_break_op (0);
+ /* but: we can continue anyway, things may just not
+ work, but probably better than to not keep going. */
+ }
+ if (GNUNET_OK !=
+ process_wire_fees (exchange,
+ &keys->master_pub,
+ keys->fees_len,
+ keys->fees))
+ {
+ /* invalid wire fee specification given */
+ GNUNET_break_op (0);
+ /* but: we can continue anyway, things may just not
+ work, but probably better than to not keep going. */
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reloaded /keys of %s from database\n",
+ url);
+ TALER_EXCHANGE_keys_decref (exchange->keys);
+ exchange->keys = keys;
+ if ( (exchange->trusted) &&
+ (0 != GNUNET_memcmp (&exchange->master_pub,
+ &keys->master_pub)) )
+ {
+ /* master pub differs => do not trust the exchange (without auditor) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Master public key of exchange `%s' differs from our configuration. Not trusting exchange.\n",
+ exchange->url);
+ exchange->trusted = false;
+ }
+ if (! exchange->trusted)
+ {
+ exchange->master_pub = keys->master_pub;
+ for (struct TMH_Exchange *e = exchange_head;
+ NULL != e;
+ e = e->next)
+ {
+ if (e == exchange)
+ continue;
+ if (! e->trusted)
+ continue;
+ if (0 ==
+ GNUNET_memcmp (&e->master_pub,
+ &exchange->master_pub))
+ exchange->trusted = true; /* same exchange, different URL => trust applies */
+ }
+ }
+
+ process_find_operations (exchange);
}
enum GNUNET_GenericReturnValue
TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
- merchant_curl_ctx
- = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
- &merchant_curl_rc);
- if (NULL == merchant_curl_ctx)
+ /* get exchanges from the merchant configuration and try to connect to them */
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof (es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
+ };
+
+ keys_eh = TMH_db->event_listen (TMH_db->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &update_exchange_keys,
+ NULL);
}
- merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (merchant_curl_ctx);
- GNUNET_CURL_enable_async_scope_header (merchant_curl_ctx,
- "Taler-Correlation-Id");
- /* get exchanges from the merchant configuration and try to connect to them */
GNUNET_CONFIGURATION_iterate_sections (cfg,
&accept_exchanges,
(void *) cfg);
/* build JSON with list of trusted exchanges (will be included in contracts) */
- TMH_trusted_exchanges = json_array ();
- for (struct Exchange *exchange = exchange_head;
- NULL != exchange;
- exchange = exchange->next)
- {
- json_t *j_exchange;
-
- if (! exchange->trusted)
- continue;
- j_exchange = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("url",
- exchange->url),
- GNUNET_JSON_pack_data_auto ("master_pub",
- &exchange->master_pub));
- GNUNET_assert (0 ==
- json_array_append_new (TMH_trusted_exchanges,
- j_exchange));
- }
- return json_array_size (TMH_trusted_exchanges);
+ return trusted_exchange_count;
}
void
TMH_EXCHANGES_done ()
{
- while (NULL != exchange_head)
- free_exchange_entry (exchange_head);
- if (NULL != merchant_curl_ctx)
+ if (NULL != keys_eh)
{
- GNUNET_CURL_fini (merchant_curl_ctx);
- merchant_curl_ctx = NULL;
- }
- if (NULL != merchant_curl_rc)
- {
- GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
- merchant_curl_rc = NULL;
- }
- if (NULL != TMH_trusted_exchanges)
- {
- json_decref (TMH_trusted_exchanges);
- TMH_trusted_exchanges = NULL;
+ TMH_db->event_listen_cancel (keys_eh);
+ keys_eh = NULL;
}
+ while (NULL != exchange_head)
+ free_exchange_entry (exchange_head);
}