diff options
author | Christian Grothoff <christian@grothoff.org> | 2022-07-02 23:00:07 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2022-07-02 23:00:07 +0200 |
commit | 09a8501e94ac33051299631022ae9e519ee58a99 (patch) | |
tree | 32256371f3a27b0a97cc2a00d0af7dc71e2848dc /src | |
parent | 4e73e59e1cc8d0934c651bba7c2b99f8ac5dc92b (diff) | |
download | merchant-09a8501e94ac33051299631022ae9e519ee58a99.tar.gz merchant-09a8501e94ac33051299631022ae9e519ee58a99.tar.bz2 merchant-09a8501e94ac33051299631022ae9e519ee58a99.zip |
use new /batch-deposit API in merchant
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-pay.c | 549 | ||||
-rw-r--r-- | src/lib/merchant_api_post_order_pay.c | 9 |
2 files changed, 336 insertions, 222 deletions
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index 82419668..a3699114 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -165,6 +165,11 @@ struct ExchangeGroup { /** + * Payment context this group is part of. + */ + struct PayContext *pc; + + /** * Handle to the batch deposit operation we are performing for this * exchange, NULL after the operation is done. */ @@ -178,10 +183,15 @@ struct ExchangeGroup struct TMH_EXCHANGES_FindOperation *fo; /** - * URL of the exchange that issued this coin. + * URL of the exchange that issued this coin. Aliases + * the exchange URL of one of the coins, do not free! */ - char *exchange_url; + const char *exchange_url; + /** + * true if we already tried a forced /keys download. + */ + bool tried_force_keys; }; @@ -205,7 +215,7 @@ struct PayContext * Array with @e num_exchange exchanges we are depositing * coins into. */ - struct ExchangeGroup *eg; + struct ExchangeGroup **egs; /** * Array with @e coins_cnt coins we are despositing. @@ -241,18 +251,6 @@ struct PayContext struct MHD_Response *response; /** - * Handle for operation to lookup /keys (and auditors) from - * the exchange used for this transaction; NULL if no operation is - * pending. - */ - struct TMH_EXCHANGES_FindOperation *fo; - - /** - * URL of the exchange used for the last @e fo. - */ - const char *current_exchange; - - /** * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state. */ void *json_parse_context; @@ -396,19 +394,14 @@ struct PayContext unsigned int retry_counter; /** - * Number of transactions still pending. Initially set to - * @e coins_cnt, decremented on each transaction that - * successfully finished. + * Number of batch transactions pending. */ - unsigned int pending; + unsigned int pending_at_eg; /** - * Number of transactions still pending for the currently selected - * exchange. Initially set to the number of coins started at the - * exchange, decremented on each transaction that successfully - * finished. Once it hits zero, we pick the next exchange. + * Number of coin deposits pending. */ - unsigned int pending_at_ce; + unsigned int pending; /** * HTTP status code to use for the reply, i.e 200 for "OK". @@ -425,11 +418,6 @@ struct PayContext */ enum GNUNET_GenericReturnValue suspended; - /** - * true if we already tried a forced /keys download. - */ - bool tried_force_keys; - }; @@ -699,11 +687,15 @@ pay_context_cleanup (void *cls) GNUNET_free (dc->exchange_url); } GNUNET_free (pc->dc); - if (NULL != pc->fo) + for (unsigned int i = 0; i<pc->num_exchanges; i++) { - TMH_EXCHANGES_find_exchange_cancel (pc->fo); - pc->fo = NULL; + struct ExchangeGroup *eg = pc->egs[i]; + + if (NULL != eg->fo) + TMH_EXCHANGES_find_exchange_cancel (eg->fo); + GNUNET_free (eg); } + GNUNET_free (pc->egs); if (NULL != pc->response) { MHD_destroy_response (pc->response); @@ -719,16 +711,6 @@ pay_context_cleanup (void *cls) /** - * Find the exchange we need to talk to for the next - * pending deposit permission. - * - * @param pc payment context we are processing - */ -static void -find_next_exchange (struct PayContext *pc); - - -/** * Execute the DB transaction. If required (from * soft/serialization errors), the transaction can be * restarted here. @@ -878,7 +860,7 @@ kyc_cb ( */ static void check_kyc (struct PayContext *pc, - const struct DepositConfirmation *dc) + const struct ExchangeGroup *eg) { enum GNUNET_DB_QueryStatus qs; struct KycContext *kc; @@ -887,7 +869,7 @@ check_kyc (struct PayContext *pc, qs = TMH_db->account_kyc_get_status (TMH_db->cls, pc->hc->instance->settings.id, &pc->wm->h_wire, - dc->exchange_url, + eg->exchange_url, &kyc_cb, kc); if (qs < 0) @@ -911,7 +893,7 @@ check_kyc (struct PayContext *pc, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not re-checking KYC status at `%s', as we already recently asked\n", - dc->exchange_url); + eg->exchange_url); GNUNET_free (kc); return; } @@ -919,13 +901,23 @@ check_kyc (struct PayContext *pc, kc->mi = pc->hc->instance; kc->mi->rc++; kc->wm = pc->wm; - kc->exchange_url = GNUNET_strdup (dc->exchange_url); + kc->exchange_url = GNUNET_strdup (eg->exchange_url); kc->h_contract_terms = pc->h_contract_terms; - kc->coin_pub = dc->cdd.coin_pub; + /* find one of the coins of the batch */ + for (unsigned int i = 0; i<pc->coins_cnt; i++) + { + struct DepositConfirmation *dc = &pc->dc[i]; + + if (0 != strcmp (eg->exchange_url, + pc->dc[i].exchange_url)) + continue; + kc->coin_pub = dc->cdd.coin_pub; + break; + } GNUNET_CONTAINER_DLL_insert (kc_head, kc_tail, kc); - kc->fo = TMH_EXCHANGES_find_exchange (dc->exchange_url, + kc->fo = TMH_EXCHANGES_find_exchange (kc->exchange_url, NULL, GNUNET_NO, &process_kyc_with_exchange, @@ -939,87 +931,156 @@ check_kyc (struct PayContext *pc, /** - * Callback to handle a deposit permission's response. + * Handle case where the batch deposit completed + * with a status of #MHD_HTTP_OK. * - * @param cls a `struct DepositConfirmation` (i.e. a pointer - * into the global array of confirmations and an index for this call - * in that array). That way, the last executed callback can detect - * that no other confirmations are on the way, and can pack a response - * for the wallet - * @param dr HTTP response code details + * @param eg group that completed + * @param dr response from the server */ static void -deposit_cb (void *cls, - const struct TALER_EXCHANGE_DepositResult *dr) +handle_batch_deposit_ok (struct ExchangeGroup *eg, + const struct TALER_EXCHANGE_BatchDepositResult *dr) { - struct DepositConfirmation *dc = cls; - struct PayContext *pc = dc->pc; + struct PayContext *pc = eg->pc; + unsigned int j = 0; + enum GNUNET_DB_QueryStatus qs + = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - dc->dh = NULL; - GNUNET_assert (GNUNET_YES == pc->suspended); - pc->pending_at_ce--; - switch (dr->hr.http_status) + /* store result to DB */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Storing successful payment %s (%s) at instance `%s'\n", + pc->hc->infix, + GNUNET_h2s (&pc->h_contract_terms.hash), + pc->hc->instance->settings.id); + for (unsigned int r = 0; r<MAX_RETRIES; r++) { - case MHD_HTTP_OK: + TMH_db->preflight (TMH_db->cls); + if (GNUNET_OK != + TMH_db->start (TMH_db->cls, + "batch-deposit-insert-confirmation")) + { + resume_pay_with_response ( + pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_MAKE_JSON_PACK ( + TALER_JSON_pack_ec ( + TALER_EC_GENERIC_DB_START_FAILED), + TMH_pack_exchange_reply (&dr->hr))); + return; + } + for (unsigned int i = 0; i<pc->coins_cnt; i++) { - /* store result to DB */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing successful payment %s (%s) at instance `%s'\n", - pc->hc->infix, - GNUNET_h2s (&pc->h_contract_terms.hash), - pc->hc->instance->settings.id); - TMH_db->preflight (TMH_db->cls); + struct DepositConfirmation *dc = &pc->dc[i]; + + if (0 != strcmp (eg->exchange_url, + pc->dc[i].exchange_url)) + continue; + if (dc->found_in_db) + continue; + /* NOTE: We might want to check if the order was fully paid concurrently + by some other wallet here, and if so, issue an auto-refund. Right now, + it is possible to over-pay if two wallets literally make a concurrent + payment, as the earlier check for 'paid' is not in the same transaction + scope as this 'insert' operation. */ + qs = TMH_db->insert_deposit ( + TMH_db->cls, + pc->hc->instance->settings.id, + dr->details.success.deposit_timestamp, + &pc->h_contract_terms, + &dc->cdd.coin_pub, + dc->exchange_url, + &dc->cdd.amount, + &dc->deposit_fee, + &dc->refund_fee, + &dc->wire_fee, + &pc->wm->h_wire, + &dr->details.success.exchange_sigs[j++], + dr->details.success.exchange_pub); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { - enum GNUNET_DB_QueryStatus qs; - - /* NOTE: We might want to check if the order was fully paid concurrently - by some other wallet here, and if so, issue an auto-refund. Right now, - it is possible to over-pay if two wallets literally make a concurrent - payment, as the earlier check for 'paid' is not in the same transaction - scope as this 'insert' operation. */ - qs = TMH_db->insert_deposit ( - TMH_db->cls, - pc->hc->instance->settings.id, - dr->details.success.deposit_timestamp, - &pc->h_contract_terms, - &dc->cdd.coin_pub, - dc->exchange_url, - &dc->cdd.amount, - &dc->deposit_fee, - &dc->refund_fee, - &dc->wire_fee, - &pc->wm->h_wire, - dr->details.success.exchange_sig, - dr->details.success.exchange_pub); - if (0 > qs) - { - /* Special report if retries insufficient */ - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - execute_pay_transaction (pc); - return; - } - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - /* Forward error including 'proof' for the body */ - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "insert_deposit"); - return; - } + TMH_db->rollback (TMH_db->cls); + continue; + } + if (0 > qs) + { + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + /* Forward error including 'proof' for the body */ + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_deposit"); + return; } + } + qs = TMH_db->commit (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TMH_db->rollback (TMH_db->cls); + continue; + } + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "insert_deposit"); + } + break; /* DB transaction succeeded */ + } /* FOR DB retries */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "insert_deposit"); + return; + } - dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */ - pc->pending--; + /* Transaction is done, mark affected coins as complete as well. */ + for (unsigned int i = 0; i<pc->coins_cnt; i++) + { + struct DepositConfirmation *dc = &pc->dc[i]; - if (0 != pc->pending_at_ce) - return; /* still more to do with current exchange */ - check_kyc (pc, - dc); - find_next_exchange (pc); - return; - } + if (0 != strcmp (eg->exchange_url, + pc->dc[i].exchange_url)) + continue; + if (dc->found_in_db) + continue; + dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */ + pc->pending--; + } + check_kyc (pc, + eg); +} + + +/** + * Callback to handle a batch deposit permission's response. + * + * @param cls a `struct ExchangeGroup` + * @param dr HTTP response code details + */ +static void +batch_deposit_cb ( + void *cls, + const struct TALER_EXCHANGE_BatchDepositResult *dr) +{ + struct ExchangeGroup *eg = cls; + struct PayContext *pc = eg->pc; + + eg->bdh = NULL; + GNUNET_assert (GNUNET_YES == pc->suspended); + pc->pending_at_eg--; + switch (dr->hr.http_status) + { + case MHD_HTTP_OK: + handle_batch_deposit_ok (eg, + dr); + if (0 == pc->pending_at_eg) + execute_pay_transaction (eg->pc); + return; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Deposit operation failed with HTTP code %u/%d\n", @@ -1050,7 +1111,7 @@ deposit_cb (void *cls, return; } - /* Forward error, adding the "coin_pub" for which the + /* Forward error, adding the "exchange_url" for which the error was being generated */ if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS == dr->hr.ec) { @@ -1061,8 +1122,8 @@ deposit_cb (void *cls, TALER_JSON_pack_ec ( TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS), TMH_pack_exchange_reply (&dr->hr), - GNUNET_JSON_pack_data_auto ("coin_pub", - &dc->cdd.coin_pub))); + GNUNET_JSON_pack_data_auto ("exchange_url", + &eg->exchange_url))); return; } resume_pay_with_response ( @@ -1072,8 +1133,8 @@ deposit_cb (void *cls, TALER_JSON_pack_ec ( TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS), TMH_pack_exchange_reply (&dr->hr), - GNUNET_JSON_pack_data_auto ("coin_pub", - &dc->cdd.coin_pub))); + GNUNET_JSON_pack_data_auto ("exchange_url", + &eg->exchange_url))); return; } /* end switch */ } @@ -1082,7 +1143,7 @@ deposit_cb (void *cls, /** * Function called with the result of our exchange lookup. * - * @param cls the `struct PayContext` + * @param cls the `struct ExchangeGroup` * @param hr HTTP response details * @param exchange_handle NULL if exchange was not found to be acceptable * @param payto_uri payto://-URI of the exchange @@ -1100,15 +1161,18 @@ process_pay_with_exchange ( const struct TALER_Amount *wire_fee, bool exchange_trusted) { - struct PayContext *pc = cls; + struct ExchangeGroup *eg = cls; + struct PayContext *pc = eg->pc; struct TMH_HandlerContext *hc = pc->hc; const struct TALER_EXCHANGE_Keys *keys; + unsigned int group_size; (void) payto_uri; - pc->fo = NULL; + eg->fo = NULL; GNUNET_assert (GNUNET_YES == pc->suspended); if (NULL == hr) { + pc->pending_at_eg--; resume_pay_with_response ( pc, MHD_HTTP_GATEWAY_TIMEOUT, @@ -1119,6 +1183,7 @@ process_pay_with_exchange ( if ( (MHD_HTTP_OK != hr->http_status) || (NULL == exchange_handle) ) { + pc->pending_at_eg--; resume_pay_with_response ( pc, MHD_HTTP_BAD_GATEWAY, @@ -1131,6 +1196,7 @@ process_pay_with_exchange ( keys = TALER_EXCHANGE_get_keys (exchange_handle); if (NULL == keys) { + pc->pending_at_eg--; GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK */ resume_pay_with_error (pc, MHD_HTTP_BAD_GATEWAY, @@ -1139,44 +1205,43 @@ process_pay_with_exchange ( return; } - /* Initiate /deposit operation for all coins of + /* Initiate /batch-deposit operation for all coins of the current exchange (!) */ - GNUNET_assert (0 == pc->pending_at_ce); + group_size = 0; for (unsigned int i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; const struct TALER_EXCHANGE_DenomPublicKey *denom_details; - enum TALER_ErrorCode ec; unsigned int http_status; + enum TALER_ErrorCode ec; bool is_age_restricted_denom = false; - if (NULL != dc->dh) - continue; /* we were here before (can happen due to - tried_force_keys logic), don't go again */ - if (dc->found_in_db) + if (0 != strcmp (eg->exchange_url, + pc->dc[i].exchange_url)) continue; - if (0 != strcmp (dc->exchange_url, - pc->current_exchange)) + if (dc->found_in_db) continue; + denom_details = TALER_EXCHANGE_get_denomination_key_by_hash (keys, &dc->cdd.h_denom_pub); if (NULL == denom_details) { - if (! pc->tried_force_keys) + if (! eg->tried_force_keys) { /* let's try *forcing* a re-download of /keys from the exchange. Maybe the wallet has seen /keys that we missed. */ - pc->tried_force_keys = true; - pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange, + eg->tried_force_keys = true; + eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, pc->wm->wire_method, GNUNET_YES, &process_pay_with_exchange, - pc); - if (NULL != pc->fo) + eg); + if (NULL != eg->fo) return; } /* Forcing failed or we already did it, give up */ + pc->pending_at_eg--; resume_pay_with_response ( pc, MHD_HTTP_BAD_REQUEST, @@ -1191,6 +1256,8 @@ process_pay_with_exchange ( (json_t *) TALER_EXCHANGE_get_keys_raw (exchange_handle))))); return; } + dc->deposit_fee = denom_details->fees.deposit; + dc->refund_fee = denom_details->fees.refund; if (GNUNET_OK != TMH_AUDITORS_check_dk (exchange_handle, @@ -1199,19 +1266,20 @@ process_pay_with_exchange ( &http_status, &ec)) { - if (! pc->tried_force_keys) + if (! eg->tried_force_keys) { /* let's try *forcing* a re-download of /keys from the exchange. Maybe the wallet has seen auditors that we missed. */ - pc->tried_force_keys = true; - pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange, + eg->tried_force_keys = true; + eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, pc->wm->wire_method, GNUNET_YES, &process_pay_with_exchange, - pc); - if (NULL != pc->fo) + eg); + if (NULL != eg->fo) return; } + pc->pending_at_eg--; resume_pay_with_response ( pc, http_status, @@ -1226,10 +1294,10 @@ process_pay_with_exchange ( /* Now that we have the details about the denomination, we can verify age * restriction requirements, if applicable. Note that denominations with an * age_mask equal to zero always pass the age verification. */ - is_age_restricted_denom = 0 < denom_details->key.age_mask.bits; + is_age_restricted_denom = (0 != denom_details->key.age_mask.bits); - if (is_age_restricted_denom - && (0 < pc->minimum_age)) + if (is_age_restricted_denom && + (0 < pc->minimum_age)) { /* Minimum age given and restricted coind provided: We need to verify the * minimum age */ @@ -1241,7 +1309,6 @@ process_pay_with_exchange ( code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING; goto AGE_FAIL; } - dc->age_commitment.mask = denom_details->key.age_mask; if ((dc->age_commitment.num + 1) != __builtin_popcount (dc->age_commitment.mask.bits)) @@ -1251,17 +1318,16 @@ process_pay_with_exchange ( TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH; goto AGE_FAIL; } - if (GNUNET_OK != TALER_age_commitment_verify ( &dc->age_commitment, pc->minimum_age, &dc->minimum_age_sig)) code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED; - AGE_FAIL: if (0 < code) { + pc->pending_at_eg--; GNUNET_free (dc->age_commitment.keys); resume_pay_with_response ( pc, @@ -1279,97 +1345,126 @@ AGE_FAIL: &dc->cdd.h_age_commitment); GNUNET_free (dc->age_commitment.keys); } - else if (is_age_restricted_denom) + else if (is_age_restricted_denom && dc->no_age_commitment) { /* The contract did not ask for a minimum_age but the client paid - * with a coin that has age restriction enabled. We need the hash + * with a coin that has age restriction enabled. We lack the hash * of the age commitment in this case in order to verify the coin * and to deposit it with the exchange. */ - if (dc->no_h_age_commitment) - { - GNUNET_break_op (0); - resume_pay_with_response ( - pc, - MHD_HTTP_BAD_REQUEST, - TALER_MHD_MAKE_JSON_PACK ( - TALER_JSON_pack_ec ( - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_HASH_MISSING), - GNUNET_JSON_pack_data_auto ("h_denom_pub", - &denom_details->h_key))); - return; - } + pc->pending_at_eg--; + GNUNET_break_op (0); + resume_pay_with_response ( + pc, + MHD_HTTP_BAD_REQUEST, + TALER_MHD_MAKE_JSON_PACK ( + TALER_JSON_pack_ec ( + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_HASH_MISSING), + GNUNET_JSON_pack_data_auto ("h_denom_pub", + &denom_details->h_key))); + return; } + group_size++; + } - dc->deposit_fee = denom_details->fees.deposit; - dc->refund_fee = denom_details->fees.refund; - dc->wire_fee = *wire_fee; - GNUNET_assert (NULL != pc->wm); - TMH_db->preflight (TMH_db->cls); + if (0 == group_size) + { + GNUNET_break (0); + pc->pending_at_eg--; + if (0 == pc->pending_at_eg) + execute_pay_transaction (pc); + return; + } + + { + struct TALER_EXCHANGE_CoinDepositDetail cdds[group_size]; + struct TALER_EXCHANGE_DepositContractDetail dcd = { + .wire_deadline = pc->wire_transfer_deadline, + .merchant_payto_uri = pc->wm->payto_uri, + .wire_salt = pc->wm->wire_salt, + .h_contract_terms = pc->h_contract_terms, + .extension_details = NULL /* FIXME-OEC */, + .timestamp = pc->timestamp, + .merchant_pub = hc->instance->merchant_pub, + .refund_deadline = pc->refund_deadline + }; + enum TALER_ErrorCode ec; + + for (unsigned int i = 0; i<pc->coins_cnt; i++) { - struct TALER_EXCHANGE_DepositContractDetail dcd = { - .wire_deadline = pc->wire_transfer_deadline, - .merchant_payto_uri = pc->wm->payto_uri, - .wire_salt = pc->wm->wire_salt, - .h_contract_terms = pc->h_contract_terms, - .extension_details = NULL /* FIXME-OEC */, - .timestamp = pc->timestamp, - .merchant_pub = hc->instance->merchant_pub, - .refund_deadline = pc->refund_deadline - }; + struct DepositConfirmation *dc = &pc->dc[i]; - dc->dh = TALER_EXCHANGE_deposit (exchange_handle, - &dcd, - &dc->cdd, - &deposit_cb, - dc, - &ec); + if (dc->found_in_db) + continue; + if (0 != strcmp (dc->exchange_url, + eg->exchange_url)) + continue; + cdds[i] = dc->cdd; + dc->wire_fee = *wire_fee; + GNUNET_assert (NULL != pc->wm); } - if (NULL == dc->dh) + eg->bdh = TALER_EXCHANGE_batch_deposit (exchange_handle, + &dcd, + group_size, + cdds, + &batch_deposit_cb, + eg, + &ec); + if (NULL == eg->bdh) { /* Signature was invalid or some other constraint was not satisfied. If the exchange was unavailable, we'd get that information in the callback. */ + pc->pending_at_eg--; GNUNET_break_op (0); resume_pay_with_response ( pc, TALER_ErrorCode_get_http_status_safe (ec), TALER_MHD_MAKE_JSON_PACK ( TALER_JSON_pack_ec (ec), - GNUNET_JSON_pack_uint64 ("coin_idx", - i))); + GNUNET_JSON_pack_string ("exchange_url", + eg->exchange_url))); return; } if (TMH_force_audit) - TALER_EXCHANGE_deposit_force_dc (dc->dh); - pc->pending_at_ce++; + TALER_EXCHANGE_batch_deposit_force_dc (eg->bdh); } } /** - * Find the exchange we need to talk to for the next - * pending deposit permission. + * Start batch deposits for all exchanges involved + * in this payment. * * @param pc payment context we are processing */ static void -find_next_exchange (struct PayContext *pc) +start_batch_deposits (struct PayContext *pc) { - GNUNET_assert (0 == pc->pending_at_ce); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (unsigned int i = 0; i<pc->num_exchanges; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct ExchangeGroup *eg = pc->egs[i]; + bool have_coins = false; - if (dc->found_in_db) - continue; + for (unsigned int j = 0; j<pc->coins_cnt; j++) + { + struct DepositConfirmation *dc = &pc->dc[j]; - pc->current_exchange = dc->exchange_url; - pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange, + if (0 != strcmp (eg->exchange_url, + pc->dc[j].exchange_url)) + continue; + if (dc->found_in_db) + continue; + have_coins = true; + break; + } + if (! have_coins) + continue; /* no coins left to deposit at this exchange */ + eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, pc->wm->wire_method, GNUNET_NO, &process_pay_with_exchange, - pc); - if (NULL == pc->fo) + eg); + if (NULL == eg->fo) { GNUNET_break (0); resume_pay_with_error (pc, @@ -1378,13 +1473,10 @@ find_next_exchange (struct PayContext *pc) "Failed to lookup exchange by URL"); return; } - return; + pc->pending_at_eg++; } - pc->current_exchange = NULL; - /* We are done with all the HTTP requests, go back and try - the 'big' database transaction! (It should work now!) */ - GNUNET_assert (0 == pc->pending); - execute_pay_transaction (pc); + if (0 == pc->pending_at_eg) + execute_pay_transaction (pc); } @@ -1672,7 +1764,7 @@ check_payment_sufficient (struct PayContext *pc) * Sum of fees of *all* the different exchanges of all the coins are * higher than the fixed limit that the merchant is willing to pay. The * difference must be paid by the customer. - */// + */ struct TALER_Amount excess_fee; /* compute fee amount to be covered by customer */ @@ -1945,7 +2037,7 @@ execute_pay_transaction (struct PayContext *pc) /* Ok, we need to first go to the network to process more coins. We that interaction in *tiny* transactions (hence the rollback above). */ - find_next_exchange (pc); + start_batch_deposits (pc); return; } @@ -2140,6 +2232,7 @@ parse_pay (struct PayContext *pc) GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; + bool have_eg = false; res = TALER_MHD_parse_json_data (pc->connection, coin, @@ -2187,8 +2280,8 @@ parse_pay (struct PayContext *pc) : GNUNET_SYSERR; } - // Check the consistency of the (potential) age restriction - // information. + /* Check the consistency of the (potential) age restriction + * information. */ if (dc->no_age_commitment != dc->no_minimum_age_sig) { GNUNET_break_op (0); @@ -2204,6 +2297,29 @@ parse_pay (struct PayContext *pc) ? GNUNET_NO : GNUNET_SYSERR; } + + /* Setup exchange group */ + for (unsigned int i = 0; i<pc->num_exchanges; i++) + { + if (0 == + strcmp (pc->egs[i]->exchange_url, + GNUNET_strdup (exchange_url))) + { + have_eg = true; + break; + } + } + if (! have_eg) + { + struct ExchangeGroup *eg; + + eg = GNUNET_new (struct ExchangeGroup); + eg->pc = pc; + eg->exchange_url = dc->exchange_url; + GNUNET_array_append (pc->egs, + pc->num_exchanges, + eg); + } } } GNUNET_JSON_parse_free (spec); @@ -2563,11 +2679,6 @@ handle_pay_timeout (void *cls) GNUNET_assert (GNUNET_YES == pc->suspended); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming pay with error after timeout\n"); - if (NULL != pc->fo) - { - TMH_EXCHANGES_find_exchange_cancel (pc->fo); - pc->fo = NULL; - } resume_pay_with_error (pc, MHD_HTTP_GATEWAY_TIMEOUT, TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c index b5e2c706..4e935965 100644 --- a/src/lib/merchant_api_post_order_pay.c +++ b/src/lib/merchant_api_post_order_pay.c @@ -284,17 +284,20 @@ parse_conflict (struct TALER_MERCHANT_OrderPayHandle *oph, const json_t *json) { json_t *ereply; - struct TALER_CoinSpendPublicKeyP coin_pub; + const char *exchange_url; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("exchange_reply", &ereply), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &coin_pub), + GNUNET_JSON_spec_string ("exchange_url", + &exchange_url), GNUNET_JSON_spec_end () }; + struct TALER_CoinSpendPublicKeyP coin_pub; struct GNUNET_JSON_Specification hspec[] = { GNUNET_JSON_spec_json ("history", &oph->error_history), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &coin_pub), GNUNET_JSON_spec_end () }; enum TALER_ErrorCode ec = TALER_JSON_get_error_code (json); |