diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/exchange/taler-exchange-httpd_keys.c | 140 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_management_extensions.c | 6 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_management_global_fees.c | 3 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 174 | ||||
-rw-r--r-- | src/include/taler_exchange_service.h | 59 | ||||
-rw-r--r-- | src/include/taler_exchangedb_plugin.h | 2 | ||||
-rw-r--r-- | src/include/taler_json_lib.h | 24 | ||||
-rw-r--r-- | src/lib/exchange_api_handle.c | 111 | ||||
-rw-r--r-- | src/lib/exchange_api_reserves_history.c | 368 | ||||
-rw-r--r-- | src/lib/exchange_api_reserves_status.c | 364 |
10 files changed, 1231 insertions, 20 deletions
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 95278ab80..1012a8c03 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -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 { @@ -297,12 +322,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; @@ -2202,6 +2256,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. * * @param[in] hs helper state to (re)use, NULL if not available @@ -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); diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 109e863d3..ce151e2e5 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -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++) { diff --git a/src/exchange/taler-exchange-httpd_management_global_fees.c b/src/exchange/taler-exchange-httpd_management_global_fees.c index eb3aa5995..37bb40d90 100644 --- a/src/exchange/taler-exchange-httpd_management_global_fees.c +++ b/src/exchange/taler-exchange-httpd_management_global_fees.c @@ -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, diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 2a955e15f..bb6f46f59 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -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", @@ -7819,6 +7841,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. * * @param cls closure @@ -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; diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 48272a6b8..56940669d 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -204,6 +204,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. */ struct TALER_EXCHANGE_Keys @@ -230,6 +279,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 * semantics of GNU libtool. See @@ -273,6 +327,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). */ unsigned int num_sign_keys; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 7696b607d..fc909a1ba 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -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 */ diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 6238c07d3..b4f999001 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -280,6 +280,30 @@ TALER_JSON_spec_amount_any_nbo (const char *name, /** + * 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. * * @param field name of the field diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index a9713a45a..f7e877913 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -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) { @@ -505,6 +505,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; diff --git a/src/lib/exchange_api_reserves_history.c b/src/lib/exchange_api_reserves_history.c new file mode 100644 index 000000000..f7191b2ad --- /dev/null +++ b/src/lib/exchange_api_reserves_history.c @@ -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 */ diff --git a/src/lib/exchange_api_reserves_status.c b/src/lib/exchange_api_reserves_status.c new file mode 100644 index 000000000..2758a3a28 --- /dev/null +++ b/src/lib/exchange_api_reserves_status.c @@ -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 */ |