diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-05-07 13:01:29 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-05-07 13:01:29 +0200 |
commit | 4344f16b33bad868e8aea32f4921640950068332 (patch) | |
tree | fe177d28e7ea6eb73c4024c0a3f0c88131459145 /src/backend/taler-merchant-httpd_private-post-transfers.c | |
parent | 1d41f0e425309d3f7e223f3ea7d3675105110019 (diff) | |
download | merchant-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.c | 822 |
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 (¤t_entry->deposit_value, - ¤t_entry->deposit_value, - &iter_value)) || - (0 > - TALER_amount_add (¤t_entry->deposit_fee, - ¤t_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 (¤t_entry->deposit_value, + ¤t_entry->deposit_value, + deposit_value)) && + (0 <= + TALER_amount_add (¤t_entry->deposit_fee, + ¤t_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; } |