summaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_db.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchange/taler-exchange-httpd_db.c')
-rw-r--r--src/exchange/taler-exchange-httpd_db.c167
1 files changed, 166 insertions, 1 deletions
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index c091f994d..383a7b297 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017 Taler Systems SA
+ Copyright (C) 2014-2017, 2021 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
@@ -29,6 +29,55 @@
/**
+ * Send a response for a failed request. The transaction history of the given
+ * coin demonstrates that the @a residual value of the coin is below the @a
+ * requested contribution of the coin for the operation. Thus, the exchange
+ * refuses the operation.
+ *
+ * @param connection the connection to send the response to
+ * @param coin_pub public key of the coin
+ * @param coin_value original value of the coin
+ * @param tl transaction history for the coin
+ * @param requested how much this coin was supposed to contribute, including fee
+ * @param residual remaining value of the coin (after subtracting @a tl)
+ * @return a MHD result code
+ */
+static MHD_RESULT
+reply_insufficient_funds (
+ struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *coin_value,
+ struct TALER_EXCHANGEDB_TransactionList *tl,
+ const struct TALER_Amount *requested,
+ const struct TALER_Amount *residual)
+{
+ json_t *history;
+
+ history = TEH_RESPONSE_compile_transaction_history (coin_pub,
+ tl);
+ if (NULL == history)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
+ NULL);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ TALER_JSON_pack_amount ("original_value",
+ coin_value),
+ TALER_JSON_pack_amount ("residual_value",
+ residual),
+ TALER_JSON_pack_amount ("requested_value",
+ requested),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
+}
+
+
+/**
* How often should we retry a transaction before giving up
* (for transactions resulting in serialization/dead locks only).
*
@@ -114,6 +163,122 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
}
+enum GNUNET_DB_QueryStatus
+TEH_check_coin_balance (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_Amount *op_cost,
+ bool check_recoup,
+ bool zombie_required,
+ MHD_RESULT *mhd_ret)
+{
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ struct TALER_Amount spent;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Start with zero cost, as we already added this melt transaction
+ to the DB, so we will see it again during the queries below. */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &spent));
+
+ /* get historic transaction costs of this coin, including recoups as
+ we might be a zombie coin */
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ coin_pub,
+ check_recoup,
+ &tl);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "coin transaction history");
+ return qs;
+ }
+ if (zombie_required)
+ {
+ /* The denomination key is only usable for a melt if this is a true
+ zombie coin, i.e. it was refreshed and the resulting fresh coin was
+ then recouped. Check that this is truly the case. */
+ for (struct TALER_EXCHANGEDB_TransactionList *tp = tl;
+ NULL != tp;
+ tp = tp->next)
+ {
+ if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type)
+ {
+ zombie_required = false; /* clear flag: was satisfied! */
+ break;
+ }
+ }
+ if (zombie_required)
+ {
+ /* zombie status not satisfied */
+ GNUNET_break_op (0);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
+ &spent,
+ &spent))
+ {
+ GNUNET_break (0);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_COIN_HISTORY_COMPUTATION_FAILED,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* Refuse to refresh when the coin's value is insufficient
+ for the cost of all transactions. */
+ if (0 > TALER_amount_cmp (coin_value,
+ &spent))
+ {
+ struct TALER_Amount coin_residual;
+ struct TALER_Amount spent_already;
+
+ /* First subtract the melt cost from 'spent' to
+ compute the total amount already spent of the coin */
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&spent_already,
+ &spent,
+ op_cost));
+ /* The residual coin value is the original coin value minus
+ what we have spent (before the melt) */
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&coin_residual,
+ coin_value,
+ &spent_already));
+ *mhd_ret = reply_insufficient_funds (
+ connection,
+ coin_pub,
+ coin_value,
+ tl,
+ op_cost,
+ &coin_residual);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* we're good, coin has sufficient funds to be melted */
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
enum GNUNET_GenericReturnValue
TEH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,