commit 041a0aef99a9d88fcc041cb3dcf628a947c4305c
parent ea6759ed25de830d702963b7798a12baec847995
Author: Bohdan Potuzhnyi <potub1@bfh.ch>
Date: Fri, 11 Oct 2024 15:16:45 +0000
updated post function to send notification to the donaukeyupdate service
Diffstat:
3 files changed, 845 insertions(+), 1 deletion(-)
diff --git a/src/backend/.gitignore b/src/backend/.gitignore
@@ -4,3 +4,4 @@ taler-merchant-kyccheck
taler-merchant-reconciliation
taler-merchant-webhook
taler-merchant-wirewatch
+taler-merchant-donaukeyupdate
+\ No newline at end of file
diff --git a/src/backend/taler-merchant-donaukeyupdate.c b/src/backend/taler-merchant-donaukeyupdate.c
@@ -0,0 +1,829 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-donaukeyupdate.c
+ * @brief Process that ensures our /keys data for all Donau instances is current
+ * @author [Your Name]
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include <taler/taler_dbevents.h>
+#include "donau/donau_service.h"
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_plugin.h"
+#include "taler_merchant_bank_lib.h"
+
+/**
+ * Maximum frequency for the Donau interaction.
+ */
+#define DONAU_MAXFREQ GNUNET_TIME_relative_multiply( \
+ GNUNET_TIME_UNIT_MINUTES, \
+ 5)
+
+/**
+ * How many inquiries do we process concurrently at most.
+ */
+#define OPEN_INQUIRY_LIMIT 1024
+
+/**
+ * How often do we retry after DB serialization errors (at most)?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * Information about a Donau instance.
+ */
+struct Donau
+{
+ /**
+ * Pointer to the next Donau instance in the doubly linked list.
+ */
+ struct Donau *next;
+
+ /**
+ * Pointer to the previous Donau instance in the doubly linked list.
+ */
+ struct Donau *prev;
+
+ /**
+ * Base URL of the Donau instance being tracked.
+ * This URL is used to query the Donau service for keys and other resources.
+ */
+ char *donau_url;
+
+ /**
+ * Expected currency of the donau.
+ */
+ char *currency;
+
+ /**
+ * Pointer to the keys obtained from the Donau instance.
+ * This structure holds the cryptographic keys for the Donau instance.
+ */
+ struct DONAU_Keys *keys;
+
+ /**
+ * A handle for an ongoing /keys request to the Donau instance.
+ * This is NULL when there is no active request.
+ */
+ struct DONAU_GetKeysHandle *conn;
+
+ /**
+ * Scheduler task for retrying a failed /keys request.
+ * This task will trigger the next attempt to download the Donau keys if the previous request failed or needs to be retried.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * The earliest time at which the Donau instance can attempt another /keys request.
+ * This is used to manage the timing between requests and ensure compliance with rate-limiting rules.
+ */
+ struct GNUNET_TIME_Absolute first_retry;
+
+ /**
+ * The delay between the next retry for fetching /keys.
+ * Used to implement exponential backoff strategies for retries in case of failures.
+ */
+ struct GNUNET_TIME_Relative retry_delay;
+
+ /**
+ * A flag indicating whether this Donau instance is currently rate-limited.
+ * If true, the instance is temporarily paused from making further requests due to reaching a limit.
+ */
+ bool limited;
+};
+
+
+/**
+ * Head of known Donau instances.
+ */
+static struct Donau *d_head;
+
+/**
+ * Tail of known Donau instances.
+ */
+static struct Donau *d_tail;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+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 Donau services.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * How many active inquiries do we have right now.
+ */
+static unsigned int active_inquiries;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+/**
+ * True if the last DB query was limited by the
+ * #OPEN_INQUIRY_LIMIT and we thus should check again
+ * as soon as we are substantially below that limit,
+ * and not only when we get a DB notification.
+ */
+static bool at_limit;
+
+
+/**
+ * Function that initiates a /keys download for a Donau instance.
+ *
+ * @param cls a `struct Donau *`
+ */
+static void
+download_keys (void *cls);
+
+
+/**
+ * An inquiry finished, check if we need to start more.
+ */
+static void
+end_inquiry (void)
+{
+ GNUNET_assert (active_inquiries > 0);
+ active_inquiries--;
+ if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
+ (at_limit) )
+ {
+ at_limit = false;
+ for (struct Donau *d = d_head;
+ NULL != d;
+ d = d->next)
+ {
+ if (!d->limited)
+ continue;
+ d->limited = false;
+ /* done synchronously so that the active_inquiries
+ is updated immediately */
+ download_keys(d);
+ if (at_limit)
+ break;
+ }
+ }
+ if ( (! at_limit) &&
+ (0 == active_inquiries) &&
+ (test_mode) )
+ {
+ GNUNET_log(GNUNET_ERROR_TYPE_INFO,
+ "No more open inquiries and in test mode. Exiting.\n");
+ GNUNET_SCHEDULER_shutdown();
+ return;
+ }
+}
+
+/**
+ * Update Donau keys in the database.
+ *
+ * @param keys Donau keys to persist
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+insert_donau_keys_data(const struct DONAU_Keys *keys)
+{
+ return db_plugin->upsert_donau_keys(db_plugin->cls, keys);
+}
+
+/**
+ * Store Donau keys in the database and handle retries.
+ *
+ * @param keys the keys to store
+ * @return true on success
+ */
+static bool
+store_donau_keys(struct DONAU_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 donau key data"))
+ {
+ db_plugin->rollback(db_plugin->cls);
+ GNUNET_break (0);
+ return false;
+ }
+
+ qs = insert_donau_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;
+ }
+ }
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Callback after Donau keys are fetched.
+ *
+ * @param cls closure with a `struct Donau *`
+ * @param kr response data
+ * @param keys the keys of the Donau instance
+ */
+static void
+donau_cert_cb(
+ void *cls,
+ const struct DONAU_KeysResponse *kr,
+ struct DONAU_Keys *keys)
+{
+ struct Donau *d = cls;
+ struct GNUNET_TIME_Absolute n;
+
+ d->conn = NULL;
+ switch (kr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ if (! store_donau_keys (keys))
+ break;
+
+ d->keys = DONAU_keys_incref(keys);
+ /* Reset back-off */
+ d->retry_delay = DONAU_MAXFREQ;
+ /* limit retry */
+ d->first_retry =
+ GNUNET_TIME_relative_to_absolute(
+ DONAU_MAXFREQ);
+
+ //Fixme: hardcoded retry delay
+ n = GNUNET_TIME_absolute_max (d->first_retry,
+ GNUNET_TIME_relative_to_absolute(
+ GNUNET_TIME_relative_multiply(
+ GNUNET_TIME_UNIT_MONTHS,
+ 3)));
+
+ if (NULL != d->retry_task)
+ GNUNET_SCHEDULER_cancel(d->retry_task);
+ d->retry_task = GNUNET_SCHEDULER_add_at(n,
+ &download_keys,
+ d);
+
+ end_inquiry();
+ return;
+ default:
+ break;
+ }
+
+ d->retry_delay
+ = GNUNET_TIME_STD_BACKOFF(d->retry_delay);
+ n = GNUNET_TIME_absolute_max(
+ d->first_retry,
+ GNUNET_TIME_relative_to_absolute(d->retry_delay));
+
+ if (NULL != d->retry_task)
+ GNUNET_SCHEDULER_cancel(d->retry_task);
+ d->retry_task
+ = GNUNET_SCHEDULER_add_at(n,
+ &download_keys,
+ d);
+
+ end_inquiry();
+}
+
+/**
+ * Initiate the download of Donau keys.
+ *
+ * @param cls closure with a `struct Donau *`
+ */
+static void
+download_keys(void *cls)
+{
+ struct Donau *d = cls;
+
+ d->retry_task = NULL;
+ GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
+ if (OPEN_INQUIRY_LIMIT <= active_inquiries)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cannot start more donaukeys inquiries, already at limit\n");
+ d->limited = true;
+ at_limit = true;
+ return;
+ }
+ d->retry_delay
+ = GNUNET_TIME_STD_BACKOFF(d->retry_delay);
+ d->conn = DONAU_get_keys(ctx,
+ d->donau_url,
+ &donau_cert_cb,
+ d);
+
+ if (NULL != d->conn)
+ {
+ active_inquiries++;
+ }
+ else
+ {
+ struct GNUNET_TIME_Relative n;
+
+ n = GNUNET_TIME_relative_max(d->retry_delay,
+ DONAU_MAXFREQ);
+
+ d->retry_task
+ = GNUNET_SCHEDULER_add_delayed(n,
+ &download_keys,
+ d);
+ }
+}
+
+/**
+ * Lookup donau by @a donau_url. Create one
+ * if it does not exist.
+ *
+ * @param donau_url base URL to match against
+ * @return NULL if not found
+ */
+static struct Donau *
+lookup_donau(const char *donau_url)
+{
+ for (struct Donau *d = d_head;
+ NULL != d;
+ d = d->next)
+ if (0 == strcmp(d->donau_url,
+ donau_url))
+ return d;
+ GNUNET_log(GNUNET_ERROR_TYPE_WARNING,
+ "Got notification about unknown Donau `%s'\n",
+ donau_url);
+ return NULL;
+}
+
+/**
+ * Force immediate (re)loading of /keys for an donau.
+ *
+ * @param cls NULL
+ * @param extra base URL of the donau that changed
+ * @param extra_len number of bytes in @a extra
+ */
+static void
+force_donau_keys(void *cls,
+ const void *extra,
+ size_t extra_len)
+{
+ const char *url = extra;
+ struct Donau *d;
+
+ 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);
+
+ d = lookup_donau(url);
+ if (NULL == d)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (NULL != d->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 (
+ d->first_retry),
+ true));
+ if (NULL != d->retry_task)
+ GNUNET_SCHEDULER_cancel(d->retry_task);
+
+ d->retry_task
+ = GNUNET_SCHEDULER_add_at(d->first_retry,
+ &download_keys,
+ d);
+}
+
+/**
+ * Function called on each configuration section. Finds sections
+ * about donau, parses the entries.
+ *
+ * @param cls NULL
+ * @param section name of the section
+ */
+static void
+accept_donau(void *cls,
+ const char *section)
+{
+ char *url;
+ char *currency;
+ //char *mks;
+
+ if (0 !=
+ strncasecmp(section,
+ "merchant-donau-",
+ strlen ("merchant-donau-")))
+ return;
+ if (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno(cfg,
+ section,
+ "DISABLE"))
+ return;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string(cfg,
+ section,
+ "DONAU_URL",
+ &url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "DONAU_BASE_URL");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown();
+ return;
+ }
+
+ for (struct Donau *d = d_head;
+ NULL != d;
+ d = d->next)
+ {
+ if (0 == strcmp (url,
+ d->donau_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate donau URL `%s', maybe set DISABLED 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",
+ ¤cy))
+ {
+ 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 Donau *d;
+
+ d = GNUNET_new (struct Donau);
+ d->donau_url = url;
+ GNUNET_CONTAINER_DLL_insert (d_head,
+ d_tail,
+ d);
+// if(GNUNET_OK !=
+// GNUNET_CRYPTO_eddsa_public_key_from_string (
+// mks,
+// strlen(mks),
+// &d->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 DONAU_Keys *keys;
+
+ qs = db_plugin->lookup_donau_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,
+ d->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,
+ d->currency);
+ DONAU_keys_decref (keys);
+ keys = NULL;
+ }
+// if ( (NULL != keys) &&
+// (0 != GNUNET_memcmp (&d->master_pub,
+// &keys->master_pub)) )
+// {
+// /* master pub differs => fetch keys again */
+// GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+// "Master public key of donau `%s' differs from our configuration. Fetching /keys again.\n",
+// d->donau_url);
+// DONAU_keys_decref (keys);
+// keys = NULL;
+// }
+ d->keys = keys;
+ if (NULL == keys)
+ {
+ /* done synchronously so that the active_inquiries
+ is updated immediately */
+
+ download_keys (d);
+ }
+ else
+ {
+ d->retry_task
+ = GNUNET_SCHEDULER_add_at (GNUNET_TIME_relative_to_absolute(
+ GNUNET_TIME_relative_multiply(
+ GNUNET_TIME_UNIT_MONTHS,
+ 3)),
+ &download_keys,
+ d);
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "DONAU `%s' setup\n",
+ d->donau_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 != d_head)
+ {
+ struct Donau *d = d_head;
+
+ GNUNET_free (d->donau_url);
+ GNUNET_free (d->currency);
+ if (NULL != d->conn)
+ {
+ DONAU_get_keys_cancel (d->conn);
+ d->conn = NULL;
+ }
+ if (NULL != d->keys)
+ {
+ DONAU_keys_decref (d->keys);
+ d->keys = NULL;
+ }
+ if (NULL != d->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (d->retry_task);
+ d->retry_task = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (d_head,
+ d_tail,
+ d);
+ GNUNET_free (d);
+ }
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = 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;
+ }
+}
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run(void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ GNUNET_SCHEDULER_add_shutdown(&shutdown_task,
+ NULL);
+ ctx = GNUNET_CURL_init(&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create(ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if( NULL ==
+ (db_plugin = TALER_MERCHANTDB_plugin_load(cfg)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->connect (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs(sizeof(es)),
+ .type = ntohs(TALER_DBEVENT_MERCHANT_DONAU_KEYS)
+ };
+
+ eh = db_plugin->event_listen(db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &force_donau_keys,
+ NULL);
+ }
+ GNUNET_CONFIGURATION_iterate_sections(cfg,
+ &accept_donau,
+ NULL);
+
+ if ( (0 == active_inquiries) &&
+ (test_mode) )
+ {
+ GNUNET_log(GNUNET_ERROR_TYPE_INFO,
+ "No donaukeys inquiries to start, exiting.\n");
+ GNUNET_SCHEDULER_shutdown();
+ return;
+ }
+}
+
+/**
+ * The main function of taler-merchant-donaukeyupdate
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int main(int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_timetravel('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_version(VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args(argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+
+ TALER_OS_init();
+ ret = GNUNET_PROGRAM_run(
+ argc, argv,
+ "taler-merchant-donaukeyupdate",
+ gettext_noop(
+ "background process that ensures our key and configuration data on Donau is up-to-date"),
+ options,
+ &run, NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+/* end of taler-merchant-donaukeyupdate.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-donau-instance.c b/src/backend/taler-merchant-httpd_private-post-donau-instance.c
@@ -78,7 +78,20 @@ TMH_private_post_donau_instance (const struct TMH_RequestHandler *rh,
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return TALER_MHD_reply_static(connection, MHD_HTTP_NO_CONTENT, NULL, NULL, 0);
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof (es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
+ };
+
+ TMH_db->event_notify(TMH_db->cls,
+ &es,
+ donau_url,
+ strlen(donau_url) + 1);
+
+ return TALER_MHD_reply_static(connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL, NULL,
+ 0);
case GNUNET_DB_STATUS_HARD_ERROR:
return TALER_MHD_reply_with_error(connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,