diff options
author | Christian Grothoff <christian@grothoff.org> | 2022-07-29 09:57:10 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2022-07-29 09:57:10 +0200 |
commit | 2056bc82f9aac337a9c7683cdb0aa43debd4d50c (patch) | |
tree | 3d33d553b6854865b9558bb37132cd6dbeed8d5a /src | |
parent | c1b43de5b4b5a1b4512c6e1a6f87b830df240fc9 (diff) | |
download | exchange-2056bc82f9aac337a9c7683cdb0aa43debd4d50c.tar.xz |
expand taler-exchange-offline and libtalerexchange with management-drain-profits implementation (#4960)
Diffstat (limited to 'src')
-rw-r--r-- | src/exchange-tools/taler-exchange-offline.c | 283 | ||||
-rw-r--r-- | src/include/taler_exchange_service.h | 57 | ||||
-rw-r--r-- | src/lib/Makefile.am | 1 | ||||
-rw-r--r-- | src/lib/exchange_api_management_drain_profits.c | 213 |
4 files changed, 554 insertions, 0 deletions
diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 41cd2597e..d6245b3ff 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -107,6 +107,10 @@ */ #define OP_EXTENSIONS "exchange-extensions-0" +/** + * Generate message to drain profits. + */ +#define OP_DRAIN_PROFITS "exchange-drain-profits-0" /** * Our private key, initialized in #load_offline_key(). @@ -385,6 +389,34 @@ struct WireFeeRequest /** + * Data structure for draining profits. + */ +struct DrainProfitsRequest +{ + + /** + * Kept in a DLL. + */ + struct DrainProfitsRequest *next; + + /** + * Kept in a DLL. + */ + struct DrainProfitsRequest *prev; + + /** + * Operation handle. + */ + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *h; + + /** + * Array index of the associated command. + */ + size_t idx; +}; + + +/** * Data structure for announcing global fees. */ struct GlobalFeeRequest @@ -576,6 +608,17 @@ static struct UploadExtensionsRequest *uer_head; static struct UploadExtensionsRequest *uer_tail; /** + * Active drain profits requests. + */ +struct DrainProfitsRequest *dpr_head; + +/** + * Active drain profits requests. + */ +static struct DrainProfitsRequest *dpr_tail; + + +/** * Shutdown task. Invoked when the application is being terminated. * * @param cls NULL @@ -736,6 +779,23 @@ do_shutdown (void *cls) GNUNET_free (uer); } } + + { + struct DrainProfitsRequest *dpr; + + while (NULL != (dpr = dpr_head)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Aborting incomplete drain profits request #%u\n", + (unsigned int) dpr->idx); + TALER_EXCHANGE_management_drain_profits_cancel (dpr->h); + GNUNET_CONTAINER_DLL_remove (dpr_head, + dpr_tail, + dpr); + GNUNET_free (dpr); + } + } + if (NULL != out) { json_dumpf (out, @@ -790,6 +850,7 @@ test_shutdown (void) (NULL == gfr_head) && (NULL == ukr_head) && (NULL == uer_head) && + (NULL == dpr_head) && (NULL == mgkh) && (NULL == nxt) ) GNUNET_SCHEDULER_shutdown (); @@ -1750,6 +1811,112 @@ upload_global_fee (const char *exchange_url, /** + * Function called with information about the drain profits operation. + * + * @param cls closure with a `struct DrainProfitsRequest` + * @param hr HTTP response data + */ +static void +drain_profits_cb ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr) +{ + struct DrainProfitsRequest *dpr = cls; + + if (MHD_HTTP_NO_CONTENT != hr->http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Upload failed for command %u with status %u: %s (%s)\n", + (unsigned int) dpr->idx, + hr->http_status, + TALER_ErrorCode_get_hint (hr->ec), + hr->hint); + global_ret = EXIT_FAILURE; + } + GNUNET_CONTAINER_DLL_remove (dpr_head, + dpr_tail, + dpr); + GNUNET_free (dpr); + test_shutdown (); +} + + +/** + * Upload drain profit action. + * + * @param exchange_url base URL of the exchange + * @param idx index of the operation we are performing (for logging) + * @param value arguments for drain profits + */ +static void +upload_drain (const char *exchange_url, + size_t idx, + const json_t *value) +{ + struct TALER_WireTransferIdentifierRawP wtid; + struct TALER_MasterSignatureP master_sig; + const char *err_name; + unsigned int err_line; + struct TALER_Amount amount; + struct GNUNET_TIME_Timestamp date; + const char *payto_uri; + const char *account_section; + struct DrainProfitsRequest *dpr; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("wtid", + &wtid), + TALER_JSON_spec_amount ("amount", + currency, + &amount), + GNUNET_JSON_spec_timestamp ("date", + &date), + GNUNET_JSON_spec_string ("account_section", + &account_section), + GNUNET_JSON_spec_string ("payto_uri", + &payto_uri), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + &err_name, + &err_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid input to drain profits: %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) idx); + json_dumpf (value, + stderr, + JSON_INDENT (2)); + global_ret = EXIT_FAILURE; + test_shutdown (); + return; + } + dpr = GNUNET_new (struct DrainProfitsRequest); + dpr->idx = idx; + dpr->h = + TALER_EXCHANGE_management_drain_profits (ctx, + exchange_url, + &wtid, + &amount, + date, + account_section, + payto_uri, + &master_sig, + &drain_profits_cb, + dpr); + GNUNET_CONTAINER_DLL_insert (dpr_head, + dpr_tail, + dpr); +} + + +/** * Function called with information about the post upload keys operation result. * * @param cls closure with a `struct UploadKeysRequest` @@ -2099,6 +2266,10 @@ trigger_upload (const char *exchange_url) .cb = &upload_keys }, { + .key = OP_DRAIN_PROFITS, + .cb = &upload_drain + }, + { .key = OP_EXTENSIONS, .cb = &upload_extensions }, @@ -2788,6 +2959,112 @@ do_set_global_fee (char *const *args) /** + * Drain profits from exchange's escrow account to + * regular exchange account. + * + * @param args the array of command-line arguments to process next; + * args[0] must be the amount, + * args[1] must be the section of the escrow account to drain + * args[2] must be the payto://-URI of the target account + */ +static void +do_drain (char *const *args) +{ + struct TALER_WireTransferIdentifierRawP wtid; + struct GNUNET_TIME_Timestamp date; + struct TALER_Amount amount; + const char *account_section; + const char *payto_uri; + struct TALER_MasterSignatureP master_sig; + char *err; + + if (NULL != in) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Downloaded data was not consumed, refusing drain\n"); + test_shutdown (); + global_ret = EXIT_FAILURE; + return; + } + if ( (NULL == args[0]) || + (NULL == args[1]) || + (NULL == args[2]) || + (GNUNET_OK != + TALER_string_to_amount (args[0], + &amount)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Drain requires an amount, section name and target payto://-URI as arguments\n"); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if ( (NULL == args[0]) || + (NULL == args[1]) || + (NULL == args[2]) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Drain requires an amount, section name and target payto://-URI as arguments\n"); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if (GNUNET_OK != + TALER_string_to_amount (args[0], + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid amount `%s' specified for drain\n", + args[0]); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + account_section = args[1]; + payto_uri = args[2]; + err = TALER_payto_validate (payto_uri); + if (NULL != err) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid payto://-URI `%s' specified for drain: %s\n", + payto_uri, + err); + GNUNET_free (err); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if (GNUNET_OK != + load_offline_key (GNUNET_NO)) + return; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &wtid, + sizeof (wtid)); + date = GNUNET_TIME_timestamp_get (); + TALER_exchange_offline_profit_drain_sign (&wtid, + date, + &amount, + account_section, + payto_uri, + &master_priv, + &master_sig); + output_operation (OP_DRAIN_PROFITS, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("wtid", + &wtid), + GNUNET_JSON_pack_string ("account_section", + account_section), + GNUNET_JSON_pack_string ("payto_uri", + payto_uri), + GNUNET_JSON_pack_timestamp ("date", + date), + GNUNET_JSON_pack_data_auto ("master_sig", + &master_sig))); + next (args + 3); +} + + +/** * Function called with information about future keys. Dumps the JSON output * (on success), either into an internal buffer or to stdout (depending on * whether there are subsequent commands). @@ -4181,6 +4458,12 @@ work (void *cls) .cb = &do_set_global_fee }, { + .name = "drain", + .help = + "drain profits from exchange escrow account to regular exchange operator account (amount, debit account configuration section and credit account payto://-URI must be given as arguments)", + .cb = &do_drain + }, + { .name = "upload", .help = "upload operation result to exchange (to be performed online!)", diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 6c3ad7a01..e14f01ca2 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -3966,6 +3966,63 @@ TALER_EXCHANGE_management_post_extensions_cancel ( /** + * Function called with information about the drain profits result. + * + * @param cls closure + * @param hr HTTP response data + */ +typedef void +(*TALER_EXCHANGE_ManagementDrainProfitsCallback) ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr); + + +/** + * @brief Handle for a POST /management/drain request. + */ +struct TALER_EXCHANGE_ManagementDrainProfitsHandle; + + +/** + * Uploads the drain profits request. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param wtid wire transfer identifier to use + * @param amount total to transfer + * @param date when was the request created + * @param account_section configuration section identifying account to debit + * @param payto_uri RFC 8905 URI of the account to credit + * @param master_sig signature affirming the operation + * @param cb function to call with the exchange's result + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_EXCHANGE_ManagementDrainProfitsHandle * +TALER_EXCHANGE_management_drain_profits ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Timestamp date, + const char *account_section, + const char *payto_uri, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementDrainProfitsCallback cb, + void *cb_cls); + + +/** + * Cancel #TALER_EXCHANGE_management_drain_profits() operation. + * + * @param dp handle of the operation to cancel + */ +void +TALER_EXCHANGE_management_drain_profits_cancel ( + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp); + + +/** * Function called with information about the post revocation operation result. * * @param cls closure diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 76157c43a..6adaac387 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -39,6 +39,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_link.c \ exchange_api_management_auditor_disable.c \ exchange_api_management_auditor_enable.c \ + exchange_api_management_drain_profits.c \ exchange_api_management_get_keys.c \ exchange_api_management_post_keys.c \ exchange_api_management_post_extensions.c \ diff --git a/src/lib/exchange_api_management_drain_profits.c b/src/lib/exchange_api_management_drain_profits.c new file mode 100644 index 000000000..9cf1af85e --- /dev/null +++ b/src/lib/exchange_api_management_drain_profits.c @@ -0,0 +1,213 @@ +/* + This file is part of TALER + Copyright (C) 2020-2022 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 lib/exchange_api_management_drain_profits.c + * @brief functions to set wire fees at an exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_curl_defaults.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementDrainProfitsHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementDrainProfitsCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/drain request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementDrainProfitsHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_drain_profits_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls; + const json_t *json = response; + struct TALER_EXCHANGE_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + dp->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_PRECONDITION_FAILED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management drain profits\n", + (unsigned int) response_code, + (int) hr.ec); + break; + } + if (NULL != dp->cb) + { + dp->cb (dp->cb_cls, + &hr); + dp->cb = NULL; + } + TALER_EXCHANGE_management_drain_profits_cancel (dp); +} + + +struct TALER_EXCHANGE_ManagementDrainProfitsHandle * +TALER_EXCHANGE_management_drain_profits ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Timestamp date, + const char *account_section, + const char *payto_uri, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementDrainProfitsCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp; + CURL *eh; + json_t *body; + + dp = GNUNET_new (struct TALER_EXCHANGE_ManagementDrainProfitsHandle); + dp->cb = cb; + dp->cb_cls = cb_cls; + dp->ctx = ctx; + dp->url = TALER_url_join (url, + "management/drain", + NULL); + if (NULL == dp->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (dp); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("debit_account_section", + account_section), + GNUNET_JSON_pack_string ("credit_payto_uri", + payto_uri), + GNUNET_JSON_pack_data_auto ("wtid", + wtid), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_timestamp ("date", + date), + TALER_JSON_pack_amount ("amount", + amount)); + eh = TALER_EXCHANGE_curl_easy_get_ (dp->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&dp->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (dp->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + dp->url); + dp->job = GNUNET_CURL_job_add2 (ctx, + eh, + dp->post_ctx.headers, + &handle_drain_profits_finished, + dp); + if (NULL == dp->job) + { + TALER_EXCHANGE_management_drain_profits_cancel (dp); + return NULL; + } + return dp; +} + + +void +TALER_EXCHANGE_management_drain_profits_cancel ( + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp) +{ + if (NULL != dp->job) + { + GNUNET_CURL_job_cancel (dp->job); + dp->job = NULL; + } + TALER_curl_easy_post_finished (&dp->post_ctx); + GNUNET_free (dp->url); + GNUNET_free (dp); +} |