diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_exchanges.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.c | 2090 |
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", - ¤cy)) + "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", + ¤cy)) { 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); } |