From 4131cf69ce62dc3b79c7d27d992a9a5f8d934c1c Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 6 Sep 2024 23:10:39 +0200 Subject: skeleton for kyccheck helper --- src/backend/taler-merchant-kyccheck.c | 640 +++++++++++++++++++++++++--------- 1 file changed, 481 insertions(+), 159 deletions(-) (limited to 'src/backend') diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c index 36970886..297106de 100644 --- a/src/backend/taler-merchant-kyccheck.c +++ b/src/backend/taler-merchant-kyccheck.c @@ -40,17 +40,6 @@ */ #define OPEN_INQUIRY_LIMIT 1024 -/** - * How many inquiries do we process concurrently per exchange at most. - */ -#define EXCHANGE_INQUIRY_LIMIT 16 - - -/** - * Information about an inquiry job. - */ -struct Inquiry; - /** * Information about an exchange. @@ -68,53 +57,48 @@ struct Exchange struct Exchange *prev; /** - * Head of active inquiries. + * The keys of this exchange */ - struct Inquiry *w_head; + struct TALER_EXCHANGE_Keys *keys; - /** - * Tail of active inquiries. - */ - struct Inquiry *w_tail; +}; - /** - * Which exchange are we tracking here. - */ - char *exchange_url; +/** + * Information about an Account. + */ +struct Account +{ /** - * The keys of this exchange + * Kept in a DLL. */ - struct TALER_EXCHANGE_Keys *keys; + struct Account *next; /** - * How many active inquiries do we have right now with this exchange. + * Kept in a DLL. */ - unsigned int exchange_inquiries; + struct Account *prev; /** - * How soon can may we, at the earliest, re-download /keys? + * Head of inquiries for this account. */ - struct GNUNET_TIME_Absolute first_retry; + struct Inquiry *i_head; /** - * How long should we wait between the next retry? + * Tail of inquiries for this account. */ - struct GNUNET_TIME_Relative retry_delay; + struct Inquiry *i_tail; /** - * How long should we wait between requests - * for transfer details? + * The payto-URI of this account. */ - struct GNUNET_TIME_Relative transfer_delay; + char *merchant_account_uri; /** - * False to indicate that there is an ongoing - * /keys transfer we are waiting for; - * true to indicate that /keys data is up-to-date. + * Database generation when this account + * was last active. */ - bool ready; - + uint64_t account_gen; }; @@ -134,9 +118,34 @@ struct Inquiry struct Inquiry *prev; /** - * Kept in a DLL. + * Main task for this inquiry. + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Which exchange is this inquiry about. + */ + struct Exchange *e; + + /** + * Which account is this inquiry about. + */ + struct Account *a; + + /** + * Did we not run this inquiry due to limits? + */ + bool limited; + + /** + * Do we believe this account's KYC is in good shape? + */ + bool kyc_ok; + + /** + * True if we did this account's KYC AUTH transfer. */ - struct Exchange *exchange; + bool auth_ok; }; @@ -151,6 +160,16 @@ static struct Exchange *e_head; */ static struct Exchange *e_tail; +/** + * Head of accounts. + */ +static struct Account *a_head; + +/** + * Tail of accounts. + */ +static struct Account *a_tail; + /** * The merchant's configuration. */ @@ -172,25 +191,32 @@ static struct GNUNET_CURL_Context *ctx; static struct GNUNET_CURL_RescheduleContext *rc; /** - * Main task for #find_work(). + * Event handler to learn that there may be new bank + * accounts to check. */ -static struct GNUNET_SCHEDULER_Task *task; +static struct GNUNET_DB_EventHandler *eh_accounts; /** - * Event handler to learn that there are new transfers - * to check. + * Event handler to learn that there may be new exchange + * keys to check. */ -static struct GNUNET_DB_EventHandler *eh; +static struct GNUNET_DB_EventHandler *eh_keys; /** - * How many active inquiries do we have right now. + * Main task to discover (new) accounts. */ -static unsigned int active_inquiries; +static struct GNUNET_SCHEDULER_Task *account_task; + +/** + * Counter determining how often we have called + * "select_accounts" on the database. + */ +static uint64_t database_gen; /** - * Set to true if we ever encountered any problem. + * How many active inquiries do we have right now. */ -static bool found_problem; +static unsigned int active_inquiries; /** * Value to return from main(). 0 on success, non-zero on errors. @@ -212,41 +238,49 @@ static bool at_limit; /** - * Finds new transfers that require work in the merchant database. + * Do KYC check work for the give inquiry in @a i. * - * @param cls NULL + * @param i inquiry to work on */ static void -find_work (void *cls); +inquiry_work (struct Inquiry *i); /** - * Free resources of @a w. - * - * @param[in] w inquiry job to terminate + * An inquiry finished, check if we should resume + * others. */ static void -end_inquiry (struct Inquiry *w) +end_inquiry (void) { - struct Exchange *e = w->exchange; - GNUNET_assert (active_inquiries > 0); active_inquiries--; - GNUNET_CONTAINER_DLL_remove (e->w_head, - e->w_tail, - w); - GNUNET_free (w); 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 Account *a = a_head; + NULL != a; + a = a->next) + { + for (struct Inquiry *i = a->i_head; + NULL != i; + i = i->next) + { + if (! i->limited) + continue; + GNUNET_assert (NULL == i->task); + /* done synchronously so that the active_inquiries + is updated immediately */ + inquiry_work (i); + if (at_limit) + break; + } + if (at_limit) + break; + } } - if ( (NULL == task) && - (! at_limit) && + if ( (! at_limit) && (0 == active_inquiries) && (test_mode) ) { @@ -258,83 +292,24 @@ end_inquiry (struct Inquiry *w) } -/** - * We're being aborted with CTRL-C (or SIGTERM). Shut down. - * - * @param cls closure (NULL) - */ -static void -shutdown_task (void *cls) -{ - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Running shutdown\n"); - while (NULL != e_head) - { - struct Exchange *e = e_head; - - while (NULL != e->w_head) - { - struct Inquiry *w = e->w_head; - - end_inquiry (w); - } - GNUNET_free (e->exchange_url); - if (NULL != e->keys) - { - TALER_EXCHANGE_keys_decref (e->keys); - e->keys = NULL; - } - GNUNET_CONTAINER_DLL_remove (e_head, - e_tail, - e); - GNUNET_free (e); - } - if (NULL != eh) - { - db_plugin->event_listen_cancel (eh); - eh = NULL; - } - if (NULL != task) - { - GNUNET_SCHEDULER_cancel (task); - task = NULL; - } - TALER_MERCHANTDB_plugin_unload (db_plugin); - db_plugin = NULL; - cfg = NULL; - if (NULL != ctx) - { - GNUNET_CURL_fini (ctx); - ctx = NULL; - } - if (NULL != rc) - { - GNUNET_CURL_gnunet_rc_destroy (rc); - rc = NULL; - } -} - - static void -find_work (void *cls) +inquiry_work (struct Inquiry *i) { // 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) + if (OPEN_INQUIRY_LIMIT <= active_inquiries) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not looking for work: at limit\n"); + i->limited = true; at_limit = true; return; } at_limit = false; + #if 0 + active_inquiries++; // FIXME: do actual work! qs = db_plugin->select_open_transfers (db_plugin->cls, limit, @@ -347,27 +322,163 @@ find_work (void *cls) GNUNET_SCHEDULER_shutdown (); return; } - if (qs == limit) +#endif + if ( (0 == active_inquiries) && + (test_mode) ) { - /* DB limited response, re-trigger DB interaction - the moment we significantly fall below the - limit */ - at_limit = true; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No more open inquiries and in test mode. Existing.\n"); + GNUNET_SCHEDULER_shutdown (); + return; } - if (0 == active_inquiries) +} + + +/** + * Start the KYC checking for account @a at exchange @a e. + * + * @param e an exchange + * @param a an account + */ +static void +start_inquiry (struct Exchange *e, + struct Account *a) +{ + struct Inquiry *i; + + i = GNUNET_new (struct Inquiry); + i->e = e; + i->a = a; + GNUNET_CONTAINER_DLL_insert (a->i_head, + a->i_tail, + i); + // FIXME: initial import from DB here!? + inquiry_work (i); +} + + +/** + * Stop KYC inquiry @a i. + * + * @param[in] i the inquiry to stop + */ +static void +stop_inquiry (struct Inquiry *i) +{ + struct Account *a = i->a; + + GNUNET_CONTAINER_DLL_remove (a->i_head, + a->i_tail, + i); + if (NULL != i->task) { - if (test_mode) + GNUNET_SCHEDULER_cancel (i->task); + i->task = NULL; + } + // FIXME: other clean-ups related to i here! + GNUNET_free (i); +} + + +/** + * Start inquries for all exchanges on account @a a. + * + * @param a an account + */ +static void +start_inquiries (struct Account *a) +{ + for (struct Exchange *e = e_head; + NULL != e; + e = e->next) + start_inquiry (e, + a); +} + + +/** + * Stop all inquries involving account @a a. + * + * @param a an account + */ +static void +stop_inquiries (struct Account *a) +{ + struct Inquiry *i; + + while (NULL != (i = a->i_head)) + stop_inquiry (i); +} + + +/** + * Callback invoked with information about a bank account. + * + * @param cls closure + * @param ad details about the account + */ +static void +account_cb ( + void *cls, + const struct TALER_MERCHANTDB_AccountDetails *ad) +{ + const char *payto_uri = ad->payto_uri; + + if (! ad->active) + return; + for (struct Account *a = a_head; + NULL != a; + a = a->next) + { + if (0 == + strcmp (payto_uri, + a->merchant_account_uri)) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No more open inquiries and in test mode. Existing.\n"); - GNUNET_SCHEDULER_shutdown (); + a->account_gen = database_gen; return; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No open inquiries found, waiting for notification to resume\n") - ; } -#endif + { + struct Account *a = GNUNET_new (struct Account); + + a->account_gen = database_gen; + a->merchant_account_uri = GNUNET_strdup (payto_uri); + GNUNET_CONTAINER_DLL_insert (a_head, + a_tail, + a); + start_inquiries (a); + } +} + + +/** + * The set of bank accounts has changed, update our + * list of active inquiries. + */ +static void +find_accounts (void *cls) +{ + enum GNUNET_DB_QueryStatus qs; + + (void) cls; + account_task = NULL; + database_gen++; + qs = db_plugin->select_accounts (db_plugin->cls, + NULL, /* all instances */ + &account_cb, + NULL); + if (qs < 0) + { + GNUNET_break (0); + return; + } + for (struct Account *a = a_head; + NULL != a; + a = a->next) + { + if (a->account_gen < database_gen) + stop_inquiries (a); + } } @@ -387,10 +498,206 @@ account_changed (void *cls, (void) cls; (void) extra; (void) extra_size; - if (NULL != task) + if (NULL != account_task) return; - task = GNUNET_SCHEDULER_add_now (&find_work, - NULL); + account_task + = GNUNET_SCHEDULER_add_now (&find_accounts, + NULL); +} + + +/** + * Interact with the database to get the current set + * of exchange keys known to us. + * + * @param exchange_url the exchange URL to check + */ +static void +find_keys (const char *exchange_url) +{ + enum GNUNET_DB_QueryStatus qs; + struct TALER_EXCHANGE_Keys *keys; + struct Exchange *e; + + qs = db_plugin->select_exchange_keys (db_plugin->cls, + exchange_url, + &keys); + if (qs < 0) + { + GNUNET_break (0); + return; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + GNUNET_break (0); + return; + } + for (e = e_head; NULL != e; e = e->next) + { + if (0 == strcmp (e->keys->exchange_url, + keys->exchange_url)) + { + TALER_EXCHANGE_keys_decref (e->keys); + e->keys = keys; + return; + } + } + e = GNUNET_new (struct Exchange); + e->keys = keys; + GNUNET_CONTAINER_DLL_insert (e_head, + e_tail, + e); + for (struct Account *a = a_head; + NULL != a; + a = a->next) + { + if (a->account_gen < database_gen) + start_inquiry (e, + a); + } +} + + +/** + * Function called when transfers are added to the merchant database. We look + * for more work. + * + * @param cls closure (NULL) + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra + */ +static void +keys_changed (void *cls, + const void *extra, + size_t extra_size) +{ + const char *url = extra; + + (void) cls; + if ( (NULL == extra) || + (0 == extra_size) ) + { + GNUNET_break (0); + return; + } + if ('\0' != url[extra_size - 1]) + { + GNUNET_break (0); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received keys change notification: reload `%s'\n", + url); + find_keys (url); +} + + +/** + * 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; + + (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; + } + find_keys (url); + GNUNET_free (url); +} + + +/** + * We're being aborted with CTRL-C (or SIGTERM). Shut down. + * + * @param cls closure (NULL) + */ +static void +shutdown_task (void *cls) +{ + (void) cls; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Running shutdown\n"); + while (NULL != e_head) + { + struct Exchange *e = e_head; + + if (NULL != e->keys) + { + TALER_EXCHANGE_keys_decref (e->keys); + e->keys = NULL; + } + GNUNET_CONTAINER_DLL_remove (e_head, + e_tail, + e); + GNUNET_free (e); + } + while (NULL != a_head) + { + struct Account *a = a_head; + + stop_inquiries (a); + GNUNET_CONTAINER_DLL_remove (a_head, + a_tail, + a); + GNUNET_free (a->merchant_account_uri); + GNUNET_free (a); + } + if (NULL != eh_accounts) + { + db_plugin->event_listen_cancel (eh_accounts); + eh_accounts = NULL; + } + if (NULL != account_task) + { + GNUNET_SCHEDULER_cancel (account_task); + account_task = NULL; + } + if (NULL != eh_keys) + { + db_plugin->event_listen_cancel (eh_keys); + eh_keys = NULL; + } + TALER_MERCHANTDB_plugin_unload (db_plugin); + db_plugin = NULL; + cfg = NULL; + if (NULL != ctx) + { + GNUNET_CURL_fini (ctx); + ctx = NULL; + } + if (NULL != rc) + { + GNUNET_CURL_gnunet_rc_destroy (rc); + rc = NULL; + } } @@ -442,21 +749,39 @@ run (void *cls, global_ret = EXIT_FAILURE; return; } + { + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) + }; + + eh_keys + = db_plugin->event_listen (db_plugin->cls, + &es, + GNUNET_TIME_UNIT_FOREVER_REL, + &keys_changed, + NULL); + } + GNUNET_CONFIGURATION_iterate_sections (cfg, + &accept_exchanges, + NULL); { struct GNUNET_DB_EventHeaderP es = { .size = htons (sizeof (es)), .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED) }; - eh = db_plugin->event_listen (db_plugin->cls, - &es, - GNUNET_TIME_UNIT_FOREVER_REL, - &account_changed, - NULL); + eh_accounts + = db_plugin->event_listen (db_plugin->cls, + &es, + GNUNET_TIME_UNIT_FOREVER_REL, + &account_changed, + NULL); } - GNUNET_assert (NULL == task); - task = GNUNET_SCHEDULER_add_now (&find_work, - NULL); + GNUNET_assert (NULL == account_task); + account_task + = GNUNET_SCHEDULER_add_now (&find_accounts, + NULL); } @@ -500,9 +825,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; } -- cgit v1.2.3