expand taler-exchange-offline and libtalerexchange with management-drain-profits implementation (#4960)

This commit is contained in:
Christian Grothoff 2022-07-29 09:57:10 +02:00
parent c1b43de5b4
commit 2056bc82f9
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
4 changed files with 554 additions and 0 deletions

View File

@ -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().
@ -384,6 +388,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.
*/
@ -575,6 +607,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.
*
@ -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 ();
@ -1749,6 +1810,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.
*
@ -2098,6 +2265,10 @@ trigger_upload (const char *exchange_url)
.key = OP_UPLOAD_SIGS,
.cb = &upload_keys
},
{
.key = OP_DRAIN_PROFITS,
.cb = &upload_drain
},
{
.key = OP_EXTENSIONS,
.cb = &upload_extensions
@ -2787,6 +2958,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
@ -4180,6 +4457,12 @@ work (void *cls)
"sign global fees for the given year (year, history fee, kyc fee, account fee, purse fee, purse timeout, kyc timeout, history expiration and the maximum number of free purses per account must be given as arguments)",
.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 =

View File

@ -3965,6 +3965,63 @@ TALER_EXCHANGE_management_post_extensions_cancel (
struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph);
/**
* 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.
*

View File

@ -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 \

View File

@ -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);
}