diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_db.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_db.c | 1900 |
1 files changed, 1900 insertions, 0 deletions
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c new file mode 100644 index 000000000..9d04efa9b --- /dev/null +++ b/src/exchange/taler-exchange-httpd_db.c @@ -0,0 +1,1900 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_db.c + * @brief High-level (transactional-layer) database operations for the exchange. + * @author Christian Grothoff + */ +#include "platform.h" +#include <pthread.h> +#include <jansson.h> +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + +/** + * How often should we retry a transaction before giving up + * (for transactions resulting in serialization/dead locks only). + */ +#define MAX_TRANSACTION_COMMIT_RETRIES 3 + +/** + * Code to begin a transaction, must be inline as we define a block + * that ends with #COMMIT_TRANSACTION() within which we perform a number + * of retries. Note that this code may call "return" internally, so + * it must be called within a function where any cleanup will be done + * by the caller. Furthermore, the function's return value must + * match that of a #TMH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define START_TRANSACTION(session,connection) \ +{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\ + unsigned int transaction_retries = 0; \ + int transaction_commit_result; \ +transaction_start_label: /* we will use goto for retries */ \ + if (GNUNET_OK != \ + TMH_plugin->start (TMH_plugin->cls, \ + session)) \ + { \ + GNUNET_break (0); \ + return TMH_RESPONSE_reply_internal_db_error (connection); \ + } + +/** + * Code to conclude a transaction, dual to #START_TRANSACTION(). Note + * that this code may call "return" internally, so it must be called + * within a function where any cleanup will be done by the caller. + * Furthermore, the function's return value must match that of a + * #TMH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define COMMIT_TRANSACTION(session,connection) \ + transaction_commit_result = \ + TMH_plugin->commit (TMH_plugin->cls, \ + session); \ + if (GNUNET_SYSERR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + return TMH_RESPONSE_reply_commit_error (connection); \ + } \ + if (GNUNET_NO == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TMH_RESPONSE_reply_commit_error (connection); \ + } \ +} /* end of scope opened by BEGIN_TRANSACTION */ + + +/** + * Calculate the total value of all transactions performed. + * Stores @a off plus the cost of all transactions in @a tl + * in @a ret. + * + * @param tl transaction list to process + * @param off offset to use as the starting value + * @param ret where the resulting total is to be stored + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionList *tl, + const struct TALER_Amount *off, + struct TALER_Amount *ret) +{ + struct TALER_Amount spent = *off; + struct TALER_EXCHANGEDB_TransactionList *pos; + + for (pos = tl; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_EXCHANGEDB_TT_DEPOSIT: + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.deposit->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + case TALER_EXCHANGEDB_TT_REFRESH_MELT: + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.melt->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + } + } + *ret = spent; + return GNUNET_OK; +} + + +/** + * Execute a deposit. The validity of the coin and signature + * have already been checked. The database must now check that + * the coin is not (double or over) spent, and execute the + * transaction (record details, generate success or failure response). + * + * @param connection the MHD connection to handle + * @param deposit information about the deposit + * @return MHD result code + */ +int +TMH_DB_execute_deposit (struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_Deposit *deposit) +{ + struct TALER_EXCHANGEDB_Session *session; + struct TALER_EXCHANGEDB_TransactionList *tl; + struct TALER_Amount spent; + struct TALER_Amount value; + struct TALER_Amount amount_without_fee; + struct TMH_KS_StateHandle *mks; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + int ret; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_YES == + TMH_plugin->have_deposit (TMH_plugin->cls, + session, + deposit)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&amount_without_fee, + &deposit->amount_with_fee, + &deposit->deposit_fee)); + return TMH_RESPONSE_reply_deposit_success (connection, + &deposit->coin.coin_pub, + &deposit->h_wire, + &deposit->h_contract, + deposit->transaction_id, + deposit->timestamp, + deposit->refund_deadline, + &deposit->merchant_pub, + &amount_without_fee); + } + mks = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (mks, + &deposit->coin.denom_pub, + TMH_KS_DKU_DEPOSIT); + if (NULL == dki) + { + TMH_KS_release (mks); + return TMH_RESPONSE_reply_arg_invalid (connection, + "denom_pub"); + } + TALER_amount_ntoh (&value, + &dki->issue.properties.value); + TMH_KS_release (mks); + + START_TRANSACTION (session, connection); + + /* fee for THIS transaction */ + spent = deposit->amount_with_fee; + /* add cost of all previous transactions */ + tl = TMH_plugin->get_coin_transactions (TMH_plugin->cls, + session, + &deposit->coin.coin_pub); + if (GNUNET_OK != + calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + /* Check that cost of all transactions is smaller than + the value of the coin. */ + if (0 < TALER_amount_cmp (&spent, + &value)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + ret = TMH_RESPONSE_reply_deposit_insufficient_funds (connection, + tl); + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return ret; + } + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + + if (GNUNET_OK != + TMH_plugin->insert_deposit (TMH_plugin->cls, + session, + deposit)) + { + TALER_LOG_WARNING ("Failed to store /deposit information in database\n"); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + COMMIT_TRANSACTION(session, connection); + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&amount_without_fee, + &deposit->amount_with_fee, + &deposit->deposit_fee)); + return TMH_RESPONSE_reply_deposit_success (connection, + &deposit->coin.coin_pub, + &deposit->h_wire, + &deposit->h_contract, + deposit->transaction_id, + deposit->timestamp, + deposit->refund_deadline, + &deposit->merchant_pub, + &amount_without_fee); +} + + +/** + * Execute a /reserve/status. Given the public key of a reserve, + * return the associated transaction history. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve to check + * @return MHD result code + */ +int +TMH_DB_execute_reserve_status (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + struct TALER_EXCHANGEDB_Session *session; + struct TALER_EXCHANGEDB_ReserveHistory *rh; + int res; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, + session, + reserve_pub); + if (NULL == rh) + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s, s:s}", + "error", "Reserve not found", + "parameter", "withdraw_pub"); + res = TMH_RESPONSE_reply_reserve_status_success (connection, + rh); + TMH_plugin->free_reserve_history (TMH_plugin->cls, + rh); + return res; +} + + +/** + * Try to execute /reserve/withdraw transaction. + * + * @param connection request we are handling + * @param session database session we are using + * @param key_state key state to lookup denomination pubs + * @param reserve reserve to withdraw from + * @param denomination_pub public key of the denomination requested + * @param dki denomination to withdraw + * @param blinded_msg blinded message to be signed + * @param blinded_msg_len number of bytes in @a blinded_msg + * @param h_blind hash of @a blinded_msg + * @param signature signature over the withdraw request, to be stored in DB + * @param denom_sig[out] where to write the resulting signature + * (used to release memory in case of transaction failure + * @return MHD result code + */ +static int +execute_reserve_withdraw_transaction (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + struct TMH_KS_StateHandle *key_state, + const struct TALER_ReservePublicKeyP *reserve, + const struct TALER_DenominationPublicKey *denomination_pub, + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + const char *blinded_msg, + size_t blinded_msg_len, + const struct GNUNET_HashCode *h_blind, + const struct TALER_ReserveSignatureP *signature, + struct TALER_DenominationSignature *denom_sig) +{ + struct TALER_EXCHANGEDB_ReserveHistory *rh; + const struct TALER_EXCHANGEDB_ReserveHistory *pos; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *tdki; + struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; + struct TALER_Amount amount_required; + struct TALER_Amount deposit_total; + struct TALER_Amount withdraw_total; + struct TALER_Amount balance; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + int res; + + /* Check if balance is sufficient */ + START_TRANSACTION (session, connection); + rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, + session, + reserve); + if (NULL == rh) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_arg_unknown (connection, + "reserve_pub"); + } + + /* calculate amount required including fees */ + TALER_amount_ntoh (&value, + &dki->issue.properties.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->issue.properties.fee_withdraw); + + if (GNUNET_OK != + TALER_amount_add (&amount_required, + &value, + &fee_withdraw)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* calculate balance of the reserve */ + res = 0; + for (pos = rh; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE: + if (0 == (res & 1)) + deposit_total = pos->details.bank->amount; + else + if (GNUNET_OK != + TALER_amount_add (&deposit_total, + &deposit_total, + &pos->details.bank->amount)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res |= 1; + break; + case TALER_EXCHANGEDB_RO_WITHDRAW_COIN: + tdki = TMH_KS_denomination_key_lookup (key_state, + &pos->details.withdraw->denom_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == tdki) + { + GNUNET_break (0); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + TALER_amount_ntoh (&value, + &tdki->issue.properties.value); + if (0 == (res & 2)) + withdraw_total = value; + else + if (GNUNET_OK != + TALER_amount_add (&withdraw_total, + &withdraw_total, + &value)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res |= 2; + break; + } + } + if (0 == (res & 1)) + { + /* did not encounter any deposit operations, how can we have a reserve? */ + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (0 == (res & 2)) + { + /* did not encounter any withdraw operations, set to zero */ + TALER_amount_get_zero (deposit_total.currency, + &withdraw_total); + } + /* All reserve balances should be non-negative */ + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&balance, + &deposit_total, + &withdraw_total)); + if (0 < TALER_amount_cmp (&amount_required, + &balance)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + res = TMH_RESPONSE_reply_reserve_withdraw_insufficient_funds (connection, + rh); + TMH_plugin->free_reserve_history (TMH_plugin->cls, + rh); + return res; + } + TMH_plugin->free_reserve_history (TMH_plugin->cls, + rh); + + /* Balance is good, sign the coin! */ + denom_sig->rsa_signature + = GNUNET_CRYPTO_rsa_sign (dki->denom_priv.rsa_private_key, + blinded_msg, + blinded_msg_len); + if (NULL == denom_sig->rsa_signature) + { + GNUNET_break (0); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_error (connection, + "Internal error"); + } + collectable.sig = *denom_sig; + collectable.denom_pub = *denomination_pub; + collectable.amount_with_fee = amount_required; + collectable.withdraw_fee = fee_withdraw; + collectable.reserve_pub = *reserve; + collectable.h_coin_envelope = *h_blind; + collectable.reserve_sig = *signature; + if (GNUNET_OK != + TMH_plugin->insert_withdraw_info (TMH_plugin->cls, + session, + &collectable)) + { + GNUNET_break (0); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + COMMIT_TRANSACTION (session, connection); + + return TMH_RESPONSE_reply_reserve_withdraw_success (connection, + &collectable); +} + + + +/** + * Execute a "/reserve/withdraw". Given a reserve and a properly signed + * request to withdraw a coin, check the balance of the reserve and + * if it is sufficient, store the request and return the signed + * blinded envelope. + * + * @param connection the MHD connection to handle + * @param reserve public key of the reserve + * @param denomination_pub public key of the denomination requested + * @param blinded_msg blinded message to be signed + * @param blinded_msg_len number of bytes in @a blinded_msg + * @param signature signature over the withdraw request, to be stored in DB + * @return MHD result code + */ +int +TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve, + const struct TALER_DenominationPublicKey *denomination_pub, + const char *blinded_msg, + size_t blinded_msg_len, + const struct TALER_ReserveSignatureP *signature) +{ + struct TALER_EXCHANGEDB_Session *session; + struct TMH_KS_StateHandle *key_state; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; + struct TALER_DenominationSignature denom_sig; + struct GNUNET_HashCode h_blind; + int res; + + GNUNET_CRYPTO_hash (blinded_msg, + blinded_msg_len, + &h_blind); + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res = TMH_plugin->get_withdraw_info (TMH_plugin->cls, + session, + &h_blind, + &collectable); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* Don't sign again if we have already signed the coin */ + if (GNUNET_YES == res) + { + res = TMH_RESPONSE_reply_reserve_withdraw_success (connection, + &collectable); + GNUNET_CRYPTO_rsa_signature_free (collectable.sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (collectable.denom_pub.rsa_public_key); + return res; + } + GNUNET_assert (GNUNET_NO == res); + + key_state = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (key_state, + denomination_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "Denomination not found"); + } + denom_sig.rsa_signature = NULL; + res = execute_reserve_withdraw_transaction (connection, + session, + key_state, + reserve, + denomination_pub, + dki, + blinded_msg, + blinded_msg_len, + &h_blind, + signature, + &denom_sig); + if (NULL != denom_sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature); + TMH_KS_release (key_state); + return res; +} + + +/** + * Parse coin melt requests from a JSON object and write them to + * the database. + * + * @param connection the connection to send errors to + * @param session the database connection + * @param key_state the exchange's key state + * @param session_hash hash identifying the refresh session + * @param coin_details details about the coin being melted + * @param oldcoin_index what is the number assigned to this coin + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error message was generated, + * #GNUNET_SYSERR on internal errors (no response generated) + */ +static int +refresh_accept_melts (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct TMH_KS_StateHandle *key_state, + const struct GNUNET_HashCode *session_hash, + const struct TMH_DB_MeltDetails *coin_details, + uint16_t oldcoin_index) +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; + struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; + struct TALER_EXCHANGEDB_TransactionList *tl; + struct TALER_Amount coin_value; + struct TALER_Amount coin_residual; + struct TALER_Amount spent; + struct TALER_EXCHANGEDB_RefreshMelt melt; + int res; + + dk = TMH_KS_denomination_key_lookup (key_state, + &coin_details->coin_info.denom_pub, + TMH_KS_DKU_DEPOSIT); + if (NULL == dk) + return (MHD_YES == + TMH_RESPONSE_reply_arg_unknown (connection, + "denom_pub")) + ? GNUNET_NO : GNUNET_SYSERR; + dki = &dk->issue; + TALER_amount_ntoh (&coin_value, + &dki->properties.value); + /* fee for THIS transaction; the melt amount includes the fee! */ + spent = coin_details->melt_amount_with_fee; + /* add historic transaction costs of this coin */ + tl = TMH_plugin->get_coin_transactions (TMH_plugin->cls, + session, + &coin_details->coin_info.coin_pub); + if (GNUNET_OK != + calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + GNUNET_break (0); + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return (MHD_YES == + TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + /* Refuse to refresh when the coin's value is insufficient + for the cost of all transactions. */ + if (TALER_amount_cmp (&coin_value, + &spent) < 0) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&coin_residual, + &spent, + &coin_details->melt_amount_with_fee)); + res = (MHD_YES == + TMH_RESPONSE_reply_refresh_melt_insufficient_funds (connection, + &coin_details->coin_info.coin_pub, + coin_value, + tl, + coin_details->melt_amount_with_fee, + coin_residual)) + ? GNUNET_NO : GNUNET_SYSERR; + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return res; + } + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + + melt.coin = coin_details->coin_info; + melt.coin_sig = coin_details->melt_sig; + melt.session_hash = *session_hash; + melt.amount_with_fee = coin_details->melt_amount_with_fee; + melt.melt_fee = coin_details->melt_fee; + if (GNUNET_OK != + TMH_plugin->insert_refresh_melt (TMH_plugin->cls, + session, + oldcoin_index, + &melt)) + { + GNUNET_break (0); + return (MHD_YES == + TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Execute a "/refresh/melt". We have been given a list of valid + * coins and a request to melt them into the given + * @a refresh_session_pub. Check that the coins all have the + * required value left and if so, store that they have been + * melted and confirm the melting operation to the client. + * + * @param connection the MHD connection to handle + * @param session_hash hash code of the session the coins are melted into + * @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array + * @param denom_pubs public keys of the coins we want to withdraw in the end + * @param coin_count number of entries in @a coin_melt_details, size of y-dimension of @a commit_link array + * @param coin_melt_details signatures and (residual) value of the respective coin should be melted + * @param commit_coin 2d array of coin commitments (what the exchange is to sign + * once the "/refres/reveal" of cut and choose is done), + * x-dimension must be #TALER_CNC_KAPPA + * @param commit_link 2d array of coin link commitments (what the exchange is + * to return via "/refresh/link" to enable linkage in the + * future) + * x-dimension must be #TALER_CNC_KAPPA + * @return MHD result code + */ +int +TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_new_denoms, + const struct TALER_DenominationPublicKey *denom_pubs, + unsigned int coin_count, + const struct TMH_DB_MeltDetails *coin_melt_details, + struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, + struct TALER_RefreshCommitLinkP *const* commit_link) +{ + struct TMH_KS_StateHandle *key_state; + struct TALER_EXCHANGEDB_RefreshSession refresh_session; + struct TALER_EXCHANGEDB_Session *session; + int res; + unsigned int i; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + START_TRANSACTION (session, connection); + res = TMH_plugin->get_refresh_session (TMH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_YES == res) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + res = TMH_RESPONSE_reply_refresh_melt_success (connection, + session_hash, + refresh_session.noreveal_index); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + if (GNUNET_SYSERR == res) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* store 'global' session data */ + refresh_session.num_oldcoins = coin_count; + refresh_session.num_newcoins = num_new_denoms; + refresh_session.noreveal_index + = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + TALER_CNC_KAPPA); + if (GNUNET_OK != + (res = TMH_plugin->create_refresh_session (TMH_plugin->cls, + session, + session_hash, + &refresh_session))) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* Melt old coins and check that they had enough residual value */ + key_state = TMH_KS_acquire (); + for (i=0;i<coin_count;i++) + { + if (GNUNET_OK != + (res = refresh_accept_melts (connection, + session, + key_state, + session_hash, + &coin_melt_details[i], + i))) + { + TMH_KS_release (key_state); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + TMH_KS_release (key_state); + + /* store requested new denominations */ + if (GNUNET_OK != + TMH_plugin->insert_refresh_order (TMH_plugin->cls, + session, + session_hash, + num_new_denoms, + denom_pubs)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + if (GNUNET_OK != + TMH_plugin->insert_refresh_commit_coins (TMH_plugin->cls, + session, + session_hash, + i, + num_new_denoms, + commit_coin[i])) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + if (GNUNET_OK != + TMH_plugin->insert_refresh_commit_links (TMH_plugin->cls, + session, + session_hash, + i, + coin_count, + commit_link[i])) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + + COMMIT_TRANSACTION (session, connection); + return TMH_RESPONSE_reply_refresh_melt_success (connection, + session_hash, + refresh_session.noreveal_index); +} + + +/** + * Send an error response with the details of the original melt + * commitment and the location of the mismatch. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param off commitment offset to check + * @param index index of the mismatch + * @param object_name name of the object with the problem + * @return #GNUNET_NO if we generated the error message + * #GNUNET_SYSERR if we could not even generate an error message + */ +static int +send_melt_commitment_error (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int off, + unsigned int index, + const char *object_name) +{ + struct TALER_EXCHANGEDB_MeltCommitment *mc; + int ret; + + mc = TMH_plugin->get_melt_commitment (TMH_plugin->cls, + session, + session_hash); + if (NULL == mc) + { + GNUNET_break (0); + return (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "Melt commitment assembly")) + ? GNUNET_NO : GNUNET_SYSERR; + } + ret = (MHD_YES == + TMH_RESPONSE_reply_refresh_reveal_missmatch (connection, + mc, + off, + index, + object_name)) + ? GNUNET_NO : GNUNET_SYSERR; + TMH_plugin->free_melt_commitment (TMH_plugin->cls, + mc); + return ret; +} + + +/** + * Check if the given @a transfer_privs correspond to an honest + * commitment for the given session. + * Checks that the transfer private keys match their commitments. + * Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param off commitment offset to check + * @param num_oldcoins size of the @a transfer_privs and @a melts arrays + * @param transfer_privs private transfer keys + * @param melts array of melted coins + * @param num_newcoins number of newcoins being generated + * @param denom_pubs array of @a num_newcoins keys for the new coins + * @return #GNUNET_OK if the committment was honest, + * #GNUNET_NO if there was a problem and we generated an error message + * #GNUNET_SYSERR if we could not even generate an error message + */ +static int +check_commitment (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int off, + unsigned int num_oldcoins, + const struct TALER_TransferPrivateKeyP *transfer_privs, + const struct TALER_EXCHANGEDB_RefreshMelt *melts, + unsigned int num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs) +{ + unsigned int j; + struct TALER_LinkSecretP last_shared_secret; + int secret_initialized = GNUNET_NO; + struct TALER_RefreshCommitLinkP *commit_links; + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins; + + commit_links = GNUNET_malloc (num_oldcoins * + sizeof (struct TALER_RefreshCommitLinkP)); + if (GNUNET_OK != + TMH_plugin->get_refresh_commit_links (TMH_plugin->cls, + session, + session_hash, + off, + num_oldcoins, + commit_links)) + { + GNUNET_break (0); + GNUNET_free (commit_links); + return (MHD_YES == TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + for (j = 0; j < num_oldcoins; j++) + { + struct TALER_LinkSecretP shared_secret; + struct TALER_TransferPublicKeyP transfer_pub_check; + + GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[j].ecdhe_priv, + &transfer_pub_check.ecdhe_pub); + if (0 != + memcmp (&transfer_pub_check, + &commit_links[j].transfer_pub, + sizeof (struct TALER_TransferPublicKeyP))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "transfer keys do not match\n"); + GNUNET_free (commit_links); + return send_melt_commitment_error (connection, + session, + session_hash, + off, + j, + "transfer key"); + } + + if (GNUNET_OK != + TALER_link_decrypt_secret (&commit_links[j].shared_secret_enc, + &transfer_privs[j], + &melts[j].coin.coin_pub, + &shared_secret)) + { + GNUNET_free (commit_links); + return (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "Transfer secret decryption error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + if (GNUNET_NO == secret_initialized) + { + secret_initialized = GNUNET_YES; + last_shared_secret = shared_secret; + } + else if (0 != memcmp (&shared_secret, + &last_shared_secret, + sizeof (struct GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "shared secrets do not match\n"); + GNUNET_free (commit_links); + return send_melt_commitment_error (connection, + session, + session_hash, + off, + j, + "transfer secret"); + } + } + GNUNET_break (GNUNET_YES == secret_initialized); + GNUNET_free (commit_links); + + /* Check that the commitments for all new coins were correct */ + commit_coins = GNUNET_malloc (num_newcoins * + sizeof (struct TALER_EXCHANGEDB_RefreshCommitCoin)); + + if (GNUNET_OK != + TMH_plugin->get_refresh_commit_coins (TMH_plugin->cls, + session, + session_hash, + off, + num_newcoins, + commit_coins)) + { + GNUNET_break (0); + GNUNET_free (commit_coins); + return (MHD_YES == TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + for (j = 0; j < num_newcoins; j++) + { + struct TALER_RefreshLinkDecrypted *link_data; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode h_msg; + char *buf; + size_t buf_len; + + link_data = TALER_refresh_decrypt (commit_coins[j].refresh_link, + &last_shared_secret); + if (NULL == link_data) + { + GNUNET_break (0); + GNUNET_free (commit_coins); + return (MHD_YES == TMH_RESPONSE_reply_internal_error (connection, + "Decryption error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&link_data->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + &h_msg); + if (0 == (buf_len = + GNUNET_CRYPTO_rsa_blind (&h_msg, + link_data->blinding_key.rsa_blinding_key, + denom_pubs[j].rsa_public_key, + &buf))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "blind failed\n"); + GNUNET_free (commit_coins); + return (MHD_YES == TMH_RESPONSE_reply_internal_error (connection, + "Blinding error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + + if ( (buf_len != commit_coins[j].coin_ev_size) || + (0 != memcmp (buf, + commit_coins[j].coin_ev, + buf_len)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "blind envelope does not match for k=%u, old=%d\n", + off, + (int) j); + GNUNET_free (commit_coins); + return send_melt_commitment_error (connection, + session, + session_hash, + off, + j, + "envelope"); + } + GNUNET_free (buf); + } + GNUNET_free (commit_coins); + + return GNUNET_OK; +} + + +/** + * Exchange a coin as part of a refresh operation. Obtains the + * envelope from the database and performs the signing operation. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param key_state key state to lookup denomination pubs + * @param denom_pub denomination key for the coin to create + * @param commit_coin the coin that was committed + * @param coin_off number of the coin + * @return NULL on error, otherwise signature over the coin + */ +static struct TALER_DenominationSignature +refresh_exchange_coin (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct TMH_KS_StateHandle *key_state, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin, + unsigned int coin_off) +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + struct TALER_DenominationSignature ev_sig; + + dki = TMH_KS_denomination_key_lookup (key_state, + denom_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + GNUNET_break (0); + ev_sig.rsa_signature = NULL; + return ev_sig; + } + ev_sig.rsa_signature + = GNUNET_CRYPTO_rsa_sign (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 != + TMH_plugin->insert_refresh_out (TMH_plugin->cls, + session, + session_hash, + coin_off, + &ev_sig)) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); + ev_sig.rsa_signature = NULL; + } + return ev_sig; +} + + +/** + * The client request was well-formed, now execute the DB transaction + * of a "/refresh/reveal" operation. We use the @a ev_sigs and + * @a commit_coins to clean up resources after this function returns + * as we might experience retries of the database transaction. + * + * @param connection the MHD connection to handle + * @param session database session + * @param session_hash hash identifying the refresh session + * @param refresh_session information about the refresh operation we are doing + * @param melts array of "num_oldcoins" with information about melted coins + * @param denom_pubs array of "num_newcoins" denomination keys for the new coins + * @param[out] ev_sigs where to store generated signatures for the new coins, + * array of length "num_newcoins", memory released by the + * caller + * @param[out] commit_coins array of length "num_newcoins" to be used for + * information about the new coins from the commitment. + * @return MHD result code + */ +static int +execute_refresh_reveal_transaction (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + const struct TALER_EXCHANGEDB_RefreshSession *refresh_session, + const struct TALER_EXCHANGEDB_RefreshMelt *melts, + const struct TALER_DenominationPublicKey *denom_pubs, + struct TALER_DenominationSignature *ev_sigs, + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins) +{ + unsigned int j; + struct TMH_KS_StateHandle *key_state; + + START_TRANSACTION (session, connection); + if (GNUNET_OK != + TMH_plugin->get_refresh_commit_coins (TMH_plugin->cls, + session, + session_hash, + refresh_session->noreveal_index, + refresh_session->num_newcoins, + commit_coins)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + key_state = TMH_KS_acquire (); + for (j=0;j<refresh_session->num_newcoins;j++) + { + if (NULL == ev_sigs[j].rsa_signature) /* could be non-NULL during retries */ + ev_sigs[j] = refresh_exchange_coin (connection, + session, + session_hash, + key_state, + &denom_pubs[j], + &commit_coins[j], + j); + if (NULL == ev_sigs[j].rsa_signature) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + TMH_KS_release (key_state); + COMMIT_TRANSACTION (session, connection); + return TMH_RESPONSE_reply_refresh_reveal_success (connection, + refresh_session->num_newcoins, + ev_sigs); +} + + +/** + * Execute a "/refresh/reveal". The client is revealing to us the + * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the + * revealed transfer keys would allow linkage to the blinded coins, + * and if so, return the signed coins for corresponding to the set of + * coins that was not chosen. + * + * @param connection the MHD connection to handle + * @param session_hash hash identifying the refresh session + * @param num_oldcoins size of y-dimension of @a transfer_privs array + * @param transfer_privs array with the revealed transfer keys, + * x-dimension must be #TALER_CNC_KAPPA - 1 + * @return MHD result code + */ +int +TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_oldcoins, + struct TALER_TransferPrivateKeyP **transfer_privs) +{ + int res; + struct TALER_EXCHANGEDB_Session *session; + struct TALER_EXCHANGEDB_RefreshSession refresh_session; + struct TALER_EXCHANGEDB_RefreshMelt *melts; + struct TALER_DenominationPublicKey *denom_pubs; + struct TALER_DenominationSignature *ev_sigs; + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins; + unsigned int i; + unsigned int j; + unsigned int off; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + res = TMH_plugin->get_refresh_session (TMH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_NO == res) + return TMH_RESPONSE_reply_arg_invalid (connection, + "session_hash"); + if (GNUNET_SYSERR == res) + return TMH_RESPONSE_reply_internal_db_error (connection); + if (0 == refresh_session.num_oldcoins) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + melts = GNUNET_malloc (refresh_session.num_oldcoins * + sizeof (struct TALER_EXCHANGEDB_RefreshMelt)); + for (j=0;j<refresh_session.num_oldcoins;j++) + { + if (GNUNET_OK != + TMH_plugin->get_refresh_melt (TMH_plugin->cls, + session, + session_hash, + j, + &melts[j])) + { + GNUNET_break (0); + for (i=0;i<j;i++) + { + GNUNET_CRYPTO_rsa_signature_free (melts[i].coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (melts[i].coin.denom_pub.rsa_public_key); + } + GNUNET_free (melts); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + denom_pubs = GNUNET_malloc (refresh_session.num_newcoins * + sizeof (struct TALER_DenominationPublicKey)); + if (GNUNET_OK != + TMH_plugin->get_refresh_order (TMH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + denom_pubs)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + for (i=0;i<refresh_session.num_oldcoins;i++) + { + GNUNET_CRYPTO_rsa_signature_free (melts[i].coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (melts[i].coin.denom_pub.rsa_public_key); + } + GNUNET_free (melts); + return (MHD_YES == TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + + off = 0; + for (i=0;i<TALER_CNC_KAPPA - 1;i++) + { + if (i == refresh_session.noreveal_index) + off = 1; + if (GNUNET_OK != + (res = check_commitment (connection, + session, + session_hash, + i + off, + refresh_session.num_oldcoins, + transfer_privs[i], + melts, + refresh_session.num_newcoins, + denom_pubs))) + { + for (j=0;j<refresh_session.num_newcoins;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + for (i=0;i<refresh_session.num_oldcoins;i++) + { + GNUNET_CRYPTO_rsa_signature_free (melts[i].coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (melts[i].coin.denom_pub.rsa_public_key); + } + GNUNET_free (melts); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + } + for (i=0;i<refresh_session.num_oldcoins;i++) + { + GNUNET_CRYPTO_rsa_signature_free (melts[i].coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (melts[i].coin.denom_pub.rsa_public_key); + } + GNUNET_free (melts); + + /* Client request OK, start transaction */ + commit_coins = GNUNET_malloc (refresh_session.num_newcoins * + sizeof (struct TALER_EXCHANGEDB_RefreshCommitCoin)); + ev_sigs = GNUNET_malloc (refresh_session.num_newcoins * + sizeof (struct TALER_DenominationSignature)); + res = execute_refresh_reveal_transaction (connection, + session, + session_hash, + &refresh_session, + melts, + denom_pubs, + ev_sigs, + commit_coins); + for (i=0;i<refresh_session.num_newcoins;i++) + if (NULL != ev_sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature); + for (j=0;j<refresh_session.num_newcoins;j++) + if (NULL != denom_pubs[j].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (ev_sigs); + GNUNET_free (denom_pubs); + GNUNET_free (commit_coins); + return res; +} + + +/** + * Closure for #handle_transfer_data(). + */ +struct HTD_Context +{ + + /** + * Session link data we collect. + */ + struct TMH_RESPONSE_LinkSessionInfo *sessions; + + /** + * Database session. Nothing to do with @a sessions. + */ + struct TALER_EXCHANGEDB_Session *session; + + /** + * MHD connection, for queueing replies. + */ + struct MHD_Connection *connection; + + /** + * Number of sessions the coin was melted into. + */ + unsigned int num_sessions; + + /** + * How are we expected to proceed. #GNUNET_SYSERR if we + * failed to return an error (should return #MHD_NO). + * #GNUNET_NO if we succeeded in queueing an MHD error + * (should return #MHD_YES from #TMH_execute_refresh_link), + * #GNUNET_OK if we should call #TMH_RESPONSE_reply_refresh_link_success(). + */ + int status; +}; + + +/** + * Function called with the session hashes and transfer secret + * information for a given coin. Gets the linkage data and + * builds the reply for the client. + * + * + * @param cls closure, a `struct HTD_Context` + * @param session_hash a session the coin was melted in + * @param transfer_pub public transfer key for the session + * @param shared_secret_enc set to shared secret for the session + */ +static void +handle_transfer_data (void *cls, + const struct GNUNET_HashCode *session_hash, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EncryptedLinkSecretP *shared_secret_enc) +{ + struct HTD_Context *ctx = cls; + struct TALER_EXCHANGEDB_LinkDataList *ldl; + struct TMH_RESPONSE_LinkSessionInfo *lsi; + + if (GNUNET_OK != ctx->status) + return; + ldl = TMH_plugin->get_link_data_list (TMH_plugin->cls, + ctx->session, + session_hash); + if (NULL == ldl) + { + ctx->status = GNUNET_NO; + if (MHD_NO == + TMH_RESPONSE_reply_json_pack (ctx->connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "link data not found (link)")) + ctx->status = GNUNET_SYSERR; + return; + } + GNUNET_array_grow (ctx->sessions, + ctx->num_sessions, + ctx->num_sessions + 1); + lsi = &ctx->sessions[ctx->num_sessions - 1]; + lsi->transfer_pub = *transfer_pub; + lsi->shared_secret_enc = *shared_secret_enc; + lsi->ldl = ldl; +} + + +/** + * Execute a "/refresh/link". Returns the linkage information that + * will allow the owner of a coin to follow the refresh trail to + * the refreshed coin. + * + * @param connection the MHD connection to handle + * @param coin_pub public key of the coin to link + * @return MHD result code + */ +int +TMH_DB_execute_refresh_link (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub) +{ + struct HTD_Context ctx; + int res; + unsigned int i; + + if (NULL == (ctx.session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ctx.connection = connection; + ctx.num_sessions = 0; + ctx.sessions = NULL; + ctx.status = GNUNET_OK; + res = TMH_plugin->get_transfer (TMH_plugin->cls, + ctx.session, + coin_pub, + &handle_transfer_data, + &ctx); + if (GNUNET_SYSERR == ctx.status) + { + res = MHD_NO; + goto cleanup; + } + if (GNUNET_NO == ctx.status) + { + res = MHD_YES; + goto cleanup; + } + GNUNET_assert (GNUNET_OK == ctx.status); + if (0 == ctx.num_sessions) + return TMH_RESPONSE_reply_arg_unknown (connection, + "coin_pub"); + res = TMH_RESPONSE_reply_refresh_link_success (connection, + ctx.num_sessions, + ctx.sessions); + cleanup: + for (i=0;i<ctx.num_sessions;i++) + TMH_plugin->free_link_data_list (TMH_plugin->cls, + ctx.sessions[i].ldl); + GNUNET_free_non_null (ctx.sessions); + return res; +} + + +/** + * Add an incoming transaction to the database. Checks if the + * transaction is fresh (not a duplicate) and if so adds it to + * the database. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve + * @param amount amount to add to the reserve + * @param execution_time when did we receive the wire transfer + * @param wire details about the wire transfer + * @return MHD result code + */ +int +TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute execution_time, + json_t *wire) +{ + struct TALER_EXCHANGEDB_Session *session; + int ret; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ret = TMH_plugin->reserves_in_insert (TMH_plugin->cls, + session, + reserve_pub, + amount, + execution_time, + wire); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s}", + "status", + (GNUNET_OK == ret) + ? "NEW" + : "DUP"); +} + + +/** + * Closure for #handle_transaction_data. + */ +struct WtidTransactionContext +{ + + /** + * Total amount of the wire transfer, as calculated by + * summing up the individual amounts. To be rounded down + * to calculate the real transfer amount at the end. + * Only valid if @e is_valid is #GNUNET_YES. + */ + struct TALER_Amount total; + + /** + * Public key of the merchant, only valid if @e is_valid + * is #GNUNET_YES. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Hash of the wire details of the merchant (identical for all + * deposits), only valid if @e is_valid is #GNUNET_YES. + */ + struct GNUNET_HashCode h_wire; + + /** + * JSON array with details about the individual deposits. + */ + json_t *deposits; + + /** + * Initially #GNUNET_NO, if we found no deposits so far. Set to + * #GNUNET_YES if we got transaction data, and the database replies + * remained consistent with respect to @e merchant_pub and @e h_wire + * (as they should). Set to #GNUNET_SYSERR if we encountered an + * internal error. + */ + int is_valid; + +}; + + +/** + * Function called with the results of the lookup of the + * transaction data for the given wire transfer identifier. + * + * @param cls our context for transmission + * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) + * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) + * @param h_contract which contract was this payment about + * @param transaction_id merchant's transaction ID for the payment + * @param coin_pub which public key was this payment about + * @param deposit_value amount contributed by this coin in total + * @param deposit_fee deposit fee charged by exchange for this coin + */ +static void +handle_transaction_data (void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *deposit_value, + const struct TALER_Amount *deposit_fee) +{ + struct WtidTransactionContext *ctx = cls; + struct TALER_Amount delta; + + if (GNUNET_SYSERR == ctx->is_valid) + return; + if (GNUNET_NO == ctx->is_valid) + { + ctx->merchant_pub = *merchant_pub; + ctx->h_wire = *h_wire; + ctx->is_valid = GNUNET_YES; + if (GNUNET_OK != + TALER_amount_subtract (&ctx->total, + deposit_value, + deposit_fee)) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + } + else + { + if ( (0 != memcmp (&ctx->merchant_pub, + merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) || + (0 != memcmp (&ctx->h_wire, + h_wire, + sizeof (struct GNUNET_HashCode))) ) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + TALER_amount_subtract (&delta, + deposit_value, + deposit_fee)) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + TALER_amount_add (&ctx->total, + &ctx->total, + &delta)) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + } + /* NOTE: We usually keep JSON stuff out of the _DB file, and this + is also ugly if we ever add signatures over this data. (#4135) */ + json_array_append (ctx->deposits, + json_pack ("{s:o, s:o, s:o, s:I, s:o}", + "deposit_value", TALER_json_from_amount (deposit_value), + "deposit_fee", TALER_json_from_amount (deposit_fee), + "H_contract", TALER_json_from_data (h_contract, + sizeof (struct GNUNET_HashCode)), + "transaction_id", (json_int_t) transaction_id, + "coin_pub", TALER_json_from_data (coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)))); +} + + +/** + * Execute a "/wire/deposits". Returns the transaction information + * associated with the given wire transfer identifier. + * + * @param connection the MHD connection to handle + * @param wtid wire transfer identifier to resolve + * @return MHD result code + */ +int +TMH_DB_execute_wire_deposits (struct MHD_Connection *connection, + const struct TALER_WireTransferIdentifierRawP *wtid) +{ + int ret; + struct WtidTransactionContext ctx; + struct TALER_EXCHANGEDB_Session *session; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ctx.is_valid = GNUNET_NO; + ctx.deposits = json_array (); + ret = TMH_plugin->lookup_wire_transfer (TMH_plugin->cls, + session, + wtid, + &handle_transaction_data, + &ctx); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + json_decref (ctx.deposits); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_SYSERR == ctx.is_valid) + { + GNUNET_break (0); + json_decref (ctx.deposits); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_NO == ctx.is_valid) + { + json_decref (ctx.deposits); + return TMH_RESPONSE_reply_arg_unknown (connection, + "wtid"); + } + return TMH_RESPONSE_reply_wire_deposit_details (connection, + &ctx.total, + &ctx.merchant_pub, + &ctx.h_wire, + ctx.deposits); +} + + +/** + * Closure for #handle_wtid_data. + */ +struct DepositWtidContext +{ + + /** + * Where should we send the reply? + */ + struct MHD_Connection *connection; + + /** + * Hash of the contract we are looking up. + */ + struct GNUNET_HashCode h_contract; + + /** + * Hash of the wire transfer details we are looking up. + */ + struct GNUNET_HashCode h_wire; + + /** + * Public key we are looking up. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Transaction ID we are looking up. + */ + uint64_t transaction_id; + + /** + * MHD result code to return. + */ + int res; +}; + + +/** + * Function called with the results of the lookup of the + * wire transfer identifier information. + * + * @param cls our context for transmission + * @param wtid raw wire transfer identifier, NULL + * if the transaction was not yet done + * @param coin_contribution how much did the coin we asked about + * contribute to the total transfer value? (deposit value including fee) + * @param coin_fee how much did the exchange charge for the deposit fee + * @param execution_time when was the transaction done, or + * when we expect it to be done (if @a wtid was NULL); + * #GNUNET_TIME_UNIT_FOREVER_ABS if the /deposit is unknown + * to the exchange + */ +static void +handle_wtid_data (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *coin_fee, + struct GNUNET_TIME_Absolute execution_time) +{ + struct DepositWtidContext *ctx = cls; + struct TALER_Amount coin_delta; + + if (NULL == wtid) + { + ctx->res = TMH_RESPONSE_reply_deposit_pending (ctx->connection, + execution_time); + } + else + { + if (GNUNET_SYSERR == + TALER_amount_subtract (&coin_delta, + coin_contribution, + coin_fee)) + { + GNUNET_break (0); + ctx->res = TMH_RESPONSE_reply_internal_db_error (ctx->connection); + } + else + { + ctx->res = TMH_RESPONSE_reply_deposit_wtid (ctx->connection, + &ctx->h_contract, + &ctx->h_wire, + &ctx->coin_pub, + &coin_delta, + ctx->transaction_id, + wtid, + execution_time); + } + } +} + + +/** + * Execute a "/deposit/wtid". Returns the transfer information + * associated with the given deposit. + * + * @param connection the MHD connection to handle + * @param h_contract hash of the contract + * @param h_wire hash of the wire details + * @param coin_pub public key of the coin to link + * @param merchant_pub public key of the merchant + * @param transaction_id transaction ID of the merchant + * @return MHD result code + */ +int +TMH_DB_execute_deposit_wtid (struct MHD_Connection *connection, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPublicKeyP *merchant_pub, + uint64_t transaction_id) +{ + int ret; + struct DepositWtidContext ctx; + struct TALER_EXCHANGEDB_Session *session; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ctx.connection = connection; + ctx.h_contract = *h_contract; + ctx.h_wire = *h_wire; + ctx.coin_pub = *coin_pub; + ctx.transaction_id = transaction_id; + ctx.res = GNUNET_SYSERR; + ret = TMH_plugin->wire_lookup_deposit_wtid (TMH_plugin->cls, + session, + h_contract, + h_wire, + coin_pub, + merchant_pub, + transaction_id, + &handle_wtid_data, + &ctx); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + GNUNET_break (GNUNET_SYSERR == ctx.res); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_NO == ret) + { + GNUNET_break (GNUNET_SYSERR == ctx.res); + return TMH_RESPONSE_reply_deposit_unknown (connection); + } + if (GNUNET_SYSERR == ctx.res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "bug resolving deposit wtid"); + } + return ctx.res; +} + + +/* end of taler-exchange-httpd_db.c */ |