refactoring /deposit towards new transaction style (#5010)
This commit is contained in:
parent
d2c7ef54a7
commit
2ec1b055a0
@ -112,6 +112,79 @@ transaction_start_label: /* we will use goto for retries */ \
|
||||
} while (0)
|
||||
|
||||
|
||||
/**
|
||||
* Run a database transaction for @a connection.
|
||||
* Starts a transaction and calls @a cb. Upon success,
|
||||
* attempts to commit the transaction. Upon soft failures,
|
||||
* retries @a cb a few times. Upon hard or persistent soft
|
||||
* errors, generates an error message for @a connection.
|
||||
*
|
||||
* @param connection MHD connection to run @a cb for
|
||||
* @param[out] set to MHD response code, if transaction failed
|
||||
* @param cb callback implementing transaction logic
|
||||
* @param cb_cls closure for @a cb, must be read-only!
|
||||
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
|
||||
*/
|
||||
int
|
||||
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
||||
int *mhd_ret,
|
||||
TEH_DB_TransactionCallback cb,
|
||||
void *cb_cls)
|
||||
{
|
||||
struct TALER_EXCHANGEDB_Session *session;
|
||||
|
||||
*mhd_ret = -1; /* invalid value */
|
||||
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DB_SETUP_FAILED);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
for (unsigned int retries = 0;retries < MAX_TRANSACTION_COMMIT_RETRIES; retries++)
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TEH_plugin->start (TEH_plugin->cls,
|
||||
session))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DB_START_FAILED);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
qs = cb (cb_cls,
|
||||
connection,
|
||||
session,
|
||||
mhd_ret);
|
||||
if (0 > qs)
|
||||
TEH_plugin->rollback (TEH_plugin->cls,
|
||||
session);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
return GNUNET_SYSERR;
|
||||
if (0 <= qs)
|
||||
qs = TEH_plugin->commit (TEH_plugin->cls,
|
||||
session);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
*mhd_ret = TEH_RESPONSE_reply_commit_error (connection,
|
||||
TALER_EC_DB_COMMIT_FAILED_HARD);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
/* make sure callback did not violate invariants! */
|
||||
GNUNET_assert (-1 == *mhd_ret);
|
||||
if (0 <= qs)
|
||||
return GNUNET_OK;
|
||||
}
|
||||
TALER_LOG_WARNING ("Transaction commit failed %u times\n",
|
||||
MAX_TRANSACTION_COMMIT_RETRIES);
|
||||
*mhd_ret = TEH_RESPONSE_reply_commit_error (connection,
|
||||
TALER_EC_DB_COMMIT_FAILED_ON_RETRY);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the total value of all transactions performed.
|
||||
* Stores @a off plus the cost of all transactions in @a tl
|
||||
@ -119,13 +192,13 @@ transaction_start_label: /* we will use goto for retries */ \
|
||||
*
|
||||
* @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
|
||||
* @param[out] 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)
|
||||
int
|
||||
TEH_DB_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;
|
||||
@ -206,146 +279,6 @@ calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionList *tl,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
TEH_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 TEH_KS_StateHandle *mks;
|
||||
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
|
||||
int ret;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
unsigned int retries = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
again:
|
||||
if (GNUNET_YES ==
|
||||
TEH_plugin->have_deposit (TEH_plugin->cls,
|
||||
session,
|
||||
deposit))
|
||||
{
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_subtract (&amount_without_fee,
|
||||
&deposit->amount_with_fee,
|
||||
&deposit->deposit_fee));
|
||||
return TEH_RESPONSE_reply_deposit_success (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&deposit->h_wire,
|
||||
&deposit->h_contract_terms,
|
||||
deposit->timestamp,
|
||||
deposit->refund_deadline,
|
||||
&deposit->merchant_pub,
|
||||
&amount_without_fee);
|
||||
}
|
||||
|
||||
/* FIXME: move the 'mks'-logic outside of _db.c? */
|
||||
mks = TEH_KS_acquire ();
|
||||
dki = TEH_KS_denomination_key_lookup (mks,
|
||||
&deposit->coin.denom_pub,
|
||||
TEH_KS_DKU_DEPOSIT);
|
||||
if (NULL == dki)
|
||||
{
|
||||
TEH_KS_release (mks);
|
||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_DB_DENOMINATION_KEY_UNKNOWN);
|
||||
}
|
||||
TALER_amount_ntoh (&value,
|
||||
&dki->issue.properties.value);
|
||||
TEH_KS_release (mks);
|
||||
|
||||
START_TRANSACTION (session, connection);
|
||||
|
||||
/* fee for THIS transaction */
|
||||
spent = deposit->amount_with_fee;
|
||||
/* add cost of all previous transactions */
|
||||
tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
session,
|
||||
&deposit->coin.coin_pub);
|
||||
if (GNUNET_OK !=
|
||||
calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
{
|
||||
TEH_plugin->rollback (TEH_plugin->cls,
|
||||
session);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_HISTORY_DB_ERROR);
|
||||
}
|
||||
/* Check that cost of all transactions is smaller than
|
||||
the value of the coin. */
|
||||
if (0 < TALER_amount_cmp (&spent,
|
||||
&value))
|
||||
{
|
||||
TEH_plugin->rollback (TEH_plugin->cls,
|
||||
session);
|
||||
ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
|
||||
TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS,
|
||||
tl);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return ret;
|
||||
}
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
|
||||
session,
|
||||
deposit);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
|
||||
TEH_plugin->rollback (TEH_plugin->cls,
|
||||
session);
|
||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_STORE_DB_ERROR);
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
{
|
||||
retries++;
|
||||
TEH_plugin->rollback (TEH_plugin->cls,
|
||||
session);
|
||||
if (retries > 5)
|
||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_STORE_DB_ERROR);
|
||||
goto again;
|
||||
}
|
||||
|
||||
COMMIT_TRANSACTION(session, connection);
|
||||
GNUNET_assert (GNUNET_SYSERR !=
|
||||
TALER_amount_subtract (&amount_without_fee,
|
||||
&deposit->amount_with_fee,
|
||||
&deposit->deposit_fee));
|
||||
return TEH_RESPONSE_reply_deposit_success (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&deposit->h_wire,
|
||||
&deposit->h_contract_terms,
|
||||
deposit->timestamp,
|
||||
deposit->refund_deadline,
|
||||
&deposit->merchant_pub,
|
||||
&amount_without_fee);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a "/refund". Returns a confirmation that the refund
|
||||
* was successful, or a failure if we are not aware of a matching
|
||||
@ -367,6 +300,7 @@ TEH_DB_execute_refund (struct MHD_Connection *connection,
|
||||
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;
|
||||
@ -595,10 +529,10 @@ TEH_DB_execute_refund (struct MHD_Connection *connection,
|
||||
tl);
|
||||
|
||||
/* Finally, store new refund data */
|
||||
if (GNUNET_OK !=
|
||||
TEH_plugin->insert_refund (TEH_plugin->cls,
|
||||
session,
|
||||
refund))
|
||||
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,
|
||||
@ -606,6 +540,11 @@ TEH_DB_execute_refund (struct MHD_Connection *connection,
|
||||
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,
|
||||
@ -1043,9 +982,9 @@ refresh_check_melt (struct MHD_Connection *connection,
|
||||
session,
|
||||
&coin_details->coin_info.coin_pub);
|
||||
if (GNUNET_OK !=
|
||||
calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
TEH_DB_calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
@ -2403,9 +2342,9 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
|
||||
TALER_amount_get_zero (value->currency,
|
||||
&spent);
|
||||
if (GNUNET_OK !=
|
||||
calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
TEH_DB_calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TEH_plugin->rollback (TEH_plugin->cls,
|
||||
|
@ -24,20 +24,63 @@
|
||||
#include <microhttpd.h>
|
||||
#include "taler_exchangedb_plugin.h"
|
||||
|
||||
/**
|
||||
* Function implementing a database transaction. Runs the transaction
|
||||
* logic; 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
|
||||
* @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
|
||||
*/
|
||||
typedef enum GNUNET_DB_QueryStatus
|
||||
(*TEH_DB_TransactionCallback)(void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
struct TALER_EXCHANGEDB_Session *session,
|
||||
int *mhd_ret);
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Run a database transaction for @a connection.
|
||||
* Starts a transaction and calls @a cb. Upon success,
|
||||
* attempts to commit the transaction. Upon soft failures,
|
||||
* retries @a cb a few times. Upon hard or persistent soft
|
||||
* errors, generates an error message for @a connection.
|
||||
*
|
||||
* @param connection MHD connection to run @a cb for
|
||||
* @param[out] set to MHD response code, if transaction failed
|
||||
* @param cb callback implementing transaction logic
|
||||
* @param cb_cls closure for @a cb, must be read-only!
|
||||
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
|
||||
*/
|
||||
int
|
||||
TEH_DB_execute_deposit (struct MHD_Connection *connection,
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit);
|
||||
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
||||
int *mhd_ret,
|
||||
TEH_DB_TransactionCallback cb,
|
||||
void *cb_cls);
|
||||
|
||||
|
||||
/**
|
||||
* 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[out] ret where the resulting total is to be stored
|
||||
* @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
|
||||
*/
|
||||
// FIXME: maybe move to another module?
|
||||
int
|
||||
TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionList *tl,
|
||||
const struct TALER_Amount *off,
|
||||
struct TALER_Amount *ret);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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,174 @@
|
||||
#include "taler-exchange-httpd_validation.h"
|
||||
|
||||
|
||||
/**
|
||||
* Send confirmation of deposit success to client. This function
|
||||
* will create a signed message affirming the given information
|
||||
* and return it to the client. By this, the exchange affirms that
|
||||
* the coin had sufficient (residual) value for the specified
|
||||
* transaction and that it will execute the requested deposit
|
||||
* operation with the given wiring details.
|
||||
*
|
||||
* @param connection connection to the client
|
||||
* @param coin_pub public key of the coin
|
||||
* @param h_wire hash of wire details
|
||||
* @param h_contract_terms hash of contract details
|
||||
* @param timestamp client's timestamp
|
||||
* @param refund_deadline until when this deposit be refunded
|
||||
* @param merchant merchant public key
|
||||
* @param amount_without_fee fraction of coin value to deposit, without the fee
|
||||
* @return MHD result code
|
||||
*/
|
||||
static int
|
||||
reply_deposit_success (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct GNUNET_HashCode *h_wire,
|
||||
const struct GNUNET_HashCode *h_contract_terms,
|
||||
struct GNUNET_TIME_Absolute timestamp,
|
||||
struct GNUNET_TIME_Absolute refund_deadline,
|
||||
const struct TALER_MerchantPublicKeyP *merchant,
|
||||
const struct TALER_Amount *amount_without_fee)
|
||||
{
|
||||
struct TALER_DepositConfirmationPS dc;
|
||||
struct TALER_ExchangePublicKeyP pub;
|
||||
struct TALER_ExchangeSignatureP sig;
|
||||
|
||||
dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
|
||||
dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS));
|
||||
dc.h_contract_terms = *h_contract_terms;
|
||||
dc.h_wire = *h_wire;
|
||||
dc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
|
||||
dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
|
||||
TALER_amount_hton (&dc.amount_without_fee,
|
||||
amount_without_fee);
|
||||
dc.coin_pub = *coin_pub;
|
||||
dc.merchant = *merchant;
|
||||
TEH_KS_sign (&dc.purpose,
|
||||
&pub,
|
||||
&sig);
|
||||
return TEH_RESPONSE_reply_json_pack (connection,
|
||||
MHD_HTTP_OK,
|
||||
"{s:s, s:o, s:o}",
|
||||
"status", "DEPOSIT_OK",
|
||||
"sig", GNUNET_JSON_from_data_auto (&sig),
|
||||
"pub", GNUNET_JSON_from_data_auto (&pub));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closure for #deposit_transaction.
|
||||
*/
|
||||
struct DepositContext
|
||||
{
|
||||
/**
|
||||
* Information about the deposit request.
|
||||
*/
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit;
|
||||
|
||||
/**
|
||||
* Value of the coin.
|
||||
*/
|
||||
struct TALER_Amount value;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute database transaction for /deposit. Runs the transaction
|
||||
* logic; 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 a `struct DepositContext`
|
||||
* @param connection MHD request context
|
||||
* @param session database session and transaction to use
|
||||
* @param[out] mhd_ret set to MHD status on error
|
||||
* @return transaction status
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
deposit_transaction (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
struct TALER_EXCHANGEDB_Session *session,
|
||||
int *mhd_ret)
|
||||
{
|
||||
struct DepositContext *dc = cls;
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit;
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
struct TALER_Amount spent;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
qs = TEH_plugin->have_deposit (TEH_plugin->cls,
|
||||
session,
|
||||
deposit);
|
||||
if (qs < 0)
|
||||
return qs;
|
||||
if (1 == qs)
|
||||
{
|
||||
struct TALER_Amount amount_without_fee;
|
||||
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_subtract (&amount_without_fee,
|
||||
&deposit->amount_with_fee,
|
||||
&deposit->deposit_fee));
|
||||
*mhd_ret = reply_deposit_success (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&deposit->h_wire,
|
||||
&deposit->h_contract_terms,
|
||||
deposit->timestamp,
|
||||
deposit->refund_deadline,
|
||||
&deposit->merchant_pub,
|
||||
&amount_without_fee);
|
||||
/* Treat as 'hard' DB error as we want to rollback and
|
||||
never try again. */
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
session,
|
||||
&deposit->coin.coin_pub);
|
||||
if (GNUNET_OK !=
|
||||
TEH_DB_calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
{
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_HISTORY_DB_ERROR);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
/* Check that cost of all transactions is smaller than
|
||||
the value of the coin. */
|
||||
if (0 < TALER_amount_cmp (&spent,
|
||||
&dc->value))
|
||||
{
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
|
||||
TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS,
|
||||
tl);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
|
||||
session,
|
||||
deposit);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
|
||||
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_STORE_DB_ERROR);
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We have parsed the JSON information about the deposit, do some
|
||||
* basic sanity checks (especially that the signature on the coin is
|
||||
@ -51,7 +219,13 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit)
|
||||
{
|
||||
struct TALER_DepositRequestPS dr;
|
||||
int mhd_ret;
|
||||
struct TALER_Amount amount_without_fee;
|
||||
struct DepositContext dc;
|
||||
struct TEH_KS_StateHandle *mks;
|
||||
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
|
||||
|
||||
/* check signature */
|
||||
dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
|
||||
dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
|
||||
dr.h_contract_terms = deposit->h_contract_terms;
|
||||
@ -76,8 +250,43 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
|
||||
"coin_sig");
|
||||
}
|
||||
|
||||
return TEH_DB_execute_deposit (connection,
|
||||
deposit);
|
||||
/* check denomination */
|
||||
mks = TEH_KS_acquire ();
|
||||
dki = TEH_KS_denomination_key_lookup (mks,
|
||||
&deposit->coin.denom_pub,
|
||||
TEH_KS_DKU_DEPOSIT);
|
||||
if (NULL == dki)
|
||||
{
|
||||
TEH_KS_release (mks);
|
||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
||||
TALER_EC_DEPOSIT_DB_DENOMINATION_KEY_UNKNOWN);
|
||||
}
|
||||
TALER_amount_ntoh (&dc.value,
|
||||
&dki->issue.properties.value);
|
||||
TEH_KS_release (mks);
|
||||
|
||||
/* execute transaction */
|
||||
dc.deposit = deposit;
|
||||
if (GNUNET_OK !=
|
||||
TEH_DB_run_transaction (connection,
|
||||
&mhd_ret,
|
||||
&deposit_transaction,
|
||||
&dc))
|
||||
return mhd_ret;
|
||||
|
||||
/* generate regular response */
|
||||
GNUNET_assert (GNUNET_SYSERR !=
|
||||
TALER_amount_subtract (&amount_without_fee,
|
||||
&deposit->amount_with_fee,
|
||||
&deposit->deposit_fee));
|
||||
return reply_deposit_success (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&deposit->h_wire,
|
||||
&deposit->h_contract_terms,
|
||||
deposit->timestamp,
|
||||
deposit->refund_deadline,
|
||||
&deposit->merchant_pub,
|
||||
&amount_without_fee);
|
||||
}
|
||||
|
||||
|
||||
|
@ -461,60 +461,6 @@ TEH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send confirmation of deposit success to client. This function
|
||||
* will create a signed message affirming the given information
|
||||
* and return it to the client. By this, the exchange affirms that
|
||||
* the coin had sufficient (residual) value for the specified
|
||||
* transaction and that it will execute the requested deposit
|
||||
* operation with the given wiring details.
|
||||
*
|
||||
* @param connection connection to the client
|
||||
* @param coin_pub public key of the coin
|
||||
* @param h_wire hash of wire details
|
||||
* @param h_contract_terms hash of contract details
|
||||
* @param timestamp client's timestamp
|
||||
* @param refund_deadline until when this deposit be refunded
|
||||
* @param merchant merchant public key
|
||||
* @param amount_without_fee fraction of coin value to deposit, without the fee
|
||||
* @return MHD result code
|
||||
*/
|
||||
int
|
||||
TEH_RESPONSE_reply_deposit_success (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct GNUNET_HashCode *h_wire,
|
||||
const struct GNUNET_HashCode *h_contract_terms,
|
||||
struct GNUNET_TIME_Absolute timestamp,
|
||||
struct GNUNET_TIME_Absolute refund_deadline,
|
||||
const struct TALER_MerchantPublicKeyP *merchant,
|
||||
const struct TALER_Amount *amount_without_fee)
|
||||
{
|
||||
struct TALER_DepositConfirmationPS dc;
|
||||
struct TALER_ExchangePublicKeyP pub;
|
||||
struct TALER_ExchangeSignatureP sig;
|
||||
|
||||
dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
|
||||
dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS));
|
||||
dc.h_contract_terms = *h_contract_terms;
|
||||
dc.h_wire = *h_wire;
|
||||
dc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
|
||||
dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
|
||||
TALER_amount_hton (&dc.amount_without_fee,
|
||||
amount_without_fee);
|
||||
dc.coin_pub = *coin_pub;
|
||||
dc.merchant = *merchant;
|
||||
TEH_KS_sign (&dc.purpose,
|
||||
&pub,
|
||||
&sig);
|
||||
return TEH_RESPONSE_reply_json_pack (connection,
|
||||
MHD_HTTP_OK,
|
||||
"{s:s, s:o, s:o}",
|
||||
"status", "DEPOSIT_OK",
|
||||
"sig", GNUNET_JSON_from_data_auto (&sig),
|
||||
"pub", GNUNET_JSON_from_data_auto (&pub));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the transaction history of a coin into a JSON object.
|
||||
*
|
||||
|
@ -243,35 +243,6 @@ int
|
||||
TEH_RESPONSE_reply_invalid_json (struct MHD_Connection *connectionx);
|
||||
|
||||
|
||||
/**
|
||||
* Send confirmation of deposit success to client. This function
|
||||
* will create a signed message affirming the given information
|
||||
* and return it to the client. By this, the exchange affirms that
|
||||
* the coin had sufficient (residual) value for the specified
|
||||
* transaction and that it will execute the requested deposit
|
||||
* operation with the given wiring details.
|
||||
*
|
||||
* @param connection connection to the client
|
||||
* @param coin_pub public key of the coin
|
||||
* @param h_wire hash of wire details
|
||||
* @param h_contract_terms hash of proposal data
|
||||
* @param timestamp client's timestamp
|
||||
* @param refund_deadline until when this deposit be refunded
|
||||
* @param merchant merchant public key
|
||||
* @param amount_without_fee fraction of coin value to deposit (without fee)
|
||||
* @return MHD result code
|
||||
*/
|
||||
int
|
||||
TEH_RESPONSE_reply_deposit_success (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct GNUNET_HashCode *h_wire,
|
||||
const struct GNUNET_HashCode *h_contract_terms,
|
||||
struct GNUNET_TIME_Absolute timestamp,
|
||||
struct GNUNET_TIME_Absolute refund_deadline,
|
||||
const struct TALER_MerchantPublicKeyP *merchant,
|
||||
const struct TALER_Amount *amount_without_fee);
|
||||
|
||||
|
||||
/**
|
||||
* Send proof that a request is invalid to client because of
|
||||
* insufficient funds. This function will create a message with all
|
||||
|
@ -1350,7 +1350,7 @@ interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
|
||||
case PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT:
|
||||
{
|
||||
unsigned int source_index;
|
||||
int ret;
|
||||
enum GNUNET_DB_QueryStatus ret;
|
||||
struct PERF_TALER_EXCHANGEDB_Data *data;
|
||||
|
||||
source_index = state->cmd[state->i].details.get_deposit.index_deposit;
|
||||
@ -1358,7 +1358,7 @@ interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
|
||||
ret = state->plugin->have_deposit (state->plugin->cls,
|
||||
state->session,
|
||||
data->data.deposit);
|
||||
GNUNET_assert (GNUNET_SYSERR != ret);
|
||||
GNUNET_assert (0 >= ret);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -2698,11 +2698,11 @@ postgres_get_reserve_history (void *cls,
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param session database connection
|
||||
* @param deposit deposit to search for
|
||||
* @return #GNUNET_YES if we know this operation,
|
||||
* #GNUNET_NO if this exact deposit is unknown to us
|
||||
* #GNUNET_SYSERR on DB error
|
||||
* @return 1 if we know this operation,
|
||||
* 0 if this exact deposit is unknown to us,
|
||||
* otherwise transaction error status
|
||||
*/
|
||||
static int
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
postgres_have_deposit (void *cls,
|
||||
struct TALER_EXCHANGEDB_Session *session,
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit)
|
||||
@ -2713,75 +2713,52 @@ postgres_have_deposit (void *cls,
|
||||
GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
|
||||
GNUNET_PQ_query_param_end
|
||||
};
|
||||
PGresult *result;
|
||||
|
||||
result = GNUNET_PQ_exec_prepared (session->conn,
|
||||
"get_deposit",
|
||||
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_NO;
|
||||
}
|
||||
struct TALER_EXCHANGEDB_Deposit deposit2;
|
||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||
TALER_PQ_result_spec_amount ("amount_with_fee",
|
||||
&deposit2.amount_with_fee),
|
||||
GNUNET_PQ_result_spec_absolute_time ("timestamp",
|
||||
&deposit2.timestamp),
|
||||
GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
|
||||
&deposit2.refund_deadline),
|
||||
GNUNET_PQ_result_spec_absolute_time ("wire_deadline",
|
||||
&deposit2.wire_deadline),
|
||||
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
|
||||
&deposit2.h_contract_terms),
|
||||
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
|
||||
&deposit2.h_wire),
|
||||
GNUNET_PQ_result_spec_end
|
||||
};
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
|
||||
"get_deposit",
|
||||
params,
|
||||
rs);
|
||||
if (0 >= qs)
|
||||
return qs;
|
||||
/* Now we check that the other information in @a deposit
|
||||
also matches, and if not report inconsistencies. */
|
||||
if ( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
|
||||
&deposit2.amount_with_fee)) ||
|
||||
(deposit->timestamp.abs_value_us !=
|
||||
deposit2.timestamp.abs_value_us) ||
|
||||
(deposit->refund_deadline.abs_value_us !=
|
||||
deposit2.refund_deadline.abs_value_us) ||
|
||||
(0 != memcmp (&deposit->h_contract_terms,
|
||||
&deposit2.h_contract_terms,
|
||||
sizeof (struct GNUNET_HashCode))) ||
|
||||
(0 != memcmp (&deposit->h_wire,
|
||||
&deposit2.h_wire,
|
||||
sizeof (struct GNUNET_HashCode))) )
|
||||
{
|
||||
struct TALER_EXCHANGEDB_Deposit deposit2;
|
||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||
TALER_PQ_result_spec_amount ("amount_with_fee",
|
||||
&deposit2.amount_with_fee),
|
||||
GNUNET_PQ_result_spec_absolute_time ("timestamp",
|
||||
&deposit2.timestamp),
|
||||
GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
|
||||
&deposit2.refund_deadline),
|
||||
GNUNET_PQ_result_spec_absolute_time ("wire_deadline",
|
||||
&deposit2.wire_deadline),
|
||||
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
|
||||
&deposit2.h_contract_terms),
|
||||
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
|
||||
&deposit2.h_wire),
|
||||
GNUNET_PQ_result_spec_end
|
||||
};
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_PQ_extract_result (result,
|
||||
rs,
|
||||
0))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
PQclear (result);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if ( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
|
||||
&deposit2.amount_with_fee)) ||
|
||||
(deposit->timestamp.abs_value_us !=
|
||||
deposit2.timestamp.abs_value_us) ||
|
||||
(deposit->refund_deadline.abs_value_us !=
|
||||
deposit2.refund_deadline.abs_value_us) ||
|
||||
(0 != memcmp (&deposit->h_contract_terms,
|
||||
&deposit2.h_contract_terms,
|
||||
sizeof (struct GNUNET_HashCode))) ||
|
||||
(0 != memcmp (&deposit->h_wire,
|
||||
&deposit2.h_wire,
|
||||
sizeof (struct GNUNET_HashCode))) )
|
||||
{
|
||||
/* Inconsistencies detected! Does not match! (We might want to
|
||||
expand the API with a 'get_deposit' function to return the
|
||||
original transaction details to be used for an error message
|
||||
in the future!) #3838 */
|
||||
PQclear (result);
|
||||
return GNUNET_NO;
|
||||
}
|
||||
/* Inconsistencies detected! Does not match! (We might want to
|
||||
expand the API with a 'get_deposit' function to return the
|
||||
original transaction details to be used for an error message
|
||||
in the future!) #3838 */
|
||||
return 0; /* Counts as if the transaction was not there */
|
||||
}
|
||||
PQclear (result);
|
||||
return GNUNET_YES;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -3267,11 +3244,9 @@ postgres_insert_deposit (void *cls,
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param session connection to the database
|
||||
* @param refund refund information to store
|
||||
* @return #GNUNET_OK on success
|
||||
* #GNUNET_NO on transient error
|
||||
* #GNUNET_SYSERR on error
|
||||
* @return query result status
|
||||
*/
|
||||
static int
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
postgres_insert_refund (void *cls,
|
||||
struct TALER_EXCHANGEDB_Session *session,
|
||||
const struct TALER_EXCHANGEDB_Refund *refund)
|
||||
@ -3289,9 +3264,9 @@ postgres_insert_refund (void *cls,
|
||||
GNUNET_assert (GNUNET_YES ==
|
||||
TALER_amount_cmp_currency (&refund->refund_amount,
|
||||
&refund->refund_fee));
|
||||
return execute_prepared_non_select (session,
|
||||
"insert_refund",
|
||||
params);
|
||||
return GNUNET_PQ_eval_prepared_non_select (session->conn,
|
||||
"insert_refund",
|
||||
params);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1772,7 +1772,7 @@ run (void *cls)
|
||||
plugin->insert_deposit (plugin->cls,
|
||||
session,
|
||||
&deposit));
|
||||
FAILIF (GNUNET_YES !=
|
||||
FAILIF (1 !=
|
||||
plugin->have_deposit (plugin->cls,
|
||||
session,
|
||||
&deposit));
|
||||
@ -1839,13 +1839,13 @@ run (void *cls)
|
||||
result = 10;
|
||||
deposit2 = deposit;
|
||||
RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */
|
||||
FAILIF (GNUNET_NO !=
|
||||
FAILIF (0 !=
|
||||
plugin->have_deposit (plugin->cls,
|
||||
session,
|
||||
&deposit2));
|
||||
deposit2.merchant_pub = deposit.merchant_pub;
|
||||
RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */
|
||||
FAILIF (GNUNET_NO !=
|
||||
FAILIF (0 !=
|
||||
plugin->have_deposit (plugin->cls,
|
||||
session,
|
||||
&deposit2));
|
||||
@ -1860,7 +1860,7 @@ run (void *cls)
|
||||
refund.rtransaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
|
||||
refund.refund_amount = deposit.amount_with_fee;
|
||||
refund.refund_fee = fee_refund;
|
||||
FAILIF (GNUNET_OK !=
|
||||
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
||||
plugin->insert_refund (plugin->cls,
|
||||
session,
|
||||
&refund));
|
||||
|
@ -1293,11 +1293,11 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param session database connection
|
||||
* @param deposit deposit to search for
|
||||
* @return #GNUNET_YES if we know this operation,
|
||||
* #GNUNET_NO if this exact deposit is unknown to us,
|
||||
* #GNUNET_SYSERR on DB error
|
||||
* @return 1 if we know this operation,
|
||||
* 0 if this exact deposit is unknown to us,
|
||||
* otherwise transaction error status
|
||||
*/
|
||||
int
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*have_deposit) (void *cls,
|
||||
struct TALER_EXCHANGEDB_Session *session,
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit);
|
||||
@ -1323,11 +1323,9 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param session connection to the database
|
||||
* @param refund refund information to store
|
||||
* @return #GNUNET_OK on success,
|
||||
* #GNUNET_NO on transient error
|
||||
* #GNUNET_SYSERR on error
|
||||
* @return query result status
|
||||
*/
|
||||
int
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*insert_refund) (void *cls,
|
||||
struct TALER_EXCHANGEDB_Session *session,
|
||||
const struct TALER_EXCHANGEDB_Refund *refund);
|
||||
|
Loading…
Reference in New Issue
Block a user