diff --git a/contrib/gana b/contrib/gana index f603a7959..df1d19891 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit f603a795963748040e41693daceae343b3a972ed +Subproject commit df1d198918cbdd03c18723d818979c8d09f8f231 diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c index 0136a9ec3..13327ccba 100644 --- a/src/auditor/taler-helper-auditor-purses.c +++ b/src/auditor/taler-helper-auditor-purses.c @@ -308,6 +308,14 @@ struct PurseSummary */ bool had_pi; + /** + * Was the purse deleted? FIXME: Not yet handled (do we need to? purse + * might just appear as expired eventually; but in the meantime, exchange + * may seem to have refunded the coins for no good reason...), also we do + * not yet check the deletion signature. + */ + bool purse_deleted; + }; @@ -407,7 +415,8 @@ setup_purse (struct PurseContext *pc, &ps->total_value, &ps->exchange_balance, &ps->h_contract_terms, - &ps->merge_timestamp); + &ps->merge_timestamp, + &ps->purse_deleted); if (0 >= qs) { GNUNET_free (ps); diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf index d662cdd0e..758e77c97 100644 --- a/src/exchange/exchange.conf +++ b/src/exchange/exchange.conf @@ -113,32 +113,3 @@ PRIVACY_DIR = $DATADIR/exchange/pp/ # Etag / filename for the privacy policy. PRIVACY_ETAG = pp-v0 - -# Set to NONE to disable KYC checks. -# Set to "OAUTH2" to use OAuth 2.0 for KYC authorization. -KYC_MODE = NONE - -# Balance threshold above which wallets are told -# to undergo a KYC check at the exchange. Optional, -# if not given there is no limit. -# KYC_WALLET_BALANCE_LIMIT = CURRENCY:150 -# -# KYC_WITHDRAW_PERIOD = 1 month - -[exchange-kyc-oauth2] - -# URL of the OAuth endpoint for KYC checks -# KYC_OAUTH2_URL = - -# URL of the "information" endpoint for KYC checks -# KYC_INFO_URL = - -# KYC Oauth client ID. -# KYC_OAUTH2_CLIENT_ID = - -# KYC Client secret used to obtain access tokens. -# KYC_OAUTH2_CLIENT_SECRET = - -# Where to redirect clients after successful -# authorization? -# KYC_OAUTH2_POST_URL = https://bank.com/ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 4b64dfd54..76b388896 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -115,11 +115,6 @@ struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0}; */ static struct MHD_Daemon *mhd; -/** - * Our KYC configuration. - */ -struct TEH_KycOptions TEH_kyc_config; - /** * How long is caching /keys allowed at most? (global) */ @@ -732,12 +727,16 @@ proceed_with_handler (struct TEH_RequestContext *rc, /* Above logic ensures that 'root' is exactly non-NULL for POST operations, so we test for 'root' to decide which handler to invoke. */ - if (NULL != root) + if (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_POST)) ret = rh->handler.post (rc, root, args); - else /* We also only have "POST" or "GET" in the API for at this point - (OPTIONS/HEAD are taken care of earlier) */ + else if (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_DELETE)) + ret = rh->handler.delete (rc, + args); + else /* Only GET left */ ret = rh->handler.get (rc, args); } @@ -975,7 +974,7 @@ handle_post_management (struct TEH_RequestContext *rc, /** - * Handle a get "/management" request. + * Handle a GET "/management" request. * * @param rc request context * @param args array of additional options (must be [0] == "keys") @@ -1225,7 +1224,7 @@ handle_mhd_request (void *cls, .url = "purses", .method = MHD_HTTP_METHOD_POST, .handler.post = &handle_post_purses, - .nargs = 2 // ?? + .nargs = 2 }, /* Getting purse status */ { @@ -1234,6 +1233,13 @@ handle_mhd_request (void *cls, .handler.get = &TEH_handler_purses_get, .nargs = 2 }, + /* Deleting purse */ + { + .url = "purses", + .method = MHD_HTTP_METHOD_DELETE, + .handler.delete = &TEH_handler_purses_delete, + .nargs = 1 + }, /* Getting contracts */ { .url = "contracts", @@ -1525,185 +1531,6 @@ handle_mhd_request (void *cls, } -/** - * Load general KYC configuration parameters for the exchange server into the - * #TEH_kyc_config variable. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_settings (void) -{ - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, - "exchange", - "KYC_WITHDRAW_PERIOD", - &TEH_kyc_config.withdraw_period)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WITHDRAW_PERIOD", - "valid relative time expected"); - return GNUNET_SYSERR; - } - if (GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) - return GNUNET_OK; - if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - "exchange", - "KYC_WITHDRAW_LIMIT", - &TEH_kyc_config.withdraw_limit)) - return GNUNET_SYSERR; - if (0 != strcasecmp (TEH_kyc_config.withdraw_limit.currency, - TEH_currency)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WITHDRAW_LIMIT", - "currency mismatch"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Load OAuth2.0 configuration parameters for the exchange server into the - * #TEH_kyc_config variable. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_oauth_cfg (void) -{ - char *s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_AUTH_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_AUTH_URL"); - return GNUNET_SYSERR; - } - if ( (! TALER_url_valid_charset (s)) || - ( (0 != strncasecmp (s, - "http://", - strlen ("http://"))) && - (0 != strncasecmp (s, - "https://", - strlen ("https://"))) ) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_AUTH_URL", - "not a valid URL"); - GNUNET_free (s); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.auth_url = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_LOGIN_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_LOGIN_URL"); - return GNUNET_SYSERR; - } - if ( (! TALER_url_valid_charset (s)) || - ( (0 != strncasecmp (s, - "http://", - strlen ("http://"))) && - (0 != strncasecmp (s, - "https://", - strlen ("https://"))) ) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_LOGIN_URL", - "not a valid URL"); - GNUNET_free (s); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.login_url = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_INFO_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_INFO_URL"); - return GNUNET_SYSERR; - } - if ( (! TALER_url_valid_charset (s)) || - ( (0 != strncasecmp (s, - "http://", - strlen ("http://"))) && - (0 != strncasecmp (s, - "https://", - strlen ("https://"))) ) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_INFO_URL", - "not a valid URL"); - GNUNET_free (s); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.info_url = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_ID", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_ID"); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.client_id = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_SECRET", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_SECRET"); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.client_secret = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_POST_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_POST_URL"); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.post_kyc_redirect_url = s; - return GNUNET_OK; -} - - /** * Load configuration parameters for the exchange * server into the corresponding global variables. @@ -1718,47 +1545,6 @@ exchange_serve_process_config (void) { return GNUNET_SYSERR; } - { - char *kyc_mode; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange", - "KYC_MODE", - &kyc_mode)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_MODE"); - return GNUNET_SYSERR; - } - if (0 == strcasecmp (kyc_mode, - "NONE")) - { - TEH_kyc_config.mode = TEH_KYC_NONE; - } - else if (0 == strcasecmp (kyc_mode, - "OAUTH2")) - { - TEH_kyc_config.mode = TEH_KYC_OAUTH2; - if (GNUNET_OK != - parse_kyc_oauth_cfg ()) - { - GNUNET_free (kyc_mode); - return GNUNET_SYSERR; - } - } - else - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_MODE", - "Must be 'NONE' or 'OAUTH2'"); - GNUNET_free (kyc_mode); - return GNUNET_SYSERR; - } - GNUNET_free (kyc_mode); - } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (TEH_cfg, "exchange", @@ -1823,35 +1609,6 @@ exchange_serve_process_config (void) return GNUNET_SYSERR; } - if (TEH_KYC_NONE != TEH_kyc_config.mode) - { - if (GNUNET_YES == - GNUNET_CONFIGURATION_have_value (TEH_cfg, - "exchange", - "KYC_WALLET_BALANCE_LIMIT")) - { - if ( (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - "exchange", - "KYC_WALLET_BALANCE_LIMIT", - &TEH_kyc_config.wallet_balance_limit)) || - (0 != strcasecmp (TEH_currency, - TEH_kyc_config.wallet_balance_limit.currency)) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WALLET_BALANCE_LIMIT", - "valid amount expected"); - return GNUNET_SYSERR; - } - } - else - { - memset (&TEH_kyc_config.wallet_balance_limit, - 0, - sizeof (TEH_kyc_config.wallet_balance_limit)); - } - } { char *master_public_key_str; @@ -1882,12 +1639,6 @@ exchange_serve_process_config (void) } GNUNET_free (master_public_key_str); } - if (TEH_KYC_NONE != TEH_kyc_config.mode) - { - if (GNUNET_OK != - parse_kyc_settings ()) - return GNUNET_SYSERR; - } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Launching exchange with public key `%s'...\n", GNUNET_p2s (&TEH_master_public_key.eddsa_pub)); diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index 67b8e75d0..2be26f14d 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -31,111 +31,6 @@ #include -/* ************* NOTE: OLD KYC logic,*********** - new logic is in taler-exchange-httpd_kyc.h! - ********************************************* */ - -/** - * Enumeration for our KYC modes. - */ -enum TEH_KycMode -{ - /** - * KYC is disabled. - */ - TEH_KYC_NONE = 0, - - /** - * We use Oauth2.0. - */ - TEH_KYC_OAUTH2 = 1 -}; - - -/** - * Structure describing our KYC configuration. - */ -struct TEH_KycOptions -{ - /** - * What KYC mode are we in? - */ - enum TEH_KycMode mode; - - /** - * Maximum amount that can be withdrawn in @e withdraw_period without - * needing KYC. - * Only valid if @e mode is not #TEH_KYC_NONE and - * if @e withdraw_period is non-zero. - */ - struct TALER_Amount withdraw_limit; - - /** - * Maximum balance a wallet can hold without - * needing KYC. - * Only valid if @e mode is not #TEH_KYC_NONE and - * if the amount specified is valid. - */ - struct TALER_Amount wallet_balance_limit; - - /** - * Time period over which @e withdraw_limit applies. - * Only valid if @e mode is not #TEH_KYC_NONE. - */ - struct GNUNET_TIME_Relative withdraw_period; - - /** - * Details depending on @e mode. - */ - union - { - - /** - * Configuration details if @e mode is #TEH_KYC_OAUTH2. - */ - struct - { - - /** - * URL of the OAuth2.0 endpoint for KYC checks. - * (token/auth) - */ - char *auth_url; - - /** - * URL of the OAuth2.0 endpoint for KYC checks. - */ - char *login_url; - - /** - * URL of the user info access endpoint. - */ - char *info_url; - - /** - * Our client ID for OAuth2.0. - */ - char *client_id; - - /** - * Our client secret for OAuth2.0. - */ - char *client_secret; - - /** - * Where to redirect clients after the - * Web-based KYC process is done? - */ - char *post_kyc_redirect_url; - - } oauth2; - - } details; -}; - - -extern struct TEH_KycOptions TEH_kyc_config; - /** * How long is caching /keys allowed at most? */ @@ -301,11 +196,10 @@ struct TEH_RequestHandler union { /** - * Function to call to handle a GET requests (and those + * Function to call to handle GET requests (and those * with @e method NULL). * * @param rc context for the request - * @param mime_type the @e mime_type for the reply (hint, can be NULL) * @param args array of arguments, needs to be of length @e args_expected * @return MHD result code */ @@ -315,7 +209,7 @@ struct TEH_RequestHandler /** - * Function to call to handle a POST request. + * Function to call to handle POST requests. * * @param rc context for the request * @param json uploaded JSON data @@ -327,6 +221,17 @@ struct TEH_RequestHandler const json_t *root, const char *const args[]); + /** + * Function to call to handle DELETE requests. + * + * @param rc context for the request + * @param args array of arguments, needs to be of length @e args_expected + * @return MHD result code + */ + MHD_RESULT + (*delete)(struct TEH_RequestContext *rc, + const char *const args[]); + } handler; /** diff --git a/src/exchange/taler-exchange-httpd_purses_delete.c b/src/exchange/taler-exchange-httpd_purses_delete.c index 34ab11b51..58cc78250 100644 --- a/src/exchange/taler-exchange-httpd_purses_delete.c +++ b/src/exchange/taler-exchange-httpd_purses_delete.c @@ -24,6 +24,7 @@ #include #include #include +#include "taler_dbevents.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_common_deposit.h" @@ -35,13 +36,27 @@ MHD_RESULT TEH_handler_purses_delete ( - struct MHD_Connection *connection, - const struct TALER_PurseContractPublicKeyP *purse_pub) + struct TEH_RequestContext *rc, + const char *const args[1]) { + struct MHD_Connection *connection = rc->connection; + struct TALER_PurseContractPublicKeyP purse_pub; struct TALER_PurseContractSignatureP purse_sig; bool found; bool decided; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &purse_pub, + sizeof (purse_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, + args[0]); + } { const char *sig; @@ -66,7 +81,7 @@ TEH_handler_purses_delete ( } if (GNUNET_OK != - TALER_wallet_purse_delete_verify (purse_pub, + TALER_wallet_purse_delete_verify (&purse_pub, &purse_sig)) { TALER_LOG_WARNING ("Invalid signature on /purses/$PID/delete request\n"); @@ -89,7 +104,7 @@ TEH_handler_purses_delete ( enum GNUNET_DB_QueryStatus qs; qs = TEH_plugin->do_purse_delete (TEH_plugin->cls, - purse_pub, + &purse_pub, &purse_sig, &decided, &found); @@ -117,6 +132,23 @@ TEH_handler_purses_delete ( TALER_EC_EXCHANGE_PURSE_DELETE_ALREADY_DECIDED, NULL); } + { + /* Possible minor optimization: integrate notification with + transaction above... */ + struct TALER_PurseEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED), + .purse_pub = purse_pub + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Notifying about purse deletion %s\n", + TALER_B2S (&purse_pub)); + TEH_plugin->event_notify (TEH_plugin->cls, + &rep.header, + NULL, + 0); + } /* success */ return TALER_MHD_reply_static (connection, MHD_HTTP_NO_CONTENT, diff --git a/src/exchange/taler-exchange-httpd_purses_delete.h b/src/exchange/taler-exchange-httpd_purses_delete.h index 15da21639..912dd43a8 100644 --- a/src/exchange/taler-exchange-httpd_purses_delete.h +++ b/src/exchange/taler-exchange-httpd_purses_delete.h @@ -29,14 +29,14 @@ /** * Handle a DELETE "/purses/$PURSE_PUB" request. * - * @param connection the MHD connection to handle - * @param purse_pub public key of the purse + * @param rc request details about the request to handle + * @param args argument with the public key of the purse * @return MHD result code */ MHD_RESULT TEH_handler_purses_delete ( - struct MHD_Connection *connection, - const struct TALER_PurseContractPublicKeyP *purse_pub); + struct TEH_RequestContext *rc, + const char *const args[1]); #endif diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c index 4bebebf6f..291807aaa 100644 --- a/src/exchange/taler-exchange-httpd_purses_deposit.c +++ b/src/exchange/taler-exchange-httpd_purses_deposit.c @@ -374,6 +374,7 @@ TEH_handler_purses_deposit ( enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Timestamp create_timestamp; struct GNUNET_TIME_Timestamp merge_timestamp; + bool was_deleted; qs = TEH_plugin->select_purse ( TEH_plugin->cls, @@ -383,7 +384,8 @@ TEH_handler_purses_deposit ( &pcc.amount, &pcc.deposit_total, &pcc.h_contract_terms, - &merge_timestamp); + &merge_timestamp, + &was_deleted); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -406,12 +408,16 @@ TEH_handler_purses_deposit ( case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; /* handled below */ } - if (GNUNET_TIME_absolute_is_past (pcc.purse_expiration.abs_time)) + if (GNUNET_TIME_absolute_is_past (pcc.purse_expiration.abs_time) || + was_deleted) { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_GONE, - TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_GONE, + was_deleted + ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED + : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, + GNUNET_TIME_timestamp2s (pcc.purse_expiration)); } } diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c index 8384086b6..e36b9a101 100644 --- a/src/exchange/taler-exchange-httpd_purses_get.c +++ b/src/exchange/taler-exchange-httpd_purses_get.c @@ -207,6 +207,7 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc, const char *const args[2]) { struct GetContext *gc = rc->rh_ctx; + bool purse_deleted; MHD_RESULT res; if (NULL == gc) @@ -312,7 +313,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc, &gc->amount, &gc->deposited, &gc->h_contract, - &gc->merge_timestamp); + &gc->merge_timestamp, + &purse_deleted); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -367,11 +369,14 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc, gc->eh = eh2; } } - if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time)) + if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time) || + purse_deleted) { return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_GONE, - TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, + purse_deleted + ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED + : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, GNUNET_TIME_timestamp2s ( gc->purse_expiration)); } diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 4adb4180c..c11828d0e 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -284,13 +284,15 @@ check_PROGRAMS = \ bench-db-postgres\ perf-exchangedb-reserves-in-insert-postgres\ test-exchangedb-by-j-postgres\ - test-exchangedb-batch-reserves-in-insert-postgres + test-exchangedb-batch-reserves-in-insert-postgres\ + test-exchangedb-populate-table-postgres AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; TESTS = \ test-exchangedb-postgres\ test-exchangedb-by-j-postgres\ perf-exchangedb-reserves-in-insert-postgres\ - test-exchangedb-batch-reserves-in-insert-postgres + test-exchangedb-batch-reserves-in-insert-postgres\ + test-exchangedb-populate-table-postgres test_exchangedb_postgres_SOURCES = \ @@ -364,6 +366,27 @@ bench_db_postgres_LDADD = \ -lgnunetutil \ $(XLIB) +test_exchangedb_populate_table_postgres_SOURCES = \ + test_exchangedb_populate_table.c +test_exchangedb_populate_table_postgres_LDADD = \ + libtalerexchangedb.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + -ljansson \ + -lgnunetjson \ + -lgnunetutil \ + $(XLIB) + +bench_db_postgres_SOURCES = \ + bench_db.c +bench_db_postgres_LDADD = \ + libtalerexchangedb.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + -lgnunetpq \ + -lgnunetutil \ + $(XLIB) EXTRA_test_exchangedb_postgres_DEPENDENCIES = \ libtaler_plugin_exchangedb_postgres.la diff --git a/src/exchangedb/exchange_do_delete_purse.sql b/src/exchangedb/exchange_do_purse_delete.sql similarity index 93% rename from src/exchangedb/exchange_do_delete_purse.sql rename to src/exchangedb/exchange_do_purse_delete.sql index a57f25454..096475b43 100644 --- a/src/exchangedb/exchange_do_delete_purse.sql +++ b/src/exchangedb/exchange_do_purse_delete.sql @@ -14,7 +14,7 @@ -- TALER; see the file COPYING. If not, see -- -CREATE OR REPLACE FUNCTION exchange_do_delete_purse( +CREATE OR REPLACE FUNCTION exchange_do_purse_delete( IN in_purse_pub BYTEA, IN in_purse_sig BYTEA, IN in_now INT8, @@ -28,7 +28,7 @@ DECLARE my_in_reserve_quota BOOLEAN; BEGIN -SELECT COUNT(*) FROM purse_decision +PERFORM refunded FROM purse_decision WHERE purse_pub=in_purse_pub; IF FOUND THEN @@ -49,7 +49,7 @@ THEN END IF; -- store reserve deletion -INSERT INTO purse_deletion +INSERT INTO exchange.purse_deletion (purse_pub ,purse_sig) VALUES @@ -115,5 +115,5 @@ END LOOP; END $$; -COMMENT ON FUNCTION exchange_do_delete_purse(BYTEA,BYTEA,INT8) +COMMENT ON FUNCTION exchange_do_purse_delete(BYTEA,BYTEA,INT8) IS 'Delete a previously undecided purse and refund the coins (if any).'; diff --git a/src/exchangedb/pg_select_purse.c b/src/exchangedb/pg_select_purse.c index e2a36c33e..9143e8721 100644 --- a/src/exchangedb/pg_select_purse.c +++ b/src/exchangedb/pg_select_purse.c @@ -35,7 +35,8 @@ TEH_PG_select_purse ( struct TALER_Amount *amount, struct TALER_Amount *deposited, struct TALER_PrivateContractHashP *h_contract_terms, - struct GNUNET_TIME_Timestamp *merge_timestamp) + struct GNUNET_TIME_Timestamp *merge_timestamp, + bool *purse_deleted) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -57,6 +58,8 @@ TEH_PG_select_purse ( GNUNET_PQ_result_spec_timestamp ("merge_timestamp", merge_timestamp), NULL), + GNUNET_PQ_result_spec_bool ("purse_deleted", + purse_deleted), GNUNET_PQ_result_spec_end }; @@ -72,8 +75,10 @@ TEH_PG_select_purse ( ",balance_val" ",balance_frac" ",merge_timestamp" + ",purse_sig IS NOT NULL AS purse_deleted" " FROM purse_requests" " LEFT JOIN purse_merges USING (purse_pub)" + " LEFT JOIN purse_deletion USING (purse_pub)" " WHERE purse_pub=$1;"); *merge_timestamp = GNUNET_TIME_UNIT_FOREVER_TS; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, diff --git a/src/exchangedb/pg_select_purse.h b/src/exchangedb/pg_select_purse.h index f522256df..db63f0c90 100644 --- a/src/exchangedb/pg_select_purse.h +++ b/src/exchangedb/pg_select_purse.h @@ -37,6 +37,7 @@ * @param[out] deposited set to actual amount put into the purse so far * @param[out] h_contract_terms set to hash of the contract for the purse * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not + * @param[out] purse_deleted set to true if purse was deleted * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -48,7 +49,8 @@ TEH_PG_select_purse ( struct TALER_Amount *amount, struct TALER_Amount *deposited, struct TALER_PrivateContractHashP *h_contract_terms, - struct GNUNET_TIME_Timestamp *merge_timestamp); + struct GNUNET_TIME_Timestamp *merge_timestamp, + bool *purse_deleted); #endif diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in index af47bbf63..194830248 100644 --- a/src/exchangedb/procedures.sql.in +++ b/src/exchangedb/procedures.sql.in @@ -28,11 +28,11 @@ SET search_path TO exchange; #include "exchange_do_recoup_to_reserve.sql" #include "exchange_do_recoup_to_coin.sql" #include "exchange_do_gc.sql" +#include "exchange_do_purse_delete.sql" #include "exchange_do_purse_deposit.sql" #include "exchange_do_purse_merge.sql" #include "exchange_do_reserve_purse.sql" #include "exchange_do_expire_purse.sql" -#include "exchange_do_delete_purse.sql" #include "exchange_do_history_request.sql" #include "exchange_do_reserve_open_deposit.sql" #include "exchange_do_reserve_open.sql" diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index eb258f002..68f94b6ea 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1351,6 +1351,7 @@ run (void *cls) RND_BLK (&age_hash); for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++) { + RND_BLK (&coin_pub); GNUNET_assert (GNUNET_OK == TALER_denom_blind (&dkp->pub, diff --git a/src/exchangedb/test_exchangedb_populate_table.c b/src/exchangedb/test_exchangedb_populate_table.c new file mode 100644 index 000000000..ed30894b1 --- /dev/null +++ b/src/exchangedb/test_exchangedb_populate_table.c @@ -0,0 +1,431 @@ +/* + 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 +*/ +/** + * @file exchangedb/test_exchangedb_populate_table.c + * @brief test cases for DB interaction functions + * @author Joseph Xu + */ +#include "platform.h" +#include "taler_exchangedb_lib.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + +/**o + * Global result from the testcase. + */ +static int result; + +/** + * Report line of error if @a cond is true, and jump to label "drop". + */ +#define FAILIF(cond) \ + do { \ + if (! (cond)) {break;} \ + GNUNET_break (0); \ + goto drop; \ + } while (0) + + +/** + * Initializes @a ptr with random data. + */ +#define RND_BLK(ptr) \ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr)) + +/** + * Initializes @a ptr with zeros. + */ +#define ZR_BLK(ptr) \ + memset (ptr, 0, sizeof (*ptr)) + + +/** + * Currency we use. Must match test-exchange-db-*.conf. + */ +#define CURRENCY "EUR" + + +/** + * Number of newly minted coins to use in the test. + */ +#define MELT_NEW_COINS 5 + + +/** + * How big do we make the RSA keys? + */ +#define RSA_KEY_SIZE 1024 + + +/** + * Database plugin under test. + */ +static struct TALER_EXCHANGEDB_Plugin *plugin; +static struct TALER_DenomFeeSet fees; + + +struct DenomKeyPair +{ + struct TALER_DenominationPrivateKey priv; + struct TALER_DenominationPublicKey pub; +}; + + +/** + * Destroy a denomination key pair. The key is not necessarily removed from the DB. + * + * @param dkp the key pair to destroy + */ +static void +destroy_denom_key_pair (struct DenomKeyPair *dkp) +{ + TALER_denom_pub_free (&dkp->pub); + TALER_denom_priv_free (&dkp->priv); + GNUNET_free (dkp); +} + + +/** + * Create a denomination key pair by registering the denomination in the DB. + * + * @param size the size of the denomination key + * @param now time to use for key generation, legal expiration will be 3h later. + * @param fees fees to use + * @return the denominaiton key pair; NULL upon error + */ +static struct DenomKeyPair * +create_denom_key_pair (unsigned int size, + struct GNUNET_TIME_Timestamp now, + const struct TALER_Amount *value, + const struct TALER_DenomFeeSet *fees) +{ + struct DenomKeyPair *dkp; + struct TALER_EXCHANGEDB_DenominationKey dki; + struct TALER_EXCHANGEDB_DenominationKeyInformation issue2; + + dkp = GNUNET_new (struct DenomKeyPair); + GNUNET_assert (GNUNET_OK == + TALER_denom_priv_create (&dkp->priv, + &dkp->pub, + TALER_DENOMINATION_RSA, + size)); + memset (&dki, + 0, + sizeof (struct TALER_EXCHANGEDB_DenominationKey)); + dki.denom_pub = dkp->pub; + dki.issue.start = now; + dki.issue.expire_withdraw + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add ( + now.abs_time, + GNUNET_TIME_UNIT_HOURS)); + dki.issue.expire_deposit + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add ( + now.abs_time, + GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_HOURS, 2))); + dki.issue.expire_legal + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add ( + now.abs_time, + GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_HOURS, 3))); + dki.issue.value = *value; + dki.issue.fees = *fees; + TALER_denom_pub_hash (&dkp->pub, + &dki.issue.denom_hash); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->insert_denomination_info (plugin->cls, + &dki.denom_pub, + &dki.issue)) + { + GNUNET_break (0); + destroy_denom_key_pair (dkp); + return NULL; + } + memset (&issue2, 0, sizeof (issue2)); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->get_denomination_info (plugin->cls, + &dki.issue.denom_hash, + &issue2)) + { + GNUNET_break (0); + destroy_denom_key_pair (dkp); + return NULL; + } + if (0 != GNUNET_memcmp (&dki.issue, + &issue2)) + { + GNUNET_break (0); + destroy_denom_key_pair (dkp); + return NULL; + } + return dkp; +} + + + + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure with config + */ + +static void +run (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + const uint32_t num_partitions = 10; + struct DenomKeyPair *dkp = NULL; + struct GNUNET_TIME_Timestamp ts; + struct TALER_EXCHANGEDB_Deposit depos; + struct GNUNET_TIME_Timestamp ts_deadline; + struct TALER_Amount value; + union TALER_DenominationBlindingKeyP bks; + struct TALER_CoinPubHashP c_hash; + struct TALER_EXCHANGEDB_CollectableBlindcoin cbc; + struct TALER_ExchangeWithdrawValues alg_values = { + .cipher = TALER_DENOMINATION_RSA + }; + struct TALER_PlanchetMasterSecretP ps; + struct TALER_ReservePublicKeyP reserve_pub; + + ZR_BLK (&cbc); + RND_BLK (&reserve_pub); + + memset (&depos, + 0, + sizeof (depos)); + + if (NULL == + (plugin = TALER_EXCHANGEDB_plugin_load (cfg))) + { + GNUNET_break (0); + result = 77; + return; + } + (void) plugin->drop_tables (plugin->cls); + if (GNUNET_OK != + plugin->create_tables (plugin->cls, + true, + num_partitions)) + { + GNUNET_break (0); + result = 77; + goto cleanup; + } + if (GNUNET_OK != + plugin->preflight (plugin->cls)) + { + GNUNET_break (0); + goto cleanup; + } + + + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":1.000010", + &value)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":0.000010", + &fees.withdraw)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":0.000010", + &fees.deposit)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":0.000010", + &fees.refresh)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":0.000010", + &fees.refund)); + + ts = GNUNET_TIME_timestamp_get (); + + dkp = create_denom_key_pair (RSA_KEY_SIZE, + ts, + &value, + &fees); + GNUNET_assert (NULL != dkp); + TALER_denom_pub_hash (&dkp->pub, + &cbc.denom_pub_hash); + RND_BLK (&cbc.reserve_sig); + RND_BLK (&ps); + TALER_planchet_blinding_secret_create (&ps, + &alg_values, + &bks); + + { + struct TALER_PlanchetDetail pd; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_AgeCommitmentHash age_hash; + struct TALER_AgeCommitmentHash *p_ah[2] = { + NULL, + &age_hash + }; + + + RND_BLK (&age_hash); + for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++) + { + fprintf(stdout, "OPEN\n"); + RND_BLK (&coin_pub); + GNUNET_assert (GNUNET_OK == + TALER_denom_blind (&dkp->pub, + &bks, + p_ah[i], + &coin_pub, + &alg_values, + &c_hash, + &pd.blinded_planchet)); + GNUNET_assert (GNUNET_OK == + TALER_coin_ev_hash (&pd.blinded_planchet, + &cbc.denom_pub_hash, + &cbc.h_coin_envelope)); + if (i != 0) + TALER_blinded_denom_sig_free (&cbc.sig); + GNUNET_assert ( + GNUNET_OK == + TALER_denom_sign_blinded ( + &cbc.sig, + &dkp->priv, + false, + &pd.blinded_planchet)); + TALER_blinded_planchet_free (&pd.blinded_planchet); + } + } + + cbc.reserve_pub = reserve_pub; + cbc.amount_with_fee = value; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (CURRENCY, + &cbc.withdraw_fee)); + + + + + ts_deadline = GNUNET_TIME_timestamp_get (); + + depos.deposit_fee = fees.deposit; + + RND_BLK (&depos.coin.coin_pub); + TALER_denom_pub_hash (&dkp->pub, + &depos.coin.denom_pub_hash); + GNUNET_assert (GNUNET_OK == + TALER_denom_sig_unblind (&depos.coin.denom_sig, + &cbc.sig, + &bks, + &c_hash, + &alg_values, + &dkp->pub)); + { + uint64_t known_coin_id; + struct TALER_DenominationHashP dph; + struct TALER_AgeCommitmentHash agh; + + FAILIF (TALER_EXCHANGEDB_CKS_ADDED != + plugin->ensure_coin_known (plugin->cls, + &depos.coin, + &known_coin_id, + &dph, + &agh)); + } + { + TALER_denom_sig_free (&depos.coin.denom_sig); + struct GNUNET_TIME_Timestamp now; + RND_BLK (&depos.merchant_pub); + RND_BLK (&depos.csig); + RND_BLK (&depos.h_contract_terms); + RND_BLK (&depos.wire_salt); + depos.amount_with_fee = value; + depos.refund_deadline = ts_deadline; + depos.wire_deadline = ts_deadline; + depos.receiver_wire_account = + "payto://iban/DE67830654080004822650?receiver-name=Test"; + depos.timestamp = ts; + + now = GNUNET_TIME_timestamp_get (); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->insert_deposit (plugin->cls, + now, + &depos)); + } + + result = 0; + drop: + GNUNET_break (GNUNET_OK == + plugin->drop_tables (plugin->cls)); +cleanup: + if (NULL != dkp) + destroy_denom_key_pair (dkp); + TALER_denom_sig_free (&depos.coin.denom_sig); + TALER_blinded_denom_sig_free (&cbc.sig); + dkp = NULL; + TALER_EXCHANGEDB_plugin_unload (plugin); + plugin = NULL; +} + + +int +main (int argc, + char *const argv[]) +{ + const char *plugin_name; + char *config_filename; + char *testname; + struct GNUNET_CONFIGURATION_Handle *cfg; + + (void) argc; + result = -1; + if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) + { + GNUNET_break (0); + return -1; + } + GNUNET_log_setup (argv[0], + "WARNING", + NULL); + plugin_name++; + (void) GNUNET_asprintf (&testname, + "test-exchange-db-%s", + plugin_name); + (void) GNUNET_asprintf (&config_filename, + "%s.conf", + testname); + fprintf (stdout, + "Using config: %s\n", + config_filename); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse (cfg, + config_filename)) + { + GNUNET_break (0); + GNUNET_free (config_filename); + GNUNET_free (testname); + return 2; + } + GNUNET_SCHEDULER_run (&run, + cfg); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (config_filename); + GNUNET_free (testname); + return result; +} + + +/* end of test_exchangedb_by_j.c */ diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 0e33f7770..938b74457 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -4983,7 +4983,7 @@ struct TALER_EXCHANGE_PurseDeleteHandle; * @param cb_cls closure for @a cb * @return the request handle; NULL upon error */ -struct TALER_EXCHANGE_PurseCreateDepositHandle * +struct TALER_EXCHANGE_PurseDeleteHandle * TALER_EXCHANGE_purse_delete ( struct TALER_EXCHANGE_Handle *exchange, const struct TALER_PurseContractPrivateKeyP *purse_priv, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 5a55b5c93..da28262a8 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -5827,6 +5827,7 @@ struct TALER_EXCHANGEDB_Plugin * @param[out] deposited set to actual amount put into the purse so far * @param[out] h_contract_terms set to hash of the contract for the purse * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not + * @param[out] purse_deleted set to true if purse was deleted * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -5838,7 +5839,8 @@ struct TALER_EXCHANGEDB_Plugin struct TALER_Amount *amount, struct TALER_Amount *deposited, struct TALER_PrivateContractHashP *h_contract_terms, - struct GNUNET_TIME_Timestamp *merge_timestamp); + struct GNUNET_TIME_Timestamp *merge_timestamp, + bool *purse_deleted); /** diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index aa475bd33..55d36b2de 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -2567,6 +2567,21 @@ TALER_TESTING_cmd_purse_create_with_deposit ( ...); +/** + * Deletes a purse. + * + * @param label command label + * @param expected_http_status what HTTP status do we expect to get returned from the exchange + * @param purse_cmd command that created the purse + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_purse_delete ( + const char *label, + unsigned int expected_http_status, + const char *purse_cmd); + + /** * Retrieve contract (also checks that the contract matches * the upload command). diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index e695cf043..b775719e6 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -52,6 +52,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_melt.c \ exchange_api_purse_create_with_deposit.c \ exchange_api_purse_create_with_merge.c \ + exchange_api_purse_delete.c \ exchange_api_purse_deposit.c \ exchange_api_purse_merge.c \ exchange_api_purses_get.c \ diff --git a/src/lib/exchange_api_purse_delete.c b/src/lib/exchange_api_purse_delete.c new file mode 100644 index 000000000..27a9082b2 --- /dev/null +++ b/src/lib/exchange_api_purse_delete.c @@ -0,0 +1,243 @@ +/* + This file is part of TALER + Copyright (C) 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 + + */ +/** + * @file lib/exchange_api_purse_delete.c + * @brief Implementation of the client to delete a purse + * into an account + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "exchange_api_common.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A purse delete with deposit handle + */ +struct TALER_EXCHANGE_PurseDeleteHandle +{ + + /** + * 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; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PurseDeleteCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Header with the purse_sig. + */ + struct curl_slist *xhdr; +}; + + +/** + * Function called when we're done processing the + * HTTP DELETE /purse/$PID request. + * + * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_delete_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PurseDeleteResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + pdh->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + 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 */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + pdh->cb (pdh->cb_cls, + &dr); + TALER_EXCHANGE_purse_delete_cancel (pdh); +} + + +struct TALER_EXCHANGE_PurseDeleteHandle * +TALER_EXCHANGE_purse_delete ( + struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_PurseContractPrivateKeyP *purse_priv, + TALER_EXCHANGE_PurseDeleteCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_PurseDeleteHandle *pdh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_PurseContractSignatureP purse_sig; + char arg_str[sizeof (purse_pub) * 2 + 32]; + + pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle); + pdh->exchange = exchange; + pdh->cb = cb; + pdh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, + &purse_pub.eddsa_pub); + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + { + char pub_str[sizeof (purse_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&purse_pub, + sizeof (purse_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/purses/%s", + pub_str); + } + pdh->url = TEAH_path_to_url (exchange, + arg_str); + if (NULL == pdh->url) + { + GNUNET_break (0); + GNUNET_free (pdh); + return NULL; + } + TALER_wallet_purse_delete_sign (purse_priv, + &purse_sig); + { + char *delete_str; + char *xhdr; + + delete_str = + GNUNET_STRINGS_data_to_string_alloc (&purse_sig, + sizeof (purse_sig)); + GNUNET_asprintf (&xhdr, + "Taler-Purse-Signature: %s", + delete_str); + GNUNET_free (delete_str); + pdh->xhdr = curl_slist_append (NULL, + xhdr); + GNUNET_free (xhdr); + } + eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_DELETE)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for purse delete: `%s'\n", + pdh->url); + ctx = TEAH_handle_to_context (exchange); + pdh->job = GNUNET_CURL_job_add2 (ctx, + eh, + pdh->xhdr, + &handle_purse_delete_finished, + pdh); + return pdh; +} + + +void +TALER_EXCHANGE_purse_delete_cancel ( + struct TALER_EXCHANGE_PurseDeleteHandle *pdh) +{ + if (NULL != pdh->job) + { + GNUNET_CURL_job_cancel (pdh->job); + pdh->job = NULL; + } + curl_slist_free_all (pdh->xhdr); + GNUNET_free (pdh->url); + GNUNET_free (pdh); +} + + +/* end of exchange_api_purse_delete.c */ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 5ae7de409..db2a54e73 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -77,6 +77,7 @@ libtalertesting_la_SOURCES = \ testing_api_cmd_offline_sign_keys.c \ testing_api_cmd_offline_sign_extensions.c \ testing_api_cmd_purse_create_deposit.c \ + testing_api_cmd_purse_delete.c \ testing_api_cmd_purse_deposit.c \ testing_api_cmd_purse_get.c \ testing_api_cmd_purse_merge.c \ diff --git a/src/testing/test_exchange_p2p.c b/src/testing/test_exchange_p2p.c index f159b2f1b..ad95bf63c 100644 --- a/src/testing/test_exchange_p2p.c +++ b/src/testing/test_exchange_p2p.c @@ -158,6 +158,19 @@ run (void *cls, TALER_TESTING_cmd_end () }; struct TALER_TESTING_Command push[] = { + TALER_TESTING_cmd_purse_create_with_deposit ( + "purse-with-deposit-for-delete", + MHD_HTTP_OK, + "{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}", + true, /* upload contract */ + GNUNET_TIME_UNIT_MINUTES, /* expiration */ + "withdraw-coin-1", + "EUR:1.01", + NULL), + TALER_TESTING_cmd_purse_delete ( + "purse-with-deposit-delete", + MHD_HTTP_NO_CONTENT, + "purse-with-deposit-for-delete"), TALER_TESTING_cmd_purse_create_with_deposit ( "purse-with-deposit", MHD_HTTP_OK, diff --git a/src/testing/testing_api_cmd_purse_delete.c b/src/testing/testing_api_cmd_purse_delete.c new file mode 100644 index 000000000..aa0853274 --- /dev/null +++ b/src/testing/testing_api_cmd_purse_delete.c @@ -0,0 +1,190 @@ +/* + This file is part of TALER + Copyright (C) 2020 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 + +*/ +/** + * @file testing/testing_api_cmd_purse_delete.c + * @brief command for testing /management/purse/disable. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "purse_delete" CMD. + */ +struct PurseDeleteState +{ + + /** + * Purse delete handle while operation is running. + */ + struct TALER_EXCHANGE_PurseDeleteHandle *pdh; + + /** + * Our interpreter. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Command that created the purse we now want to + * delete. + */ + const char *purse_cmd; +}; + + +/** + * Callback to analyze the DELETE /purses/$PID response, just used to check if + * the response code is acceptable. + * + * @param cls closure. + * @param pdr HTTP response details + */ +static void +purse_delete_cb (void *cls, + const struct TALER_EXCHANGE_PurseDeleteResponse *pdr) +{ + struct PurseDeleteState *pds = cls; + + pds->pdh = NULL; + if (pds->expected_response_code != pdr->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + pdr->hr.http_status, + pds->is->commands[pds->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (pdr->hr.reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (pds->is); + return; + } + TALER_TESTING_interpreter_next (pds->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +purse_delete_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct PurseDeleteState *pds = cls; + const struct TALER_PurseContractPrivateKeyP *purse_priv; + const struct TALER_TESTING_Command *ref; + + (void) cmd; + ref = TALER_TESTING_interpreter_lookup_command (is, + pds->purse_cmd); + if (NULL == ref) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_purse_priv (ref, + &purse_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + pds->is = is; + pds->pdh = TALER_EXCHANGE_purse_delete ( + is->exchange, + purse_priv, + &purse_delete_cb, + pds); + if (NULL == pds->pdh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "purse_delete" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, must be a `struct PurseDeleteState`. + * @param cmd the command which is being cleaned up. + */ +static void +purse_delete_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct PurseDeleteState *pds = cls; + + if (NULL != pds->pdh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + pds->is->ip, + cmd->label); + TALER_EXCHANGE_purse_delete_cancel (pds->pdh); + pds->pdh = NULL; + } + GNUNET_free (pds); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_purse_delete (const char *label, + unsigned int expected_http_status, + const char *purse_cmd) +{ + struct PurseDeleteState *ds; + + ds = GNUNET_new (struct PurseDeleteState); + ds->expected_response_code = expected_http_status; + ds->purse_cmd = purse_cmd; + { + struct TALER_TESTING_Command cmd = { + .cls = ds, + .label = label, + .run = &purse_delete_run, + .cleanup = &purse_delete_cleanup + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_purse_delete.c */ diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index 7a81d1c33..1bd3c187e 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -360,14 +360,12 @@ withdraw_run (void *cls, = TALER_TESTING_interpreter_lookup_command ( is, ws->reserve_reference); - if (NULL == create_reserve) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } - if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv (create_reserve, &rp)) @@ -376,7 +374,6 @@ withdraw_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } - if (NULL == ws->exchange_url) ws->exchange_url = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));