merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 58a20632468cbac1e72b8b353aec6743e4f243c6
parent cd514603d94bf0cf639d9e0ba117f1d5dbdcf670
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  6 Sep 2024 22:09:30 +0200

first implementation of exchangekeyupdate

Diffstat:
Msrc/backend/taler-merchant-exchangekeyupdate.c | 825++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 655 insertions(+), 170 deletions(-)

diff --git a/src/backend/taler-merchant-exchangekeyupdate.c b/src/backend/taler-merchant-exchangekeyupdate.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -28,24 +28,21 @@ #include "taler_merchantdb_plugin.h" /** - * Timeout for the exchange interaction. Rather long as we should do - * long-polling and do not want to wake up too often. + * Maximum frequency for the exchange interaction. */ -#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \ +#define EXCHANGE_MAXFREQ GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MINUTES, \ - 30) + 5) /** * How many inquiries do we process concurrently at most. */ #define OPEN_INQUIRY_LIMIT 1024 - /** - * Information about an inquiry job. + * How often do we retry after DB serialization errors (at most)? */ -struct Inquiry; - +#define MAX_RETRIES 3 /** * Information about an exchange. @@ -63,17 +60,22 @@ struct Exchange struct Exchange *prev; /** - * Which exchange are we tracking here. + * Base URL of the exchange are we tracking here. */ char *exchange_url; /** - * A connection to this exchange + * Expected currency of the exchange. + */ + char *currency; + + /** + * A /keys request to this exchange, NULL if not active. */ struct TALER_EXCHANGE_GetKeysHandle *conn; /** - * The keys of this exchange + * The keys of this exchange, NULL if not known. */ struct TALER_EXCHANGE_Keys *keys; @@ -83,28 +85,26 @@ struct Exchange struct GNUNET_SCHEDULER_Task *retry_task; /** + * Master public key expected for this exchange. + */ + struct TALER_MasterPublicKeyP master_pub; + + /** * How soon can may we, at the earliest, re-download /keys? */ struct GNUNET_TIME_Absolute first_retry; /** * How long should we wait between the next retry? + * Used for exponential back-offs. */ struct GNUNET_TIME_Relative retry_delay; /** - * How long should we wait between requests - * for transfer details? + * Are we waiting for /keys downloads due to our + * hard limit? */ - struct GNUNET_TIME_Relative transfer_delay; - - /** - * False to indicate that there is an ongoing - * /keys transfer we are waiting for; - * true to indicate that /keys data is up-to-date. - */ - bool ready; - + bool limited; }; @@ -129,6 +129,11 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg; static struct TALER_MERCHANTDB_Plugin *db_plugin; /** + * Our event handler listening for /keys forced downloads. + */ +static struct GNUNET_DB_EventHandler *eh; + +/** * Handle to the context for interacting with the bank. */ static struct GNUNET_CURL_Context *ctx; @@ -139,21 +144,11 @@ static struct GNUNET_CURL_Context *ctx; static struct GNUNET_CURL_RescheduleContext *rc; /** - * Main task for #find_work(). - */ -static struct GNUNET_SCHEDULER_Task *task; - -/** * How many active inquiries do we have right now. */ static unsigned int active_inquiries; /** - * Set to true if we ever encountered any problem. - */ -static bool found_problem; - -/** * Value to return from main(). 0 on success, non-zero on errors. */ static int global_ret; @@ -182,36 +177,32 @@ download_keys (void *cls); /** - * Finds new transfers that require work in the merchant database. - * - * @param cls NULL - */ -static void -find_work (void *cls); - - -/** - * Free resources of @a e. - * - * @param[in] e inquiry job to terminate + * An inquiry finished, check if we need to start more. */ static void -end_inquiry (struct Exchange *e) +end_inquiry (void) { GNUNET_assert (active_inquiries > 0); - active_inquiries--; if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) && - (NULL == task) && (at_limit) ) { at_limit = false; - GNUNET_assert (NULL == task); - task = GNUNET_SCHEDULER_add_now (&find_work, - NULL); + for (struct Exchange *e = e_head; + NULL != e; + e = e->next) + { + if (! e->limited) + continue; + e->limited = false; + /* done synchronously so that the active_inquiries + is updated immediately */ + download_keys (e); + if (at_limit) + break; + } } - if ( (NULL == task) && - (! at_limit) && + if ( (! at_limit) && (0 == active_inquiries) && (test_mode) ) { @@ -224,6 +215,268 @@ end_inquiry (struct Exchange *e) /** + * Add account restriction @a a to array of @a restrictions. + * + * @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; + +} + + +/** + * 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 keys information to persist + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +insert_keys_data (const struct TALER_EXCHANGE_Keys *keys) +{ + enum GNUNET_DB_QueryStatus qs; + + /* store exchange online signing keys in our DB */ + for (unsigned int i = 0; i<keys->num_sign_keys; i++) + { + const struct TALER_EXCHANGE_SigningPublicKey *sign_key + = &keys->sign_keys[i]; + + qs = db_plugin->insert_exchange_signkey ( + db_plugin->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 (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + } + + qs = db_plugin->insert_exchange_keys (db_plugin->cls, + keys); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + + qs = db_plugin->delete_exchange_accounts (db_plugin->cls, + &keys->master_pub); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + + for (unsigned int i = 0; i<keys->accounts_len; i++) + { + 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])) + { + db_plugin->rollback (db_plugin->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])) + { + db_plugin->rollback (db_plugin->cls); + GNUNET_break (0); + json_decref (debit_restrictions); + json_decref (credit_restrictions); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + qs = db_plugin->insert_exchange_account ( + db_plugin->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; + + while (NULL != fees) + { + struct GNUNET_HashCode h_wire_method; + + GNUNET_CRYPTO_hash (wire_method, + strlen (wire_method) + 1, + &h_wire_method); + qs = db_plugin->store_wire_fee_by_exchange ( + db_plugin->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) */ + + { + struct GNUNET_DB_EventHeaderP es = { + .size = ntohs (sizeof (es)), + .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) + }; + + db_plugin->event_notify (db_plugin->cls, + &es, + keys->exchange_url, + strlen (keys->exchange_url) + 1); + } + return qs; +} + + +/** + * Run database transaction to store the @a keys in + * the merchant database (and notify other processes + * that may care about them). + * + * @param keys the keys to store + * @return true on success + */ +static bool +store_keys (struct TALER_EXCHANGE_Keys *keys) +{ + enum GNUNET_DB_QueryStatus qs; + + db_plugin->preflight (db_plugin->cls); + for (unsigned int r = 0; r<MAX_RETRIES; r++) + { + if (GNUNET_OK != + db_plugin->start (db_plugin->cls, + "update exchange key data")) + { + db_plugin->rollback (db_plugin->cls); + GNUNET_break (0); + return false; + } + + qs = insert_keys_data (keys); + if (qs < 0) + { + db_plugin->rollback (db_plugin->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + continue; + GNUNET_break (0); + return false; + } + + qs = db_plugin->commit (db_plugin->cls); + if (qs < 0) + { + db_plugin->rollback (db_plugin->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + continue; + GNUNET_break (0); + return false; + } + } /* end of retry loop */ + if (qs < 0) + { + GNUNET_break (0); + return false; + } + return true; +} + + +/** * Function called with information about who is auditing * a particular exchange and what keys the exchange is using. * @@ -241,18 +494,39 @@ cert_cb ( struct GNUNET_TIME_Absolute n; e->conn = NULL; + /* limit retry */ + e->first_retry + = GNUNET_TIME_relative_to_absolute ( + EXCHANGE_MAXFREQ); switch (kr->hr.http_status) { case MHD_HTTP_OK: - e->ready = true; TALER_EXCHANGE_keys_decref (e->keys); - e->keys = keys; - end_inquiry (e); + e->keys = NULL; + if (0 != strcmp (e->currency, + keys->currency)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n", + e->exchange_url, + keys->currency, + e->currency); + break; + } + if (0 != GNUNET_memcmp (&keys->master_pub, + &e->master_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Master public key in %skeys response does not match. Ignoring response.\n", + e->exchange_url); + break; + } + if (! store_keys (keys)) + break; + e->keys = TALER_EXCHANGE_keys_incref (keys); /* Reset back-off */ - e->retry_delay = GNUNET_TIME_UNIT_ZERO; - /* Success: rate limit at once per minute */ - e->first_retry = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_UNIT_MINUTES); + e->retry_delay = EXCHANGE_MAXFREQ; + /* Limit by expiration */ n = GNUNET_TIME_absolute_max (e->first_retry, keys->key_data_expiration.abs_time); if (NULL != e->retry_task) @@ -260,19 +534,30 @@ cert_cb ( e->retry_task = GNUNET_SCHEDULER_add_at (n, &download_keys, e); - break; + end_inquiry (); + return; default: - e->retry_delay - = GNUNET_TIME_STD_BACKOFF (e->retry_delay); - e->first_retry - = GNUNET_TIME_relative_to_absolute (e->retry_delay); - if (NULL != e->retry_task) - GNUNET_SCHEDULER_cancel (e->retry_task); - e->retry_task = GNUNET_SCHEDULER_add_delayed (e->retry_delay, - &download_keys, - e); break; } + /* Try again (soon-ish) */ + e->retry_delay + = GNUNET_TIME_STD_BACKOFF (e->retry_delay); + n = GNUNET_TIME_absolute_max ( + e->first_retry, + GNUNET_TIME_relative_to_absolute (e->retry_delay)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Will download %skeys in %s\n", + e->exchange_url, + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining (n), + true)); + if (NULL != e->retry_task) + GNUNET_SCHEDULER_cancel (e->retry_task); + e->retry_task + = GNUNET_SCHEDULER_add_at (n, + &download_keys, + e); + end_inquiry (); } @@ -280,55 +565,291 @@ static void download_keys (void *cls) { struct Exchange *e = cls; - struct GNUNET_TIME_Relative n; - - /* If we do not hear back again soon, try again automatically */ - n = GNUNET_TIME_STD_BACKOFF (e->retry_delay); - n = GNUNET_TIME_relative_max (n, - GNUNET_TIME_UNIT_MINUTES); - e->retry_task = GNUNET_SCHEDULER_add_delayed (n, - &download_keys, - e); - - if ( (NULL == e->keys) || - (GNUNET_TIME_absolute_is_past (e->keys->key_data_expiration.abs_time)) ) - { - e->conn = TALER_EXCHANGE_get_keys (ctx, - e->exchange_url, - e->keys, - &cert_cb, - e); - if (NULL != e->conn) - active_inquiries++; + + e->retry_task = NULL; + GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); + if (OPEN_INQUIRY_LIMIT <= active_inquiries) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Cannot run job: at limit\n"); + e->limited = true; + at_limit = true; + return; + } + e->retry_delay + = GNUNET_TIME_STD_BACKOFF (e->retry_delay); + e->conn = TALER_EXCHANGE_get_keys (ctx, + e->exchange_url, + e->keys, + &cert_cb, + e); + if (NULL != e->conn) + { + active_inquiries++; + } + else + { + struct GNUNET_TIME_Relative n; + + n = GNUNET_TIME_relative_max (e->retry_delay, + EXCHANGE_MAXFREQ); + e->retry_task + = GNUNET_SCHEDULER_add_delayed (n, + &download_keys, + e); } } /** - * Lookup our internal data structure for the given - * @a exchange_url or create one if we do not yet have - * one. + * Lookup exchange by @a exchange_url. Create one + * if it does not exist. * - * @param exchange_url base URL of the exchange - * @return our state for this exchange + * @param exchange_url base URL to match against + * @return NULL if not found */ static struct Exchange * -find_exchange (const char *exchange_url) +lookup_exchange (const char *exchange_url) +{ + for (struct Exchange *e = e_head; + NULL != e; + e = e->next) + if (0 == strcmp (e->exchange_url, + exchange_url)) + return e; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Got notification about unknown exchange `%s'\n", + exchange_url); + return NULL; +} + + +/** + * Force immediate (re)loading of /keys for an exchange. + * + * @param cls NULL + * @param extra base URL of the exchange that changed + * @param extra_len number of bytes in @a extra + */ +static void +force_exchange_keys (void *cls, + const void *extra, + size_t extra_len) { + const char *url = extra; struct Exchange *e; - for (e = e_head; NULL != e; e = e->next) - if (0 == strcmp (exchange_url, - e->exchange_url)) - return e; - e = GNUNET_new (struct Exchange); - e->exchange_url = GNUNET_strdup (exchange_url); - GNUNET_CONTAINER_DLL_insert (e_head, - e_tail, + if ( (NULL == extra) || + (0 == extra_len) ) + { + GNUNET_break (0); + return; + } + if ('\0' != url[extra_len - 1]) + { + GNUNET_break (0); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received keys change notification: reload `%s'\n", + url); + e = lookup_exchange (url); + if (NULL == e) + { + GNUNET_break (0); + return; + } + if (NULL != e->conn) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Already downloading %skeys\n", + url); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Will download %skeys in %s\n", + url, + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + e->first_retry), + true)); + if (NULL != e->retry_task) + GNUNET_SCHEDULER_cancel (e->retry_task); + e->retry_task + = GNUNET_SCHEDULER_add_at (e->first_retry, + &download_keys, e); - e->retry_task = GNUNET_SCHEDULER_add_now (&download_keys, - e); - return e; +} + + +/** + * Function called on each configuration section. Finds sections + * about exchanges, parses the entries. + * + * @param cls NULL + * @param section name of the section + */ +static void +accept_exchanges (void *cls, + const char *section) +{ + char *url; + char *mks; + char *currency; + + (void) cls; + if (0 != + strncasecmp (section, + "merchant-exchange-", + strlen ("merchant-exchange-"))) + return; + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + "DISABLED")) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "EXCHANGE_BASE_URL", + &url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "EXCHANGE_BASE_URL"); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + for (struct Exchange *e = e_head; + NULL != e; + e = e->next) + { + if (0 == strcmp (url, + e->exchange_url)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n", + url, + section); + GNUNET_free (url); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "CURRENCY", + &currency)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "CURRENCY"); + GNUNET_free (url); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "MASTER_KEY", + &mks)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "MASTER_KEY"); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + GNUNET_free (currency); + GNUNET_free (url); + return; + } + + { + struct Exchange *e; + + e = GNUNET_new (struct Exchange); + e->exchange_url = url; + e->currency = currency; + GNUNET_CONTAINER_DLL_insert (e_head, + e_tail, + e); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string ( + mks, + strlen (mks), + &e->master_pub.eddsa_pub)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "MASTER_KEY", + "malformed EdDSA key"); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + GNUNET_free (mks); + return; + } + GNUNET_free (mks); + + { + enum GNUNET_DB_QueryStatus qs; + struct TALER_EXCHANGE_Keys *keys = NULL; + + qs = db_plugin->select_exchange_keys (db_plugin->cls, + url, + &keys); + if (qs < 0) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + if ( (NULL != keys) && + (0 != strcmp (keys->currency, + e->currency)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n", + keys->currency, + e->currency); + TALER_EXCHANGE_keys_decref (keys); + keys = NULL; + } + if ( (NULL != keys) && + (0 != GNUNET_memcmp (&e->master_pub, + &keys->master_pub)) ) + { + /* master pub differs => fetch keys again */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n", + e->exchange_url); + TALER_EXCHANGE_keys_decref (keys); + keys = NULL; + } + e->keys = keys; + if (NULL == keys) + { + /* done synchronously so that the active_inquiries + is updated immediately */ + + download_keys (e); + } + else + { + e->retry_task + = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time, + &download_keys, + e); + } + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Exchange `%s' setup\n", + e->exchange_url); + } } @@ -348,6 +869,7 @@ shutdown_task (void *cls) struct Exchange *e = e_head; GNUNET_free (e->exchange_url); + GNUNET_free (e->currency); if (NULL != e->conn) { TALER_EXCHANGE_get_keys_cancel (e->conn); @@ -368,10 +890,10 @@ shutdown_task (void *cls) e); GNUNET_free (e); } - if (NULL != task) + if (NULL != eh) { - GNUNET_SCHEDULER_cancel (task); - task = NULL; + db_plugin->event_listen_cancel (eh); + eh = NULL; } TALER_MERCHANTDB_plugin_unload (db_plugin); db_plugin = NULL; @@ -389,60 +911,6 @@ shutdown_task (void *cls) } -static void -find_work (void *cls) -{ - // enum GNUNET_DB_QueryStatus qs; - int limit; - - (void) cls; - task = NULL; - GNUNET_assert (OPEN_INQUIRY_LIMIT >= active_inquiries); - limit = OPEN_INQUIRY_LIMIT - active_inquiries; - if (0 == limit) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not looking for work: at limit\n"); - at_limit = true; - return; - } - at_limit = false; -#if FIXME - qs = db_plugin->select_open_transfers (db_plugin->cls, - limit, - &start_inquiry, - NULL); - if (qs < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to obtain open transfers from database\n"); - GNUNET_SCHEDULER_shutdown (); - return; - } - if (qs == limit) - { - /* DB limited response, re-trigger DB interaction - the moment we significantly fall below the - limit */ - at_limit = true; - } -#endif - if (0 == active_inquiries) - { - if (test_mode) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No more open inquiries and in test mode. Existing.\n"); - GNUNET_SCHEDULER_shutdown (); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No open inquiries found, waiting for notification to resume\n") - ; - } -} - - /** * First task. * @@ -491,9 +959,29 @@ run (void *cls, global_ret = EXIT_FAILURE; return; } - GNUNET_assert (NULL == task); - task = GNUNET_SCHEDULER_add_now (&find_work, - NULL); + { + struct GNUNET_DB_EventHeaderP es = { + .size = ntohs (sizeof (es)), + .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS) + }; + + eh = db_plugin->event_listen (db_plugin->cls, + &es, + GNUNET_TIME_UNIT_FOREVER_REL, + &force_exchange_keys, + NULL); + } + GNUNET_CONFIGURATION_iterate_sections (cfg, + &accept_exchanges, + NULL); + if ( (0 == active_inquiries) && + (test_mode) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No more open inquiries and in test mode. Existing.\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } } @@ -537,9 +1025,6 @@ main (int argc, return EXIT_INVALIDARGUMENT; if (GNUNET_NO == ret) return EXIT_SUCCESS; - if ( (found_problem) && - (0 == global_ret) ) - global_ret = 7; return global_ret; }