summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-transfers.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-05-07 13:01:29 +0200
committerChristian Grothoff <christian@grothoff.org>2020-05-07 13:01:29 +0200
commit4344f16b33bad868e8aea32f4921640950068332 (patch)
treefe177d28e7ea6eb73c4024c0a3f0c88131459145 /src/backend/taler-merchant-httpd_private-post-transfers.c
parent1d41f0e425309d3f7e223f3ea7d3675105110019 (diff)
downloadmerchant-4344f16b33bad868e8aea32f4921640950068332.tar.gz
merchant-4344f16b33bad868e8aea32f4921640950068332.tar.bz2
merchant-4344f16b33bad868e8aea32f4921640950068332.zip
implement POST /transfers
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-transfers.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-transfers.c822
1 files changed, 375 insertions, 447 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c b/src/backend/taler-merchant-httpd_private-post-transfers.c
index 6071800d..d78746cb 100644
--- a/src/backend/taler-merchant-httpd_private-post-transfers.c
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.c
@@ -31,8 +31,9 @@
/**
* How long to wait before giving up processing with the exchange?
*/
-#define TRACK_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \
- 30))
+#define TRANSFER_TIMEOUT (GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, \
+ 30))
/**
* How often do we retry the simple INSERT database transaction?
@@ -42,18 +43,18 @@
/**
* Context used for handing POST /private/transfers requests.
*/
-struct TrackTransferContext
+struct PostTransfersContext
{
/**
* Kept in a DLL.
*/
- struct TrackTransferContext *next;
+ struct PostTransfersContext *next;
/**
* Kept in a DLL.
*/
- struct TrackTransferContext *prev;
+ struct PostTransfersContext *prev;
/**
* Argument for the /wire/transfers request.
@@ -112,7 +113,6 @@ struct TrackTransferContext
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
-
/**
* Pointer to the detail that we are currently
* checking in #check_transfer().
@@ -141,285 +141,116 @@ struct TrackTransferContext
/**
* Head of list of suspended requests.
*/
-static struct TrackTransferContext *ttc_head;
+static struct PostTransfersContext *ptc_head;
/**
* Tail of list of suspended requests.
*/
-static struct TrackTransferContext *ttc_tail;
-
-
-/**
- * Represents an entry in the table used to sum up
- * individual deposits for each h_contract_terms/order_id
- * (as the exchange gives us per coin, and we return
- * per order). FIXME: decide whether we do this HERE
- * or in the DB! (SUM()...)
- */
-struct Entry
-{
-
- /**
- * Order of the entry. FIXME: to be used!?
- */
- char *order_id;
-
- /**
- * Sum accumulator for deposited value.
- */
- struct TALER_Amount deposit_value;
-
- /**
- * Sum accumulator for deposit fee.
- */
- struct TALER_Amount deposit_fee;
-
-};
-
-
-/**
- * Free the @a ttc.
- *
- * @param ttc data to free
- */
-static void
-free_transfer_track_context (struct TrackTransferContext *ttc)
-{
- if (NULL != ttc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (ttc->fo);
- ttc->fo = NULL;
- }
- if (NULL != ttc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (ttc->timeout_task);
- ttc->timeout_task = NULL;
- }
- if (NULL != ttc->wdh)
- {
- TALER_EXCHANGE_transfers_get_cancel (ttc->wdh);
- ttc->wdh = NULL;
- }
- GNUNET_free (ttc);
-}
-
-
-/**
- * Callback that frees all the elements in the hashmap
- *
- * @param cls closure, NULL
- * @param key current key
- * @param value a `struct Entry`
- * @return #GNUNET_YES if the iteration should continue,
- * #GNUNET_NO otherwise.
- */
-static int
-hashmap_free (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
-{
- struct TALER_Entry *entry = value;
-
- (void) cls;
- (void) key;
- GNUNET_free (entry);
- return GNUNET_YES;
-}
+static struct PostTransfersContext *ptc_tail;
/**
- * Builds JSON response containing the summed-up amounts
- * from individual deposits.
- *
- * @param cls a `json_t *` array to append additional entries to
- * @param key map's current key
- * @param map's current value
- * @return #GNUNET_YES if iteration is to be continued,
- * #GNUNET_NO otherwise.
+ * We are shutting down, force resume of all POST /transfers requests.
*/
-static int
-build_deposits_response (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
+void
+TMH_force_post_transfers_resume ()
{
- json_t *deposits_response;
- struct Entry *entry = value;
- json_t *element;
-
- element = json_pack ("{s:s, s:o, s:o}",
- "order_id", entry->order_id,
- "deposit_value", TALER_JSON_from_amount (
- &entry->deposit_value),
- "deposit_fee", TALER_JSON_from_amount (
- &entry->deposit_fee));
- GNUNET_assert (NULL != element);
- GNUNET_assert (0 ==
- json_array_append_new (deposits_response,
- element));
- return GNUNET_YES;
-}
+ struct PostTransfersContext *ptc;
-
-/**
- * Transform /track/transfer result as gotten from the exchange
- * and transforms it in a format liked by the backoffice Web interface.
- *
- * @param result response from exchange's /track/transfer
- * @result pointer to new JSON, or NULL upon errors.
- */
-static json_t *
-transform_response (const json_t *result,
- struct TrackTransferContext *ttc)
-{
- json_t *deposits;
- json_t *value;
- json_t *result_mod = NULL;
- size_t index;
- const char *key;
- struct GNUNET_HashCode h_key;
- struct GNUNET_CONTAINER_MultiHashMap *map;
- struct TALER_Amount iter_value;
- struct TALER_Amount iter_fee;
- struct Entry *current_entry;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("deposit_value", &iter_value),
- TALER_JSON_spec_amount ("deposit_fee", &iter_fee),
- GNUNET_JSON_spec_string ("h_contract_terms", &key),
- GNUNET_JSON_spec_end ()
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Transforming /track/transfer response.\n");
- map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
- deposits = json_object_get (result,
- "deposits");
-
- json_array_foreach (deposits, index, value)
+ while (NULL != (ptc = ptc_head))
{
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return NULL;
- }
- GNUNET_CRYPTO_hash_from_string (key,
- &h_key);
-
- if (NULL != (current_entry =
- GNUNET_CONTAINER_multihashmap_get (map,
- &h_key)))
+ GNUNET_CONTAINER_DLL_remove (ptc_head,
+ ptc_tail,
+ ptc);
+ MHD_resume_connection (ptc->connection);
+ if (NULL != ptc->timeout_task)
{
- /* The map already knows this h_contract_terms*/
- if ( (0 >
- TALER_amount_add (&current_entry->deposit_value,
- &current_entry->deposit_value,
- &iter_value)) ||
- (0 >
- TALER_amount_add (&current_entry->deposit_fee,
- &current_entry->deposit_fee,
- &iter_fee)) )
- {
- GNUNET_JSON_parse_free (spec);
- goto cleanup;
- }
+ GNUNET_SCHEDULER_cancel (ptc->timeout_task);
+ ptc->timeout_task = NULL;
}
- else
- {
- /* First time in the map for this h_contract_terms*/
- current_entry = GNUNET_new (struct Entry);
- current_entry->deposit_value = iter_value;
- current_entry->deposit_fee = iter_fee;
- current_entry->order_id = "FIXME";
-
- if (GNUNET_SYSERR ==
- GNUNET_CONTAINER_multihashmap_put (map,
- &h_key,
- current_entry,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- GNUNET_JSON_parse_free (spec);
- goto cleanup;
- }
- }
- GNUNET_JSON_parse_free (spec);
}
-
- {
- json_t *deposits_response;
-
- deposits_response = json_array ();
- GNUNET_assert (NULL != deposits_response);
- if (GNUNET_SYSERR ==
- GNUNET_CONTAINER_multihashmap_iterate (map,
- &build_deposits_response,
- deposits_response))
- {
- json_decref (deposits_response);
- goto cleanup;
- }
-
- result_mod = json_copy ((struct json_t *) result);
- json_object_del (result_mod,
- "deposits");
- json_object_set_new (result_mod,
- "deposits_sums",
- deposits_response);
- }
-cleanup:
- GNUNET_CONTAINER_multihashmap_iterate (map,
- &hashmap_free,
- NULL);
- GNUNET_CONTAINER_multihashmap_destroy (map);
- return result_mod;
}
/**
* Resume the given /track/transfer operation and send the given response.
- * Stores the response in the @a ttc and signals MHD to resume
+ * Stores the response in the @a ptc and signals MHD to resume
* the connection. Also ensures MHD runs immediately.
*
- * @param ttc transfer tracking context
+ * @param ptc transfer tracking context
* @param response_code response code to use
* @param response response data to send back
*/
static void
-resume_track_transfer_with_response (struct TrackTransferContext *ttc,
- unsigned int response_code,
- struct MHD_Response *response)
+resume_transfer_with_response (struct PostTransfersContext *ptc,
+ unsigned int response_code,
+ struct MHD_Response *response)
{
- ttc->response_code = response_code;
- ttc->response = response;
+ ptc->response_code = response_code;
+ ptc->response = response;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming POST /transfers handling as exchange interaction is done (%u)\n",
response_code);
- if (NULL != ttc->timeout_task)
+ if (NULL != ptc->timeout_task)
{
- GNUNET_SCHEDULER_cancel (ttc->timeout_task);
- ttc->timeout_task = NULL;
+ GNUNET_SCHEDULER_cancel (ptc->timeout_task);
+ ptc->timeout_task = NULL;
}
- GNUNET_CONTAINER_DLL_remove (ttc_head,
- ttc_tail,
- ttc);
- MHD_resume_connection (ttc->connection);
- TMH_trigger_daemon (); /* we resumed, kick MHD */
+ GNUNET_CONTAINER_DLL_remove (ptc_head,
+ ptc_tail,
+ ptc);
+ MHD_resume_connection (ptc->connection);
+ TMH_trigger_daemon (); /* we resumed, kick MHD */
}
/**
- * Custom cleanup routine for a `struct TrackTransferContext`.
+ * Resume the given POST /transfers operation with an error.
*
- * @param cls the `struct TrackTransferContext` to clean up.
+ * @param ptc transfer tracking context
+ * @param response_code response code to use
+ * @param ec error code to use
+ * @param hint hint text to provide
*/
static void
-track_transfer_cleanup (void *cls)
+resume_transfer_with_error (struct PostTransfersContext *ptc,
+ unsigned int response_code,
+ enum TALER_ErrorCode ec,
+ const char *hint)
{
- struct TrackTransferContext *ttc = cls;
+ resume_transfer_with_response (ptc,
+ response_code,
+ TALER_MHD_make_error (ec,
+ hint));
+}
+
- free_transfer_track_context (ttc);
+/**
+ * Custom cleanup routine for a `struct PostTransfersContext`.
+ *
+ * @param cls the `struct PostTransfersContext` to clean up.
+ */
+static void
+transfer_cleanup (void *cls)
+{
+ struct PostTransfersContext *ptc = cls;
+
+ if (NULL != ptc->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
+ ptc->fo = NULL;
+ }
+ if (NULL != ptc->timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (ptc->timeout_task);
+ ptc->timeout_task = NULL;
+ }
+ if (NULL != ptc->wdh)
+ {
+ TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
+ ptc->wdh = NULL;
+ }
+ GNUNET_free (ptc);
}
@@ -428,7 +259,7 @@ track_transfer_cleanup (void *cls)
* was paid back by _this_ wire transfer matches what _we_ (the merchant)
* knew about this coin.
*
- * @param cls closure with our `struct TrackTransferContext *`
+ * @param cls closure with our `struct PostTransfersContext *`
* @param transaction_id of the contract
* @param coin_pub public key of the coin
* @param exchange_url URL of the exchange that issued @a coin_pub
@@ -448,11 +279,11 @@ check_transfer (void *cls,
const struct TALER_Amount *wire_fee,
const json_t *exchange_proof)
{
- struct TrackTransferContext *ttc = cls;
- const struct TALER_TrackTransferDetails *ttd = ttc->current_detail;
+ struct PostTransfersContext *ptc = cls;
+ const struct TALER_TrackTransferDetails *ttd = ptc->current_detail;
- if (GNUNET_SYSERR == ttc->check_transfer_result)
- return; /* already had a serious issue; odd that we're called more than once as well... */
+ if (GNUNET_SYSERR == ptc->check_transfer_result)
+ return; /* already had a serious issue; odd that we're called more than once as well... */
if ( (0 != TALER_amount_cmp (amount_with_fee,
&ttd->coin_value)) ||
(0 != TALER_amount_cmp (deposit_fee,
@@ -461,15 +292,15 @@ check_transfer (void *cls,
/* Disagreement between the exchange and us about how much this
coin is worth! */
GNUNET_break_op (0);
- ttc->check_transfer_result = GNUNET_SYSERR;
+ ptc->check_transfer_result = GNUNET_SYSERR;
/* Build the `TrackTransferConflictDetails` */
- ttc->response
+ ptc->response
= TALER_MHD_make_json_pack (
"{s:I, s:s, s:o, s:I, s:o, s:s, s:o, s:o}",
- "code", (json_int_t) TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS,
+ "code", (json_int_t) TALER_EC_POST_TRANSFERS_CONFLICTING_REPORTS,
"hint", "disagreement about deposit valuation",
"exchange_deposit_proof", exchange_proof,
- "conflict_offset", (json_int_t) ttc->current_offset,
+ "conflict_offset", (json_int_t) ptc->current_offset,
"coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
"h_contract_terms", GNUNET_JSON_from_data_auto (
&ttd->h_contract_terms),
@@ -477,7 +308,7 @@ check_transfer (void *cls,
"deposit_fee", TALER_JSON_from_amount (deposit_fee));
return;
}
- ttc->check_transfer_result = GNUNET_OK;
+ ptc->check_transfer_result = GNUNET_OK;
}
@@ -490,7 +321,7 @@ check_transfer (void *cls,
* if we have proof that the fee is bogus, we respond with
* the proof to the client and return #GNUNET_SYSERR.
*
- * @param ttc context of the transfer to respond to
+ * @param ptc context of the transfer to respond to
* @param json response from the exchange
* @param execution_time time of the wire transfer
* @param wire_fee fee claimed by the exchange
@@ -498,7 +329,7 @@ check_transfer (void *cls,
* missbehavior from the exchange to the client
*/
static int
-check_wire_fee (struct TrackTransferContext *ttc,
+check_wire_fee (struct PostTransfersContext *ptc,
const json_t *json,
struct GNUNET_TIME_Absolute execution_time,
const struct TALER_Amount *wire_fee)
@@ -511,10 +342,10 @@ check_wire_fee (struct TrackTransferContext *ttc,
enum GNUNET_DB_QueryStatus qs;
char *wire_method;
- wire_method = TALER_payto_get_method (ttc->payto_uri);
+ wire_method = TALER_payto_get_method (ptc->payto_uri);
TMH_db->preflight (TMH_db->cls);
qs = TMH_db->lookup_wire_fee (TMH_db->cls,
- &ttc->master_pub,
+ &ptc->master_pub,
wire_method,
execution_time,
&expected_fee,
@@ -526,7 +357,7 @@ check_wire_fee (struct TrackTransferContext *ttc,
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n",
- TALER_B2S (&ttc->master_pub),
+ TALER_B2S (&ptc->master_pub),
wire_method,
GNUNET_STRINGS_absolute_time_to_string (execution_time),
TALER_amount2s (wire_fee));
@@ -537,15 +368,15 @@ check_wire_fee (struct TrackTransferContext *ttc,
wire_fee))
{
GNUNET_free (wire_method);
- return GNUNET_OK; /* expected_fee >= wire_fee */
+ return GNUNET_OK; /* expected_fee >= wire_fee */
}
/* Wire fee check failed, export proof to client */
- resume_track_transfer_with_response (
- ttc,
+ resume_transfer_with_response (
+ ptc,
MHD_HTTP_FAILED_DEPENDENCY,
TALER_MHD_make_json_pack (
"{s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:O}",
- "code", (json_int_t) TALER_EC_TRACK_TRANSFER_JSON_BAD_WIRE_FEE,
+ "code", (json_int_t) TALER_EC_POST_TRANSFERS_JSON_BAD_WIRE_FEE,
"wire_fee", TALER_JSON_from_amount (wire_fee),
"execution_time", GNUNET_JSON_from_time_abs (execution_time),
"expected_wire_fee", TALER_JSON_from_amount (&expected_fee),
@@ -553,7 +384,7 @@ check_wire_fee (struct TrackTransferContext *ttc,
"start_date", GNUNET_JSON_from_time_abs (start_date),
"end_date", GNUNET_JSON_from_time_abs (end_date),
"master_sig", GNUNET_JSON_from_data_auto (&master_sig),
- "master_pub", GNUNET_JSON_from_data_auto (&ttc->master_pub),
+ "master_pub", GNUNET_JSON_from_data_auto (&ptc->master_pub),
"json", json));
GNUNET_free (wire_method);
return GNUNET_SYSERR;
@@ -573,54 +404,30 @@ wire_transfer_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr,
const struct TALER_EXCHANGE_TransferData *td)
{
- struct TrackTransferContext *ttc = cls;
- const char *instance_id = ttc->hc->instance->settings.id;
+ struct PostTransfersContext *ptc = cls;
+ const char *instance_id = ptc->hc->instance->settings.id;
enum GNUNET_DB_QueryStatus qs;
- ttc->wdh = NULL;
+ ptc->wdh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got response code %u from exchange for GET /transfers/$WTID\n",
hr->http_status);
if (MHD_HTTP_OK != hr->http_status)
{
- resume_track_transfer_with_response (
- ttc,
+ resume_transfer_with_response (
+ ptc,
MHD_HTTP_FAILED_DEPENDENCY,
TALER_MHD_make_json_pack (
"{s:I, s:I, s:I, s:O}",
- "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_ERROR,
+ "code", (json_int_t) TALER_EC_POST_TRANSFERS_EXCHANGE_ERROR,
"exchange_code", (json_int_t) hr->ec,
"exchange_http_status", (json_int_t) hr->http_status,
"exchange_reply", hr->reply));
return;
}
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->insert_transfer_details (TMH_db->cls,
- instance_id,
- ttc->exchange_url,
- ttc->payto_uri,
- &ttc->wtid,
- td);
- if (0 > qs)
- {
- /* Special report if retries insufficient */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_track_transfer_with_response (
- ttc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_MHD_make_json_pack (
- "{s:I, s:s}",
- "code",
- (json_int_t) TALER_EC_TRACK_TRANSFER_DB_STORE_TRANSFER_ERROR,
- "details",
- "failed to store response from exchange to local database"));
- return;
- }
if (GNUNET_SYSERR ==
- check_wire_fee (ttc,
+ check_wire_fee (ptc,
hr->reply,
td->execution_time,
&td->wire_fee))
@@ -638,17 +445,17 @@ wire_transfer_cb (void *cls,
{
const struct TALER_TrackTransferDetails *ttd = &td->details[i];
- ttc->current_offset = i;
- ttc->current_detail = ttd;
+ ptc->current_offset = i;
+ ptc->current_detail = ttd;
/* Set the coin as "never seen" before. */
- ttc->check_transfer_result = GNUNET_NO;
+ ptc->check_transfer_result = GNUNET_NO;
TMH_db->preflight (TMH_db->cls);
qs = TMH_db->lookup_deposits_by_contract_and_coin (TMH_db->cls,
instance_id,
&ttd->h_contract_terms,
&ttd->coin_pub,
&check_transfer,
- ttc);
+ ptc);
if (0 > qs)
{
/* single, read-only SQL statements should never cause
@@ -656,15 +463,11 @@ wire_transfer_cb (void *cls,
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_track_transfer_with_response (
- ttc,
+ resume_transfer_with_error (
+ ptc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_MHD_make_json_pack ("{s:I, s:s}",
- "code",
- (json_int_t)
- TALER_EC_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR,
- "details",
- "failed to obtain deposit data from local database"));
+ TALER_EC_POST_TRANSFERS_DB_FETCH_DEPOSIT_ERROR,
+ "failed to obtain deposit data from local database");
return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
@@ -674,110 +477,138 @@ wire_transfer_cb (void *cls,
Well, let's say thanks and accept the money! */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to find payment data in DB\n");
- ttc->check_transfer_result = GNUNET_OK;
+ ptc->check_transfer_result = GNUNET_OK;
}
- if (GNUNET_NO == ttc->check_transfer_result)
+ if (GNUNET_NO == ptc->check_transfer_result)
{
/* Internal error: how can we have called #check_transfer()
but still have no result? */
GNUNET_break (0);
- resume_track_transfer_with_response (
- ttc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_MHD_make_json_pack ("{s:I, s:s, s:I, s:s}",
- "code",
- (json_int_t)
- TALER_EC_TRACK_TRANSFER_DB_INTERNAL_LOGIC_ERROR,
- "details", "internal logic error",
- "line", (json_int_t) __LINE__,
- "file", __FILE__));
+ resume_transfer_with_error (ptc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_POST_TRANSFERS_DB_INTERNAL_LOGIC_ERROR,
+ "internal logic error");
return;
}
- if (GNUNET_SYSERR == ttc->check_transfer_result)
+ if (GNUNET_SYSERR == ptc->check_transfer_result)
{
/* #check_transfer() failed, report conflict! */
GNUNET_break_op (0);
- GNUNET_assert (NULL != ttc->response);
- resume_track_transfer_with_response (
- ttc,
- MHD_HTTP_FAILED_DEPENDENCY,
- ttc->response);
- ttc->response = NULL;
+ GNUNET_assert (NULL != ptc->response);
+ resume_transfer_with_response (ptc,
+ MHD_HTTP_FAILED_DEPENDENCY,
+ ptc->response);
+ ptc->response = NULL;
return;
}
- /* Response is consistent with the /deposit we made,
- remember it for future reference */
- for (unsigned int r = 0; r<MAX_RETRIES; r++)
+ }
+
+ /* Response is consistent with the /deposit we made,
+ remember it for future reference */
+ for (unsigned int r = 0; r<MAX_RETRIES; r++)
+ {
+ TMH_db->preflight (TMH_db->cls);
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "insert transaction details"))
+ {
+ GNUNET_break (0);
+ resume_transfer_with_error (ptc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_POST_TRANSFERS_DB_STORE_TRANSFER_ERROR,
+ "could not start transaction");
+ return;
+ }
+ for (unsigned int i = 0; i < td->details_length; i++)
{
const struct TALER_TrackTransferDetails *ttd = &td->details[i];
- TMH_db->preflight (TMH_db->cls);
qs = TMH_db->store_coin_to_transfer (TMH_db->cls,
&ttd->h_contract_terms,
&ttd->coin_pub,
- &ttc->wtid);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
+ &ptc->wtid);
+ if (0 > qs)
+ goto retry;
}
+
+ /* Ok, exchange answer is acceptable, store it */
+ qs = TMH_db->insert_transfer_details (TMH_db->cls,
+ instance_id,
+ ptc->exchange_url,
+ ptc->payto_uri,
+ &ptc->wtid,
+ td);
if (0 > qs)
+ goto retry;
+ qs = TMH_db->commit (TMH_db->cls);
+retry:
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- /* Special report if retries insufficient */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TMH_db->rollback (TMH_db->cls);
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_track_transfer_with_response (
- ttc,
+ resume_transfer_with_error (
+ ptc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_MHD_make_json_pack ("{s:I, s:s}",
- "code",
- (json_int_t)
- TALER_EC_TRACK_TRANSFER_DB_STORE_COIN_ERROR,
- "details",
- "failed to store response from exchange to local database"));
+ TALER_EC_POST_TRANSFERS_DB_STORE_TRANSFER_ERROR,
+ "failed to commit transaction to local database");
return;
}
+ if (0 <= qs)
+ break; /* success! */
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ resume_transfer_with_error (
+ ptc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_POST_TRANSFERS_DB_STORE_TRANSFER_ERROR,
+ "repeated serialization failures trying to commit transaction to local database");
+ return;
}
/* resume processing, main function will build the response */
- resume_track_transfer_with_response (ttc,
- 0,
- NULL);
+ resume_transfer_with_response (ptc,
+ 0,
+ NULL);
}
/**
* Function called with the result of our exchange lookup.
*
- * @param cls the `struct TrackTransferContext`
+ * @param cls the `struct PostTransfersContext`
* @param hr HTTP response details
* @param eh NULL if exchange was not found to be acceptable
* @param wire_fee NULL (we did not specify a wire method)
* @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
*/
static void
-process_track_transfer_with_exchange (
- void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const struct TALER_Amount *wire_fee,
- int exchange_trusted)
+process_transfer_with_exchange (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ struct TALER_EXCHANGE_Handle *eh,
+ const struct TALER_Amount *wire_fee,
+ int exchange_trusted)
{
- struct TrackTransferContext *ttc = cls;
+ struct PostTransfersContext *ptc = cls;
- ttc->fo = NULL;
+ ptc->fo = NULL;
if (MHD_HTTP_OK != hr->http_status)
{
/* The request failed somehow */
GNUNET_break_op (0);
- resume_track_transfer_with_response (
- ttc,
+ resume_transfer_with_response (
+ ptc,
MHD_HTTP_FAILED_DEPENDENCY,
TALER_MHD_make_json_pack (
(NULL != hr->reply)
? "{s:s, s:I, s:I, s:I, s:O}"
: "{s:s, s:I, s:I, s:I}",
"hint", "failed to obtain meta-data from exchange",
- "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE,
+ "code", (json_int_t) TALER_EC_POST_TRANSFERS_EXCHANGE_KEYS_FAILURE,
"exchange_http_status", (json_int_t) hr->http_status,
"exchange_code", (json_int_t) hr->ec,
"exchange_reply", hr->reply));
@@ -792,39 +623,62 @@ process_track_transfer_with_exchange (
if (NULL == keys)
{
GNUNET_break (0);
- resume_track_transfer_with_response (
- ttc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_MHD_make_error (
- TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE,
- "failed to get keys"));
+ resume_transfer_with_error (ptc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_POST_TRANSFERS_EXCHANGE_KEYS_FAILURE,
+ "failed to get keys");
return;
}
- ttc->master_pub = keys->master_pub;
+ ptc->master_pub = keys->master_pub;
}
- ttc->wdh = TALER_EXCHANGE_transfers_get (eh,
- &ttc->wtid,
+ ptc->wdh = TALER_EXCHANGE_transfers_get (eh,
+ &ptc->wtid,
&wire_transfer_cb,
- ttc);
- if (NULL == ttc->wdh)
+ ptc);
+ if (NULL == ptc->wdh)
{
GNUNET_break (0);
- resume_track_transfer_with_response (
- ttc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_MHD_make_error (
- TALER_EC_TRACK_TRANSFER_REQUEST_ERROR,
- "failed to run GET /transfers/ on exchange"));
+ resume_transfer_with_error (ptc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_POST_TRANSFERS_REQUEST_ERROR,
+ "failed to run GET /transfers/ on exchange");
}
}
/**
+ * Represents an entry in the table used to sum up
+ * individual deposits for each h_contract_terms/order_id
+ * (as the exchange gives us per coin, and we return
+ * per order).
+ */
+struct Entry
+{
+
+ /**
+ * Order of the entry.
+ */
+ char *order_id;
+
+ /**
+ * Sum accumulator for deposited value.
+ */
+ struct TALER_Amount deposit_value;
+
+ /**
+ * Sum accumulator for deposit fee.
+ */
+ struct TALER_Amount deposit_fee;
+
+};
+
+
+/**
* Function called with information about a wire transfer identifier.
* Generate a response array based on the given information.
*
- * @param cls closure, a `json_t` array to update
+ * @param cls closure, a hashmap to update
* @param order_id the order to which the deposits belong
* @param deposit_value the amount deposited under @a order_id
* @param deposit_fee the fee charged for @a deposit_value
@@ -835,16 +689,78 @@ transfer_details_cb (void *cls,
const struct TALER_Amount *deposit_value,
const struct TALER_Amount *deposit_fee)
{
+ struct GNUNET_CONTAINER_MultiHashMap *map = cls;
+ struct Entry *current_entry;
+ struct GNUNET_HashCode h_key;
+
+ GNUNET_CRYPTO_hash_from_string (order_id,
+ &h_key);
+ current_entry = GNUNET_CONTAINER_multihashmap_get (map,
+ &h_key);
+ if (NULL != current_entry)
+ {
+ /* The map already knows this order, do aggregation */
+ GNUNET_assert ( (0 <=
+ TALER_amount_add (&current_entry->deposit_value,
+ &current_entry->deposit_value,
+ deposit_value)) &&
+ (0 <=
+ TALER_amount_add (&current_entry->deposit_fee,
+ &current_entry->deposit_fee,
+ deposit_fee)) );
+ }
+ else
+ {
+ /* First time in the map for this h_contract_terms*/
+ current_entry = GNUNET_new (struct Entry);
+ current_entry->deposit_value = *deposit_value;
+ current_entry->deposit_fee = *deposit_fee;
+ current_entry->order_id = GNUNET_strdup (order_id);
+ GNUNET_assert (GNUNET_SYSERR !=
+ GNUNET_CONTAINER_multihashmap_put (map,
+ &h_key,
+ current_entry,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+}
+
+
+/**
+ * Callback that frees all the elements in the hashmap, and @a cls
+ * is non-NULL, appends them as JSON to the array
+ *
+ * @param cls closure, NULL or a `json_t *` array
+ * @param key current key
+ * @param value a `struct Entry`
+ * @return #GNUNET_YES if the iteration should continue,
+ * #GNUNET_NO otherwise.
+ */
+static int
+hashmap_free (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
json_t *ja = cls;
+ struct Entry *entry = value;
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- ja,
- json_pack ("{s:s,s:o,s:o}",
- "order_id", order_id,
- "deposit_value", TALER_JSON_from_amount (deposit_value),
- "deposit_fee", TALER_JSON_from_amount (deposit_fee))));
+ (void) key;
+ if (NULL != ja)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ ja,
+ json_pack ("{s:s,s:o,s:o}",
+ "order_id",
+ entry->order_id,
+ "deposit_value",
+ TALER_JSON_from_amount (&entry->deposit_value),
+ "deposit_fee",
+ TALER_JSON_from_amount (&entry->deposit_fee))));
+ }
+ GNUNET_free (entry->order_id);
+ GNUNET_free (entry);
+ return GNUNET_YES;
}
@@ -854,28 +770,27 @@ transfer_details_cb (void *cls,
* @param cls closure
*/
static void
-handle_track_transfer_timeout (void *cls)
+handle_transfer_timeout (void *cls)
{
- struct TrackTransferContext *ttc = cls;
+ struct PostTransfersContext *ptc = cls;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming POST /private/transfers with error after timeout\n");
- ttc->timeout_task = NULL;
- if (NULL != ttc->fo)
+ ptc->timeout_task = NULL;
+ if (NULL != ptc->fo)
{
- TMH_EXCHANGES_find_exchange_cancel (ttc->fo);
- ttc->fo = NULL;
+ TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
+ ptc->fo = NULL;
}
- if (NULL != ttc->wdh)
+ if (NULL != ptc->wdh)
{
- TALER_EXCHANGE_transfers_get_cancel (ttc->wdh);
- ttc->wdh = NULL;
+ TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
+ ptc->wdh = NULL;
}
- resume_track_transfer_with_response (ttc,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TALER_MHD_make_error (
- TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT,
- "exchange not reachable"));
+ resume_transfer_with_error (ptc,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_POST_TRANSFERS_EXCHANGE_TIMEOUT,
+ "exchange not reachable");
}
@@ -894,43 +809,44 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
- struct TrackTransferContext *ttc = hc->ctx;
+ struct PostTransfersContext *ptc = hc->ctx;
enum GNUNET_DB_QueryStatus qs;
- if (NULL == ttc)
+ if (NULL == ptc)
{
- ttc = GNUNET_new (struct TrackTransferContext);
- ttc->connection = connection;
- hc->ctx = ttc;
- hc->cc = &track_transfer_cleanup;
+ ptc = GNUNET_new (struct PostTransfersContext);
+ ptc->connection = connection;
+ hc->ctx = ptc;
+ hc->cc = &transfer_cleanup;
}
- if (0 != ttc->response_code)
+ if (0 != ptc->response_code)
{
MHD_RESULT ret;
/* We are *done* processing the request, just queue the response (!) */
- if (UINT_MAX == ttc->response_code)
+ if (UINT_MAX == ptc->response_code)
{
GNUNET_break (0);
return MHD_NO; /* hard error */
}
ret = MHD_queue_response (connection,
- ttc->response_code,
- ttc->response);
- if (NULL != ttc->response)
+ ptc->response_code,
+ ptc->response);
+ if (NULL != ptc->response)
{
- MHD_destroy_response (ttc->response);
- ttc->response = NULL;
+ MHD_destroy_response (ptc->response);
+ ptc->response = NULL;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Queueing response (%u) for POST /private/transfers (%s).\n",
- (unsigned int) ttc->response_code,
+ (unsigned int) ptc->response_code,
ret ? "OK" : "FAILED");
return ret;
}
- if ( (NULL != ttc->fo) ||
- (NULL != ttc->wdh) )
+
+ if ( (NULL != ptc->fo) ||
+ (NULL != ptc->wdh) )
{
/* likely old MHD version */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -938,16 +854,18 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
return MHD_YES; /* still work in progress */
}
+ if (NULL == ptc->exchange_url)
{
+ /* First request, parse it! */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("exchange",
- &ttc->exchange_url),
+ &ptc->exchange_url),
GNUNET_JSON_spec_string ("payto_uri",
- &ttc->payto_uri),
+ &ptc->payto_uri),
TALER_JSON_spec_amount ("amount",
- &ttc->amount),
+ &ptc->amount),
GNUNET_JSON_spec_fixed_auto ("wtid",
- &ttc->wtid),
+ &ptc->wtid),
GNUNET_JSON_spec_end ()
};
@@ -964,40 +882,51 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
}
}
- /* Check if reply is already in database! */
+ /* Check if transfer data is in database! */
{
- json_t *deposit_sums;
+ struct GNUNET_CONTAINER_MultiHashMap *map;
struct GNUNET_TIME_Absolute execution_time;
struct TALER_Amount total_amount;
struct TALER_Amount wire_fee;
- deposit_sums = json_array ();
- GNUNET_assert (NULL != deposit_sums);
TMH_db->preflight (TMH_db->cls);
+ map = GNUNET_CONTAINER_multihashmap_create (16,
+ GNUNET_NO);
qs = TMH_db->lookup_transfer_details (TMH_db->cls,
hc->instance->settings.id,
- ttc->exchange_url,
- ttc->payto_uri,
- &ttc->wtid,
+ ptc->exchange_url,
+ ptc->payto_uri,
+ &ptc->wtid,
&total_amount,
&wire_fee,
execution_time,
&transfer_details_cb,
- deposit_sums);
+ map);
if (0 > qs)
{
/* Simple select queries should not cause serialization issues */
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- json_decref (deposit_sums);
+ GNUNET_CONTAINER_multihashmap_iterate (map,
+ &hashmap_free,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (map);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_POST_TRANSFERS_DB_LOOKUP_ERROR,
- "Fail to query database about proofs");
+ "Failed to query database about transfer details");
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
{
+ json_t *deposit_sums;
+
+ deposit_sums = json_array ();
+ GNUNET_assert (NULL != deposit_sums);
+ GNUNET_CONTAINER_multihashmap_iterate (map,
+ &hashmap_free,
+ deposit_sums);
+ GNUNET_CONTAINER_multihashmap_destroy (map);
return TALER_MHD_reply_json_pack (
connection,
MHD_HTTP_OK,
@@ -1007,17 +936,16 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
"execution_time", GNUNET_JSON_from_time_abs (execution_time),
"deposit_sums", deposit_sums);
}
- json_decref (deposit_sums);
}
/* reply not in database, ensure the POST is in the database, and
start work to obtain the reply from the exchange */
qs = TMH_db->insert_transfer (TMH_db->cls,
- ttc->hc->instance->settings.id,
- ttc->exchange_url,
- &ttc->wtid,
- &ttc->amount,
- ttc->payto_uri,
+ ptc->hc->instance->settings.id,
+ ptc->exchange_url,
+ &ptc->wtid,
+ &ptc->amount,
+ ptc->payto_uri,
true /* confirmed! */);
if (0 > qs)
{
@@ -1038,8 +966,8 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
the INSERT failed because we did not find the account based on
the given payto-URI and the instance. */
qs = TMH_db->lookup_account (TMH_db->cls,
- ttc->hc->instance->settings.id,
- ttc->payto_uri,
+ ptc->hc->instance->settings.id,
+ ptc->payto_uri,
&account_serial);
if (0 >= qs)
return TALER_MHD_reply_with_error (connection,
@@ -1051,18 +979,18 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending POST /private/transfers handling while working with exchange\n");
MHD_suspend_connection (connection);
- GNUNET_CONTAINER_DLL_insert (ttc_head,
- ttc_tail,
- ttc);
- ttc->fo = TMH_EXCHANGES_find_exchange (ttc->exchange_url,
+ GNUNET_CONTAINER_DLL_insert (ptc_head,
+ ptc_tail,
+ ptc);
+ ptc->fo = TMH_EXCHANGES_find_exchange (ptc->exchange_url,
NULL,
GNUNET_NO,
- &process_track_transfer_with_exchange,
- ttc);
- ttc->timeout_task
- = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT,
- &handle_track_transfer_timeout,
- ttc);
+ &process_transfer_with_exchange,
+ ptc);
+ ptc->timeout_task
+ = GNUNET_SCHEDULER_add_delayed (TRANSFER_TIMEOUT,
+ &handle_transfer_timeout,
+ ptc);
return MHD_YES;
}