diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-12-25 13:56:33 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-12-25 13:56:40 +0100 |
commit | 87376e02eba3f5c2cf83a493446dee0c300565a4 (patch) | |
tree | 18103edb2bdf2b29a773cce2de596b06d8265abb /src/exchange/taler-exchange-httpd_recoup.c | |
parent | 2c14d338704f4574055c4b5c51d8a79dd2e22345 (diff) | |
download | exchange-87376e02eba3f5c2cf83a493446dee0c300565a4.tar.gz exchange-87376e02eba3f5c2cf83a493446dee0c300565a4.tar.bz2 exchange-87376e02eba3f5c2cf83a493446dee0c300565a4.zip |
protocol v12 changes (/recoup split, signature changes) plus database sharding plus O(n^2)=>O(n) worst-case complexity reduction on coin balance checks
Diffstat (limited to 'src/exchange/taler-exchange-httpd_recoup.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_recoup.c | 320 |
1 files changed, 83 insertions, 237 deletions
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c index 58495e530..28e81f9ec 100644 --- a/src/exchange/taler-exchange-httpd_recoup.c +++ b/src/exchange/taler-exchange-httpd_recoup.c @@ -45,9 +45,10 @@ struct RecoupContext struct TALER_BlindedCoinHash h_blind; /** - * Full value of the coin. + * Set by #recoup_transaction() to the reserve that will + * receive the recoup, if #refreshed is #GNUNET_NO. */ - struct TALER_Amount value; + struct TALER_ReservePublicKeyP reserve_pub; /** * Details about the coin. @@ -65,45 +66,29 @@ struct RecoupContext const struct TALER_CoinSpendSignatureP *coin_sig; /** - * Where does the value of the recouped coin go? Which member - * of the union is valid depends on @e refreshed. + * The amount requested to be recouped. */ - union - { - /** - * Set by #recoup_transaction() to the reserve that will - * receive the recoup, if #refreshed is #GNUNET_NO. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Set by #recoup_transaction() to the old coin that will - * receive the recoup, if #refreshed is #GNUNET_YES. - */ - struct TALER_CoinSpendPublicKeyP old_coin_pub; - } target; + const struct TALER_Amount *requested_amount; /** - * Set by #recoup_transaction() to the amount that will be paid back + * Unique ID of the withdraw operation in the reserves_out table. */ - struct TALER_Amount amount; - const struct TALER_Amount *requested_amount; + uint64_t reserve_out_serial_id; /** - * Set by #recoup_transaction to the timestamp when the recoup - * was accepted. + * Unique ID of the coin in the known_coins table. */ - struct GNUNET_TIME_Timestamp now; + uint64_t known_coin_id; /** - * true if the client claims the coin originated from a refresh. + * Set by #recoup_transaction to the timestamp when the recoup + * was accepted. */ - bool refreshed; + struct GNUNET_TIME_Timestamp now; }; -// FIXME: this code should be simplified by using TEH_check_coin_balance() /** * Execute a "recoup". The validity of the coin and signature have * already been checked. The database must now check that the coin is @@ -127,159 +112,53 @@ recoup_transaction (void *cls, MHD_RESULT *mhd_ret) { struct RecoupContext *pc = cls; - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_Amount spent; - struct TALER_Amount recouped; enum GNUNET_DB_QueryStatus qs; - bool existing_recoup_found; + bool recoup_ok; + bool internal_failure; - /* Check whether a recoup is allowed, and if so, to which - reserve / account the money should go */ - - /* Calculate remaining balance, including recoups already applied. */ - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - &pc->coin->coin_pub, - GNUNET_YES, - &tl); + /* Finally, store new refund data */ + pc->now = GNUNET_TIME_timestamp_get (); + qs = TEH_plugin->do_recoup (TEH_plugin->cls, + &pc->reserve_pub, + pc->reserve_out_serial_id, + pc->requested_amount, + pc->coin_bks, + &pc->coin->coin_pub, + pc->known_coin_id, + pc->coin_sig, + &pc->now, + &recoup_ok, + &internal_failure); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "coin transaction list"); - } + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "do_recoup"); return qs; } - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (pc->value.currency, - &spent)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (pc->value.currency, - &recouped)); - /* Check if this coin has been recouped already at least once */ - existing_recoup_found = false; - for (struct TALER_EXCHANGEDB_TransactionList *pos = tl; - NULL != pos; - pos = pos->next) - { - if ( (TALER_EXCHANGEDB_TT_RECOUP == pos->type) || - (TALER_EXCHANGEDB_TT_RECOUP_REFRESH == pos->type) ) - { - existing_recoup_found = true; - break; - } - } - if (GNUNET_OK != - TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, - &spent, - &spent)) + if (internal_failure) { GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_INVARIANT_FAILURE, - "coin transaction history"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Recoup: calculated spent %s\n", - TALER_amount2s (&spent)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Recoup: coin value %s\n", - TALER_amount2s (&pc->value)); - if (0 > - TALER_amount_subtract (&pc->amount, - &pc->value, - &spent)) - { - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_NEGATIVE, - NULL); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + "do_recoup"); return GNUNET_DB_STATUS_HARD_ERROR; } - if (TALER_amount_is_zero (&pc->amount)) + if (! recoup_ok) { - /* Recoup has no effect: coin fully spent! */ - enum GNUNET_DB_QueryStatus ret; - - TEH_plugin->rollback (TEH_plugin->cls); - if (GNUNET_NO == existing_recoup_found) - { - /* Refuse: insufficient funds for recoup */ - *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection, - TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO, - &pc->coin->coin_pub, - tl); - ret = GNUNET_DB_STATUS_HARD_ERROR; - } - else - { - /* We didn't add any new recoup transaction, but there was at least - one recoup before, so we give a success response (idempotency!) */ - ret = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return ret; - } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - pc->now = GNUNET_TIME_timestamp_get (); - if (0 != TALER_amount_cmp (&pc->amount, - pc->requested_amount)) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, - TALER_amount2s (&pc->amount)); + *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, + &pc->coin->coin_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - - /* add coin to list of wire transfers for recoup */ - if (pc->refreshed) - { - qs = TEH_plugin->insert_recoup_refresh_request (TEH_plugin->cls, - pc->coin, - pc->coin_sig, - pc->coin_bks, - &pc->amount, - &pc->h_blind, - pc->now); - } - else - { - qs = TEH_plugin->insert_recoup_request (TEH_plugin->cls, - &pc->target.reserve_pub, - pc->coin, - pc->coin_sig, - pc->coin_bks, - &pc->amount, - &pc->h_blind, - pc->now); - } - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - TALER_LOG_WARNING ("Failed to store recoup information in database\n"); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "recoup request"); - } - return qs; - } - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + return qs; } @@ -295,7 +174,6 @@ recoup_transaction (void *cls, * @param coin_bks blinding data of the coin (to be checked) * @param coin_sig signature of the coin * @param requested_amount requested amount to be recouped - * @param refreshed true if the coin was refreshed * @return MHD result code */ static MHD_RESULT @@ -304,12 +182,10 @@ verify_and_execute_recoup ( const struct TALER_CoinPublicInfo *coin, const union TALER_DenominationBlindingKeyP *coin_bks, const struct TALER_CoinSpendSignatureP *coin_sig, - const struct TALER_Amount *requested_amount, - bool refreshed) + const struct TALER_Amount *requested_amount) { struct RecoupContext pc; const struct TEH_DenominationKey *dk; - struct TALER_CoinPubHash c_hash; MHD_RESULT mret; /* check denomination exists and is in recoup mode */ @@ -324,7 +200,6 @@ verify_and_execute_recoup ( return TEH_RESPONSE_reply_expired_denom_pub_hash ( connection, &coin->denom_pub_hash, - GNUNET_TIME_timestamp_get (), TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, "RECOUP"); } @@ -334,7 +209,6 @@ verify_and_execute_recoup ( return TEH_RESPONSE_reply_expired_denom_pub_hash ( connection, &coin->denom_pub_hash, - GNUNET_TIME_timestamp_get (), TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, "RECOUP"); } @@ -344,23 +218,21 @@ verify_and_execute_recoup ( return TEH_RESPONSE_reply_expired_denom_pub_hash ( connection, &coin->denom_pub_hash, - GNUNET_TIME_timestamp_get (), TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE, "RECOUP"); } - pc.value = dk->meta.value; - /* check denomination signature */ if (GNUNET_YES != TALER_test_coin_valid (coin, &dk->denom_pub)) { - TALER_LOG_WARNING ("Invalid coin passed for recoup\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, - NULL); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, + NULL); } /* check recoup request signature */ @@ -372,15 +244,17 @@ verify_and_execute_recoup ( coin_sig)) { GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID, + NULL); } { void *coin_ev; size_t coin_ev_size; + struct TALER_CoinPubHash c_hash; if (GNUNET_OK != TALER_denom_blind (&dk->denom_pub, @@ -392,10 +266,11 @@ verify_and_execute_recoup ( &coin_ev_size)) { GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED, + NULL); } TALER_coin_ev_hash (coin_ev, coin_ev_size, @@ -406,7 +281,6 @@ verify_and_execute_recoup ( pc.coin_sig = coin_sig; pc.coin_bks = coin_bks; pc.coin = coin; - pc.refreshed = refreshed; pc.requested_amount = requested_amount; { @@ -416,6 +290,7 @@ verify_and_execute_recoup ( /* make sure coin is 'known' in database */ qs = TEH_make_coin_known (coin, connection, + &pc.known_coin_id, &mhd_ret); /* no transaction => no serialization failures should be possible */ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); @@ -426,35 +301,18 @@ verify_and_execute_recoup ( { enum GNUNET_DB_QueryStatus qs; - if (pc.refreshed) - { - qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls, - &pc.h_blind, - &pc.target.old_coin_pub); - if (0 > qs) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "old coin by h_blind"); - } - } - else + qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls, + &pc.h_blind, + &pc.reserve_pub, + &pc.reserve_out_serial_id); + if (0 > qs) { - qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls, - &pc.h_blind, - &pc.target.reserve_pub); - if (0 > qs) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserve by h_blind"); - } + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_by_h_blind"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { @@ -483,21 +341,11 @@ verify_and_execute_recoup ( return mhd_ret; } /* Recoup succeeded, return result */ - return (refreshed) - ? TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ( - "old_coin_pub", - &pc.target.old_coin_pub), - GNUNET_JSON_pack_bool ("refreshed", - true)) - : TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ( - "reserve_pub", - &pc.target.reserve_pub), - GNUNET_JSON_pack_bool ("refreshed", - false)); + return TALER_MHD_REPLY_JSON_PACK (connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ( + "reserve_pub", + &pc.reserve_pub)); } @@ -522,7 +370,6 @@ TEH_handler_recoup (struct MHD_Connection *connection, union TALER_DenominationBlindingKeyP coin_bks; struct TALER_CoinSpendSignatureP coin_sig; struct TALER_Amount amount; - bool refreshed = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", &coin.denom_pub_hash), @@ -535,12 +382,12 @@ TEH_handler_recoup (struct MHD_Connection *connection, TALER_JSON_spec_amount ("amount", TEH_currency, &amount), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("refreshed", - &refreshed)), GNUNET_JSON_spec_end () }; + memset (&coin, + 0, + sizeof (coin)); coin.coin_pub = *coin_pub; ret = TALER_MHD_parse_json_data (connection, root, @@ -556,8 +403,7 @@ TEH_handler_recoup (struct MHD_Connection *connection, &coin, &coin_bks, &coin_sig, - &amount, - refreshed); + &amount); GNUNET_JSON_parse_free (spec); return res; } |