return new global fees from /keys

This commit is contained in:
Christian Grothoff 2022-03-20 09:44:42 +01:00
parent 1bb5a77c8d
commit dee45bf022
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
10 changed files with 1231 additions and 20 deletions

View File

@ -57,7 +57,7 @@
* #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
* exchange_api_handle.c!
*/
#define EXCHANGE_PROTOCOL_VERSION "12:0:0"
#define EXCHANGE_PROTOCOL_VERSION "13:0:1"
/**
@ -280,6 +280,31 @@ struct SigningKey
};
/**
* Set of global fees (and options) for a time range.
*/
struct GlobalFee
{
/**
* Kept in a DLL.
*/
struct GlobalFee *next;
/**
* Kept in a DLL.
*/
struct GlobalFee *prev;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct GNUNET_TIME_Relative purse_timeout;
struct GNUNET_TIME_Relative kyc_timeout;
struct GNUNET_TIME_Relative history_expiration;
struct TALER_MasterSignatureP master_sig;
struct TALER_GlobalFeeSet fees;
uint32_t purse_account_limit;
};
struct TEH_KeyStateHandle
{
@ -296,12 +321,28 @@ struct TEH_KeyStateHandle
*/
struct GNUNET_CONTAINER_MultiPeerMap *signkey_map;
/**
* Head of DLL of our global fees.
*/
struct GlobalFee *gf_head;
/**
* Tail of DLL of our global fees.
*/
struct GlobalFee *gf_tail;
/**
* json array with the auditors of this exchange. Contains exactly
* the information needed for the "auditors" field of the /keys response.
*/
json_t *auditors;
/**
* json array with the global fees of this exchange. Contains exactly
* the information needed for the "global_fees" field of the /keys response.
*/
json_t *global_fees;
/**
* Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of
* length @e krd_array_length;
@ -548,7 +589,7 @@ suspend_request (struct MHD_Connection *connection)
* @param value a `struct TEH_DenominationKey`
* @return #GNUNET_OK
*/
static int
static enum GNUNET_GenericReturnValue
check_dk (void *cls,
const struct GNUNET_HashCode *hc,
void *value)
@ -1174,7 +1215,16 @@ static void
destroy_key_state (struct TEH_KeyStateHandle *ksh,
bool free_helper)
{
struct GlobalFee *gf;
clear_response_cache (ksh);
while (NULL != (gf = ksh->gf_head))
{
GNUNET_CONTAINER_DLL_remove (ksh->gf_head,
ksh->gf_tail,
gf);
GNUNET_free (gf);
}
GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
&clear_denomination_cb,
ksh);
@ -1185,6 +1235,8 @@ destroy_key_state (struct TEH_KeyStateHandle *ksh,
GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map);
json_decref (ksh->auditors);
ksh->auditors = NULL;
json_decref (ksh->global_fees);
ksh->global_fees = NULL;
if (free_helper)
{
destroy_key_helpers (ksh->helpers);
@ -1817,6 +1869,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
denoms),
GNUNET_JSON_pack_array_incref ("auditors",
ksh->auditors),
GNUNET_JSON_pack_array_incref ("global_fees",
ksh->global_fees),
GNUNET_JSON_pack_timestamp ("list_issue_date",
last_cpd),
GNUNET_JSON_pack_data_auto ("eddsa_pub",
@ -1825,7 +1879,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
&exchange_sig));
GNUNET_assert (NULL != keys);
// Set wallet limit if KYC is configured
/* Set wallet limit if KYC is configured */
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(GNUNET_OK ==
TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) )
@ -1839,7 +1893,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
&TEH_kyc_config.wallet_balance_limit)));
}
// Signal support for the configured, enabled extensions.
/* Signal support for the configured, enabled extensions. */
{
json_t *extensions = json_object ();
bool has_extensions = false;
@ -2201,6 +2255,70 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
}
/**
* Called with information about global fees.
*
* @param cls `struct TEH_KeyStateHandle *` we are building
* @param fees the global fees we charge
* @param purse_timeout when do purses time out
* @param kyc_timeout when do reserves without KYC time out
* @param history_expiration how long are account histories preserved
* @param purse_account_limit how many purses are free per account
* @param start_date from when are these fees valid (start date)
* @param end_date until when are these fees valid (end date, exclusive)
* @param master_sig master key signature affirming that this is the correct
* fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES)
*/
static void
global_fee_info_cb (
void *cls,
const struct TALER_GlobalFeeSet *fees,
struct GNUNET_TIME_Relative purse_timeout,
struct GNUNET_TIME_Relative kyc_timeout,
struct GNUNET_TIME_Relative history_expiration,
uint32_t purse_account_limit,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
const struct TALER_MasterSignatureP *master_sig)
{
struct TEH_KeyStateHandle *ksh = cls;
struct GlobalFee *gf;
gf = GNUNET_new (struct GlobalFee);
gf->start_date = start_date;
gf->end_date = end_date;
gf->fees = *fees;
gf->purse_timeout = purse_timeout;
gf->kyc_timeout = kyc_timeout;
gf->history_expiration = history_expiration;
gf->purse_account_limit = purse_account_limit;
gf->master_sig = *master_sig;
GNUNET_CONTAINER_DLL_insert (ksh->gf_head,
ksh->gf_tail,
gf);
GNUNET_assert (
0 ==
json_array_append_new (
ksh->global_fees,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("start_date",
start_date),
GNUNET_JSON_pack_timestamp ("end_date",
end_date),
TALER_JSON_PACK_GLOBAL_FEES (fees),
GNUNET_JSON_pack_time_rel ("history_expiration",
history_expiration),
GNUNET_JSON_pack_time_rel ("account_kyc_timeout",
kyc_timeout),
GNUNET_JSON_pack_time_rel ("purse_timeout",
purse_timeout),
GNUNET_JSON_pack_uint64 ("purse_account_limit",
purse_account_limit),
GNUNET_JSON_pack_data_auto ("master_sig",
master_sig))));
}
/**
* Create a key state.
*
@ -2246,6 +2364,20 @@ build_key_state (struct HelperState *hs,
/* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */
GNUNET_break (GNUNET_OK ==
TEH_plugin->preflight (TEH_plugin->cls));
if (NULL != ksh->global_fees)
json_decref (ksh->global_fees);
ksh->global_fees = json_array ();
qs = TEH_plugin->get_global_fees (TEH_plugin->cls,
&global_fee_info_cb,
ksh);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
destroy_key_state (ksh,
true);
return NULL;
}
qs = TEH_plugin->iterate_denominations (TEH_plugin->cls,
&denomination_info_cb,
ksh);

View File

@ -126,6 +126,11 @@ set_extensions (void *cls,
.size = htons (sizeof (ev)),
.type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)
};
// FIXME-Oec: bug: convert type to NBO first!
// FIXME-Oec: bug: sizeof enum is ill-defined...
// FIXME-Oec: bug: don't see /keys listening to the event
// FIXME-Oec: why is TEH_keys_update_states (); not enough?
TEH_plugin->event_notify (TEH_plugin->cls,
&ev,
type,
@ -294,7 +299,6 @@ TEH_handler_management_post_extensions (
NULL,
0);
CLEANUP:
for (unsigned int i = 0; i < sec.num_extensions; i++)
{

View File

@ -27,6 +27,7 @@
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
@ -256,7 +257,7 @@ TEH_handler_management_post_global_fees (
if (GNUNET_SYSERR == res)
return ret;
}
// TEH_global_update_state (); // FIXME: trigger!
TEH_keys_update_states ();
return TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,

View File

@ -1385,6 +1385,28 @@ prepare_statements (struct PostgresClosure *pg)
" WHERE start_date <= $1"
" AND end_date > $1;",
1),
/* Used in #postgres_get_global_fees() */
GNUNET_PQ_make_prepare (
"get_global_fees",
"SELECT "
" start_date"
",end_date"
",history_fee_val"
",history_fee_frac"
",kyc_fee_val"
",kyc_fee_frac"
",account_fee_val"
",account_fee_frac"
",purse_fee_val"
",purse_fee_frac"
",purse_timeout"
",kyc_timeout"
",history_expiration"
",purse_account_limit"
",master_sig"
" FROM global_fee"
" WHERE start_date >= $1",
1),
/* Used in #postgres_insert_wire_fee */
GNUNET_PQ_make_prepare (
"insert_wire_fee",
@ -7818,6 +7840,142 @@ postgres_get_global_fee (void *cls,
}
/**
* Closure for #global_fees_cb().
*/
struct GlobalFeeContext
{
/**
* Function to call for each global fee block.
*/
TALER_EXCHANGEDB_GlobalFeeCallback cb;
/**
* Closure to give to @e rec.
*/
void *cb_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Set to #GNUNET_SYSERR on error.
*/
enum GNUNET_GenericReturnValue status;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls closure
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
global_fees_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct GlobalFeeContext *gctx = cls;
struct PostgresClosure *pg = gctx->pg;
for (unsigned int i = 0; i<num_results; i++)
{
struct TALER_GlobalFeeSet fees;
struct GNUNET_TIME_Relative purse_timeout;
struct GNUNET_TIME_Relative kyc_timeout;
struct GNUNET_TIME_Relative history_expiration;
uint32_t purse_account_limit;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct TALER_MasterSignatureP master_sig;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_timestamp ("start_date",
&start_date),
GNUNET_PQ_result_spec_timestamp ("end_date",
&end_date),
TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
&fees.history),
TALER_PQ_RESULT_SPEC_AMOUNT ("kyc_fee",
&fees.kyc),
TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
&fees.account),
TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
&fees.purse),
GNUNET_PQ_result_spec_relative_time ("purse_timeout",
&purse_timeout),
GNUNET_PQ_result_spec_relative_time ("kyc_timeout",
&kyc_timeout),
GNUNET_PQ_result_spec_relative_time ("history_expiration",
&history_expiration),
GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
&purse_account_limit),
GNUNET_PQ_result_spec_auto_from_type ("master_sig",
&master_sig),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
gctx->status = GNUNET_SYSERR;
break;
}
gctx->cb (gctx->cb_cls,
&fees,
purse_timeout,
kyc_timeout,
history_expiration,
purse_account_limit,
start_date,
end_date,
&master_sig);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Obtain global fees from database.
*
* @param cls closure
* @param cb function to call on each fee entry
* @param cb_cls closure for @a cb
* @return status of the transaction
*/
static enum GNUNET_DB_QueryStatus
postgres_get_global_fees (void *cls,
TALER_EXCHANGEDB_GlobalFeeCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp date
= GNUNET_TIME_timestamp_get ();
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_timestamp (&date),
GNUNET_PQ_query_param_end
};
struct GlobalFeeContext gctx = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg,
.status = GNUNET_OK
};
return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"get_global_fees",
params,
&global_fees_cb,
&gctx);
}
/**
* Insert wire transfer fee into database.
*
@ -8034,7 +8192,7 @@ struct ExpiredReserveContext
/**
* Set to #GNUNET_SYSERR on error.
*/
int status;
enum GNUNET_GenericReturnValue status;
};
@ -8053,7 +8211,7 @@ reserve_expired_cb (void *cls,
{
struct ExpiredReserveContext *erc = cls;
struct PostgresClosure *pg = erc->pg;
int ret;
enum GNUNET_GenericReturnValue ret;
ret = GNUNET_OK;
for (unsigned int i = 0; i<num_results; i++)
@ -8117,13 +8275,14 @@ postgres_get_expired_reserves (void *cls,
GNUNET_PQ_query_param_timestamp (&now),
GNUNET_PQ_query_param_end
};
struct ExpiredReserveContext ectx;
struct ExpiredReserveContext ectx = {
.rec = rec,
.rec_cls = rec_cls,
.pg = pg,
.status = GNUNET_OK
};
enum GNUNET_DB_QueryStatus qs;
ectx.rec = rec;
ectx.rec_cls = rec_cls;
ectx.pg = pg;
ectx.status = GNUNET_OK;
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"get_expired_reserves",
params,
@ -12371,6 +12530,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
plugin->insert_global_fee = &postgres_insert_global_fee;
plugin->get_wire_fee = &postgres_get_wire_fee;
plugin->get_global_fee = &postgres_get_global_fee;
plugin->get_global_fees = &postgres_get_global_fees;
plugin->get_expired_reserves = &postgres_get_expired_reserves;
plugin->insert_reserve_closed = &postgres_insert_reserve_closed;
plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert;

View File

@ -203,6 +203,55 @@ struct TALER_EXCHANGE_AuditorInformation
};
/**
* Global fees and options of an exchange for a given time period.
*/
struct TALER_EXCHANGE_GlobalFee
{
/**
* Signature affirming all of the data.
*/
struct TALER_MasterSignatureP master_sig;
/**
* Starting time of the validity period (inclusive).
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* End time of the validity period (exclusive).
*/
struct GNUNET_TIME_Timestamp end_date;
/**
* Unmerged purses will be timed out after at most this time.
*/
struct GNUNET_TIME_Relative purse_timeout;
/**
* Accounts without KYC will be closed after this time.
*/
struct GNUNET_TIME_Relative kyc_timeout;
/**
* Account history is limited to this timeframe.
*/
struct GNUNET_TIME_Relative history_expiration;
/**
* Fees that apply globally, independent of denomination
* and wire method.
*/
struct TALER_GlobalFeeSet fees;
/**
* Number of free purses per account.
*/
uint32_t purse_account_limit;
};
/**
* @brief Information about keys from the exchange.
*/
@ -229,6 +278,11 @@ struct TALER_EXCHANGE_Keys
*/
struct TALER_EXCHANGE_AuditorInformation *auditors;
/**
* Array with the global fees of the exchange.
*/
struct TALER_EXCHANGE_GlobalFee *global_fees;
/**
* Supported Taler protocol version by the exchange.
* String in the format current:revision:age using the
@ -272,6 +326,11 @@ struct TALER_EXCHANGE_Keys
*/
struct TALER_AgeMask age_mask;
/**
* Length of the @e global_fees array.
*/
unsigned int num_global_fees;
/**
* Length of the @e sign_keys array (number of valid entries).
*/

View File

@ -3968,7 +3968,7 @@ struct TALER_EXCHANGEDB_Plugin
* Obtain information about the global fee structure of the exchange.
*
* @param cls closure
* @param cb function to call on each account
* @param cb function to call on each fee entry
* @param cb_cls closure for @a cb
* @return transaction status code
*/

View File

@ -279,6 +279,30 @@ TALER_JSON_spec_amount_any_nbo (const char *name,
TALER_JSON_pack_amount (pfx "_refund", &(dfs)->refund)
/**
* Generate specification to parse all global fees.
*
* @param currency which currency to expect
* @param[out] gfs a `struct TALER_GlobalFeeSet` to initialize
*/
#define TALER_JSON_SPEC_GLOBAL_FEES(currency,gfs) \
TALER_JSON_spec_amount ("kyc_fee", (currency), &(gfs)->kyc), \
TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history), \
TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account), \
TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse)
/**
* Macro to pack all of the global fees.
*
* @param gfs a `struct TALER_GlobalFeeSet` to pack
*/
#define TALER_JSON_PACK_GLOBAL_FEES(gfs) \
TALER_JSON_pack_amount ("kyc_fee", &(gfs)->kyc), \
TALER_JSON_pack_amount ("history_fee", &(gfs)->history), \
TALER_JSON_pack_amount ("account_fee", &(gfs)->account), \
TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse)
/**
* Generate line in parser specification for denomination public key.
*

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA
Copyright (C) 2014-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
@ -40,7 +40,7 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
#define EXCHANGE_PROTOCOL_CURRENT 12
#define EXCHANGE_PROTOCOL_CURRENT 13
/**
* How many versions are we backwards compatible with?
@ -255,7 +255,7 @@ free_keys_request (struct KeysRequest *kr)
*/
static enum GNUNET_GenericReturnValue
parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
int check_sigs,
bool check_sigs,
json_t *sign_key_obj,
const struct TALER_MasterPublicKeyP *master_key)
{
@ -317,7 +317,7 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
static enum GNUNET_GenericReturnValue
parse_json_denomkey (const char *currency,
struct TALER_EXCHANGE_DenomPublicKey *denom_key,
int check_sigs,
bool check_sigs,
json_t *denom_key_obj,
struct TALER_MasterPublicKeyP *master_key,
struct GNUNET_HashContext *hash_context)
@ -394,7 +394,7 @@ EXITIF_exit:
*/
static enum GNUNET_GenericReturnValue
parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
int check_sigs,
bool check_sigs,
json_t *auditor_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
@ -504,6 +504,79 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
}
/**
* Parse a exchange's global fee information encoded in JSON.
*
* @param[out] gf where to return the result
* @param check_sigs should we check signatures
* @param[in] fee_obj json to parse
* @param key_data already parsed information about the exchange
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
static enum GNUNET_GenericReturnValue
parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
bool check_sigs,
json_t *fee_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("start_time",
&gf->start_date),
GNUNET_JSON_spec_timestamp ("end_time",
&gf->end_date),
GNUNET_JSON_spec_relative_time ("purse_timeout",
&gf->purse_timeout),
GNUNET_JSON_spec_relative_time ("kyc_timeout",
&gf->kyc_timeout),
GNUNET_JSON_spec_relative_time ("history_expiration",
&gf->history_expiration),
GNUNET_JSON_spec_uint32 ("purse_account_limit",
&gf->purse_account_limit),
TALER_JSON_SPEC_GLOBAL_FEES (key_data->currency,
&gf->fees),
GNUNET_JSON_spec_fixed_auto ("master_sig",
&gf->master_sig),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (fee_obj,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
#if DEBUG
json_dumpf (fee_obj,
stderr,
JSON_INDENT (2));
#endif
return GNUNET_SYSERR;
}
if (check_sigs)
{
if (GNUNET_OK !=
TALER_exchange_offline_global_fee_verify (
gf->start_date,
gf->end_date,
&gf->fees,
gf->purse_timeout,
gf->kyc_timeout,
gf->history_expiration,
gf->purse_account_limit,
&key_data->master_pub,
&gf->master_sig))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
/**
* Function called with information about the auditor. Marks an
* auditor as 'up'.
@ -691,7 +764,7 @@ decode_keys_json (const json_t *resp_obj,
stderr,
JSON_INDENT (2));
#endif
/* check the version */
/* check the version first */
{
const char *ver;
unsigned int age;
@ -762,6 +835,32 @@ decode_keys_json (const json_t *resp_obj,
hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();
}
/* parse the global fees */
{
json_t *global_fees;
json_t *global_fee;
unsigned int index;
EXITIF (NULL == (global_fees =
json_object_get (resp_obj,
"global_fees")));
EXITIF (! json_is_array (global_fees));
if (0 != (key_data->num_global_fees =
json_array_size (global_fees)))
{
key_data->global_fees
= GNUNET_new_array (key_data->num_global_fees,
struct TALER_EXCHANGE_GlobalFee);
json_array_foreach (global_fees, index, global_fee) {
EXITIF (GNUNET_SYSERR ==
parse_global_fee (&key_data->global_fees[index],
check_sig,
global_fee,
key_data));
}
}
}
/* parse the signing keys */
{
json_t *sign_keys_array;

View File

@ -0,0 +1,368 @@
/*
This file is part of TALER
Copyright (C) 2014-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_reserves_history.c
* @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
* @author Christian Grothoff
*/
#include "platform.h"
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP history codes */
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
#include "exchange_api_curl_defaults.h"
/**
* @brief A /reserves/$RID/history Handle
*/
struct TALER_EXCHANGE_ReservesHistoryHandle
{
/**
* The connection to exchange this request handle will use
*/
struct TALER_EXCHANGE_Handle *exchange;
/**
* The url for this request.
*/
char *url;
/**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
/**
* Context for #TEH_curl_easy_post(). Keeps the data that must
* persist for Curl to make the upload.
*/
struct TALER_CURL_PostContext post_ctx;
/**
* Function to call with the result.
*/
TALER_EXCHANGE_ReservesHistoryCallback cb;
/**
* Public key of the reserve we are querying.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* Closure for @a cb.
*/
void *cb_cls;
};
/**
* We received an #MHD_HTTP_OK history code. Handle the JSON
* response.
*
* @param rgh handle of the request
* @param j JSON response
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rgh,
const json_t *j)
{
json_t *history;
unsigned int len;
bool kyc_ok;
bool kyc_required;
struct TALER_Amount balance;
struct TALER_Amount balance_from_history;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("balance",
&balance),
GNUNET_JSON_spec_bool ("kyc_passed",
&kyc_ok),
GNUNET_JSON_spec_bool ("kyc_required",
&kyc_required),
GNUNET_JSON_spec_json ("history",
&history),
GNUNET_JSON_spec_end ()
};
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_history = MHD_HTTP_OK
};
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL,
NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
len = json_array_size (history);
{
struct TALER_EXCHANGE_ReserveHistory *rhistory;
rhistory = GNUNET_new_array (len,
struct TALER_EXCHANGE_ReserveHistory);
if (GNUNET_OK !=
TALER_EXCHANGE_parse_reserve_history (rgh->exchange,
history,
&rgh->reserve_pub,
balance.currency,
&balance_from_history,
len,
rhistory))
{
GNUNET_break_op (0);
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (0 !=
TALER_amount_cmp (&balance_from_history,
&balance))
{
/* exchange cannot add up balances!? */
GNUNET_break_op (0);
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
&hr,
&balance,
len,
rhistory);
rgh->cb = NULL;
}
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
}
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
/**
* Function called when we're done processing the
* HTTP /reserves/$RID/history request.
*
* @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
* @param response_code HTTP response code, 0 on error
* @param response parsed JSON result, NULL on error
*/
static void
handle_reserves_history_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_EXCHANGE_ReservesHistoryHandle *rgh = cls;
const json_t *j = response;
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_history = (unsigned int) response_code
};
rgh->job = NULL;
switch (response_code)
{
case 0:
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
handle_reserves_history_ok (rgh,
j))
{
hr.http_history = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break (0);
hr.ec = TALER_JSON_history_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j);
break;
case MHD_HTTP_FORBIDDEN:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break (0);
hr.ec = TALER_JSON_history_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
hr.ec = TALER_JSON_history_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
hr.ec = TALER_JSON_history_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
hr.ec = TALER_JSON_history_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for reserves history\n",
(unsigned int) response_code,
(int) hr.ec);
break;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
&hr,
NULL,
0,
NULL);
rgh->cb = NULL;
}
TALER_EXCHANGE_reserves_history_cancel (rgh);
}
struct TALER_EXCHANGE_ReservesHistoryHandle *
TALER_EXCHANGE_reserves_history (
struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_ReservePrivateKeyP *reserve_priv,
TALER_EXCHANGE_ReservesHistoryCallback cb,
void *cb_cls)
{
struct TALER_EXCHANGE_ReservesHistoryHandle *rgh;
struct GNUNET_CURL_Context *ctx;
CURL *eh;
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
const struct TALER_Amount *history_fee;
const struct TALER_EXCHANGE_Keys *keys;
struct GNUNET_TIME_Timestamp ts
= GNUNET_TIME_timestamp_get ();
if (GNUNET_YES !=
TEAH_handle_is_ready (exchange))
{
GNUNET_break (0);
return NULL;
}
keys = TALER_EXCHANGE_get_keys (exchange);
// FIXME: extract history_fee from keys!
history_fee = FIXME;
rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
rgh->exchange = exchange;
rgh->cb = cb;
rgh->cb_cls = cb_cls;
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&rgh->reserve_pub.eddsa_pub);
{
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
char *end;
end = GNUNET_STRINGS_data_to_string (
&rgh->reserve_pub,
sizeof (rgh->reserve_pub),
pub_str,
sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
"/reserves/%s/history",
pub_str);
}
rgh->url = TEAH_path_to_url (exchange,
arg_str);
if (NULL == rgh->url)
{
GNUNET_free (rgh);
return NULL;
}
eh = TALER_EXCHANGE_curl_easy_history_ (rgh->url);
if (NULL == eh)
{
GNUNET_break (0);
GNUNET_free (rgh->url);
GNUNET_free (rgh);
return NULL;
}
TALER_wallet_reserve_history_sign (ts,
history_fee,
reserve_priv,
&reserve_sig);
{
json_t *history_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("request_timestamp",
&ts),
GNUNET_JSON_pack_data_auto ("reserve_sig",
&reserve_sig));
if (GNUNET_OK !=
TALER_curl_easy_post (&rgh->post_ctx,
eh,
history_obj))
)
{
GNUNET_break (0);
curl_easy_cleanup (eh);
json_decref (history_obj);
GNUNET_free (rgh->url);
GNUNET_free (rgh);
return NULL;
}
json_decref (history_obj);
}
ctx = TEAH_handle_to_context (exchange);
rgh->job = GNUNET_CURL_job_add (ctx,
eh,
&handle_reserves_history_finished,
rgh);
return rgh;
}
void
TALER_EXCHANGE_reserves_history_cancel (
struct TALER_EXCHANGE_ReservesHistoryHandle *rgh)
{
if (NULL != rgh->job)
{
GNUNET_CURL_job_cancel (rgh->job);
rgh->job = NULL;
}
TALER_curl_easy_post_finished (&rgh->post_ctx);
GNUNET_free (rgh->url);
GNUNET_free (rgh);
}
/* end of exchange_api_reserves_history.c */

View File

@ -0,0 +1,364 @@
/*
This file is part of TALER
Copyright (C) 2014-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_reserves_status.c
* @brief Implementation of the POST /reserves/$RESERVE_PUB/status requests
* @author Christian Grothoff
*/
#include "platform.h"
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
#include "exchange_api_curl_defaults.h"
/**
* @brief A /reserves/$RID/status Handle
*/
struct TALER_EXCHANGE_ReservesStatusHandle
{
/**
* The connection to exchange this request handle will use
*/
struct TALER_EXCHANGE_Handle *exchange;
/**
* The url for this request.
*/
char *url;
/**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
/**
* Context for #TEH_curl_easy_post(). Keeps the data that must
* persist for Curl to make the upload.
*/
struct TALER_CURL_PostContext post_ctx;
/**
* Function to call with the result.
*/
TALER_EXCHANGE_ReservesStatusCallback cb;
/**
* Public key of the reserve we are querying.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* Closure for @a cb.
*/
void *cb_cls;
};
/**
* We received an #MHD_HTTP_OK status code. Handle the JSON
* response.
*
* @param rgh handle of the request
* @param j JSON response
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rgh,
const json_t *j)
{
json_t *history;
unsigned int len;
bool kyc_ok;
bool kyc_required;
struct TALER_Amount balance;
struct TALER_Amount balance_from_history;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("balance",
&balance),
GNUNET_JSON_spec_bool ("kyc_passed",
&kyc_ok),
GNUNET_JSON_spec_bool ("kyc_required",
&kyc_required),
GNUNET_JSON_spec_json ("history",
&history),
GNUNET_JSON_spec_end ()
};
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_status = MHD_HTTP_OK
};
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL,
NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
len = json_array_size (history);
{
struct TALER_EXCHANGE_ReserveHistory *rhistory;
rhistory = GNUNET_new_array (len,
struct TALER_EXCHANGE_ReserveHistory);
if (GNUNET_OK !=
TALER_EXCHANGE_parse_reserve_history (rgh->exchange,
history,
&rgh->reserve_pub,
balance.currency,
&balance_from_history,
len,
rhistory))
{
GNUNET_break_op (0);
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
// FIXME: status history is allowed to be
// partial, so this is NOT ok...
if (0 !=
TALER_amount_cmp (&balance_from_history,
&balance))
{
/* exchange cannot add up balances!? */
GNUNET_break_op (0);
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
&hr,
&balance,
len,
rhistory);
rgh->cb = NULL;
}
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
}
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
/**
* Function called when we're done processing the
* HTTP /reserves/$RID/status request.
*
* @param cls the `struct TALER_EXCHANGE_ReservesStatusHandle`
* @param response_code HTTP response code, 0 on error
* @param response parsed JSON result, NULL on error
*/
static void
handle_reserves_status_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_EXCHANGE_ReservesStatusHandle *rgh = cls;
const json_t *j = response;
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_status = (unsigned int) response_code
};
rgh->job = NULL;
switch (response_code)
{
case 0:
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
handle_reserves_status_ok (rgh,
j))
{
hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break (0);
hr.ec = TALER_JSON_status_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j);
break;
case MHD_HTTP_FORBIDDEN:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break (0);
hr.ec = TALER_JSON_status_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
hr.ec = TALER_JSON_status_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
hr.ec = TALER_JSON_status_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
hr.ec = TALER_JSON_status_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for reserves status\n",
(unsigned int) response_code,
(int) hr.ec);
break;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
&hr,
NULL,
0,
NULL);
rgh->cb = NULL;
}
TALER_EXCHANGE_reserves_status_cancel (rgh);
}
struct TALER_EXCHANGE_ReservesStatusHandle *
TALER_EXCHANGE_reserves_status (
struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_ReservePrivateKeyP *reserve_priv,
TALER_EXCHANGE_ReservesStatusCallback cb,
void *cb_cls)
{
struct TALER_EXCHANGE_ReservesStatusHandle *rgh;
struct GNUNET_CURL_Context *ctx;
CURL *eh;
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
struct GNUNET_TIME_Timestamp ts
= GNUNET_TIME_timestamp_get ();
if (GNUNET_YES !=
TEAH_handle_is_ready (exchange))
{
GNUNET_break (0);
return NULL;
}
rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle);
rgh->exchange = exchange;
rgh->cb = cb;
rgh->cb_cls = cb_cls;
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&rgh->reserve_pub.eddsa_pub);
{
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
char *end;
end = GNUNET_STRINGS_data_to_string (
&rgh->reserve_pub,
sizeof (rgh->reserve_pub),
pub_str,
sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
"/reserves/%s/status",
pub_str);
}
rgh->url = TEAH_path_to_url (exchange,
arg_str);
if (NULL == rgh->url)
{
GNUNET_free (rgh);
return NULL;
}
eh = TALER_EXCHANGE_curl_easy_status_ (rgh->url);
if (NULL == eh)
{
GNUNET_break (0);
GNUNET_free (rgh->url);
GNUNET_free (rgh);
return NULL;
}
TALER_wallet_reserve_status_sign (ts,
reserve_priv,
&reserve_sig);
{
json_t *status_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("request_timestamp",
&ts),
GNUNET_JSON_pack_data_auto ("reserve_sig",
&reserve_sig));
if (GNUNET_OK !=
TALER_curl_easy_post (&rgh->post_ctx,
eh,
status_obj))
)
{
GNUNET_break (0);
curl_easy_cleanup (eh);
json_decref (status_obj);
GNUNET_free (rgh->url);
GNUNET_free (rgh);
return NULL;
}
json_decref (status_obj);
}
ctx = TEAH_handle_to_context (exchange);
rgh->job = GNUNET_CURL_job_add (ctx,
eh,
&handle_reserves_status_finished,
rgh);
return rgh;
}
void
TALER_EXCHANGE_reserves_status_cancel (
struct TALER_EXCHANGE_ReservesStatusHandle *rgh)
{
if (NULL != rgh->job)
{
GNUNET_CURL_job_cancel (rgh->job);
rgh->job = NULL;
}
TALER_curl_easy_post_finished (&rgh->post_ctx);
GNUNET_free (rgh->url);
GNUNET_free (rgh);
}
/* end of exchange_api_reserves_status.c */