diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_db.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_db.c | 167 |
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, |