retry transactions on serialization/dead-lock failures (#3990)
This commit is contained in:
parent
70c28e53d0
commit
93a84d5e5a
@ -629,7 +629,8 @@ struct TALER_MINTDB_Plugin
|
|||||||
*
|
*
|
||||||
* @param cls the @e cls of this struct with the plugin-specific state
|
* @param cls the @e cls of this struct with the plugin-specific state
|
||||||
* @param session connection to use
|
* @param session connection to use
|
||||||
* @return #GNUNET_OK on success
|
* @return #GNUNET_OK on success, #GNUNET_NO if the transaction
|
||||||
|
* can be retried, #GNUNET_SYSERR on hard failures
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
(*commit) (void *cls,
|
(*commit) (void *cls,
|
||||||
|
@ -24,6 +24,67 @@
|
|||||||
#include "taler-mint-httpd_responses.h"
|
#include "taler-mint-httpd_responses.h"
|
||||||
#include "taler-mint-httpd_keystate.h"
|
#include "taler-mint-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.
|
* Calculate the total value of all transactions performed.
|
||||||
@ -130,13 +191,8 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection,
|
|||||||
&dki->issue.properties.value);
|
&dki->issue.properties.value);
|
||||||
TMH_KS_release (mks);
|
TMH_KS_release (mks);
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
START_TRANSACTION (session, connection);
|
||||||
TMH_plugin->start (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
/* fee for THIS transaction */
|
/* fee for THIS transaction */
|
||||||
spent = deposit->amount_with_fee;
|
spent = deposit->amount_with_fee;
|
||||||
/* add cost of all previous transactions */
|
/* add cost of all previous transactions */
|
||||||
@ -179,13 +235,7 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection,
|
|||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
return TMH_RESPONSE_reply_internal_db_error (connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
COMMIT_TRANSACTION(session, connection);
|
||||||
TMH_plugin->commit (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
TALER_LOG_WARNING ("/deposit transaction commit failed\n");
|
|
||||||
return TMH_RESPONSE_reply_commit_error (connection);
|
|
||||||
}
|
|
||||||
GNUNET_assert (GNUNET_OK ==
|
GNUNET_assert (GNUNET_OK ==
|
||||||
TALER_amount_subtract (&amount_without_fee,
|
TALER_amount_subtract (&amount_without_fee,
|
||||||
&deposit->amount_with_fee,
|
&deposit->amount_with_fee,
|
||||||
@ -241,6 +291,187 @@ TMH_DB_execute_reserve_status (struct MHD_Connection *connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to execute /reserve/withdraw transaction.
|
||||||
|
*
|
||||||
|
* @param connection request we are handling
|
||||||
|
* @param session database session we are using
|
||||||
|
* @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 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_MINTDB_Session *session,
|
||||||
|
struct TMH_KS_StateHandle *key_state,
|
||||||
|
const struct TALER_ReservePublicKeyP *reserve,
|
||||||
|
const struct TALER_DenominationPublicKey *denomination_pub,
|
||||||
|
const struct TALER_MINTDB_DenominationKeyIssueInformation *dki,
|
||||||
|
const char *blinded_msg,
|
||||||
|
size_t blinded_msg_len,
|
||||||
|
const struct TALER_ReserveSignatureP *signature,
|
||||||
|
struct TALER_DenominationSignature *denom_sig)
|
||||||
|
{
|
||||||
|
struct TALER_MINTDB_ReserveHistory *rh;
|
||||||
|
const struct TALER_MINTDB_ReserveHistory *pos;
|
||||||
|
struct TALER_MINTDB_DenominationKeyIssueInformation *tdki;
|
||||||
|
struct TALER_MINTDB_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;
|
||||||
|
struct GNUNET_HashCode h_blind;
|
||||||
|
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_MINTDB_RO_BANK_TO_MINT:
|
||||||
|
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_MINTDB_RO_WITHDRAW_COIN:
|
||||||
|
tdki = TMH_KS_denomination_key_lookup (key_state,
|
||||||
|
&pos->details.withdraw->denom_pub,
|
||||||
|
TMH_KS_DKU_WITHDRAW);
|
||||||
|
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
|
* Execute a "/reserve/withdraw". Given a reserve and a properly signed
|
||||||
* request to withdraw a coin, check the balance of the reserve and
|
* request to withdraw a coin, check the balance of the reserve and
|
||||||
@ -264,19 +495,10 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection,
|
|||||||
const struct TALER_ReserveSignatureP *signature)
|
const struct TALER_ReserveSignatureP *signature)
|
||||||
{
|
{
|
||||||
struct TALER_MINTDB_Session *session;
|
struct TALER_MINTDB_Session *session;
|
||||||
struct TALER_MINTDB_ReserveHistory *rh;
|
|
||||||
const struct TALER_MINTDB_ReserveHistory *pos;
|
|
||||||
struct TMH_KS_StateHandle *key_state;
|
struct TMH_KS_StateHandle *key_state;
|
||||||
struct TALER_MINTDB_CollectableBlindcoin collectable;
|
|
||||||
struct TALER_MINTDB_DenominationKeyIssueInformation *dki;
|
struct TALER_MINTDB_DenominationKeyIssueInformation *dki;
|
||||||
struct TALER_MINTDB_DenominationKeyIssueInformation *tdki;
|
struct TALER_MINTDB_CollectableBlindcoin collectable;
|
||||||
struct GNUNET_CRYPTO_rsa_Signature *sig;
|
struct TALER_DenominationSignature denom_sig;
|
||||||
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;
|
|
||||||
struct GNUNET_HashCode h_blind;
|
struct GNUNET_HashCode h_blind;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
@ -304,14 +526,13 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection,
|
|||||||
if (GNUNET_YES == res)
|
if (GNUNET_YES == res)
|
||||||
{
|
{
|
||||||
res = TMH_RESPONSE_reply_reserve_withdraw_success (connection,
|
res = TMH_RESPONSE_reply_reserve_withdraw_success (connection,
|
||||||
&collectable);
|
&collectable);
|
||||||
GNUNET_CRYPTO_rsa_signature_free (collectable.sig.rsa_signature);
|
GNUNET_CRYPTO_rsa_signature_free (collectable.sig.rsa_signature);
|
||||||
GNUNET_CRYPTO_rsa_public_key_free (collectable.denom_pub.rsa_public_key);
|
GNUNET_CRYPTO_rsa_public_key_free (collectable.denom_pub.rsa_public_key);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
GNUNET_assert (GNUNET_NO == res);
|
GNUNET_assert (GNUNET_NO == res);
|
||||||
|
|
||||||
/* Check if balance is sufficient */
|
|
||||||
key_state = TMH_KS_acquire ();
|
key_state = TMH_KS_acquire ();
|
||||||
dki = TMH_KS_denomination_key_lookup (key_state,
|
dki = TMH_KS_denomination_key_lookup (key_state,
|
||||||
denomination_pub,
|
denomination_pub,
|
||||||
@ -325,162 +546,19 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection,
|
|||||||
"error",
|
"error",
|
||||||
"Denomination not found");
|
"Denomination not found");
|
||||||
}
|
}
|
||||||
if (GNUNET_OK !=
|
denom_sig.rsa_signature = NULL;
|
||||||
TMH_plugin->start (TMH_plugin->cls,
|
res = execute_reserve_withdraw_transaction (connection,
|
||||||
session))
|
session,
|
||||||
{
|
key_state,
|
||||||
GNUNET_break (0);
|
reserve,
|
||||||
TMH_KS_release (key_state);
|
denomination_pub,
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
dki,
|
||||||
}
|
blinded_msg,
|
||||||
|
blinded_msg_len,
|
||||||
rh = TMH_plugin->get_reserve_history (TMH_plugin->cls,
|
signature,
|
||||||
session,
|
&denom_sig);
|
||||||
reserve);
|
GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature);
|
||||||
if (NULL == rh)
|
|
||||||
{
|
|
||||||
TMH_plugin->rollback (TMH_plugin->cls,
|
|
||||||
session);
|
|
||||||
TMH_KS_release (key_state);
|
|
||||||
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);
|
|
||||||
TMH_KS_release (key_state);
|
|
||||||
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_MINTDB_RO_BANK_TO_MINT:
|
|
||||||
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);
|
|
||||||
TMH_KS_release (key_state);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
res |= 1;
|
|
||||||
break;
|
|
||||||
case TALER_MINTDB_RO_WITHDRAW_COIN:
|
|
||||||
tdki = TMH_KS_denomination_key_lookup (key_state,
|
|
||||||
&pos->details.withdraw->denom_pub,
|
|
||||||
TMH_KS_DKU_WITHDRAW);
|
|
||||||
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);
|
|
||||||
TMH_KS_release (key_state);
|
|
||||||
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_KS_release (key_state);
|
|
||||||
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! */
|
|
||||||
sig = GNUNET_CRYPTO_rsa_sign (dki->denom_priv.rsa_private_key,
|
|
||||||
blinded_msg,
|
|
||||||
blinded_msg_len);
|
|
||||||
TMH_KS_release (key_state);
|
TMH_KS_release (key_state);
|
||||||
if (NULL == sig)
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
TMH_plugin->rollback (TMH_plugin->cls,
|
|
||||||
session);
|
|
||||||
return TMH_RESPONSE_reply_internal_error (connection,
|
|
||||||
"Internal error");
|
|
||||||
}
|
|
||||||
collectable.sig.rsa_signature = 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);
|
|
||||||
GNUNET_CRYPTO_rsa_signature_free (sig);
|
|
||||||
TMH_plugin->rollback (TMH_plugin->cls,
|
|
||||||
session);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
TMH_plugin->commit (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
TALER_LOG_WARNING ("/reserve/withdraw transaction commit failed\n");
|
|
||||||
return TMH_RESPONSE_reply_commit_error (connection);
|
|
||||||
}
|
|
||||||
res = TMH_RESPONSE_reply_reserve_withdraw_success (connection,
|
|
||||||
&collectable);
|
|
||||||
GNUNET_CRYPTO_rsa_signature_free (sig);
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,13 +711,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection,
|
|||||||
GNUNET_break (0);
|
GNUNET_break (0);
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
return TMH_RESPONSE_reply_internal_db_error (connection);
|
||||||
}
|
}
|
||||||
if (GNUNET_OK !=
|
START_TRANSACTION (session, connection);
|
||||||
TMH_plugin->start (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
res = TMH_plugin->get_refresh_session (TMH_plugin->cls,
|
res = TMH_plugin->get_refresh_session (TMH_plugin->cls,
|
||||||
session,
|
session,
|
||||||
session_hash,
|
session_hash,
|
||||||
@ -741,13 +813,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
COMMIT_TRANSACTION (session, connection);
|
||||||
TMH_plugin->commit (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
TALER_LOG_WARNING ("/refresh/melt transaction commit failed\n");
|
|
||||||
return TMH_RESPONSE_reply_commit_error (connection);
|
|
||||||
}
|
|
||||||
return TMH_RESPONSE_reply_refresh_melt_success (connection,
|
return TMH_RESPONSE_reply_refresh_melt_success (connection,
|
||||||
session_hash,
|
session_hash,
|
||||||
refresh_session.noreveal_index);
|
refresh_session.noreveal_index);
|
||||||
@ -1038,10 +1104,10 @@ refresh_mint_coin (struct MHD_Connection *connection,
|
|||||||
}
|
}
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
TMH_plugin->insert_refresh_out (TMH_plugin->cls,
|
TMH_plugin->insert_refresh_out (TMH_plugin->cls,
|
||||||
session,
|
session,
|
||||||
session_hash,
|
session_hash,
|
||||||
coin_off,
|
coin_off,
|
||||||
&ev_sig))
|
&ev_sig))
|
||||||
{
|
{
|
||||||
GNUNET_break (0);
|
GNUNET_break (0);
|
||||||
GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature);
|
GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature);
|
||||||
@ -1051,6 +1117,74 @@ refresh_mint_coin (struct MHD_Connection *connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 denom_pubs array of "num_newcoins" denomination keys for the new coins
|
||||||
|
* @param ev_sigs[out] where to store generated signatures for the new coins,
|
||||||
|
* array of length "num_newcoins", memory released by the
|
||||||
|
* caller
|
||||||
|
* @param commit_coins[out] 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_MINTDB_Session *session,
|
||||||
|
const struct GNUNET_HashCode *session_hash,
|
||||||
|
struct TALER_MINTDB_RefreshSession *refresh_session,
|
||||||
|
struct TALER_MINTDB_RefreshMelt *melts,
|
||||||
|
struct TALER_DenominationPublicKey *denom_pubs,
|
||||||
|
struct TALER_DenominationSignature *ev_sigs,
|
||||||
|
struct TALER_MINTDB_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_mint_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
|
* 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
|
* transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the
|
||||||
@ -1074,7 +1208,6 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection,
|
|||||||
int res;
|
int res;
|
||||||
struct TALER_MINTDB_Session *session;
|
struct TALER_MINTDB_Session *session;
|
||||||
struct TALER_MINTDB_RefreshSession refresh_session;
|
struct TALER_MINTDB_RefreshSession refresh_session;
|
||||||
struct TMH_KS_StateHandle *key_state;
|
|
||||||
struct TALER_MINTDB_RefreshMelt *melts;
|
struct TALER_MINTDB_RefreshMelt *melts;
|
||||||
struct TALER_DenominationPublicKey *denom_pubs;
|
struct TALER_DenominationPublicKey *denom_pubs;
|
||||||
struct TALER_DenominationSignature *ev_sigs;
|
struct TALER_DenominationSignature *ev_sigs;
|
||||||
@ -1164,82 +1297,27 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection,
|
|||||||
GNUNET_free (melts);
|
GNUNET_free (melts);
|
||||||
|
|
||||||
/* Client request OK, start transaction */
|
/* Client request OK, start transaction */
|
||||||
if (GNUNET_OK !=
|
|
||||||
TMH_plugin->start (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
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);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
commit_coins = GNUNET_malloc (refresh_session.num_newcoins *
|
commit_coins = GNUNET_malloc (refresh_session.num_newcoins *
|
||||||
sizeof (struct TALER_MINTDB_RefreshCommitCoin));
|
sizeof (struct TALER_MINTDB_RefreshCommitCoin));
|
||||||
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);
|
|
||||||
GNUNET_free (commit_coins);
|
|
||||||
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);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
ev_sigs = GNUNET_malloc (refresh_session.num_newcoins *
|
ev_sigs = GNUNET_malloc (refresh_session.num_newcoins *
|
||||||
sizeof (struct TALER_DenominationSignature));
|
sizeof (struct TALER_DenominationSignature));
|
||||||
key_state = TMH_KS_acquire ();
|
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++)
|
for (j=0;j<refresh_session.num_newcoins;j++)
|
||||||
{
|
if (NULL != denom_pubs[j].rsa_public_key)
|
||||||
ev_sigs[j] = refresh_mint_coin (connection,
|
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
|
||||||
session,
|
GNUNET_free (ev_sigs);
|
||||||
session_hash,
|
|
||||||
key_state,
|
|
||||||
&denom_pubs[j],
|
|
||||||
&commit_coins[j],
|
|
||||||
j);
|
|
||||||
if (NULL == ev_sigs[j].rsa_signature)
|
|
||||||
{
|
|
||||||
TMH_KS_release (key_state);
|
|
||||||
for (i=0;i<j;i++)
|
|
||||||
GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature);
|
|
||||||
GNUNET_free (ev_sigs);
|
|
||||||
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);
|
|
||||||
GNUNET_free (commit_coins);
|
|
||||||
return TMH_RESPONSE_reply_internal_db_error (connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TMH_KS_release (key_state);
|
|
||||||
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);
|
GNUNET_free (denom_pubs);
|
||||||
GNUNET_free (commit_coins);
|
GNUNET_free (commit_coins);
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
TMH_plugin->commit (TMH_plugin->cls,
|
|
||||||
session))
|
|
||||||
{
|
|
||||||
TALER_LOG_WARNING ("/refresh/reveal transaction commit failed\n");
|
|
||||||
for (i=0;i<refresh_session.num_newcoins;i++)
|
|
||||||
GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature);
|
|
||||||
GNUNET_free (ev_sigs);
|
|
||||||
return TMH_RESPONSE_reply_commit_error (connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = TMH_RESPONSE_reply_refresh_reveal_success (connection,
|
|
||||||
refresh_session.num_newcoins,
|
|
||||||
ev_sigs);
|
|
||||||
for (i=0;i<refresh_session.num_newcoins;i++)
|
|
||||||
GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature);
|
|
||||||
GNUNET_free (ev_sigs);
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
src/mint/test_taler_mint_httpd_afl.sh
Executable file
33
src/mint/test_taler_mint_httpd_afl.sh
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# This file is part of TALER
|
||||||
|
# Copyright (C) 2015 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
|
||||||
|
# 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with
|
||||||
|
# TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This script uses 'curl' to POST various ill-formed requests to the
|
||||||
|
# taler-mint-httpd. Basically, the goal is to make sure that the
|
||||||
|
# HTTP server survives (and produces the 'correct' error code).
|
||||||
|
#
|
||||||
|
# We read the JSON snippets from afl-tests/
|
||||||
|
#
|
||||||
|
# Setup keys.
|
||||||
|
taler-mint-keyup -d test-mint-home -m test-mint-home/master.priv
|
||||||
|
# Run test...
|
||||||
|
for n in afl-tests/*.req
|
||||||
|
do
|
||||||
|
echo -n "Test $n"
|
||||||
|
taler-mint-httpd -d test-mint-home/ -t 1 -f $n -C > /dev/null || { echo "FAIL!"; exit 1; }
|
||||||
|
echo "OK"
|
||||||
|
done
|
||||||
|
exit 0
|
@ -1095,7 +1095,31 @@ postgres_commit (void *cls,
|
|||||||
if (PGRES_COMMAND_OK !=
|
if (PGRES_COMMAND_OK !=
|
||||||
PQresultStatus (result))
|
PQresultStatus (result))
|
||||||
{
|
{
|
||||||
GNUNET_break (0);
|
const char *sqlstate;
|
||||||
|
|
||||||
|
sqlstate = PQresultErrorField (result,
|
||||||
|
PG_DIAG_SQLSTATE);
|
||||||
|
if (NULL == sqlstate)
|
||||||
|
{
|
||||||
|
/* very unexpected... */
|
||||||
|
GNUNET_break (0);
|
||||||
|
PQclear (result);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
/* 40P01: deadlock, 40001: serialization failure */
|
||||||
|
if ( (0 == strcmp (sqlstate,
|
||||||
|
"40P01")) ||
|
||||||
|
(0 == strcmp (sqlstate,
|
||||||
|
"40001")) )
|
||||||
|
{
|
||||||
|
/* These two can be retried and have a fair chance of working
|
||||||
|
the next time */
|
||||||
|
PQclear (result);
|
||||||
|
return GNUNET_NO;
|
||||||
|
}
|
||||||
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||||
|
"Database commit failure: %s\n",
|
||||||
|
sqlstate);
|
||||||
PQclear (result);
|
PQclear (result);
|
||||||
return GNUNET_SYSERR;
|
return GNUNET_SYSERR;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user