/* This file is part of TALER (C) 2014-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file backend/taler-merchant-httpd_private-post-transfers.c * @brief implement API for registering wire transfers * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd_auditors.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_private-post-transfers.h" /** * How long to wait before giving up processing with the exchange? */ #define TRACK_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \ 30)) /** * How often do we retry the simple INSERT database transaction? */ #define MAX_RETRIES 3 /** * Context used for handing POST /private/transfers requests. */ struct TrackTransferContext { /** * Kept in a DLL. */ struct TrackTransferContext *next; /** * Kept in a DLL. */ struct TrackTransferContext *prev; /** * Argument for the /wire/transfers request. */ struct TALER_WireTransferIdentifierRawP wtid; /** * Amount of the wire transfer. */ struct TALER_Amount amount; /** * URL of the exchange. */ char *exchange_url; /** * payto:// URI used for the transfer. */ char *payto_uri; /** * Master public key of the exchange at @e exchange_url. */ struct TALER_MasterPublicKeyP master_pub; /** * Handle for the /wire/transfers request. */ struct TALER_EXCHANGE_TransfersGetHandle *wdh; /** * For which merchant instance is this tracking request? */ struct MerchantInstance *mi; /** * HTTP connection we are handling. */ struct MHD_Connection *connection; /** * Response to return upon resume. */ 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; /** * Task run on timeout. */ struct GNUNET_SCHEDULER_Task *timeout_task; /** * Pointer to the detail that we are currently * checking in #check_transfer(). */ const struct TALER_TrackTransferDetails *current_detail; /** * Which transaction detail are we currently looking at? */ unsigned int current_offset; /** * Response code to return. */ unsigned int response_code; /** * #GNUNET_NO if we did not find a matching coin. * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match. * #GNUNET_OK if we did find a matching coin. */ int check_transfer_result; }; /** * Head of list of suspended requests. */ static struct TrackTransferContext *pth_head; /** * Tail of list of suspended requests. */ static struct TrackTransferContext *pth_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 rctx. * * @param rctx data to free */ static void free_transfer_track_context (struct TrackTransferContext *rctx) { if (NULL != rctx->fo) { TMH_EXCHANGES_find_exchange_cancel (rctx->fo); rctx->fo = NULL; } if (NULL != rctx->timeout_task) { GNUNET_SCHEDULER_cancel (rctx->timeout_task); rctx->timeout_task = NULL; } if (NULL != rctx->wdh) { TALER_EXCHANGE_transfers_get_cancel (rctx->wdh); rctx->wdh = NULL; } if (NULL != rctx->url) { GNUNET_free (rctx->url); rctx->url = NULL; } if (NULL != rctx->wire_method) { GNUNET_free (rctx->wire_method); rctx->wire_method = NULL; } GNUNET_free (rctx); } /** * 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; } /** * Builds JSON response containing the summed-up amounts * from individual deposits. * * @param cls closure * @param key map's current key * @param map's current value * @return #GNUNET_YES if iteration is to be continued, * #GNUNET_NO otherwise. */ static int build_deposits_response (void *cls, const struct GNUNET_HashCode *key, void *value) { struct TrackTransferContext *rctx = cls; struct Entry *entry = value; json_t *element; json_t *contract_terms; json_t *order_id; TMH_db->preflight (TMH_db->cls); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != TMH_db->find_contract_terms_from_hash (TMH_db->cls, &contract_terms, key, &rctx->mi->pubkey)) { GNUNET_break_op (0); return GNUNET_NO; } order_id = json_object_get (contract_terms, "order_id"); if (NULL == order_id) { GNUNET_break_op (0); json_decref (contract_terms); return GNUNET_NO; } element = json_pack ("{s:O, s:o, s:o}", "order_id", order_id, "deposit_value", TALER_JSON_from_amount ( &entry->deposit_value), "deposit_fee", TALER_JSON_from_amount ( &entry->deposit_fee)); json_decref (contract_terms); if (NULL == element) { GNUNET_break_op (0); return GNUNET_NO; } GNUNET_break (0 == json_array_append_new (rctx->deposits_response, element)); return GNUNET_YES; } /** * 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 *rctx) { 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) { 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))) { /* 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; } } 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; 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); } rctx->deposits_response = json_array (); if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_iterate (map, &build_deposits_response, rctx)) goto cleanup; result_mod = json_copy ((struct json_t *) result); json_object_del (result_mod, "deposits"); json_object_set_new (result_mod, "deposits_sums", rctx->deposits_response); rctx->deposits_response = NULL; 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 rctx and signals MHD to resume * the connection. Also ensures MHD runs immediately. * * @param rctx 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 *rctx, unsigned int response_code, struct MHD_Response *response) { rctx->response_code = response_code; rctx->response = response; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming POST /transfers handling as exchange interaction is done (%u)\n", response_code); if (NULL != rctx->timeout_task) { GNUNET_SCHEDULER_cancel (rctx->timeout_task); rctx->timeout_task = NULL; } GNUNET_CONTAINER_DLL_remove (pth_head, pth_tail, rctx); MHD_resume_connection (rctx->connection); TMH_trigger_daemon (); /* we resumed, kick MHD */ } /** * Custom cleanup routine for a `struct TrackTransferContext`. * * @param cls the `struct TrackTransferContext` to clean up. */ static void track_transfer_cleanup (void *cls) { struct TrackTransferContext *rctx = cls; free_transfer_track_context (rctx); } /** * This function checks that the information about the coin which * was paid back by _this_ wire transfer matches what _we_ (the merchant) * knew about this coin. * * @param cls closure with our `struct TrackTransferContext *` * @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 * @param amount_with_fee amount the exchange will transfer for this coin * @param deposit_fee fee the exchange will charge for this coin * @param refund_fee fee the exchange will charge for refunding this coin * @param exchange_proof proof from exchange that coin was accepted */ static void check_transfer (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, const char *exchange_url, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_Amount *refund_fee, const struct TALER_Amount *wire_fee, const json_t *exchange_proof) { struct TrackTransferContext *rctx = cls; const struct TALER_TrackTransferDetails *ttd = rctx->current_detail; if (GNUNET_SYSERR == rctx->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, &ttd->coin_fee)) ) { /* Disagreement between the exchange and us about how much this coin is worth! */ GNUNET_break_op (0); rctx->check_transfer_result = GNUNET_SYSERR; /* Build the `TrackTransferConflictDetails` */ rctx->response = TALER_MHD_make_json_pack ( "{s:I, s:s, s:o, s:I, s:o, s:o, s:s, s:o, s:o}", "code", (json_int_t) TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS, "hint", "disagreement about deposit valuation", "exchange_deposit_proof", exchange_proof, "conflict_offset", (json_int_t) rctx->current_offset, "exchange_transfer_proof", rctx->original_response, "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), "h_contract_terms", GNUNET_JSON_from_data_auto ( &ttd->h_contract_terms), "amount_with_fee", TALER_JSON_from_amount (amount_with_fee), "deposit_fee", TALER_JSON_from_amount (deposit_fee)); return; } rctx->check_transfer_result = GNUNET_OK; } /** * Check that the given @a wire_fee is what the * @a exchange_pub should charge at the @a execution_time. * If the fee is correct (according to our database), * return #GNUNET_OK. If we do not have the fee structure * in our DB, we just accept it and return #GNUNET_NO; * if we have proof that the fee is bogus, we respond with * the proof to the client and return #GNUNET_SYSERR. * * @param rctx 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 * @return #GNUNET_SYSERR if we returned hard proof of * missbehavior from the exchange to the client */ static int check_wire_fee (struct TrackTransferContext *rctx, const json_t *json, struct GNUNET_TIME_Absolute execution_time, const struct TALER_Amount *wire_fee) { struct TALER_Amount expected_fee; struct TALER_Amount closing_fee; struct TALER_MasterSignatureP master_sig; struct GNUNET_TIME_Absolute start_date; struct GNUNET_TIME_Absolute end_date; enum GNUNET_DB_QueryStatus qs; char *wire_method; wire_method = TALER_payto_get_method (rctx->payto_uri); TMH_db->preflight (TMH_db->cls); qs = TMH_db->lookup_wire_fee (TMH_db->cls, &rctx->master_pub, wire_method, execution_time, &expected_fee, &closing_fee, &start_date, &end_date, &master_sig); if (0 >= qs) { 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 (&rctx->master_pub), wire_method, GNUNET_STRINGS_absolute_time_to_string (execution_time), TALER_amount2s (wire_fee)); GNUNET_free (wire_method); return GNUNET_NO; } if (0 <= TALER_amount_cmp (&expected_fee, wire_fee)) { GNUNET_free (wire_method); return GNUNET_OK; /* expected_fee >= wire_fee */ } /* Wire fee check failed, export proof to client */ resume_track_transfer_with_response ( rctx, 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, "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), "expected_closing_fee", TALER_JSON_from_amount (&closing_fee), "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 (&rctx->master_pub), "json", json)); GNUNET_free (wire_method); return GNUNET_SYSERR; } /** * Function called with detailed wire transfer data, including all * of the coin transactions that were combined into the wire transfer. * * @param cls closure * @param hr HTTP response details * @param exchange_pub public key of the exchange used to sign @a json * @param h_wire hash of the wire transfer address the transfer went to, or NULL on error * @param execution_time time when the exchange claims to have performed the wire transfer * @param total_amount total amount of the wire transfer, or NULL if the exchange could * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) * @param wire_fee wire fee that was charged by the exchange * @param details_length length of the @a details array * @param details array with details about the combined transactions */ static void wire_transfer_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute execution_time, const struct TALER_Amount *total_amount, const struct TALER_Amount *wire_fee, unsigned int details_length, const struct TALER_TrackTransferDetails *details) { struct TrackTransferContext *rctx = cls; json_t *jresponse; enum GNUNET_DB_QueryStatus qs; rctx->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 ( rctx, 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, "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, rctx->exchange_url, rctx->payto_uri, &rctx->wtid, total_amount, wire_fee, execution_time, details_length, details); 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 ( rctx, 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 (rctx, hr->reply, execution_time, wire_fee)) return; /* Now we want to double-check that any (Taler coin) deposit * which is accounted into _this_ wire transfer, does exist * into _our_ database. This is the rationale: if the * exchange paid us for it, we must have received it _beforehands_! * * details_length is how many (Taler coin) deposits have been * aggregated into _this_ wire transfer. */// for (unsigned int i = 0; icurrent_offset = i; rctx->current_detail = &details[i]; /* Set the coin as "never seen" before. */ rctx->check_transfer_result = GNUNET_NO; TMH_db->preflight (TMH_db->cls); qs = TMH_db->find_payments_by_hash_and_coin (TMH_db->cls, &details[i].h_contract_terms, &rctx->mi->pubkey, &details[i].coin_pub, &check_transfer, rctx); if (0 > qs) { /* single, read-only SQL statements should never cause serialization problems */ 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 (rctx, 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")); return; } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { /* The exchange says we made this deposit, but WE do not recall making it (corrupted / unreliable database?)! Well, let's say thanks and accept the money! */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to find payment data in DB\n"); rctx->check_transfer_result = GNUNET_OK; } if (GNUNET_NO == rctx->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 (rctx, 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__)); return; } if (GNUNET_SYSERR == rctx->check_transfer_result) { /* #check_transfer() failed, report conflict! */ GNUNET_break_op (0); GNUNET_assert (NULL != rctx->response); resume_track_transfer_with_response (rctx, MHD_HTTP_FAILED_DEPENDENCY, rctx->response); rctx->response = NULL; return; } /* Response is consistent with the /deposit we made, remember it for future reference */ for (unsigned int r = 0; rpreflight (TMH_db->cls); qs = TMH_db->store_coin_to_transfer (TMH_db->cls, &details[i].h_contract_terms, &details[i].coin_pub, &rctx->wtid); if (GNUNET_DB_STATUS_SOFT_ERROR != qs) break; } 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 (rctx, 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")); return; } } /* resume processing, main function will build the response */ resume_track_transfer_with_response (rctx, 0, NULL); } /** * Function called with the result of our exchange lookup. * * @param cls the `struct TrackTransferContext` * @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) { struct TrackTransferContext *rctx = cls; rctx->fo = NULL; if (MHD_HTTP_OK != hr->http_status) { /* The request failed somehow */ GNUNET_break_op (0); resume_track_transfer_with_response ( rctx, 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, "exchange_http_status", (json_int_t) hr->http_status, "exchange_code", (json_int_t) hr->ec, "exchange_reply", hr->reply)); return; } /* keep master key for later */ { const struct TALER_EXCHANGE_Keys *keys; keys = TALER_EXCHANGE_get_keys (eh); if (NULL == keys) { GNUNET_break (0); return GNUNET_NO; } rctx->master_pub = keys->master_pub; } rctx->wdh = TALER_EXCHANGE_transfers_get (eh, &rctx->wtid, &wire_transfer_cb, rctx); if (NULL == rctx->wdh) { GNUNET_break (0); resume_track_transfer_with_response ( rctx, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_MHD_make_json_pack ( "{s:I, s:s}", "code", (json_int_t) TALER_EC_TRACK_TRANSFER_REQUEST_ERROR, "error", "failed to run GET /transfers/ on exchange")); } } /** * 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 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 */ static void proof_cb (void *cls, const char *order_id, const struct TALER_Amount *deposit_value, const struct TALER_Amount *deposit_fee) { json_t *ja = cls; GNUNET_assert ( 0 == json_array_append_new ( ja, "{s:s,s:o,s:o}", "order_id", order_id, "deposit_value", TALER_JSON_spec_amount (deposit_value), "deposit_fee", TALER_JSON_spec_amount (deposit_fee))); } /** * Handle a timeout for the processing of the track transfer request. * * @param cls closure */ static void handle_track_transfer_timeout (void *cls) { struct TrackTransferContext *rctx = cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming POST /private/transfers with error after timeout\n"); rctx->timeout_task = NULL; if (NULL != rctx->fo) { TMH_EXCHANGES_find_exchange_cancel (rctx->fo); rctx->fo = NULL; } if (NULL != rctx->wdh) { TALER_EXCHANGE_transfers_get_cancel (rctx->wdh); rctx->wdh = NULL; } resume_track_transfer_with_response (rctx, MHD_HTTP_SERVICE_UNAVAILABLE, TALER_MHD_make_error ( TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT, "exchange not reachable")); } /** * Manages a POST /private/transfers call. It calls the GET /transfers/$WTID * offered by the exchange in order to obtain the set of transfers * (of coins) associated with a given wire transfer. * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] hc context with further information about the request * @return MHD result code */ MHD_RESULT TMH_private_post_transfers (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct TrackTransferContext *rctx = hc->ctx; const char *str; const char *url; const char *wire_method; enum GNUNET_DB_QueryStatus qs; if (NULL == rctx) { rctx = GNUNET_new (struct TrackTransferContext); rctx->connection = connection; hc->ctx = rctx; hc->cleanup_cb = &track_transfer_cleanup; } if (0 != rctx->response_code) { MHD_RESULT ret; /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == rctx->response_code) { GNUNET_break (0); return MHD_NO; /* hard error */ } ret = MHD_queue_response (connection, rctx->response_code, rctx->response); if (NULL != rctx->response) { MHD_destroy_response (rctx->response); rctx->response = NULL; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Queueing response (%u) for POST /private/transfers (%s).\n", (unsigned int) rctx->response_code, ret ? "OK" : "FAILED"); return ret; } if ( (NULL != rctx->fo) || (NULL != rctx->wdh) ) { /* likely old MHD version */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Not sure why we are here, should be suspended\n"); return MHD_YES; /* still work in progress */ } { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("exchange", &rctx->exchange_url), GNUNET_JSON_spec_string ("payto_uri", &rctx->payto_uri), TALER_JSON_spec_amount ("amount", &rctx->amount), GNUNET_JSON_spec_fixed_auto ("wtid", &rctx->wtid), GNUNET_JSON_spec_end () }; GNUNET_assert (NULL != mi); { enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, hc->request_body, spec); if (GNUNET_OK != res) return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } } /* Check if reply is already in database! */ { json_t *deposit_sums; 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); qs = TMH_db->lookup_transfer_details (TMH_db->cls, rctx->exchange_url, rctx->payto_uri, &rctx->wtid, &total_amount, &wire_fee, &execution_time, &transfer_details_cb, deposit_sums); 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); 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"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs) { return TALER_MHD_reply_json_pack ( connection, MHD_HTTP_OK, "{s:o,s:o,s:o,s:o}", "total", TALER_JSON_from_amount (&total_amount), "wire_fee", TALER_JSON_from_amount (&wire_fee), "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, rctx->exchange_url, &rctx->wtid, &rctx->amount, rctx->payto_uri); 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); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_POST_TRANSFER_DB_STORE_ERROR, "Fail to update database with transfer record"); } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending POST /private/transfers handling while working with exchange\n"); MHD_suspend_connection (connection); GNUNET_CONTAINER_DLL_insert (pth_head, pth_tail, rctx); rctx->fo = TMH_EXCHANGES_find_exchange (rctx->exchange_url, NULL, GNUNET_NO, &process_track_transfer_with_exchange, rctx); rctx->timeout_task = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT, &handle_track_transfer_timeout, rctx); return MHD_YES; } /* end of taler-merchant-httpd_private-post-transfers.c */