summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c4
-rw-r--r--src/exchange/taler-exchange-httpd_payback.c2
-rw-r--r--src/exchange/taler-exchange-httpd_refresh_melt.c107
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c2
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c2
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c35
-rw-r--r--src/include/taler_error_codes.h8
-rw-r--r--src/lib/test_exchange_api_revocation.c31
-rw-r--r--src/lib/testing_api_cmd_payback.c101
9 files changed, 188 insertions, 104 deletions
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index 5320c9c75..51adacb7c 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -179,7 +179,9 @@ deposit_transaction (void *cls,
/* Start with fee for THIS transaction */
spent = deposit->amount_with_fee;
- /* add cost of all previous transactions */
+ /* add cost of all previous transactions; skip PAYBACK as revoked
+ denominations are not eligible for deposit, and if we are the old coin
+ pub of a revoked coin (aka a zombie), then ONLY refresh is allowed. */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&deposit->coin.coin_pub,
diff --git a/src/exchange/taler-exchange-httpd_payback.c b/src/exchange/taler-exchange-httpd_payback.c
index 195e5613c..8cfc1aecc 100644
--- a/src/exchange/taler-exchange-httpd_payback.c
+++ b/src/exchange/taler-exchange-httpd_payback.c
@@ -289,7 +289,7 @@ payback_transaction (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
- /* Calculate remaining balance. */
+ /* Calculate remaining balance, including paybacks already applied. */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&pc->coin->coin_pub,
diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c
index 5674a12f8..8677d6270 100644
--- a/src/exchange/taler-exchange-httpd_refresh_melt.c
+++ b/src/exchange/taler-exchange-httpd_refresh_melt.c
@@ -48,11 +48,11 @@
*/
static int
reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct TALER_Amount coin_value,
- struct TALER_EXCHANGEDB_TransactionList *tl,
- const struct TALER_Amount *requested,
- const struct TALER_Amount *residual)
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount coin_value,
+ struct TALER_EXCHANGEDB_TransactionList *tl,
+ const struct TALER_Amount *requested,
+ const struct TALER_Amount *residual)
{
json_t *history;
@@ -64,9 +64,9 @@ reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection,
MHD_HTTP_FORBIDDEN,
"{s:s, s:I, s:o, s:o, s:o, s:o, s:o}",
"error",
- "insufficient funds",
- "code",
- (json_int_t) TALER_EC_REFRESH_MELT_INSUFFICIENT_FUNDS,
+ "insufficient funds",
+ "code",
+ (json_int_t) TALER_EC_REFRESH_MELT_INSUFFICIENT_FUNDS,
"coin_pub",
GNUNET_JSON_from_data_auto (coin_pub),
"original_value",
@@ -90,8 +90,8 @@ reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection,
*/
static int
reply_refresh_melt_success (struct MHD_Connection *connection,
- const struct TALER_RefreshCommitmentP *rc,
- uint32_t noreveal_index)
+ const struct TALER_RefreshCommitmentP *rc,
+ uint32_t noreveal_index)
{
struct TALER_RefreshMeltConfirmationPS body;
struct TALER_ExchangePublicKeyP pub;
@@ -139,6 +139,13 @@ struct RefreshMeltContext
*/
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
+ /**
+ * Set to #GNUNET_YES if this @a dki was revoked and the operation
+ * is thus only allowed for zombie coins where the transaction
+ * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_PAYBACK.
+ */
+ int zombie_required;
+
};
@@ -168,11 +175,12 @@ refresh_check_melt (struct MHD_Connection *connection,
/* Start with cost of this melt transaction */
spent = rmc->refresh_session.amount_with_fee;
- /* add historic transaction costs of this coin */
+ /* add historic transaction costs of this coin, including paybacks as
+ we might be a zombie coin */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&rmc->refresh_session.coin.coin_pub,
- GNUNET_NO,
+ GNUNET_YES,
&tl);
if (0 > qs)
{
@@ -181,16 +189,40 @@ refresh_check_melt (struct MHD_Connection *connection,
TALER_EC_REFRESH_MELT_DB_FETCH_ERROR);
return qs;
}
+ if (rmc->zombie_required)
+ {
+ for (struct TALER_EXCHANGEDB_TransactionList *tp = tl;
+ NULL != tp;
+ tp = tp->next)
+ {
+ if (TALER_EXCHANGEDB_TT_OLD_COIN_PAYBACK == tp->type)
+ {
+ rmc->zombie_required = GNUNET_NO; /* was satisfied! */
+ break;
+ }
+ }
+ if (rmc->zombie_required)
+ {
+ /* zombie status not satisfied */
+ GNUNET_break (0);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TEH_RESPONSE_reply_external_error (connection,
+ TALER_EC_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE,
+ "denomination expired");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl,
- &spent,
- &spent))
+ &spent,
+ &spent))
{
GNUNET_break (0);
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
- TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED);
+ TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED);
return GNUNET_DB_STATUS_HARD_ERROR;
}
@@ -206,11 +238,11 @@ refresh_check_melt (struct MHD_Connection *connection,
&spent,
&rmc->refresh_session.amount_with_fee));
*mhd_ret = reply_refresh_melt_insufficient_funds (connection,
- &rmc->refresh_session.coin.coin_pub,
- coin_value,
- tl,
- &rmc->refresh_session.amount_with_fee,
- &coin_residual);
+ &rmc->refresh_session.coin.coin_pub,
+ coin_value,
+ tl,
+ &rmc->refresh_session.amount_with_fee,
+ &coin_residual);
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
return GNUNET_DB_STATUS_HARD_ERROR;
@@ -245,9 +277,9 @@ refresh_check_melt (struct MHD_Connection *connection,
*/
static enum GNUNET_DB_QueryStatus
refresh_melt_transaction (void *cls,
- struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Session *session,
- int *mhd_ret)
+ struct MHD_Connection *connection,
+ struct TALER_EXCHANGEDB_Session *session,
+ int *mhd_ret)
{
struct RefreshMeltContext *rmc = cls;
enum GNUNET_DB_QueryStatus qs;
@@ -262,8 +294,8 @@ refresh_melt_transaction (void *cls,
{
TALER_LOG_DEBUG ("Found already-melted coin\n");
*mhd_ret = reply_refresh_melt_success (connection,
- &rmc->refresh_session.rc,
- noreveal_index);
+ &rmc->refresh_session.rc,
+ noreveal_index);
/* Note: we return "hard error" to ensure the wrapper
does not retry the transaction, and to also not generate
a "fresh" response (as we would on "success") */
@@ -273,22 +305,22 @@ refresh_melt_transaction (void *cls,
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
- TALER_EC_REFRESH_MELT_DB_FETCH_ERROR);
+ TALER_EC_REFRESH_MELT_DB_FETCH_ERROR);
return qs;
}
/* check coin has enough funds remaining on it to cover melt cost */
qs = refresh_check_melt (connection,
- session,
- rmc,
- mhd_ret);
+ session,
+ rmc,
+ mhd_ret);
if (0 > qs)
return qs;
/* pick challenge and persist it */
rmc->refresh_session.noreveal_index
= GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
- TALER_CNC_KAPPA);
+ TALER_CNC_KAPPA);
if (0 >=
(qs = TEH_plugin->insert_melt (TEH_plugin->cls,
@@ -298,7 +330,7 @@ refresh_melt_transaction (void *cls,
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
{
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
- TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR);
+ TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR);
return GNUNET_DB_STATUS_HARD_ERROR;
}
return qs;
@@ -327,9 +359,9 @@ handle_refresh_melt (struct MHD_Connection *connection,
struct TALER_Amount fee_refresh;
TALER_amount_ntoh (&fee_refresh,
- &rmc->dki->issue.properties.fee_refresh);
+ &rmc->dki->issue.properties.fee_refresh);
if (TALER_amount_cmp (&fee_refresh,
- &rmc->refresh_session.amount_with_fee) > 0)
+ &rmc->refresh_session.amount_with_fee) > 0)
{
GNUNET_break_op (0);
return TEH_RESPONSE_reply_external_error (connection,
@@ -358,8 +390,8 @@ handle_refresh_melt (struct MHD_Connection *connection,
{
GNUNET_break_op (0);
return TEH_RESPONSE_reply_signature_invalid (connection,
- TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID,
- "confirm_sig");
+ TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID,
+ "confirm_sig");
}
}
@@ -505,9 +537,8 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
TEH_KS_DKU_ZOMBIE);
if (NULL != dki)
{
- /* Test if zombie-condition is actually satisfied for the coin */
- if (0 /* FIXME: test if zombie-satisfied */)
- rmc.dki = dki;
+ rmc.dki = dki;
+ rmc.zombie_required = GNUNET_YES;
}
}
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index 69b5cae96..5fea37da0 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -165,7 +165,7 @@ refund_transaction (void *cls,
session,
&refund->coin.coin_pub,
GNUNET_NO,
- &tl);
+ &tl);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 3cf3e781b..f05e42604 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -393,7 +393,7 @@ TEH_RESPONSE_reply_external_error (struct MHD_Connection *connection,
*/
int
TEH_RESPONSE_reply_commit_error (struct MHD_Connection *connection,
- enum TALER_ErrorCode ec)
+ enum TALER_ErrorCode ec)
{
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 56e9202b7..c95ed2616 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -322,11 +322,10 @@ postgres_create_tables (void *cls)
",h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)"
",ev_sig BYTEA NOT NULL"
",PRIMARY KEY (rc, newcoin_index)"
+ ",UNIQUE (h_coin_ev)"
");"),
GNUNET_PQ_make_try_execute ("CREATE INDEX refresh_revealed_coins_coin_pub_index ON "
"refresh_revealed_coins (denom_pub_hash);"),
- GNUNET_PQ_make_try_execute ("CREATE INDEX refresh_revealed_coins_h_coin_ev_index ON "
- "refresh_revealed_coins (h_coin_ev);"),
/* Table with the transfer keys of a refresh operation; includes
the rc for which this is the link information, the
@@ -1661,23 +1660,24 @@ postgres_prepare (PGconn *db_conn)
affecting old coins of refreshed coins */
GNUNET_PQ_make_prepare ("payback_by_old_coin",
"SELECT"
- " pr.coin_pub"
- ",pr.coin_sig"
- ",pr.coin_blind"
- ",pr.amount_val"
- ",pr.amount_frac"
- ",pr.amount_curr"
- ",pr.timestamp"
+ " coin_pub"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount_val"
+ ",amount_frac"
+ ",amount_curr"
+ ",timestamp"
",coins.denom_pub_hash"
",coins.denom_sig"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (rc)"
- " JOIN payback_refresh pr"
- " ON (rrc.coin_ev = pr.h_blind_ev)"
+ " FROM payback_refresh"
" JOIN known_coins coins"
- " ON (coins.coin_pub = pr.coin_pub)"
- " WHERE old_coin_pub=$1"
+ " USING (coin_pub)"
+ " WHERE h_blind_ev IN"
+ " (SELECT rrc.h_coin_ev"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (rc)"
+ " WHERE old_coin_pub=$1)"
" FOR UPDATE;",
1),
/* Used in #postgres_get_reserve_history() */
@@ -4821,9 +4821,6 @@ postgres_get_coin_transactions (void *cls,
/** #TALER_EXCHANGEDB_TT_REFUND */
{ "get_refunds_by_coin",
&add_coin_refund },
- /** #TALER_EXCHANGEDB_TT_OLD_COIN_PAYBACK */
- { "payback_by_old_coin",
- &add_old_coin_payback },
{ NULL, NULL }
};
static const struct Work work_wp[] = {
diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h
index 5767a73b2..cd99a40fc 100644
--- a/src/include/taler_error_codes.h
+++ b/src/include/taler_error_codes.h
@@ -523,6 +523,12 @@ enum TALER_ErrorCode
*/
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS = 1308,
+ /**
+ * The denomination of the given coin has past its expiration date and it is
+ * also not a valid zombie (that is, was not refreshed with the fresh coin
+ * being subjected to payback).
+ */
+ TALER_EC_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE = 1309,
/**
* The provided transfer keys do not match up with the
@@ -624,7 +630,7 @@ enum TALER_ErrorCode
*/
TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID = 1362,
-
+
/**
* The coin specified in the link request is unknown to the exchange.
* This response is provided with HTTP status code
diff --git a/src/lib/test_exchange_api_revocation.c b/src/lib/test_exchange_api_revocation.c
index 090b38b25..bd1d91c66 100644
--- a/src/lib/test_exchange_api_revocation.c
+++ b/src/lib/test_exchange_api_revocation.c
@@ -159,16 +159,27 @@ run (void *cls,
"refresh-melt-1",
CONFIG_FILE),
/* Refund coin to original coin */
- TALER_TESTING_cmd_payback ("payback-1",
+ TALER_TESTING_cmd_payback ("payback-1a",
MHD_HTTP_OK,
- "refresh-reveal-1",
- "EUR:5",
+ "refresh-reveal-1#0",
+ "EUR:1",
"refresh-melt-1"),
- /**
- * Melt original coin AGAIN
- * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+ TALER_TESTING_cmd_payback ("payback-1b",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#1",
+ "EUR:1",
+ "refresh-melt-1"),
+ TALER_TESTING_cmd_payback ("payback-1c",
+ MHD_HTTP_OK,
+ "refresh-reveal-1#2",
+ "EUR:1",
+ "refresh-melt-1"),
+ /* Melt original coin AGAIN (FIXME: this command
+ is simply WRONG as it neither matches
+ the EUR:3 that were paid back NOR is melt_double
+ precisely right here!) -- it always tries to MELT EUR:4, which is too much! */
TALER_TESTING_cmd_refresh_melt_double
- ("refresh-melt-2", "EUR:4",
+ ("refresh-melt-2", "EUR:3",
"withdraw-coin-1", MHD_HTTP_OK),
/**
* Complete (successful) melt operation, and withdraw the coins
@@ -190,18 +201,18 @@ run (void *cls,
TALER_TESTING_cmd_payback ("payback-2",
MHD_HTTP_OK,
"refresh-melt-2",
- "EUR:5",
+ "EUR:1",
"refresh-melt-2"),
/* Refund original coin to reserve */
TALER_TESTING_cmd_payback ("payback-3",
MHD_HTTP_OK,
"withdraw-coin-1",
- "EUR:5",
+ "EUR:1",
NULL),
/* Check the money is back with the reserve */
TALER_TESTING_cmd_status ("payback-reserve-status-1",
"create-reserve-1",
- "EUR:4.0",
+ "EUR:1.0",
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
diff --git a/src/lib/testing_api_cmd_payback.c b/src/lib/testing_api_cmd_payback.c
index c7ea5091d..de394fe26 100644
--- a/src/lib/testing_api_cmd_payback.c
+++ b/src/lib/testing_api_cmd_payback.c
@@ -107,6 +107,51 @@ struct PaybackState
};
+
+/**
+ * Parser reference to a coin.
+ *
+ * @param coin_reference of format $LABEL['#' $INDEX]?
+ * @param cref[out] where we return a copy of $LABEL
+ * @param idx[out] where we set $INDEX
+ * @return #GNUNET_SYSERR if $INDEX is present but not numeric
+ */
+static int
+parse_coin_reference (const char *coin_reference,
+ char **cref,
+ unsigned int *idx)
+{
+ const char *index;
+
+ /* We allow command references of the form "$LABEL#$INDEX" or
+ just "$LABEL", which implies the index is 0. Figure out
+ which one it is. */
+ index = strchr (coin_reference, '#');
+ if (NULL == index)
+ {
+ *idx = 0;
+ *cref = GNUNET_strdup (coin_reference);
+ return GNUNET_OK;
+ }
+ *cref = GNUNET_strndup (coin_reference,
+ index - coin_reference);
+ if (1 != sscanf (index + 1,
+ "%u",
+ idx))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
+ index,
+ __FILE__,
+ __LINE__);
+ GNUNET_free (*cref);
+ *cref = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
/**
* Check the result of the payback request: checks whether
* the HTTP response code is good, and that the coin that
@@ -137,7 +182,6 @@ payback_cb (void *cls,
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
const struct TALER_TESTING_Command *reserve_cmd;
char *cref;
- const char *index;
unsigned int idx;
ps->ph = NULL;
@@ -155,34 +199,15 @@ payback_cb (void *cls,
return;
}
- /* We allow command references of the form "$LABEL#$INDEX" or
- just "$LABEL", which implies the index is 0. Figure out
- which one it is. */
- index = strchr (ps->coin_reference, '#');
- if (NULL == index)
+ if (GNUNET_OK !=
+ parse_coin_reference (ps->coin_reference,
+ &cref,
+ &idx))
{
- idx = 0;
- cref = GNUNET_strdup (ps->coin_reference);
- }
- else
- {
- cref = GNUNET_strndup (ps->coin_reference,
- index - ps->coin_reference);
- if (1 != sscanf (index,
- "%u",
- &idx))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Numeric index (not `%s') required after `#' in command reference of command %s in %s:%u\n",
- index,
- cmd->label,
- __FILE__,
- __LINE__);
- TALER_TESTING_interpreter_fail (is);
- GNUNET_free (cref);
- return;
- }
+ TALER_TESTING_interpreter_fail (is);
+ return;
}
+
reserve_cmd = TALER_TESTING_interpreter_lookup_command
(is, cref);
GNUNET_free (cref);
@@ -309,10 +334,22 @@ payback_run (void *cls,
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_DenominationSignature *coin_sig;
struct TALER_PlanchetSecretsP planchet;
+ char *cref;
+ unsigned int idx;
ps->is = is;
+ if (GNUNET_OK !=
+ parse_coin_reference (ps->coin_reference,
+ &cref,
+ &idx))
+ {
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
coin_cmd = TALER_TESTING_interpreter_lookup_command
- (is, ps->coin_reference);
+ (is, cref);
+ GNUNET_free (cref);
if (NULL == coin_cmd)
{
@@ -322,7 +359,7 @@ payback_run (void *cls,
}
if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
- (coin_cmd, 0, &coin_priv))
+ (coin_cmd, idx, &coin_priv))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -330,7 +367,7 @@ payback_run (void *cls,
}
if (GNUNET_OK != TALER_TESTING_get_trait_blinding_key
- (coin_cmd, 0, &blinding_key))
+ (coin_cmd, idx, &blinding_key))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -340,7 +377,7 @@ payback_run (void *cls,
planchet.blinding_key = *blinding_key;
if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
- (coin_cmd, 0, &denom_pub))
+ (coin_cmd, idx, &denom_pub))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -348,7 +385,7 @@ payback_run (void *cls,
}
if (GNUNET_OK != TALER_TESTING_get_trait_denom_sig
- (coin_cmd, 0, &coin_sig))
+ (coin_cmd, idx, &coin_sig))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);