summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2022-07-02 23:00:07 +0200
committerChristian Grothoff <christian@grothoff.org>2022-07-02 23:00:07 +0200
commit09a8501e94ac33051299631022ae9e519ee58a99 (patch)
tree32256371f3a27b0a97cc2a00d0af7dc71e2848dc /src
parent4e73e59e1cc8d0934c651bba7c2b99f8ac5dc92b (diff)
downloadmerchant-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.c549
-rw-r--r--src/lib/merchant_api_post_order_pay.c9
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);