diff options
author | Christian Grothoff <christian@grothoff.org> | 2015-09-21 14:36:18 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2015-09-21 14:36:18 +0200 |
commit | 93a84d5e5aee9df869ac9e78a88c294d6311a9ef (patch) | |
tree | 060813cef751493abfa6c6417ed15031aec4842b /src/mint/taler-mint-httpd_db.c | |
parent | 70c28e53d0615bc6694ea2339a739d387f8d8691 (diff) |
retry transactions on serialization/dead-lock failures (#3990)
Diffstat (limited to 'src/mint/taler-mint-httpd_db.c')
-rw-r--r-- | src/mint/taler-mint-httpd_db.c | 466 |
1 files changed, 272 insertions, 194 deletions
diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 021a3155d..da656abc2 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -24,6 +24,67 @@ #include "taler-mint-httpd_responses.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. @@ -130,13 +191,8 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection, &dki->issue.properties.value); TMH_KS_release (mks); - if (GNUNET_OK != - TMH_plugin->start (TMH_plugin->cls, - session)) - { - GNUNET_break (0); - return TMH_RESPONSE_reply_internal_db_error (connection); - } + START_TRANSACTION (session, connection); + /* fee for THIS transaction */ spent = deposit->amount_with_fee; /* 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); } - if (GNUNET_OK != - TMH_plugin->commit (TMH_plugin->cls, - session)) - { - TALER_LOG_WARNING ("/deposit transaction commit failed\n"); - return TMH_RESPONSE_reply_commit_error (connection); - } + COMMIT_TRANSACTION(session, connection); GNUNET_assert (GNUNET_OK == TALER_amount_subtract (&amount_without_fee, &deposit->amount_with_fee, @@ -242,35 +292,36 @@ TMH_DB_execute_reserve_status (struct MHD_Connection *connection, /** - * Execute a "/reserve/withdraw". Given a reserve and a properly signed - * request to withdraw a coin, check the balance of the reserve and - * if it is sufficient, store the request and return the signed - * blinded envelope. + * Try to execute /reserve/withdraw transaction. * - * @param connection the MHD connection to handle - * @param reserve public key of the reserve + * @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 */ -int -TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, - const struct TALER_ReservePublicKeyP *reserve, - const struct TALER_DenominationPublicKey *denomination_pub, - const char *blinded_msg, - size_t blinded_msg_len, - const struct TALER_ReserveSignatureP *signature) +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_Session *session; struct TALER_MINTDB_ReserveHistory *rh; const struct TALER_MINTDB_ReserveHistory *pos; - struct TMH_KS_StateHandle *key_state; - struct TALER_MINTDB_CollectableBlindcoin collectable; - struct TALER_MINTDB_DenominationKeyIssueInformation *dki; struct TALER_MINTDB_DenominationKeyIssueInformation *tdki; - struct GNUNET_CRYPTO_rsa_Signature *sig; + struct TALER_MINTDB_CollectableBlindcoin collectable; struct TALER_Amount amount_required; struct TALER_Amount deposit_total; struct TALER_Amount withdraw_total; @@ -280,60 +331,8 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, struct GNUNET_HashCode h_blind; int res; - GNUNET_CRYPTO_hash (blinded_msg, - blinded_msg_len, - &h_blind); - - if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, - TMH_test_mode))) - { - GNUNET_break (0); - return TMH_RESPONSE_reply_internal_db_error (connection); - } - res = TMH_plugin->get_withdraw_info (TMH_plugin->cls, - session, - &h_blind, - &collectable); - if (GNUNET_SYSERR == res) - { - GNUNET_break (0); - return TMH_RESPONSE_reply_internal_db_error (connection); - } - - /* Don't sign again if we have already signed the coin */ - if (GNUNET_YES == res) - { - res = TMH_RESPONSE_reply_reserve_withdraw_success (connection, - &collectable); - GNUNET_CRYPTO_rsa_signature_free (collectable.sig.rsa_signature); - GNUNET_CRYPTO_rsa_public_key_free (collectable.denom_pub.rsa_public_key); - return res; - } - GNUNET_assert (GNUNET_NO == res); - /* Check if balance is sufficient */ - key_state = TMH_KS_acquire (); - dki = TMH_KS_denomination_key_lookup (key_state, - denomination_pub, - TMH_KS_DKU_WITHDRAW); - if (NULL == dki) - { - TMH_KS_release (key_state); - return TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_NOT_FOUND, - "{s:s}", - "error", - "Denomination not found"); - } - if (GNUNET_OK != - TMH_plugin->start (TMH_plugin->cls, - session)) - { - GNUNET_break (0); - TMH_KS_release (key_state); - return TMH_RESPONSE_reply_internal_db_error (connection); - } - + START_TRANSACTION (session, connection); rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, session, reserve); @@ -341,7 +340,6 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, { TMH_plugin->rollback (TMH_plugin->cls, session); - TMH_KS_release (key_state); return TMH_RESPONSE_reply_arg_unknown (connection, "reserve_pub"); } @@ -359,7 +357,6 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, { TMH_plugin->rollback (TMH_plugin->cls, session); - TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -380,7 +377,6 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, { TMH_plugin->rollback (TMH_plugin->cls, session); - TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_db_error (connection); } res |= 1; @@ -401,7 +397,6 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, { TMH_plugin->rollback (TMH_plugin->cls, session); - TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_db_error (connection); } res |= 2; @@ -428,11 +423,10 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, 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); + rh); TMH_plugin->free_reserve_history (TMH_plugin->cls, rh); return res; @@ -441,11 +435,11 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, 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); - if (NULL == sig) + 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, @@ -453,7 +447,7 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, return TMH_RESPONSE_reply_internal_error (connection, "Internal error"); } - collectable.sig.rsa_signature = sig; + collectable.sig = *denom_sig; collectable.denom_pub = *denomination_pub; collectable.amount_with_fee = amount_required; collectable.withdraw_fee = fee_withdraw; @@ -466,21 +460,105 @@ TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, &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)) + COMMIT_TRANSACTION (session, connection); + + return TMH_RESPONSE_reply_reserve_withdraw_success (connection, + &collectable); +} + + + +/** + * Execute a "/reserve/withdraw". Given a reserve and a properly signed + * request to withdraw a coin, check the balance of the reserve and + * if it is sufficient, store the request and return the signed + * blinded envelope. + * + * @param connection the MHD connection to handle + * @param reserve public key of the reserve + * @param denomination_pub public key of the denomination requested + * @param blinded_msg blinded message to be signed + * @param blinded_msg_len number of bytes in @a blinded_msg + * @param signature signature over the withdraw request, to be stored in DB + * @return MHD result code + */ +int +TMH_DB_execute_reserve_withdraw (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve, + const struct TALER_DenominationPublicKey *denomination_pub, + const char *blinded_msg, + size_t blinded_msg_len, + const struct TALER_ReserveSignatureP *signature) +{ + struct TALER_MINTDB_Session *session; + struct TMH_KS_StateHandle *key_state; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TALER_MINTDB_CollectableBlindcoin collectable; + struct TALER_DenominationSignature denom_sig; + struct GNUNET_HashCode h_blind; + int res; + + GNUNET_CRYPTO_hash (blinded_msg, + blinded_msg_len, + &h_blind); + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res = TMH_plugin->get_withdraw_info (TMH_plugin->cls, + session, + &h_blind, + &collectable); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* Don't sign again if we have already signed the coin */ + if (GNUNET_YES == res) { - 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 (collectable.sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (collectable.denom_pub.rsa_public_key); + return res; } - res = TMH_RESPONSE_reply_reserve_withdraw_success (connection, - &collectable); - GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_assert (GNUNET_NO == res); + + key_state = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (key_state, + denomination_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "Denomination not found"); + } + denom_sig.rsa_signature = NULL; + res = execute_reserve_withdraw_transaction (connection, + session, + key_state, + reserve, + denomination_pub, + dki, + blinded_msg, + blinded_msg_len, + signature, + &denom_sig); + GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature); + TMH_KS_release (key_state); return res; } @@ -633,13 +711,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } - if (GNUNET_OK != - TMH_plugin->start (TMH_plugin->cls, - session)) - { - GNUNET_break (0); - return TMH_RESPONSE_reply_internal_db_error (connection); - } + START_TRANSACTION (session, connection); res = TMH_plugin->get_refresh_session (TMH_plugin->cls, session, session_hash, @@ -741,13 +813,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, } } - if (GNUNET_OK != - TMH_plugin->commit (TMH_plugin->cls, - session)) - { - TALER_LOG_WARNING ("/refresh/melt transaction commit failed\n"); - return TMH_RESPONSE_reply_commit_error (connection); - } + COMMIT_TRANSACTION (session, connection); return TMH_RESPONSE_reply_refresh_melt_success (connection, session_hash, refresh_session.noreveal_index); @@ -1038,10 +1104,10 @@ refresh_mint_coin (struct MHD_Connection *connection, } if (GNUNET_OK != TMH_plugin->insert_refresh_out (TMH_plugin->cls, - session, - session_hash, - coin_off, - &ev_sig)) + session, + session_hash, + coin_off, + &ev_sig)) { GNUNET_break (0); GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); @@ -1052,6 +1118,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 * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the * revealed transfer keys would allow linkage to the blinded coins, @@ -1074,7 +1208,6 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, int res; struct TALER_MINTDB_Session *session; struct TALER_MINTDB_RefreshSession refresh_session; - struct TMH_KS_StateHandle *key_state; struct TALER_MINTDB_RefreshMelt *melts; struct TALER_DenominationPublicKey *denom_pubs; struct TALER_DenominationSignature *ev_sigs; @@ -1164,82 +1297,27 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, GNUNET_free (melts); /* 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 * 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 * sizeof (struct TALER_DenominationSignature)); - key_state = TMH_KS_acquire (); - for (j=0;j<refresh_session.num_newcoins;j++) - { - 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); - 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 (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); + res = execute_refresh_reveal_transaction (connection, + session, + session_hash, + &refresh_session, + melts, + denom_pubs, + ev_sigs, + commit_coins); for (i=0;i<refresh_session.num_newcoins;i++) + if (NULL != ev_sigs[i].rsa_signature) GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature); + for (j=0;j<refresh_session.num_newcoins;j++) + if (NULL != denom_pubs[j].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); GNUNET_free (ev_sigs); + GNUNET_free (denom_pubs); + GNUNET_free (commit_coins); return res; } |