From 92e6744ac032a3c4c4118ac6b251f769c5478aa6 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 19 Jun 2017 16:07:34 +0200 Subject: address #5010 for /refund --- src/auditor/taler-auditor.c | 9 +- src/exchange/taler-exchange-httpd_db.c | 289 +-------- src/exchange/taler-exchange-httpd_db.h | 14 - src/exchange/taler-exchange-httpd_deposit.c | 7 +- src/exchange/taler-exchange-httpd_refund.c | 357 ++++++++++- src/exchange/taler-exchange-httpd_responses.c | 88 +-- src/exchange/taler-exchange-httpd_responses.h | 51 +- src/exchangedb/perf_taler_exchangedb_interpreter.c | 11 +- src/exchangedb/plugin_exchangedb_postgres.c | 688 +++++++++++---------- src/exchangedb/test_exchangedb.c | 13 +- src/include/taler_exchangedb_plugin.h | 19 +- 11 files changed, 784 insertions(+), 762 deletions(-) diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c index 0d7d0b01a..21c9579cd 100644 --- a/src/auditor/taler-auditor.c +++ b/src/auditor/taler-auditor.c @@ -1864,12 +1864,15 @@ wire_transfer_information_cb (void *cls, struct TALER_Amount coin_value_without_fee; struct TALER_EXCHANGEDB_TransactionList *tl; const struct TALER_CoinPublicInfo *coin; + enum GNUNET_DB_QueryStatus qs; /* Obtain coin's transaction history */ - tl = edb->get_coin_transactions (edb->cls, + qs = edb->get_coin_transactions (edb->cls, esession, - coin_pub); - if (NULL == tl) + coin_pub, + &tl); + if ( (qs < 0) || + (NULL == tl) ) { wcc->ok = GNUNET_SYSERR; report_row_inconsistency ("aggregation", diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 298f6c35a..9e95ff40a 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -279,279 +279,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis } -/** - * Execute a "/refund". Returns a confirmation that the refund - * was successful, or a failure if we are not aware of a matching - * /deposit or if it is too late to do the refund. - * - * @param connection the MHD connection to handle - * @param refund refund details - * @return MHD result code - */ -int -TEH_DB_execute_refund (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_Refund *refund) -{ - struct TALER_EXCHANGEDB_Session *session; - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_EXCHANGEDB_TransactionList *tlp; - const struct TALER_EXCHANGEDB_Deposit *dep; - const struct TALER_EXCHANGEDB_Refund *ref; - struct TEH_KS_StateHandle *mks; - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - struct TALER_Amount expect_fee; - enum GNUNET_DB_QueryStatus qs; - int ret; - int deposit_found; - int refund_found; - int done; - int fee_cmp; - - if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) - { - GNUNET_break (0); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_DB_SETUP_FAILED); - } - dep = NULL; - ref = NULL; - START_TRANSACTION (session, connection); - tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - session, - &refund->coin.coin_pub); - if (NULL == tl) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_refund_failure (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_COIN_NOT_FOUND); - } - deposit_found = GNUNET_NO; - refund_found = GNUNET_NO; - for (tlp = tl; NULL != tlp; tlp = tlp->next) - { - switch (tlp->type) - { - case TALER_EXCHANGEDB_TT_DEPOSIT: - if (GNUNET_NO == deposit_found) - { - if ( (0 == memcmp (&tlp->details.deposit->merchant_pub, - &refund->merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (&tlp->details.deposit->h_contract_terms, - &refund->h_contract_terms, - sizeof (struct GNUNET_HashCode))) ) - { - dep = tlp->details.deposit; - deposit_found = GNUNET_YES; - break; - } - } - break; - case TALER_EXCHANGEDB_TT_REFRESH_MELT: - /* Melts cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_REFUND: - if (GNUNET_NO == refund_found) - { - /* First, check if existing refund request is identical */ - if ( (0 == memcmp (&tlp->details.refund->merchant_pub, - &refund->merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (&tlp->details.refund->h_contract_terms, - &refund->h_contract_terms, - sizeof (struct GNUNET_HashCode))) && - (tlp->details.refund->rtransaction_id == refund->rtransaction_id) ) - { - ref = tlp->details.refund; - refund_found = GNUNET_YES; - break; - } - /* Second, check if existing refund request conflicts */ - if ( (0 == memcmp (&tlp->details.refund->merchant_pub, - &refund->merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (&tlp->details.refund->h_contract_terms, - &refund->h_contract_terms, - sizeof (struct GNUNET_HashCode))) && - (tlp->details.refund->rtransaction_id != refund->rtransaction_id) ) - { - GNUNET_break_op (0); /* conflicting refund found */ - refund_found = GNUNET_SYSERR; - /* NOTE: Alternatively we could total up all existing - refunds and check if the sum still permits the - refund requested (thus allowing multiple, partial - refunds). Fow now, we keep it simple. */ - break; - } - } - break; - case TALER_EXCHANGEDB_TT_PAYBACK: - /* Paybacks cannot be refunded, ignore here */ - break; - } - } - /* handle if deposit was NOT found */ - if (GNUNET_NO == deposit_found) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return TEH_RESPONSE_reply_transaction_unknown (connection, - TALER_EC_REFUND_DEPOSIT_NOT_FOUND); - } - /* handle if conflicting refund found */ - if (GNUNET_SYSERR == refund_found) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - ret = TEH_RESPONSE_reply_refund_conflict (connection, - tl); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return ret; - } - /* handle if identical refund found */ - if (GNUNET_YES == refund_found) - { - /* /refund already done, simply re-transmit confirmation */ - TEH_plugin->rollback (TEH_plugin->cls, - session); - ret = TEH_RESPONSE_reply_refund_success (connection, - ref); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return ret; - } - - /* check currency is compatible */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&refund->refund_amount, - &dep->amount_with_fee)) || - (GNUNET_YES != - TALER_amount_cmp_currency (&refund->refund_fee, - &dep->deposit_fee)) ) - { - GNUNET_break_op (0); /* currency missmatch */ - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_refund_failure (connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_REFUND_CURRENCY_MISSMATCH); - } - - /* check if we already send the money for the /deposit */ - done = TEH_plugin->test_deposit_done (TEH_plugin->cls, - session, - dep); - if (GNUNET_SYSERR == done) - { - /* Internal error, we first had the deposit in the history, - but now it is gone? */ - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_REFUND_DB_INCONSISTENT, - "database inconsistent"); - } - if (GNUNET_YES == done) - { - /* money was already transferred to merchant, can no longer refund */ - TEH_plugin->rollback (TEH_plugin->cls, - session); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return TEH_RESPONSE_reply_refund_failure (connection, - MHD_HTTP_GONE, - TALER_EC_REFUND_MERCHANT_ALREADY_PAID); - } - - /* check refund amount is sufficiently low */ - if (1 == TALER_amount_cmp (&refund->refund_amount, - &dep->amount_with_fee) ) - { - GNUNET_break_op (0); /* cannot refund more than original value */ - TEH_plugin->rollback (TEH_plugin->cls, - session); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return TEH_RESPONSE_reply_refund_failure (connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_REFUND_INSUFFICIENT_FUNDS); - } - - /* Check refund fee matches fee of denomination key! */ - mks = TEH_KS_acquire (); - dki = TEH_KS_denomination_key_lookup (mks, - &dep->coin.denom_pub, - TEH_KS_DKU_DEPOSIT); - if (NULL == dki) - { - /* DKI not found, but we do have a coin with this DK in our database; - not good... */ - GNUNET_break (0); - TEH_plugin->rollback (TEH_plugin->cls, - session); - TEH_KS_release (mks); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND, - "denomination key not found"); - } - TALER_amount_ntoh (&expect_fee, - &dki->issue.properties.fee_refund); - fee_cmp = TALER_amount_cmp (&refund->refund_fee, - &expect_fee); - TEH_KS_release (mks); - - if (-1 == fee_cmp) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return TEH_RESPONSE_reply_arg_invalid (connection, - TALER_EC_REFUND_FEE_TOO_LOW, - "refund_fee"); - } - if (1 == fee_cmp) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Refund fee proposed by merchant is higher than necessary.\n"); - } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - - /* Finally, store new refund data */ - qs = TEH_plugin->insert_refund (TEH_plugin->cls, - session, - refund); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - TALER_LOG_WARNING ("Failed to store /refund information in database\n"); - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFUND_STORE_DB_ERROR); - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - /* FIXME: #5010: retry! */ - } - - COMMIT_TRANSACTION (session, connection); - - return TEH_RESPONSE_reply_refund_success (connection, - refund); -} - - /** * Parse coin melt requests from a JSON object and write them to * the database. @@ -581,6 +308,7 @@ refresh_check_melt (struct MHD_Connection *connection, struct TALER_Amount coin_residual; struct TALER_Amount spent; int res; + enum GNUNET_DB_QueryStatus qs; dk = TEH_KS_denomination_key_lookup (key_state, &coin_details->coin_info.denom_pub, @@ -597,9 +325,11 @@ refresh_check_melt (struct MHD_Connection *connection, /* 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 = TEH_plugin->get_coin_transactions (TEH_plugin->cls, + qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, session, - &coin_details->coin_info.coin_pub); + &coin_details->coin_info.coin_pub, + &tl); + (void) qs; /* FIXME #5010 */ if (GNUNET_OK != TEH_DB_calculate_transaction_list_totals (tl, &spent, @@ -1921,7 +1651,8 @@ TEH_DB_execute_payback (struct MHD_Connection *connection, struct TALER_Amount amount; struct TALER_Amount spent; struct GNUNET_TIME_Absolute now; - + enum GNUNET_DB_QueryStatus qs; + if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) { GNUNET_break (0); @@ -1955,9 +1686,11 @@ TEH_DB_execute_payback (struct MHD_Connection *connection, } /* Calculate remaining balance. */ - tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls, + qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, session, - &coin->coin_pub); + &coin->coin_pub, + &tl); + (void) qs; /* FIXME #5010 */ TALER_amount_get_zero (value->currency, &spent); if (GNUNET_OK != diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h index e3717bdb4..5e2a27a31 100644 --- a/src/exchange/taler-exchange-httpd_db.h +++ b/src/exchange/taler-exchange-httpd_db.h @@ -83,20 +83,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis struct TALER_Amount *ret); -/** - * Execute a "/refund". Returns a confirmation that the refund - * was successful, or a failure if we are not aware of a matching - * /deposit or if it is too late to do the refund. - * - * @param connection the MHD connection to handle - * @param refund refund details - * @return MHD result code - */ -int -TEH_DB_execute_refund (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_Refund *refund); - - /** * @brief Details about a melt operation of an individual coin. */ diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index c9c59edef..ccbd7754c 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -163,9 +163,12 @@ deposit_transaction (void *cls, /* Start with fee for THIS transaction */ spent = deposit->amount_with_fee; /* add cost of all previous transactions */ - tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls, + qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, session, - &deposit->coin.coin_pub); + &deposit->coin.coin_pub, + &tl); + if (0 > qs) + return qs; if (GNUNET_OK != TEH_DB_calculate_transaction_list_totals (tl, &spent, diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c index a5787eea9..9846c73ba 100644 --- a/src/exchange/taler-exchange-httpd_refund.c +++ b/src/exchange/taler-exchange-httpd_refund.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V. + Copyright (C) 2014-2017 Inria and GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -36,6 +36,350 @@ #include "taler-exchange-httpd_validation.h" +/** + * Generate successful refund confirmation message. + * + * @param connection connection to the client + * @param refund details about the successful refund + * @return MHD result code + */ +static int +reply_refund_success (struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_Refund *refund) +{ + struct TALER_RefundConfirmationPS rc; + struct TALER_ExchangePublicKeyP pub; + struct TALER_ExchangeSignatureP sig; + + rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND); + rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS)); + rc.h_contract_terms = refund->h_contract_terms; + rc.coin_pub = refund->coin.coin_pub; + rc.merchant = refund->merchant_pub; + rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id); + TALER_amount_hton (&rc.refund_amount, + &refund->refund_amount); + TALER_amount_hton (&rc.refund_fee, + &refund->refund_fee); + TEH_KS_sign (&rc.purpose, + &pub, + &sig); + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s, s:o, s:o}", + "status", "REFUND_OK", + "sig", GNUNET_JSON_from_data_auto (&sig), + "pub", GNUNET_JSON_from_data_auto (&pub)); +} + + +/** + * Generate generic refund failure message. All the details + * are in the @a response_code. The body can be empty. + * + * @param connection connection to the client + * @param response_code response code to generate + * @param ec taler error code to include + * @return MHD result code + */ +static int +reply_refund_failure (struct MHD_Connection *connection, + unsigned int response_code, + enum TALER_ErrorCode ec) +{ + return TEH_RESPONSE_reply_json_pack (connection, + response_code, + "{s:s, s:I}", + "status", "refund failure", + "code", (json_int_t) ec); +} + + +/** + * Generate refund conflict failure message. Returns the + * transaction list @a tl with the details about the conflict. + * + * @param connection connection to the client + * @param tl transaction list showing the conflict + * @return MHD result code + */ +static int +reply_refund_conflict (struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_TransactionList *tl) +{ + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o}", + "status", "conflicting refund", + "code", (json_int_t) TALER_EC_REFUND_CONFLICT, + "history", TEH_RESPONSE_compile_transaction_history (tl)); +} + + +/** + * Execute a "/refund" transaction. Returns a confirmation that the + * refund was successful, or a failure if we are not aware of a + * matching /deposit or if it is too late to do the refund. + * + * IF it returns a non-error code, the transaction logic MUST + * NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF + * it returns the soft error code, the function MAY be called again to + * retry and MUST not queue a MHD response. + * + * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refund_transaction (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + const struct TALER_EXCHANGEDB_Refund *refund = cls; + struct TALER_EXCHANGEDB_TransactionList *tl; + const struct TALER_EXCHANGEDB_Deposit *dep; + const struct TALER_EXCHANGEDB_Refund *ref; + struct TEH_KS_StateHandle *mks; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + struct TALER_Amount expect_fee; + enum GNUNET_DB_QueryStatus qs; + int deposit_found; + int refund_found; + int fee_cmp; + + dep = NULL; + ref = NULL; + tl = NULL; + qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, + session, + &refund->coin.coin_pub, + &tl); + if (0 > qs) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = reply_refund_failure (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFUND_COIN_NOT_FOUND); + return qs; + } + deposit_found = GNUNET_NO; + refund_found = GNUNET_NO; + for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl; + NULL != tlp; + tlp = tlp->next) + { + switch (tlp->type) + { + case TALER_EXCHANGEDB_TT_DEPOSIT: + if (GNUNET_NO == deposit_found) + { + if ( (0 == memcmp (&tlp->details.deposit->merchant_pub, + &refund->merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) && + (0 == memcmp (&tlp->details.deposit->h_contract_terms, + &refund->h_contract_terms, + sizeof (struct GNUNET_HashCode))) ) + { + dep = tlp->details.deposit; + deposit_found = GNUNET_YES; + break; + } + } + break; + case TALER_EXCHANGEDB_TT_REFRESH_MELT: + /* Melts cannot be refunded, ignore here */ + break; + case TALER_EXCHANGEDB_TT_REFUND: + if (GNUNET_NO == refund_found) + { + /* First, check if existing refund request is identical */ + if ( (0 == memcmp (&tlp->details.refund->merchant_pub, + &refund->merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) && + (0 == memcmp (&tlp->details.refund->h_contract_terms, + &refund->h_contract_terms, + sizeof (struct GNUNET_HashCode))) && + (tlp->details.refund->rtransaction_id == refund->rtransaction_id) ) + { + ref = tlp->details.refund; + refund_found = GNUNET_YES; + break; + } + /* Second, check if existing refund request conflicts */ + if ( (0 == memcmp (&tlp->details.refund->merchant_pub, + &refund->merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) && + (0 == memcmp (&tlp->details.refund->h_contract_terms, + &refund->h_contract_terms, + sizeof (struct GNUNET_HashCode))) && + (tlp->details.refund->rtransaction_id != refund->rtransaction_id) ) + { + GNUNET_break_op (0); /* conflicting refund found */ + refund_found = GNUNET_SYSERR; + /* NOTE: Alternatively we could total up all existing + refunds and check if the sum still permits the + refund requested (thus allowing multiple, partial + refunds). Fow now, we keep it simple. */ + break; + } + } + break; + case TALER_EXCHANGEDB_TT_PAYBACK: + /* Paybacks cannot be refunded, ignore here */ + break; + } + } + /* handle if deposit was NOT found */ + if (GNUNET_NO == deposit_found) + { + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = TEH_RESPONSE_reply_transaction_unknown (connection, + TALER_EC_REFUND_DEPOSIT_NOT_FOUND); + return GNUNET_DB_STATUS_HARD_ERROR; + } + /* handle if conflicting refund found */ + if (GNUNET_SYSERR == refund_found) + { + *mhd_ret = reply_refund_conflict (connection, + tl); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + return GNUNET_DB_STATUS_HARD_ERROR; + } + /* handle if identical refund found */ + if (GNUNET_YES == refund_found) + { + /* /refund already done, simply re-transmit confirmation */ + *mhd_ret = reply_refund_success (connection, + ref); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* check currency is compatible */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&refund->refund_amount, + &dep->amount_with_fee)) || + (GNUNET_YES != + TALER_amount_cmp_currency (&refund->refund_fee, + &dep->deposit_fee)) ) + { + GNUNET_break_op (0); /* currency missmatch */ + *mhd_ret = reply_refund_failure (connection, + MHD_HTTP_PRECONDITION_FAILED, + TALER_EC_REFUND_CURRENCY_MISSMATCH); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* check if we already send the money for the /deposit */ + // FIXME: DB API... + qs = TEH_plugin->test_deposit_done (TEH_plugin->cls, + session, + dep); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + /* Internal error, we first had the deposit in the history, + but now it is gone? */ + GNUNET_break (0); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFUND_DB_INCONSISTENT, + "database inconsistent"); + return qs; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; /* go and retry */ + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + /* money was already transferred to merchant, can no longer refund */ + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = reply_refund_failure (connection, + MHD_HTTP_GONE, + TALER_EC_REFUND_MERCHANT_ALREADY_PAID); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* check refund amount is sufficiently low */ + if (1 == TALER_amount_cmp (&refund->refund_amount, + &dep->amount_with_fee) ) + { + GNUNET_break_op (0); /* cannot refund more than original value */ + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = reply_refund_failure (connection, + MHD_HTTP_PRECONDITION_FAILED, + TALER_EC_REFUND_INSUFFICIENT_FUNDS); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + // FIXME: do this outside of transaction function? + /* Check refund fee matches fee of denomination key! */ + mks = TEH_KS_acquire (); + dki = TEH_KS_denomination_key_lookup (mks, + &dep->coin.denom_pub, + TEH_KS_DKU_DEPOSIT); + if (NULL == dki) + { + /* DKI not found, but we do have a coin with this DK in our database; + not good... */ + GNUNET_break (0); + TEH_KS_release (mks); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND, + "denomination key not found"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + TALER_amount_ntoh (&expect_fee, + &dki->issue.properties.fee_refund); + fee_cmp = TALER_amount_cmp (&refund->refund_fee, + &expect_fee); + TEH_KS_release (mks); + + if (-1 == fee_cmp) + { + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = TEH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_REFUND_FEE_TOO_LOW, + "refund_fee"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (1 == fee_cmp) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Refund fee proposed by merchant is higher than necessary.\n"); + } + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + + /* Finally, store new refund data */ + qs = TEH_plugin->insert_refund (TEH_plugin->cls, + session, + refund); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + TALER_LOG_WARNING ("Failed to store /refund information in database\n"); + *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFUND_STORE_DB_ERROR); + return qs; + } + /* Success or soft failure */ + return qs; +} + + /** * We have parsed the JSON information about the refund, do some basic * sanity checks (especially that the signature on the coin is valid) @@ -51,6 +395,7 @@ verify_and_execute_refund (struct MHD_Connection *connection, const struct TALER_EXCHANGEDB_Refund *refund) { struct TALER_RefundRequestPS rr; + int mhd_ret; rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); @@ -90,8 +435,14 @@ verify_and_execute_refund (struct MHD_Connection *connection, TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID, "merchant_sig"); } - return TEH_DB_execute_refund (connection, - refund); + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + &mhd_ret, + &refund_transaction, + (void *) refund)) + return mhd_ret; + return reply_refund_success (connection, + refund); } diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index 48df955f7..3f3f57215 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -467,8 +467,8 @@ TEH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection) * @param tl transaction history to JSON-ify * @return json representation of the @a rh */ -static json_t * -compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl) +json_t * +TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl) { json_t *history; @@ -663,7 +663,7 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection, { json_t *history; - history = compile_transaction_history (tl); + history = TEH_RESPONSE_compile_transaction_history (tl); if (NULL == history) return TEH_RESPONSE_reply_internal_error (connection, TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, @@ -878,86 +878,6 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto } -/** - * Generate refund conflict failure message. Returns the - * transaction list @a tl with the details about the conflict. - * - * @param connection connection to the client - * @param tl transaction list showing the conflict - * @return MHD result code - */ -int -TEH_RESPONSE_reply_refund_conflict (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_TransactionList *tl) -{ - return TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o}", - "status", "conflicting refund", - "code", (json_int_t) TALER_EC_REFUND_CONFLICT, - "history", compile_transaction_history (tl)); -} - - -/** - * Generate generic refund failure message. All the details - * are in the @a response_code. The body can be empty. - * - * @param connection connection to the client - * @param response_code response code to generate - * @param ec taler error code to include - * @return MHD result code - */ -int -TEH_RESPONSE_reply_refund_failure (struct MHD_Connection *connection, - unsigned int response_code, - enum TALER_ErrorCode ec) -{ - return TEH_RESPONSE_reply_json_pack (connection, - response_code, - "{s:s, s:I}", - "status", "refund failure", - "code", (json_int_t) ec); -} - - -/** - * Generate successful refund confirmation message. - * - * @param connection connection to the client - * @param refund details about the successful refund - * @return MHD result code - */ -int -TEH_RESPONSE_reply_refund_success (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_Refund *refund) -{ - struct TALER_RefundConfirmationPS rc; - struct TALER_ExchangePublicKeyP pub; - struct TALER_ExchangeSignatureP sig; - - rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND); - rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS)); - rc.h_contract_terms = refund->h_contract_terms; - rc.coin_pub = refund->coin.coin_pub; - rc.merchant = refund->merchant_pub; - rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id); - TALER_amount_hton (&rc.refund_amount, - &refund->refund_amount); - TALER_amount_hton (&rc.refund_fee, - &refund->refund_fee); - TEH_KS_sign (&rc.purpose, - &pub, - &sig); - return TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:s, s:o, s:o}", - "status", "REFUND_OK", - "sig", GNUNET_JSON_from_data_auto (&sig), - "pub", GNUNET_JSON_from_data_auto (&pub)); -} - - /** * Send a response for a failed "/refresh/melt" request. The * transaction history of the given coin demonstrates that the @@ -983,7 +903,7 @@ TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *conne { json_t *history; - history = compile_transaction_history (tl); + history = TEH_RESPONSE_compile_transaction_history (tl); if (NULL == history) return TEH_RESPONSE_reply_internal_db_error (connection, TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS); diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index aef5bf94d..27f253b78 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -273,46 +273,6 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection, const struct TALER_EXCHANGEDB_TransactionList *tl); -/** - * Generate refund conflict failure message. Returns the - * transaction list @a tl with the details about the conflict. - * - * @param connection connection to the client - * @param tl transaction list showing the conflict - * @return MHD result code - */ -int -TEH_RESPONSE_reply_refund_conflict (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_TransactionList *tl); - - -/** - * Generate generic refund failure message. All the details - * are in the @a response_code. The body can be empty. - * - * @param connection connection to the client - * @param response_code response code to generate - * @param ec error code uniquely identifying the error - * @return MHD result code - */ -int -TEH_RESPONSE_reply_refund_failure (struct MHD_Connection *connection, - unsigned int response_code, - enum TALER_ErrorCode ec); - - -/** - * Generate successful refund confirmation message. - * - * @param connection connection to the client - * @param refund details about the successful refund - * @return MHD result code - */ -int -TEH_RESPONSE_reply_refund_success (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_Refund *refund); - - /** * A merchant asked for details about a deposit, but * we do not know anything about the deposit. Generate the @@ -555,8 +515,19 @@ int TEH_RESPONSE_reply_payback_success (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, struct GNUNET_TIME_Absolute timestamp); +/** + * Compile the transaction history of a coin into a JSON object. + * + * @param tl transaction history to JSON-ify + * @return json representation of the @a rh + */ +json_t * +TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl); + + #endif diff --git a/src/exchangedb/perf_taler_exchangedb_interpreter.c b/src/exchangedb/perf_taler_exchangedb_interpreter.c index 5e5c6a399..3f52e90e2 100644 --- a/src/exchangedb/perf_taler_exchangedb_interpreter.c +++ b/src/exchangedb/perf_taler_exchangedb_interpreter.c @@ -1529,13 +1529,16 @@ interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state) unsigned int coin_index; struct PERF_TALER_EXCHANGEDB_Coin *coin; struct TALER_EXCHANGEDB_TransactionList *transactions; + enum GNUNET_DB_QueryStatus qs; coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin; coin = state->cmd[coin_index].exposed.data.coin; - transactions = state->plugin->get_coin_transactions (state->plugin->cls, - state->session, - &coin->public_info.coin_pub); - GNUNET_assert (transactions != NULL); + qs = state->plugin->get_coin_transactions (state->plugin->cls, + state->session, + &coin->public_info.coin_pub, + &transactions); + GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + GNUNET_assert (transactions != NULL); state->plugin->free_coin_transaction_list (state->plugin->cls, transactions); } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 41b5f720d..9eec10399 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -2827,10 +2827,11 @@ postgres_mark_deposit_tiny (void *cls, * @param cls the @e cls of this struct with the plugin-specific state * @param session connection to the database * @param deposit the deposit to check - * @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not, - * #GNUNET_SYSERR on error (deposit unknown) + * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not, + * otherwise transaction error status (incl. deposit unknown) */ -static int +static enum GNUNET_DB_QueryStatus postgres_test_deposit_done (void *cls, struct TALER_EXCHANGEDB_Session *session, const struct TALER_EXCHANGEDB_Deposit *deposit) @@ -2842,50 +2843,25 @@ postgres_test_deposit_done (void *cls, GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire), GNUNET_PQ_query_param_end }; - PGresult *result; - - result = GNUNET_PQ_exec_prepared (session->conn, - "test_deposit_done", - params); - if (PGRES_TUPLES_OK != - PQresultStatus (result)) - { - BREAK_DB_ERR (result, session->conn); - PQclear (result); - return GNUNET_SYSERR; - } - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_SYSERR; - } - if (1 != PQntuples (result)) - { - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - { - uint8_t done = 0; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("done", - &done), - GNUNET_PQ_result_spec_end - }; + uint8_t done = 0; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("done", + &done), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - 0)) - { - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - return (done ? GNUNET_YES : GNUNET_NO); - } + qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn, + "test_deposit_done", + params, + rs); + if (qs < 0) + return qs; + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return GNUNET_DB_STATUS_HARD_ERROR; /* deposit MUST exist */ + return (done + ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT + : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS); } @@ -4071,317 +4047,385 @@ postgres_get_transfer (void *cls, /** - * Compile a list of all (historic) transactions performed - * with the given coin (/refresh/melt, /deposit and /refund operations). - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param coin_pub coin to investigate - * @return list of transactions, NULL if coin is fresh + * Closure for callbacks called from #postgres_get_coin_transactions() */ -static struct TALER_EXCHANGEDB_TransactionList * -postgres_get_coin_transactions (void *cls, - struct TALER_EXCHANGEDB_Session *session, - const struct TALER_CoinSpendPublicKeyP *coin_pub) +struct CoinHistoryContext { + /** + * Head of the coin's history list. + */ struct TALER_EXCHANGEDB_TransactionList *head; - head = NULL; - /** #TALER_EXCHANGEDB_TT_DEPOSIT */ + /** + * Public key of the coin we are building the history for. + */ + const struct TALER_CoinSpendPublicKeyP *coin_pub; + + /** + * Closure for all callbacks of this database plugin. + */ + void *db_cls; + + /** + * Database session we are using. + */ + struct TALER_EXCHANGEDB_Session *session; + + /** + * Set to #GNUNET_SYSERR on errors + */ + int status; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +add_coin_deposit (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + + for (unsigned int i = 0; i < num_results; i++) { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_end - }; - int nrows; - PGresult *result; + struct TALER_EXCHANGEDB_Deposit *deposit; struct TALER_EXCHANGEDB_TransactionList *tl; - - result = GNUNET_PQ_exec_prepared (session->conn, - "get_deposit_with_coin_pub", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result, session->conn); - PQclear (result); - goto cleanup; - } - nrows = PQntuples (result); - for (int i = 0; i < nrows; i++) + + deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit); { - struct TALER_EXCHANGEDB_Deposit *deposit; - - deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit); - { - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_amount ("amount_with_fee", - &deposit->amount_with_fee), - TALER_PQ_result_spec_amount ("fee_deposit", - &deposit->deposit_fee), - GNUNET_PQ_result_spec_absolute_time ("timestamp", - &deposit->timestamp), - GNUNET_PQ_result_spec_absolute_time ("refund_deadline", - &deposit->refund_deadline), - GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", - &deposit->merchant_pub), - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - &deposit->h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("h_wire", - &deposit->h_wire), - TALER_PQ_result_spec_json ("wire", - &deposit->receiver_wire_account), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &deposit->csig), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (deposit); - PQclear (result); - goto cleanup; - } - deposit->coin.coin_pub = *coin_pub; - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = head; - tl->type = TALER_EXCHANGEDB_TT_DEPOSIT; - tl->details.deposit = deposit; - if (GNUNET_SYSERR == get_known_coin (cls, - session, - &deposit->coin.coin_pub, - &deposit->coin)) + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_amount ("amount_with_fee", + &deposit->amount_with_fee), + TALER_PQ_result_spec_amount ("fee_deposit", + &deposit->deposit_fee), + GNUNET_PQ_result_spec_absolute_time ("timestamp", + &deposit->timestamp), + GNUNET_PQ_result_spec_absolute_time ("refund_deadline", + &deposit->refund_deadline), + GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", + &deposit->merchant_pub), + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + &deposit->h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("h_wire", + &deposit->h_wire), + TALER_PQ_result_spec_json ("wire", + &deposit->receiver_wire_account), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &deposit->csig), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) { - GNUNET_break (0); - GNUNET_free (deposit); - PQclear (result); - goto cleanup; + GNUNET_break (0); + GNUNET_free (deposit); + chc->status = GNUNET_SYSERR; + return; } - head = tl; - continue; + deposit->coin.coin_pub = *chc->coin_pub; } - PQclear (result); + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_DEPOSIT; + tl->details.deposit = deposit; + if (GNUNET_SYSERR == get_known_coin (chc->db_cls, + chc->session, + chc->coin_pub, + &deposit->coin)) + { + GNUNET_break (0); + GNUNET_free (deposit); + chc->status = GNUNET_SYSERR; + return; + } + chc->head = tl; } - /** #TALER_EXCHANGEDB_TT_REFRESH_MELT */ +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +add_coin_melt (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + + for (unsigned int i=0;ieddsa_pub), - GNUNET_PQ_query_param_end - }; - int nrows; - PGresult *result; + struct TALER_EXCHANGEDB_RefreshMelt *melt; struct TALER_EXCHANGEDB_TransactionList *tl; - /* check if the melt records exist and get them */ - result = GNUNET_PQ_exec_prepared (session->conn, - "get_refresh_session_by_coin", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result, session->conn); - PQclear (result); - goto cleanup; - } - nrows = PQntuples (result); - for (int i=0;isession_hash), - /* oldcoin_index not needed */ - GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", - &melt->coin_sig), - TALER_PQ_result_spec_amount ("amount_with_fee", - &melt->amount_with_fee), - TALER_PQ_result_spec_amount ("fee_refresh", - &melt->melt_fee), - GNUNET_PQ_result_spec_end - }; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (melt); - PQclear (result); - goto cleanup; - } - melt->coin.coin_pub = *coin_pub; - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = head; - tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT; - tl->details.melt = melt; - if (GNUNET_SYSERR == get_known_coin (cls, - session, - coin_pub, - &melt->coin)) + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("session_hash", + &melt->session_hash), + /* oldcoin_index not needed */ + GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", + &melt->coin_sig), + TALER_PQ_result_spec_amount ("amount_with_fee", + &melt->amount_with_fee), + TALER_PQ_result_spec_amount ("fee_refresh", + &melt->melt_fee), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) { - GNUNET_break (0); - GNUNET_free (melt); - PQclear (result); - goto cleanup; + GNUNET_break (0); + GNUNET_free (melt); + chc->status = GNUNET_SYSERR; + return; } - head = tl; - continue; + melt->coin.coin_pub = *chc->coin_pub; } - PQclear (result); + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT; + tl->details.melt = melt; + if (GNUNET_SYSERR == get_known_coin (chc->db_cls, + chc->session, + chc->coin_pub, + &melt->coin)) + { + GNUNET_break (0); + GNUNET_free (melt); + chc->status = GNUNET_SYSERR; + return; + } + chc->head = tl; } - /** #TALER_EXCHANGEDB_TT_REFUND */ +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +add_coin_refund (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + + for (unsigned int i=0;iconn, - "get_refunds_by_coin", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) + refund = GNUNET_new (struct TALER_EXCHANGEDB_Refund); { - BREAK_DB_ERR (result, session->conn); - PQclear (result); - goto cleanup; - } - nrows = PQntuples (result); - for (int i=0;imerchant_pub), - GNUNET_PQ_result_spec_auto_from_type ("merchant_sig", - &refund->merchant_sig), - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - &refund->h_contract_terms), - GNUNET_PQ_result_spec_uint64 ("rtransaction_id", - &refund->rtransaction_id), - TALER_PQ_result_spec_amount ("amount_with_fee", - &refund->refund_amount), - TALER_PQ_result_spec_amount ("fee_refund", - &refund->refund_fee), - GNUNET_PQ_result_spec_end - }; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (refund); - PQclear (result); - goto cleanup; - } - refund->coin.coin_pub = *coin_pub; - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = head; - tl->type = TALER_EXCHANGEDB_TT_REFUND; - tl->details.refund = refund; - if (GNUNET_SYSERR == - get_known_coin (cls, - session, - coin_pub, - &refund->coin)) + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", + &refund->merchant_pub), + GNUNET_PQ_result_spec_auto_from_type ("merchant_sig", + &refund->merchant_sig), + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + &refund->h_contract_terms), + GNUNET_PQ_result_spec_uint64 ("rtransaction_id", + &refund->rtransaction_id), + TALER_PQ_result_spec_amount ("amount_with_fee", + &refund->refund_amount), + TALER_PQ_result_spec_amount ("fee_refund", + &refund->refund_fee), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) { - GNUNET_break (0); - GNUNET_free (refund); - PQclear (result); - goto cleanup; + GNUNET_break (0); + GNUNET_free (refund); + chc->status = GNUNET_SYSERR; + return; } - head = tl; - continue; + refund->coin.coin_pub = *chc->coin_pub; } - PQclear (result); + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_REFUND; + tl->details.refund = refund; + if (GNUNET_SYSERR == + get_known_coin (chc->db_cls, + chc->session, + chc->coin_pub, + &refund->coin)) + { + GNUNET_break (0); + GNUNET_free (refund); + chc->status = GNUNET_SYSERR; + return; + } + chc->head = tl; } - /** #TALER_EXCHANGEDB_TT_PAYBACK */ +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +add_coin_payback (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + + for (unsigned int i=0;iconn, - "payback_by_coin", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result, session->conn); - PQclear (result); - goto cleanup; - } - nrows = PQntuples (result); - for (int i=0;ivalue), + GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", + &payback->reserve_pub), + GNUNET_PQ_result_spec_auto_from_type ("coin_blind", + &payback->coin_blind), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &payback->coin_sig), + GNUNET_PQ_result_spec_absolute_time ("timestamp", + &payback->timestamp), + GNUNET_PQ_result_spec_rsa_public_key ("denom_pub", + &payback->coin.denom_pub.rsa_public_key), + GNUNET_PQ_result_spec_rsa_signature ("denom_sig", + &payback->coin.denom_sig.rsa_signature), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) { - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_amount ("amount", - &payback->value), - GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", - &payback->reserve_pub), - GNUNET_PQ_result_spec_auto_from_type ("coin_blind", - &payback->coin_blind), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &payback->coin_sig), - GNUNET_PQ_result_spec_absolute_time ("timestamp", - &payback->timestamp), - GNUNET_PQ_result_spec_rsa_public_key ("denom_pub", - &payback->coin.denom_pub.rsa_public_key), - GNUNET_PQ_result_spec_rsa_signature ("denom_sig", - &payback->coin.denom_sig.rsa_signature), - GNUNET_PQ_result_spec_end - }; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (payback); - PQclear (result); - goto cleanup; - } - payback->coin.coin_pub = *coin_pub; + GNUNET_break (0); + GNUNET_free (payback); + chc->status = GNUNET_SYSERR; + return; } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = head; - tl->type = TALER_EXCHANGEDB_TT_PAYBACK; - tl->details.payback = payback; - head = tl; - continue; + payback->coin.coin_pub = *chc->coin_pub; } - PQclear (result); + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_PAYBACK; + tl->details.payback = payback; + chc->head = tl; } +} - return head; - cleanup: - if (NULL != head) - common_free_coin_transaction_list (cls, - head); - return NULL; + +/** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt, /deposit and /refund operations). + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub coin to investigate + * @param[out] tlp set to list of transactions, NULL if coin is fresh + * @return database transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_get_coin_transactions (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_EXCHANGEDB_TransactionList **tlp) +{ + struct CoinHistoryContext chc; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + struct { + /** + * SQL prepared statement name. + */ + const char *statement; + + /** + * Function to call to handle the result(s). + */ + GNUNET_PQ_PostgresResultHandler cb; + } work[] = { + /** #TALER_EXCHANGEDB_TT_DEPOSIT */ + { "get_deposit_with_coin_pub", + &add_coin_deposit }, + /** #TALER_EXCHANGEDB_TT_REFRESH_MELT */ + { "get_refresh_session_by_coin", + &add_coin_melt }, + /** #TALER_EXCHANGEDB_TT_REFUND */ + { "get_refunds_by_coin", + &add_coin_refund }, + /** #TALER_EXCHANGEDB_TT_PAYBACK */ + { "payback_by_coin", + &add_coin_payback }, + { NULL, NULL } + }; + + chc.head = NULL; + chc.status = GNUNET_OK; + chc.coin_pub = coin_pub; + chc.session = session; + chc.db_cls = cls; + for (unsigned int i=0;NULL != work[i].statement; i++) + { + qs = GNUNET_PQ_eval_prepared_multi_select (session->conn, + work[i].statement, + params, + work[i].cb, + &chc); + if ( (0 > qs) || + (GNUNET_OK != chc.status) ) + { + if (NULL != chc.head) + common_free_coin_transaction_list (cls, + chc.head); + *tlp = NULL; + if (GNUNET_OK != chc.status) + qs = GNUNET_DB_STATUS_HARD_ERROR; + return qs; + } + } + *tlp = chc.head; + if (NULL == chc.head) + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index ac1301d44..19e8caa2e 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -726,10 +726,13 @@ test_melting (struct TALER_EXCHANGEDB_Session *session) { /* Just to test fetching a coin with melt history */ struct TALER_EXCHANGEDB_TransactionList *tl; + enum GNUNET_DB_QueryStatus qs; - tl = plugin->get_coin_transactions (plugin->cls, + qs = plugin->get_coin_transactions (plugin->cls, session, - &meltp->coin.coin_pub); + &meltp->coin.coin_pub, + &tl); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs); plugin->free_coin_transaction_list (plugin->cls, tl); } @@ -1930,9 +1933,11 @@ run (void *cls) NULL)); FAILIF (1 != auditor_row_cnt); - tl = plugin->get_coin_transactions (plugin->cls, + qs = plugin->get_coin_transactions (plugin->cls, session, - &refund.coin.coin_pub); + &refund.coin.coin_pub, + &tl); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs); GNUNET_assert (NULL != tl); matched = 0; for (tlp = tl; NULL != tlp; tlp = tlp->next) diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index af6b5547d..8a2010819 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1346,16 +1346,17 @@ struct TALER_EXCHANGEDB_Plugin /** - * Test if a deposit was marked as done, thereby declaring that it cannot be - * refunded anymore. + * Test if a deposit was marked as done, thereby declaring that it + * cannot be refunded anymore. * * @param cls the @e cls of this struct with the plugin-specific state * @param session connection to the database * @param deposit the deposit to check - * @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not, - * #GNUNET_SYSERR on error (deposit unknown) + * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not, + * otherwise transaction error status (incl. deposit unknown) */ - int + enum GNUNET_DB_QueryStatus (*test_deposit_done) (void *cls, struct TALER_EXCHANGEDB_Session *session, const struct TALER_EXCHANGEDB_Deposit *deposit); @@ -1700,12 +1701,14 @@ struct TALER_EXCHANGEDB_Plugin * @param cls the @e cls of this struct with the plugin-specific state * @param session database connection * @param coin_pub coin to investigate - * @return list of transactions, NULL if coin is fresh + * @param[out] tlp set to list of transactions, NULL if coin is fresh + * @return database transaction status */ - struct TALER_EXCHANGEDB_TransactionList * + enum GNUNET_DB_QueryStatus (*get_coin_transactions) (void *cls, struct TALER_EXCHANGEDB_Session *session, - const struct TALER_CoinSpendPublicKeyP *coin_pub); + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_EXCHANGEDB_TransactionList **tlp); /** -- cgit v1.2.3