aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2022-12-29 10:10:25 +0100
committerChristian Grothoff <christian@grothoff.org>2022-12-29 10:10:25 +0100
commitfa840f7071da56c4794c887b813ca2a6f491f836 (patch)
tree7b126d737aa1fdc3df2ea60f76540af6011ea049
parent5828eead705965b5ac87cfad78636b1363b16396 (diff)
parent915d6ddfaa638a9759766eaf69dfef7e8e17474b (diff)
downloadexchange-fa840f7071da56c4794c887b813ca2a6f491f836.tar.xz
Merge branch 'master' of git+ssh://git.taler.net/exchange
m---------contrib/gana0
-rw-r--r--src/auditor/taler-helper-auditor-purses.c11
-rw-r--r--src/exchange/exchange.conf29
-rw-r--r--src/exchange/taler-exchange-httpd.c281
-rw-r--r--src/exchange/taler-exchange-httpd.h121
-rw-r--r--src/exchange/taler-exchange-httpd_purses_delete.c40
-rw-r--r--src/exchange/taler-exchange-httpd_purses_delete.h8
-rw-r--r--src/exchange/taler-exchange-httpd_purses_deposit.c18
-rw-r--r--src/exchange/taler-exchange-httpd_purses_get.c11
-rw-r--r--src/exchangedb/Makefile.am27
-rw-r--r--src/exchangedb/exchange_do_purse_delete.sql (renamed from src/exchangedb/exchange_do_delete_purse.sql)8
-rw-r--r--src/exchangedb/pg_select_purse.c7
-rw-r--r--src/exchangedb/pg_select_purse.h4
-rw-r--r--src/exchangedb/procedures.sql.in2
-rw-r--r--src/exchangedb/test_exchangedb.c1
-rw-r--r--src/exchangedb/test_exchangedb_populate_table.c431
-rw-r--r--src/include/taler_exchange_service.h2
-rw-r--r--src/include/taler_exchangedb_plugin.h4
-rw-r--r--src/include/taler_testing_lib.h15
-rw-r--r--src/lib/Makefile.am1
-rw-r--r--src/lib/exchange_api_purse_delete.c243
-rw-r--r--src/testing/Makefile.am1
-rw-r--r--src/testing/test_exchange_p2p.c13
-rw-r--r--src/testing/testing_api_cmd_purse_delete.c190
-rw-r--r--src/testing/testing_api_cmd_withdraw.c3
25 files changed, 1037 insertions, 434 deletions
diff --git a/contrib/gana b/contrib/gana
-Subproject f603a795963748040e41693daceae343b3a972e
+Subproject df1d198918cbdd03c18723d818979c8d09f8f23
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
@@ -116,11 +116,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)
*/
struct GNUNET_TIME_Relative TEH_max_keys_caching;
@@ -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",
@@ -1526,185 +1532,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 <gnunet/gnunet_mhd_compat.h>
-/* ************* 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 <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
+#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
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 <http://www.gnu.org/licenses/>
--
-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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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
@@ -2568,6 +2568,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
+ <http://www.gnu.org/licenses/>
+ */
+/**
+ * @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 <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_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
@@ -159,6 +159,19 @@ run (void *cls,
};
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,
"{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <gnunet/gnunet_curl_lib.h>
+#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));