diff options
-rw-r--r-- | debian/changelog | 7 | ||||
-rw-r--r-- | debian/taler-merchant.taler-merchant-depositcheck.service | 14 | ||||
-rw-r--r-- | doc/Makefile.am | 5 | ||||
m--------- | doc/prebuilt | 0 | ||||
-rw-r--r-- | src/backend/Makefile.am | 17 | ||||
-rw-r--r-- | src/backend/taler-merchant-depositcheck.c | 572 | ||||
-rw-r--r-- | src/backenddb/Makefile.am | 1 | ||||
-rw-r--r-- | src/backenddb/pg_lookup_pending_deposits.c | 40 | ||||
-rw-r--r-- | src/backenddb/pg_lookup_pending_deposits.h | 50 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 3 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 67 |
11 files changed, 601 insertions, 175 deletions
diff --git a/debian/changelog b/debian/changelog index ecd3e21c..52cddd43 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +taler-merchant (0.9.4) unstable; urgency=low + + * Add support for new taler-merchant-depositcheck service. + * Packages the v0.9.4 release. + + -- Christian Grothoff <grothoff@gnu.org> Sat, 6 Jan 2024 14:50:12 +0100 + taler-merchant (0.9.3-5) unstable; urgency=low * Tolerate missing currencies.conf, but log a warning. diff --git a/debian/taler-merchant.taler-merchant-depositcheck.service b/debian/taler-merchant.taler-merchant-depositcheck.service new file mode 100644 index 00000000..85964dbc --- /dev/null +++ b/debian/taler-merchant.taler-merchant-depositcheck.service @@ -0,0 +1,14 @@ +[Unit] +Description=GNU Taler payment system merchant deposit check service +After=postgres.service + +[Service] +User=taler-merchant-httpd +Type=simple +Restart=always +RestartSec=1s +ExecStart=/usr/bin/taler-merchant-depositcheck -c /etc/taler/taler.conf +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=full +RuntimeMaxSec=3600s diff --git a/doc/Makefile.am b/doc/Makefile.am index dadf6083..3d6852a7 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,12 +1,15 @@ SUBDIRS = . doxygen man_MANS = \ + prebuilt/man/taler-merchant-benchmark.1 prebuilt/man/taler-merchant-dbconfig.1\ prebuilt/man/taler-merchant-dbinit.1\ + prebuilt/man/taler-merchant-depositcheck.1\ prebuilt/man/taler-merchant-httpd.1 \ prebuilt/man/taler-merchant-passwd.1\ prebuilt/man/taler-merchant-setup-reserve.1\ - prebuilt/man/taler-merchant-benchmark.1 + prebuilt/man/taler-merchant-webhook.1 \ + prebuilt/man/taler-merchant-wirewatch.1 info_TEXINFOS = \ prebuilt/texinfo/taler-merchant-api-tutorial.texi \ diff --git a/doc/prebuilt b/doc/prebuilt -Subproject 5e47a72e8a2b5086dfdae4078f695155f5ed7af +Subproject 5e7026c5cef101c90b6dc58096e0a0946ef11b0 diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 7978258d..2fe09eec 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -16,6 +16,7 @@ EXTRA_DIST = \ $(pkgcfg_DATA) bin_PROGRAMS = \ + taler-merchant-depositcheck \ taler-merchant-exchange \ taler-merchant-httpd \ taler-merchant-webhook \ @@ -226,6 +227,22 @@ taler_merchant_webhook_LDADD = \ taler_merchant_webhook_CFLAGS = \ $(AM_CFLAGS) +taler_merchant_depositcheck_SOURCES = \ + taler-merchant-depositcheck.c +taler_merchant_depositcheck_LDADD = \ + $(top_builddir)/src/backenddb/libtalermerchantdb.la \ + -ltalerexchange \ + -ltalerjson \ + -ltalerutil \ + -ltalerpq \ + -ljansson \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -lcurl \ + $(XLIB) +taler_merchant_depositcheck_CFLAGS = \ + $(AM_CFLAGS) taler_merchant_wirewatch_SOURCES = \ taler-merchant-wirewatch.c diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c index 6982d891..4364c9f5 100644 --- a/src/backend/taler-merchant-depositcheck.c +++ b/src/backend/taler-merchant-depositcheck.c @@ -26,37 +26,59 @@ #include "taler_merchantdb_plugin.h" #include <taler/taler_dbevents.h> +/** + * How many requests do we make at most in parallel to the same exchange? + */ +#define CONCURRENCY_LIMIT 32 + /** * Information we keep per exchange. */ -struct ExchangeState +struct Child { /** * Kept in a DLL. */ - struct ExchangeState *next; + struct Child *next; /** * Kept in a DLL. */ - struct ExchangeState *prev; + struct Child *prev; + + /** + * The child process. + */ + struct GNUNET_OS_Process *process; + + /** + * Wait handle. + */ + struct GNUNET_ChildWaitHandle *cwh; /** * Which exchange is this state for? */ - const char *base_url; + char *base_url; /** - * Key material of the exchange. + * Task to restart the child. */ - struct TALER_EXCHANGE_Keys *keys; + struct GNUNET_SCHEDULER_Task *rt; /** - * Handle for active /keys request. + * When should the child be restarted at the earliest? */ - struct TALER_EXCHANGE_GetKeysHandle *gkh; + struct GNUNET_TIME_Absolute next_start; + + /** + * Current minimum delay between restarts, grows + * exponentially if child exits befor this time. + */ + struct GNUNET_TIME_Relative rd; + }; @@ -76,9 +98,9 @@ struct ExchangeInteraction struct ExchangeInteraction *prev; /** - * Exchange we are interacting with. + * Handle for exchange interaction. */ - struct ExchangeState *es; + struct TALER_EXCHANGE_DepositGetHandle *dgh; /** * Wire deadline for the deposit. @@ -115,18 +137,39 @@ struct ExchangeInteraction */ struct TALER_MerchantPrivateKeyP merchant_priv; + /** + * Serial number of the row in the deposits table + * that we are processing. + */ + uint64_t deposit_serial; + + /** + * The instance the deposit belongs to. + */ + char *instance_id; + }; /** - * Head of list of exchanges we interact with. + * Head of list of children we forked. + */ +static struct Child *c_head; + +/** + * Tail of list of children we forked. + */ +static struct Child *c_tail; + +/** + * Key material of the exchange. */ -static struct ExchangeState *e_head; +static struct TALER_EXCHANGE_Keys *keys; /** - * Tail of list of exchanges we interact with. + * Handle for active /keys request. */ -static struct ExchangeState *e_tail; +static struct TALER_EXCHANGE_GetKeysHandle *gkh; /** * Head of list of active exchange interactions. @@ -139,6 +182,11 @@ static struct ExchangeInteraction *w_head; static struct ExchangeInteraction *w_tail; /** + * Number of active entries in the @e w_head list. + */ +static uint64_t w_count; + +/** * Notification handler from database on new work. */ static struct GNUNET_DB_EventHandler *eh; @@ -149,6 +197,11 @@ static struct GNUNET_DB_EventHandler *eh; static const struct GNUNET_CONFIGURATION_Handle *cfg; /** + * Name of the configuration file we use. + */ +static const char *cfg_filename; + +/** * Our database plugin. */ static struct TALER_MERCHANTDB_Plugin *db_plugin; @@ -174,6 +227,12 @@ static struct GNUNET_CURL_Context *ctx; static struct GNUNET_CURL_RescheduleContext *rc; /** + * Which exchange are we monitoring? NULL if we + * are the parent of the workers. + */ +static char *exchange_url; + +/** * Value to return from main(). 0 on success, non-zero on errors. */ static int global_ret; @@ -192,7 +251,7 @@ static int test_mode; static void shutdown_task (void *cls) { - struct ExchangeState *es; + struct Child *c; struct ExchangeInteraction *w; (void) cls; @@ -208,24 +267,70 @@ shutdown_task (void *cls) GNUNET_SCHEDULER_cancel (task); task = NULL; } + if (NULL != gkh) + { + TALER_EXCHANGE_get_keys_cancel (gkh); + gkh = NULL; + } while (NULL != (w = w_head)) { GNUNET_CONTAINER_DLL_remove (w_head, w_tail, w); + if (NULL != w->dgh) + { + TALER_EXCHANGE_deposits_get_cancel (w->dgh); + w->dgh = NULL; + } + w_count--; + GNUNET_free (w->instance_id); GNUNET_free (w); } - while (NULL != (es = e_head)) + while (NULL != (c = c_head)) + { + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + + GNUNET_CONTAINER_DLL_remove (c_head, + c_tail, + c); + if (NULL != c->rt) + { + GNUNET_SCHEDULER_cancel (c->rt); + c->rt = NULL; + } + if (NULL != c->cwh) + { + GNUNET_wait_child_cancel (c->cwh); + c->cwh = NULL; + } + if (NULL != c->process) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (c->process, + SIGTERM)); + GNUNET_break (GNUNET_OK == + GNUNET_OS_process_wait_status (c->process, + &type, + &code)); + if ( (GNUNET_OS_PROCESS_EXITED != type) || + (0 != code) ) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Process for exchange %s had trouble (%d/%d)\n", + c->base_url, + (int) type, + (int) code); + GNUNET_OS_process_destroy (c->process); + } + GNUNET_free (c->base_url); + GNUNET_free (c); + } + if (NULL != db_plugin) { - GNUNET_CONTAINER_DLL_remove (e_head, - e_tail, - es); - GNUNET_free (es->base_url); - GNUNET_free (es); + db_plugin->rollback (db_plugin->cls); /* just in case */ + TALER_MERCHANTDB_plugin_unload (db_plugin); + db_plugin = NULL; } - db_plugin->rollback (db_plugin->cls); /* just in case */ - TALER_MERCHANTDB_plugin_unload (db_plugin); - db_plugin = NULL; cfg = NULL; if (NULL != ctx) { @@ -253,22 +358,21 @@ select_work (void *cls); * Make sure to run the select_work() task at * the @a next_deadline. * - * @param next_deadline deadline when work becomes ready + * @param deadline time when work becomes ready */ static void -run_at (struct GNUNET_TIME_Absolute next_deadline) +run_at (struct GNUNET_TIME_Absolute deadline) { if (GNUNET_TIME_absolute_cmp (deadline, - <, + >, next_deadline)) - { - if (NULL != task) - GNUNET_SCHEDULER_cancel (task); - next_deadline = deadline; - task = GNUNET_SCHEDULER_add_at (deadline, - &select_work, - NULL); - } + return; + if (NULL != task) + GNUNET_SCHEDULER_cancel (task); + next_deadline = deadline; + task = GNUNET_SCHEDULER_add_at (deadline, + &select_work, + NULL); } @@ -282,7 +386,7 @@ static void deposit_get_cb (void *cls, const struct TALER_EXCHANGE_GetDepositResponse *dr) { - struct ExchangeState *es = cls; + struct ExchangeInteraction *w = cls; switch (dr->hr.http_status) { @@ -293,10 +397,12 @@ deposit_get_cb (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange returned wire transfer over %s for deposited coin %s\n", TALER_amount2s (&dr->details.ok.coin_contribution), - TALER_B2S (&tq->coin_pub)); - qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls, - tq->deposit_serial, - &dr->details.ok); + TALER_B2S (&w->coin_pub)); + // FIXME: this must filter this particular entry from the + // select below for good! + qs = db_plugin->insert_deposit_to_transfer (db_plugin->cls, + w->deposit_serial, + &dr->details.ok); if (qs < 0) { GNUNET_break (0); @@ -316,13 +422,16 @@ deposit_get_cb (void *cls, "Exchange returned KYC requirement (%d/%d) for deposited coin %s\n", dr->details.accepted.kyc_ok, dr->details.accepted.aml_decision, - TALER_B2S (&tq->coin_pub)); + TALER_B2S (&w->coin_pub)); now = GNUNET_TIME_timestamp_get (); - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - gorc->hc->instance->settings.id, - &tq->h_wire, - tq->exchange_url, + // FIXME: this must filter this particular entry from the + // select below, at least until the KYC/AML request is + // satisfied; how will we learn that? + qs = db_plugin->account_kyc_set_status ( + db_plugin->cls, + w->instance_id, + &w->h_wire, + exchange_url, dr->details.accepted.requirement_row, NULL, NULL, @@ -340,8 +449,9 @@ deposit_get_cb (void *cls, default: { GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Exchange returned tracking failure for deposited coin %s\n", - TALER_B2S (&tq->coin_pub)); + "Exchange %s returned tracking failure for deposited coin %s\n", + exchange_url, + TALER_B2S (&w->coin_pub)); /* FIXME: how to handle? */ return; } @@ -350,107 +460,26 @@ deposit_get_cb (void *cls, GNUNET_CONTAINER_DLL_remove (w_head, w_tail, w); + w_count--; GNUNET_free (w); - // FIXME: not the best condition to go for more work, - // one down exchange would halt us entirely! - if (NULL == w_head) + if ( (w_count < CONCURRENCY_LIMIT / 2) || + (0 == w_count) ) task = GNUNET_SCHEDULER_add_now (&select_work, NULL); } /** - * Initiate request with the exchange about deposits. - * - * @param[in,out] exchange interaction handle - */ -static void -inquire_at_exchange (struct ExchangeInteraction *w) -{ - struct ExchangeState *es = w->es; - - GNUNET_assert (NULL == w->dgh); - w->dgh = TALER_EXCHANGE_deposits_get ( - ctx, - es->exchange_url, - es->keys, - &w->merchant_priv, - &w->h_wire, - &w->h_contract_terms, - &w->coin_pub, - GNUNET_TIME_UNIT_ZERO, - &deposit_get_cb, - w); -} - - -/** - * Function called with information about who is auditing - * a particular exchange and what keys the exchange is using. - * The ownership over the @a keys object is passed to - * the callee, thus it is given explicitly and not - * (only) via @a kr. - * - * @param cls closure - * @param kr response from /keys - * @param[in] keys keys object passed to callback with - * reference counter of 1. Must be freed by callee - * using #TALER_EXCHANGE_keys_decref(). NULL on failure. - */ -static void -keys_cb ( - void *cls, - const struct TALER_EXCHANGE_KeysResponse *kr, - struct TALER_EXCHANGE_Keys *keys) -{ - struct ExchangeState *es = cls; - - es->gkh = NULL; - if (NULL == keys) - return; - if (NULL != es->keys) - TALER_EXCHANGE_keys_decref (keys); - es->keys = TALER_EXCHANGE_keys_incref (keys); - /* Trigger all deposits blocked on fetching /keys */ - for (struct ExchangeInteraction *w = w_head; - NULL != w; - w = w->next) - { - if (w->es != es) - continue; - if (NULL != w->dgh) - continue; - inquire_at_exchange (w); - } -} - - -/** - * Download /keys from an exchange. - * - * @param[in,out] es exchange state - */ -static void -fetch_keys (struct ExchangeState *es) -{ - GNUNET_assert (NULL == es->gkh); - es->gkh = TALER_EXCHANGE_get_keys (ctx, - es->base_url, - es->keys, - &keys_cb, - es); -} - - -/** * Typically called by `select_work`. * * @param cls NULL * @param deposit_serial identifies the deposit operation - * @param exchange_url URL of the exchange that issued @a coin_pub - * @param amount_with_fee amount the exchange will deposit for this coin - * @param deposit_fee fee the exchange will charge for this coin - * @param h_wire hash of the merchant's wire account into which the deposit was made + * @param wire_deadline when is the wire due + * @param h_contract_terms hash of the contract terms + * @param merchant_priv private key of the merchant + * @param instance_id row ID of the instance + * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin which the deposit was made * @param coin_pub public key of the deposited coin */ static void @@ -458,16 +487,15 @@ pending_deposits_cb ( void *cls, uint64_t deposit_serial, struct GNUNET_TIME_Absolute wire_deadline, /* missing in DB! Funky migration needed! */ - const char *exchange_url, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_MerchantPrivateKeyP *merchant_priv, + const char *instance_id, const struct TALER_MerchantWireHashP *h_wire, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub) { struct ExchangeInteraction *w = GNUNET_new (struct ExchangeInteraction); - struct ExchangeState *es = NULL; (void) cls; if (GNUNET_TIME_absolute_is_future (wire_deadline)) @@ -475,6 +503,7 @@ pending_deposits_cb ( run_at (wire_deadline); return; } + w->deposit_serial = deposit_serial; w->wire_deadline = wire_deadline; w->h_contract_terms = *h_contract_terms; w->merchant_priv = *merchant_priv; @@ -482,29 +511,30 @@ pending_deposits_cb ( w->amount_with_fee = *amount_with_fee; w->deposit_fee = *deposit_fee; w->coin_pub = *coin_pub; + w->instance_id = GNUNET_strdup (instance_id); GNUNET_CONTAINER_DLL_insert (w_head, w_tail, w); - for (es = e_head; NULL != es; es = es->next) - if (0 == strcmp (exchange_url, - es->base_url)) - break; - if (NULL == es) + w_count++; + GNUNET_assert (NULL != keys); + if (GNUNET_TIME_absolute_is_past (keys->key_data_expiration.abs_time)) { - es = GNUNET_new (struct ExchangeState); - es->base_url = GNUNET_strdup (exchange_url); - GNUNET_CONTAINER_DLL_insert (e_head, - e_tail, - es); - } - w->es = es; - if ( (NULL == es->keys) || - (GNUNET_TIME_absolute_is_past (es->keys->key_data_expiration)) ) - { - fetch_keys (es); + /* Parent should re-start us, then we will re-fetch /keys */ + GNUNET_SCHEDULER_shutdown (); return; } - inquire_at_exchange (w); + GNUNET_assert (NULL == w->dgh); + w->dgh = TALER_EXCHANGE_deposits_get ( + ctx, + exchange_url, + keys, + &w->merchant_priv, + &w->h_wire, + &w->h_contract_terms, + &w->coin_pub, + GNUNET_TIME_UNIT_ZERO, + &deposit_get_cb, + w); } @@ -529,6 +559,8 @@ db_notify (void *cls, GNUNET_break (0); return; } + if (0 != w_count) + return; /* already at work! */ memcpy (&nbo_deadline, extra, extra_size); @@ -541,18 +573,35 @@ static void select_work (void *cls) { bool retry = false; + uint32_t limit = CONCURRENCY_LIMIT - w_count; (void) cls; task = NULL; + GNUNET_assert (NULL != keys); + if (GNUNET_TIME_absolute_is_past (keys->key_data_expiration.abs_time)) + { + /* Parent should re-start us, then we will re-fetch /keys */ + GNUNET_SCHEDULER_shutdown (); + return; + } while (1) { - struct GNUNET_TIME_Absolute now; enum GNUNET_DB_QueryStatus qs; - now = GNUNET_TIME_absolute_get (); db_plugin->preflight (db_plugin->cls); + // NOTE: + // SQL must filter all deposits for accounts + // that are already KYC/AML-blocked! + // (FIXME: How do we then learn about KYC unblocking?) + // Usually, select up to limit + // deposits with wire deadlines < now, + // or *1* deposit with smallest wire deadline if + // retry is 'true'. + if (retry) + limit = 1; qs = db_plugin->lookup_pending_deposits (db_plugin->cls, - now, + exchange_url, + limit, retry, &pending_deposits_cb, NULL); @@ -582,6 +631,178 @@ select_work (void *cls) /** + * Function called with information about who is auditing + * a particular exchange and what keys the exchange is using. + * The ownership over the @a keys object is passed to + * the callee, thus it is given explicitly and not + * (only) via @a kr. + * + * @param cls closure, NULL + * @param kr response from /keys + * @param[in] keys keys object passed to callback with + * reference counter of 1. Must be freed by callee + * using #TALER_EXCHANGE_keys_decref(). NULL on failure. + */ +static void +keys_cb ( + void *cls, + const struct TALER_EXCHANGE_KeysResponse *kr, + struct TALER_EXCHANGE_Keys *keys) +{ + gkh = NULL; + if (NULL == keys) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to download %skeys\n", + exchange_url); + GNUNET_SCHEDULER_shutdown (); + return; + } + keys = TALER_EXCHANGE_keys_incref (keys); + GNUNET_assert (NULL == task); + task = GNUNET_SCHEDULER_add_now (&select_work, + NULL); +} + + +/** + * Start a copy of this process with the exchange URL + * set to the given @a base_url + * + * @param base_url base URL to run with + */ +static struct GNUNET_OS_Process * +start_worker (const char *base_url) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Launching worker for exchange `%s'\n", + base_url); + return GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL, + NULL, + NULL, + NULL, + "taler-merchant-depositcheck", + "-c", cfg_filename, + "-e", base_url, + NULL); +} + + +/** + * Restart worker process for the given child. + * + * @param cls a `struct Child *` that needs a worker. + */ +static void +restart_child (void *cls); + + +/** + * Function called upon death or completion of a child process. + * + * @param cls a `struct Child *` + * @param type type of the process + * @param exit_code status code of the process + */ +static void +child_done_cb (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int exit_code) +{ + struct Child *c = cls; + + c->cwh = NULL; + if ( (GNUNET_OS_PROCESS_EXITED != type) || + (0 != exit_code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Process for exchange %s had trouble (%d/%d)\n", + c->base_url, + (int) type, + (int) exit_code); + GNUNET_SCHEDULER_shutdown (); + global_ret = 1; + return; + } + GNUNET_OS_process_destroy (c->process); + if (GNUNET_TIME_absolute_is_future (c->next_start)) + c->rd = GNUNET_TIME_STD_BACKOFF (c->rd); + else + c->rd = GNUNET_TIME_UNIT_SECONDS; + c->rt = GNUNET_SCHEDULER_add_at (c->next_start, + &restart_child, + c); +} + + +static void +restart_child (void *cls) +{ + struct Child *c = cls; + + c->rt = NULL; + c->next_start = GNUNET_TIME_relative_to_absolute (c->rd); + c->process = start_worker (c->base_url); + if (NULL == c->process) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "exec"); + global_ret = 1; + GNUNET_SCHEDULER_shutdown (); + return; + } + c->cwh = GNUNET_wait_child (c->process, + &child_done_cb, + c); +} + + +/** + * Function to iterate over section. + * + * @param cls closure + * @param section name of the section + */ +static void +cfg_iter_cb (void *cls, + const char *section) +{ + char *base_url; + struct Child *c; + + 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", + &base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + section, + "EXCHANGE_BASE_URL"); + return; + } + c = GNUNET_new (struct Child); + c->rd = GNUNET_TIME_UNIT_SECONDS; + c->base_url = base_url; + GNUNET_CONTAINER_DLL_insert (c_head, + c_tail, + c); + c->rt = GNUNET_SCHEDULER_add_now (&restart_child, + c); +} + + +/** * First task. * * @param cls closure, NULL @@ -596,11 +817,25 @@ run (void *cls, const struct GNUNET_CONFIGURATION_Handle *c) { (void) args; - (void) cfgfile; cfg = c; + cfg_filename = cfgfile; GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); + if (NULL == exchange_url) + { + GNUNET_CONFIGURATION_iterate_sections (c, + &cfg_iter_cb, + NULL); + if (NULL == c_head) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No exchanges found in configuration\n"); + return; + } + return; + } + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc); rc = GNUNET_CURL_gnunet_rc_create (ctx); @@ -616,6 +851,7 @@ run (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize DB subsystem\n"); GNUNET_SCHEDULER_shutdown (); + global_ret = 1; return; } if (GNUNET_OK != @@ -624,6 +860,7 @@ run (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to connect to database\n"); GNUNET_SCHEDULER_shutdown (); + global_ret = 1; return; } { @@ -638,9 +875,11 @@ run (void *cls, &db_notify, NULL); } - GNUNET_assert (NULL == task); - task = GNUNET_SCHEDULER_add_now (&select_work, - NULL); + gkh = TALER_EXCHANGE_get_keys (ctx, + exchange_url, + NULL, + &keys_cb, + NULL); } @@ -656,6 +895,11 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('e', + "exchange", + "BASE_URL", + "limit us to checking deposits of this exchange", + &exchange_url), GNUNET_GETOPT_option_flag ('t', "test", "run in test mode and exit when idle", diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index 1818a877..8681d0ec 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -79,6 +79,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_select_account_by_uri.h pg_select_account_by_uri.c \ pg_lookup_reserves.h pg_lookup_reserves.c \ pg_lookup_instance_auth.h pg_lookup_instance_auth.c \ + pg_lookup_pending_deposits.h pg_lookup_pending_deposits.c \ pg_insert_instance.h pg_insert_instance.c \ pg_account_kyc_set_status.h pg_account_kyc_set_status.c \ pg_account_kyc_get_status.h pg_account_kyc_get_status.c \ diff --git a/src/backenddb/pg_lookup_pending_deposits.c b/src/backenddb/pg_lookup_pending_deposits.c new file mode 100644 index 00000000..411b7eb5 --- /dev/null +++ b/src/backenddb/pg_lookup_pending_deposits.c @@ -0,0 +1,40 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_lookup_pending_deposits.c + * @brief Implementation of the lookup_pending_deposits function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_pending_deposits.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_pending_deposits ( + void *cls, + const char *exchange_url, + uint32_t limit, + bool allow_future, + TALER_MERCHANTDB_PendingDepositsCallback cb, + void *cb_cls) +{ + GNUNET_break (0); + return -2; // FIXME! +} diff --git a/src/backenddb/pg_lookup_pending_deposits.h b/src/backenddb/pg_lookup_pending_deposits.h new file mode 100644 index 00000000..47cadfac --- /dev/null +++ b/src/backenddb/pg_lookup_pending_deposits.h @@ -0,0 +1,50 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_lookup_pending_deposits.h + * @brief implementation of the lookup_pending_deposits function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_LOOKUP_PENDING_DEPOSITS_H +#define PG_LOOKUP_PENDING_DEPOSITS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Lookup deposits that are finished and awaiting a wire transfer. + * + * @param cls closure + * @param exchange_url exchange to filter deposits by + * @param limit maximum number of deposits to return + * @param allow_future true to allow deposits with wire deadline in the future + * @param cb function to call with deposit data + * @param cb_cls closure for @a cb + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_pending_deposits ( + void *cls, + const char *exchange_url, + uint32_t limit, + bool allow_future, + TALER_MERCHANTDB_PendingDepositsCallback cb, + void *cb_cls); + + +#endif diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 0e538ba9..e57e617f 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -43,6 +43,7 @@ #include "pg_update_account.h" #include "pg_lookup_instances.h" #include "pg_lookup_transfers.h" +#include "pg_lookup_pending_deposits.h" #include "pg_update_wirewatch_progress.h" #include "pg_select_wirewatch_accounts.h" #include "pg_select_open_transfers.h" @@ -582,6 +583,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_insert_webhook; plugin->update_webhook = &TMH_PG_update_webhook; + plugin->lookup_pending_deposits + = &TMH_PG_lookup_pending_deposits; plugin->lookup_webhook_by_event = &TMH_PG_lookup_webhook_by_event; plugin->lookup_all_webhooks diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 7e2b748c..8635c350 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -796,6 +796,33 @@ typedef void /** + * Callback for results from `lookup_pending_deposits`. + * + * @param cls NULL + * @param deposit_serial identifies the deposit operation + * @param wire_deadline when is the wire due + * @param h_contract_terms hash of the contract terms + * @param merchant_priv private key of the merchant + * @param instance_id name of the instance + * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin which the deposit was made + * @param coin_pub public key of the deposited coin + */ +typedef void +(*TALER_MERCHANTDB_PendingDepositsCallback) ( + void *cls, + uint64_t deposit_serial, + struct GNUNET_TIME_Absolute wire_deadline, /* missing in DB! Funky migration needed! */ + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const char *instance_id, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub); + + +/** * Function called with detailed information about a wire transfer and * the underlying deposits that are being aggregated. * @@ -3629,11 +3656,11 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_token_family)(void *cls, - const char *instance_id, - const char *token_family_slug, - const struct - TALER_MERCHANTDB_TokenFamilyDetails *details); + (*update_token_family)( + void *cls, + const char *instance_id, + const char *token_family_slug, + const struct TALER_MERCHANTDB_TokenFamilyDetails *details); /** @@ -3646,11 +3673,31 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_token_family)(void *cls, - const char *instance_id, - const char *token_family_slug, - const struct - TALER_MERCHANTDB_TokenFamilyDetails *details); + (*insert_token_family)( + void *cls, + const char *instance_id, + const char *token_family_slug, + const struct TALER_MERCHANTDB_TokenFamilyDetails *details); + + /** + * Lookup deposits that are finished and awaiting a wire transfer. + * + * @param cls closure + * @param exchange_url exchange to filter deposits by + * @param limit maximum number of deposits to return + * @param allow_future true to allow deposits with wire deadline in the future + * @param cb function to call with deposit data + * @param cb_cls closure for @a cb + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*lookup_pending_deposits)( + void *cls, + const char *exchange_url, + uint32_t limit, + bool allow_future, + TALER_MERCHANTDB_PendingDepositsCallback cb, + void *cb_cls); }; |