aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-11-17 16:37:40 +0100
committerChristian Grothoff <christian@grothoff.org>2016-11-17 16:37:40 +0100
commit7fd6be5cef06d0bd495f4e03d33c4d6f04c36131 (patch)
tree74dd8e086a3f584b51fcff7721a4b9dba4582784
parente140ca9dcef9bd86f9c9214872107693ef8c441c (diff)
add idempotency checks for /refresh/reveal, fixing #4793
-rw-r--r--ChangeLog3
-rw-r--r--src/exchange-lib/test_exchange_api.c17
-rw-r--r--src/exchange/taler-exchange-httpd_db.c21
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c2
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c72
-rw-r--r--src/include/taler_exchangedb_plugin.h22
6 files changed, 125 insertions, 12 deletions
diff --git a/ChangeLog b/ChangeLog
index ec9eddc36..d34dc843f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+Thu Nov 17 16:37:22 CET 2016
+ Added missing idempotency checks for /refresh/reveal. -CG
+
Thu Nov 17 11:37:56 CET 2016
Fixed a few cases of missing database rollbacks, causing the
exchange to be stuck. -CG
diff --git a/src/exchange-lib/test_exchange_api.c b/src/exchange-lib/test_exchange_api.c
index e25fa66cd..8c5e17d15 100644
--- a/src/exchange-lib/test_exchange_api.c
+++ b/src/exchange-lib/test_exchange_api.c
@@ -1710,6 +1710,9 @@ interpreter_run (void *cls)
fail (is);
return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Running command `%s'\n",
+ cmd->label);
switch (cmd->oc)
{
case OC_END:
@@ -2836,6 +2839,12 @@ run (void *cls)
.expected_response_code = MHD_HTTP_OK,
.details.refresh_reveal.melt_ref = "refresh-melt-1" },
+ /* do it again to check idempotency */
+ { .oc = OC_REFRESH_REVEAL,
+ .label = "refresh-reveal-1-idempotency",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.refresh_reveal.melt_ref = "refresh-melt-1" },
+
/* Test that /refresh/link works */
{ .oc = OC_REFRESH_LINK,
.label = "refresh-link-1",
@@ -2849,7 +2858,7 @@ run (void *cls)
.label = "refresh-deposit-refreshed-1a",
.expected_response_code = MHD_HTTP_OK,
.details.deposit.amount = "EUR:1",
- .details.deposit.coin_ref = "refresh-reveal-1",
+ .details.deposit.coin_ref = "refresh-reveal-1-idempotency",
.details.deposit.coin_idx = 0,
.details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42 }",
.details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }",
@@ -3072,12 +3081,6 @@ main (int argc,
enum GNUNET_OS_ProcessStatusType type;
unsigned long code;
- GNUNET_log_setup ("test-exchange-api",
- "DEBUG",
- "/tmp/logs");
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "test log\n");
- return 0;
-
/* These might get in the way... */
unsetenv ("XDG_DATA_HOME");
unsetenv ("XDG_CONFIG_HOME");
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 2c6e90656..238112771 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -1280,16 +1280,28 @@ refresh_exchange_coin (struct MHD_Connection *connection,
ev_sig.rsa_signature = NULL;
return ev_sig;
}
+ if (GNUNET_OK ==
+ TEH_plugin->get_refresh_out (TEH_plugin->cls,
+ session,
+ session_hash,
+ coin_off,
+ &ev_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning cashed reply for /refresh/reveal signature\n");
+ return ev_sig;
+ }
+
ev_sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key,
- commit_coin->coin_ev,
- commit_coin->coin_ev_size);
+ = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key,
+ commit_coin->coin_ev,
+ commit_coin->coin_ev_size);
if (NULL == ev_sig.rsa_signature)
{
GNUNET_break (0);
return ev_sig;
}
- if (GNUNET_OK !=
+ if (GNUNET_SYSERR ==
TEH_plugin->insert_refresh_out (TEH_plugin->cls,
session,
session_hash,
@@ -1300,6 +1312,7 @@ refresh_exchange_coin (struct MHD_Connection *connection,
GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature);
ev_sig.rsa_signature = NULL;
}
+
return ev_sig;
}
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index b31f22e10..2ecd3b4e5 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -318,7 +318,7 @@ TEH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection,
{
return TEH_RESPONSE_reply_internal_error (connection,
ec,
- "Failed to connect to database");
+ "Failure in database interaction");
}
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 7ae8b5753..11d08f82d 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -1154,6 +1154,15 @@ postgres_prepare (PGconn *db_conn)
"($1, $2, $3)",
3, NULL);
+ /* Used in #postgres_get_refresh_out() to test if the
+ generated signature(s) already exists */
+ PREPARE ("get_refresh_out",
+ "SELECT ev_sig"
+ " FROM refresh_out"
+ " WHERE session_hash=$1"
+ " AND newcoin_index=$2",
+ 2, NULL);
+
/* Used in #postgres_get_link_data_list(). We use the session_hash
to obtain the "noreveal_index" for that session, and then select the
corresponding signatures (ev_sig) and the denomination keys from
@@ -3442,7 +3451,69 @@ postgres_get_refresh_transfer_public_key (void *cls,
* @param session_hash hash to identify refresh session
* @param newcoin_index coin index
* @param ev_sig coin signature
+ * @return #GNUNET_OK on success, #GNUNET_NO if we have no such result
+ * #GNUNET_SYSERR on error
+ */
+static int
+postgres_get_refresh_out (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t newcoin_index,
+ struct TALER_DenominationSignature *ev_sig)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&newcoin_index),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
+ &ev_sig->rsa_signature),
+ GNUNET_PQ_result_spec_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_refresh_out",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (1 != PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Insert signature of a new coin generated during refresh into
+ * the database indexed by the refresh session and the index
+ * of the coin. This data is later used should an old coin
+ * be used to try to obtain the private keys during "/refresh/link".
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param session database connection
+ * @param session_hash hash to identify refresh session
+ * @param newcoin_index coin index
+ * @param ev_sig coin signature
* @return #GNUNET_OK on success
+ * #GNUNET_SYSERR on error
*/
static int
postgres_insert_refresh_out (void *cls,
@@ -5050,6 +5121,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
plugin->free_refresh_commit_coins = &postgres_free_refresh_commit_coins;
plugin->insert_refresh_transfer_public_key = &postgres_insert_refresh_transfer_public_key;
plugin->get_refresh_transfer_public_key = &postgres_get_refresh_transfer_public_key;
+ plugin->get_refresh_out = &postgres_get_refresh_out;
plugin->insert_refresh_out = &postgres_insert_refresh_out;
plugin->get_link_data_list = &postgres_get_link_data_list;
plugin->free_link_data_list = &common_free_link_data_list;
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index 02d41f2bf..19fb1d042 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -1360,6 +1360,27 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * Get signature of a new coin generated during refresh into
+ * the database indexed by the refresh session and the index
+ * of the coin.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session database connection
+ * @param session_hash hash to identify refresh session
+ * @param newcoin_index coin index
+ * @param[out] ev_sig coin signature
+ * @return #GNUNET_OK on success, #GNUNET_NO if we have no such entry,
+ * #GNUNET_SYSERR on error
+ */
+ int
+ (*get_refresh_out) (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t newcoin_index,
+ struct TALER_DenominationSignature *ev_sig);
+
+
+ /**
* Insert signature of a new coin generated during refresh into
* the database indexed by the refresh session and the index
* of the coin. This data is later used should an old coin
@@ -1371,6 +1392,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param newcoin_index coin index
* @param ev_sig coin signature
* @return #GNUNET_OK on success
+ * #GNUNET_SYSERR on error
*/
int
(*insert_refresh_out) (void *cls,