diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-05-09 17:13:47 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-05-09 17:13:47 +0200 |
commit | 47811a60b692e575274305d4cac429220d27a328 (patch) | |
tree | 94632a2e10476b84070a8873d31662ff132c3b83 /src | |
parent | 7b3fd48e42192c59b8eb402ce3e91187deac7fae (diff) | |
download | merchant-47811a60b692e575274305d4cac429220d27a328.tar.gz merchant-47811a60b692e575274305d4cac429220d27a328.tar.bz2 merchant-47811a60b692e575274305d4cac429220d27a328.zip |
towards GET /transfers
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-transfers.c | 1164 |
1 files changed, 157 insertions, 1007 deletions
diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c b/src/backend/taler-merchant-httpd_private-get-transfers.c index 7f55c917..23163129 100644 --- a/src/backend/taler-merchant-httpd_private-get-transfers.c +++ b/src/backend/taler-merchant-httpd_private-get-transfers.c @@ -14,8 +14,8 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ /** - * @file backend/taler-merchant-httpd_track-transfer.c - * @brief implement API for tracking transfers and wire transfers + * @file backend/taler-merchant-httpd_private-get-transfers.c + * @brief implement API for obtaining a list of wire transfers * @author Marcello Stanisci * @author Christian Grothoff */ @@ -31,1058 +31,208 @@ /** - * 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 /track/transfer requests. - */ -struct TrackTransferContext -{ - - /** - * This MUST be first! - */ - struct TM_HandlerContext hc; - - /** - * Handle to the exchange. - */ - struct TALER_EXCHANGE_Handle *eh; - - /** - * 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; - - /** - * URL of the exchange. - */ - char *url; - - /** - * Wire method used for the transfer. - */ - char *wire_method; - - /** - * Pointer to the detail that we are currently - * checking in #check_transfer(). - */ - const struct TALER_TrackTransferDetails *current_detail; - - /** - * Argument for the /wire/transfers request. - */ - struct TALER_WireTransferIdentifierRawP wtid; - - /** - * Full original response we are currently processing. - */ - const json_t *original_response; - - /** - * Modified response to return to the frontend. - */ - json_t *deposits_response; - - /** - * 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; -}; - - -/** - * Represents an entry in the table used to sum up - * individual deposits for each h_contract_terms. - */ -struct Entry -{ - - /** - * Sum accumulator for deposited value. - */ - struct TALER_Amount deposit_value; - - /** - * Sum accumulator for deposit fee. - */ - struct TALER_Amount deposit_fee; - -}; - - -/** - * Free the @a rctx. + * Function called with information about a wire transfer. + * Generate a response (array entry) based on the given arguments. * - * @param rctx data to free + * @param cls closure with a `json_t *` array to build up the response + * @param credit_amount how much was wired to the merchant (minus fees) + * @param wtid wire transfer identifier + * @param payto_uri target account that received the wire transfer + * @param exchange_url base URL of the exchange that made the wire transfer + * @param transfer_serial_id serial number identifying the transfer in the backend + * @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_ABS + * if it did not yet happen + * @param verified YES if we checked the exchange's answer and liked it, + * NO if we checked the exchange's answer and it is problematic, + * ALL if we did not yet check */ static void -free_transfer_track_context (struct TrackTransferContext *rctx) +transfer_cb (void *cls, + const struct TALER_Amount *credit_amount, + const struct TALER_WireTransferIdentifierRawP *wtid, + const char *payto_uri, + const char *exchange_url, + uint64_t transfer_serial_id, + struct GNUNET_TIME_Absolute execution_time, + enum TALER_MERCHANTDB_YesNoAll verified) { - 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); -} + json_t *ja = cls; -/** - * 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. + * Manages a GET /private/transfers call. * - * @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. + * @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 */ -static int -build_deposits_response (void *cls, - const struct GNUNET_HashCode *key, - void *value) +MHD_RESULT +TMH_private_get_transfers (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) { - struct TrackTransferContext *rctx = cls; - struct Entry *entry = value; - json_t *element; - json_t *contract_terms; - json_t *order_id; - - db->preflight (db->cls); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - db->find_contract_terms_from_hash (db->cls, - &contract_terms, - key, - &rctx->mi->pubkey)) - { - GNUNET_break_op (0); - return GNUNET_NO; + const char *payto_uri; + struct GNUNET_TIME_Absolute before = GNUNET_TIME_UNIT_FOREVER_ABS; + struct GNUNET_TIME_Absolute after = GNUNET_TIME_UNIT_ZERO_ABS; + int64_t limit = -20; /* FIXME: default? Nothing documented!!? */ + uint64_t offset; + enum TALER_MERCHANTDB_YesNoAll verified; + + payto_uri = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "payto_uri"); + { + const char *before_s; + + before_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "before"); + if ( (NULL != before_s) && + (GNUNET_OK != + GNUNET_STRINGS_fancy_time_to_absolute (before_s, + &before)) ) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "before"); } - - order_id = json_object_get (contract_terms, - "order_id"); - if (NULL == order_id) { - GNUNET_break_op (0); - json_decref (contract_terms); - return GNUNET_NO; + const char *after_s; + + after_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "after"); + if ( (NULL != after_s) && + (GNUNET_OK != + GNUNET_STRINGS_fancy_time_to_absolute (after_s, + &after)) ) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "after"); } - 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"); + const char *limit_s; - json_array_foreach (deposits, index, value) - { - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, - NULL)) + limit_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "limit"); + if (NULL != limit_s) { - GNUNET_break_op (0); - return NULL; + char dummy[2]; + long long l; + + if (1 != + sscanf (limit_s, + "%lld%1s", + &l, + dummy)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "limit"); + limit = (int64_t) l; } - GNUNET_CRYPTO_hash_from_string (key, - &h_key); + } + { + const char *offset_s; - if (NULL != (current_entry = - GNUNET_CONTAINER_multihashmap_get (map, - &h_key))) + offset_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "offset"); + if (NULL != offset_s) { - /* 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; - } + char dummy[2]; + unsigned long long o; + + if (1 != + sscanf (limit_s, + "%llu%1s", + &o, + dummy)) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "offset"); + offset = (uint64_t) o; } 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; - } + if (limit > 0) + offset = INT64_MAX; + else + offset = 0; } - 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 /track/transfer 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; - } - MHD_resume_connection (rctx->connection); - TMH_trigger_daemon (); /* we resumed, kick MHD */ -} - - -/** - * Custom cleanup routine for a `struct TrackTransferContext`. - * - * @param hc the `struct TrackTransferContext` to clean up. - */ -static void -track_transfer_cleanup (struct TM_HandlerContext *hc) -{ - struct TrackTransferContext *rctx = (struct TrackTransferContext *) hc; + const char *verified_s; - 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) -{ - const struct TALER_MasterPublicKeyP *master_pub; - struct GNUNET_HashCode h_wire_method; - 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; - const struct TALER_EXCHANGE_Keys *keys; - - keys = TALER_EXCHANGE_get_keys (rctx->eh); - if (NULL == keys) - { - GNUNET_break (0); - return GNUNET_NO; - } - master_pub = &keys->master_pub; - GNUNET_CRYPTO_hash (rctx->wire_method, - strlen (rctx->wire_method) + 1, - &h_wire_method); - db->preflight (db->cls); - qs = db->lookup_wire_fee (db->cls, - master_pub, - &h_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 (master_pub), - rctx->wire_method, - GNUNET_STRINGS_absolute_time_to_string (execution_time), - TALER_amount2s (wire_fee)); - return GNUNET_NO; - } - if (0 <= TALER_amount_cmp (&expected_fee, - wire_fee)) - 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 (master_pub), - "json", json)); - 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 /track/transfer\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; - } - for (unsigned int i = 0; i<MAX_RETRIES; i++) - { - db->preflight (db->cls); - qs = db->store_transfer_to_proof (db->cls, - rctx->url, - &rctx->wtid, - execution_time, - exchange_pub, - hr->reply); - 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_TRANSFER_ERROR, - "details", - "failed to store response from exchange to local database")); - return; - } - rctx->original_response = hr->reply; - - 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; i<details_length; i++) - { - rctx->current_offset = i; - rctx->current_detail = &details[i]; - /* Set the coin as "never seen" before. */ - rctx->check_transfer_result = GNUNET_NO; - db->preflight (db->cls); - qs = db->find_payments_by_hash_and_coin (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) + verified_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "verified"); + if (NULL == verified_s) { - /* 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; + verified = TALER_MERCHANTDB_YNA_ALL; } - 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; r<MAX_RETRIES; r++) - { - db->preflight (db->cls); - qs = db->store_coin_to_transfer (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; - } - } - rctx->original_response = NULL; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "About to call tracks transformator.\n"); - - if (NULL == (jresponse = - transform_response (hr->reply, - rctx))) - { - resume_track_transfer_with_response - (rctx, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_MHD_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, - "Fail to elaborate the response.")); - return; - } - - resume_track_transfer_with_response (rctx, - MHD_HTTP_OK, - TALER_MHD_make_json (jresponse)); - json_decref (jresponse); -} - - -/** - * 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; - } - rctx->eh = eh; - 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 /transfers/ GET on exchange")); - } -} - - -/** - * 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 /track/transfer with error after timeout\n"); - rctx->timeout_task = NULL; - - if (NULL != rctx->fo) - { - TMH_EXCHANGES_find_exchange_cancel (rctx->fo); - rctx->fo = NULL; - } - resume_track_transfer_with_response (rctx, - MHD_HTTP_SERVICE_UNAVAILABLE, - TALER_MHD_make_error ( - TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT, - "exchange not reachable")); -} - - -/** - * Function called with information about a wire transfer identifier. - * Generate a response based on the given @a proof. - * - * @param cls closure - * @param proof proof from exchange about what the wire transfer was for. - * should match the `TrackTransactionResponse` format - * of the exchange - */ -static void -proof_cb (void *cls, - const json_t *proof) -{ - struct TrackTransferContext *rctx = cls; - json_t *transformed_response; - - if (NULL == (transformed_response = - transform_response (proof, - rctx))) - { - rctx->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - rctx->response - = TALER_MHD_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, - "Fail to elaborate response."); - return; - } - - rctx->response_code = MHD_HTTP_OK; - rctx->response = TALER_MHD_make_json (transformed_response); - json_decref (transformed_response); -} - - -/** - * Manages a /track/transfer call, thus it calls the /track/wtid - * offered by the exchange in order to return 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] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, never NULL - * @return MHD result code - */ -MHD_RESULT -MH_handler_track_transfer (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi) -{ - struct TrackTransferContext *rctx; - const char *str; - const char *url; - const char *wire_method; - MHD_RESULT ret; - enum GNUNET_DB_QueryStatus qs; - - if (NULL == *connection_cls) - { - rctx = GNUNET_new (struct TrackTransferContext); - rctx->hc.cc = &track_transfer_cleanup; - rctx->connection = connection; - *connection_cls = rctx; - } - else - { - /* not first call, recover state */ - rctx = *connection_cls; - } - - if (0 != rctx->response_code) - { - /* We are *done* processing the request, just queue the response (!) */ - if (UINT_MAX == rctx->response_code) + else if (0 == strcasecmp (verified_s, + "yes")) { - GNUNET_break (0); - return MHD_NO; /* hard error */ + verified = TALER_MERCHANTDB_YNA_YES; } - ret = MHD_queue_response (connection, - rctx->response_code, - rctx->response); - if (NULL != rctx->response) + else if (0 == strcasecmp (verified_s, + "no")) { - MHD_destroy_response (rctx->response); - rctx->response = NULL; + verified = TALER_MERCHANTDB_YNA_NO; } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response (%u) for /track/transfer (%s).\n", - (unsigned int) rctx->response_code, - ret ? "OK" : "FAILED"); - return ret; - } - if ( (NULL != rctx->fo) || - (NULL != rctx->eh) ) - { - /* 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 */ - } - - url = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "exchange"); - if (NULL == url) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MISSING, - "exchange"); - rctx->url = GNUNET_strdup (url); - - /* FIXME: change again: we probably don't want the wire_method - but rather the _account_ (section) here! */ - wire_method = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "wire_method"); - if (NULL == wire_method) - { - if (1) + else if (0 == strcasecmp (verified_s, + "all")) { - /* temporary work-around until demo is adjusted... */ - GNUNET_break (0); - wire_method = "x-taler-bank"; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Client needs fixing, see API change for #4943!\n"); + verified = TALER_MERCHANTDB_YNA_ALL; } else return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MISSING, - "wire_method"); - } - rctx->wire_method = GNUNET_strdup (wire_method); - rctx->mi = mi; - str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "wtid"); - if (NULL == str) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MISSING, - "wtid"); - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (str, - strlen (str), - &rctx->wtid, - sizeof (rctx->wtid))) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MALFORMED, - "wtid"); + TALER_EC_PARAMETER_MALFORMED, + "verified"); } - /* Check if reply is already in database! */ db->preflight (db->cls); - qs = db->find_proof_by_wtid (db->cls, - rctx->url, - &rctx->wtid, - &proof_cb, - rctx); - 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_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR, - "Fail to query database about proofs"); - } - if (0 != rctx->response_code) { - ret = MHD_queue_response (connection, - rctx->response_code, - rctx->response); - if (NULL != rctx->response) + json_t *ja; + enum GNUNET_DB_QueryStatus qs; + + ja = json_array (); + GNUNET_assert (NULL != ja); + qs = TMH_db->lookup_transfers (TMH_db->cls, + hc->instance->settings.id, + payto_uri, + before, + after, + limit, + offset, + verified, + &transfer_cb, + ja); + if (0 > qs) { - MHD_destroy_response (rctx->response); - rctx->response = NULL; + /* 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_GET_TRANSFERS_DB_FETCH_DEPOSIT_ERROR, + "Fail to query database about transfers"); } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response (%u) for /track/transfer (%s).\n", - (unsigned int) rctx->response_code, - ret ? "OK" : "FAILED"); - return ret; + return MHD_MHD_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o}", + "transfers", ja); } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Suspending /track/transfer handling while working with the exchange\n"); - MHD_suspend_connection (connection); - rctx->fo = TMH_EXCHANGES_find_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; } |