/* 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_track-transaction.c * @brief implement API for tracking deposits and wire transfers * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd.h" #include "taler_merchant_service.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_auditors.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_track-transaction.h" /** * Information about a wire transfer for a /track/transaction response. */ struct TransactionWireTransfer { /** * Wire transfer identifier this struct is about. */ struct TALER_WireTransferIdentifierRawP wtid; /** * When was this wire transfer executed? */ struct GNUNET_TIME_Absolute execution_time; /** * Number of coins of the selected transaction that * is covered by this wire transfer. */ unsigned int num_coins; /** * Information about the coins of the selected transaction * that are part of the wire transfer. */ struct TALER_MERCHANT_CoinWireTransfer *coins; /** * URL of the exchange that executed the wire transfer. */ char *exchange_url; }; /** * Map containing all the known merchant instances */ extern struct GNUNET_CONTAINER_MultiHashMap *by_id_map; /** * 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 /** * Generate /track/transaction response. * * @param num_transfers how many wire transfers make up the transaction * @param transfers data on each wire transfer * @return MHD response object */ static struct MHD_Response * make_track_transaction_ok (unsigned int num_transfers, const struct TransactionWireTransfer *transfers) { json_t *j_transfers; j_transfers = json_array (); for (unsigned int i = 0; icoins[0].amount_with_fee; for (unsigned int j = 1; jnum_coins; j++) { const struct TALER_MERCHANT_CoinWireTransfer *coin = &transfer->coins[j]; GNUNET_assert (0 <= TALER_amount_add (&sum, &sum, &coin->amount_with_fee)); } GNUNET_assert (0 == json_array_append_new ( j_transfers, json_pack ( "{s:s, s:o, s:o, s:o}", "exchange", transfer->exchange_url, "wtid", GNUNET_JSON_from_data_auto (&transfer->wtid), "execution_time", GNUNET_JSON_from_time_abs (transfer->execution_time), "amount", TALER_JSON_from_amount (&sum)))); } { struct MHD_Response *ret; ret = TALER_MHD_make_json (j_transfers); json_decref (j_transfers); return ret; } } /** * Context for a /track/transaction operation. */ struct TrackTransactionContext; /** * Merchant instance being tracked */ struct MerchantInstance; /** * Information we keep for each coin in a /track/transaction operation. */ struct TrackCoinContext { /** * Kept in a DLL. */ struct TrackCoinContext *next; /** * Kept in a DLL. */ struct TrackCoinContext *prev; /** * Our context for a /track/transaction operation. */ struct TrackTransactionContext *tctx; /** * Public key of the coin. */ struct TALER_CoinSpendPublicKeyP coin_pub; /** * Exchange that was used for the transaction. */ char *exchange_url; /** * Handle for the request to resolve the WTID for this coin. */ struct TALER_EXCHANGE_DepositGetHandle *dwh; /** * Wire transfer identifier for this coin. */ struct TALER_WireTransferIdentifierRawP wtid; /** * Execution time of the wire transfer @e wtid. */ struct GNUNET_TIME_Absolute execution_time; /** * Value of the coin including deposit fee. */ struct TALER_Amount amount_with_fee; /** * Deposit fee for the coin. */ struct TALER_Amount deposit_fee; /** * Have we obtained the WTID for this coin yet? */ int have_wtid; }; /** * Context for a /track/transaction operation. */ struct TrackTransactionContext { /** * This field MUST be first. */ struct TM_HandlerContext hc; /** * HTTP request we are handling. */ struct MHD_Connection *connection; /** * Kept in a DLL. */ struct TrackCoinContext *tcc_head; /** * Kept in a DLL. */ struct TrackCoinContext *tcc_tail; /** * Task run on timeout. */ struct GNUNET_SCHEDULER_Task *timeout_task; /** * 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; /** * Handle to our exchange, once we found it. */ struct TALER_EXCHANGE_Handle *eh; /** * URL of the exchange we currently have in @e eh. */ const char *current_exchange; /** * Handle we use to resolve transactions for a given WTID. */ struct TALER_EXCHANGE_TransfersGetHandle *wdh; /** * Response to return upon resume. */ struct MHD_Response *response; /** * Wire transfer identifier we are currently looking up in @e wdh. */ struct TALER_WireTransferIdentifierRawP current_wtid; /** * Execution time of the wire transfer we are currently looking up in @e wdh. */ struct GNUNET_TIME_Absolute current_execution_time; /** * Hash of wire details for the transaction. */ struct GNUNET_HashCode h_wire; /** * Timestamp of the transaction. */ struct GNUNET_TIME_Absolute timestamp; /** * Refund deadline for the transaction. */ struct GNUNET_TIME_Absolute refund_deadline; /** * Total value of the transaction. */ struct TALER_Amount total_amount; /** * Transaction this request is about. */ const char *transaction_id; /** * Proposal's hashcode. */ struct GNUNET_HashCode h_contract_terms; /** * Response code to return upon resume. */ unsigned int response_code; /** * Which merchant instance is being tracked */ struct MerchantInstance *mi; /** * Set to negative values in #coin_cb() if we encounter * a database problem. */ enum GNUNET_DB_QueryStatus qs; }; /** * Free the @a tctx. * * @param tctx data to free */ static void free_tctx (struct TrackTransactionContext *tctx) { struct TrackCoinContext *tcc; while (NULL != (tcc = tctx->tcc_head)) { GNUNET_CONTAINER_DLL_remove (tctx->tcc_head, tctx->tcc_tail, tcc); if (NULL != tcc->dwh) { TALER_EXCHANGE_deposits_get_cancel (tcc->dwh); tcc->dwh = NULL; } if (NULL != tcc->exchange_url) { GNUNET_free (tcc->exchange_url); tcc->exchange_url = NULL; } GNUNET_free (tcc); } if (NULL != tctx->wdh) { TALER_EXCHANGE_transfers_get_cancel (tctx->wdh); tctx->wdh = NULL; } if (NULL != tctx->fo) { TMH_EXCHANGES_find_exchange_cancel (tctx->fo); tctx->fo = NULL; } if (NULL != tctx->timeout_task) { GNUNET_SCHEDULER_cancel (tctx->timeout_task); tctx->timeout_task = NULL; } GNUNET_free (tctx); } /** * Custom cleanup routine for a `struct TrackTransactionContext`. * * @param hc the `struct PayContext` to clean up. */ static void track_transaction_cleanup (struct TM_HandlerContext *hc) { struct TrackTransactionContext *tctx = (struct TrackTransactionContext *) hc; free_tctx (tctx); } /** * Resume the given /track/transaction operation and send the given * response. Stores the response in the @a tctx and signals MHD to * resume the connection. Also ensures MHD runs immediately. * * @param tctx transaction tracking context * @param response_code response code to use * @param response response data to send back */ static void resume_track_transaction_with_response (struct TrackTransactionContext *tctx, unsigned int response_code, struct MHD_Response *response) { tctx->response_code = response_code; tctx->response = response; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /track/transaction handling as exchange interaction is done (%u)\n", response_code); if (NULL != tctx->timeout_task) { GNUNET_SCHEDULER_cancel (tctx->timeout_task); tctx->timeout_task = NULL; } MHD_resume_connection (tctx->connection); TMH_trigger_daemon (); /* we resumed, kick MHD */ } /** * This function is called to trace the wire transfers for * all of the coins of the transaction of the @a tctx. Once * we have traced all coins, we build the response. * * @param tctx track context with established connection to exchange */ static void trace_coins (struct TrackTransactionContext *tctx); /** * Function called with detailed wire transfer data, including all * of the coin transactions that were combined into the wire transfer. * * We now store this information. Then we check if we still have * any coins of the original wire transfer not taken care of. * * @param cls closure * @param hr HTTP response details * @param exchange_pub public key of the exchange used for signing * @param execution_time time when the exchange claims to have performed the wire transfer * @param wtid extracted wire transfer identifier, or NULL if the exchange could * not provide any (set only if @a http_status is #MHD_HTTP_OK) * @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_deposits_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 TrackTransactionContext *tctx = cls; enum GNUNET_DB_QueryStatus qs; tctx->wdh = NULL; if (MHD_HTTP_OK != hr->http_status) { GNUNET_break_op (0); resume_track_transaction_with_response ( tctx, MHD_HTTP_FAILED_DEPENDENCY, TALER_MHD_make_json_pack ( "{s:I, s:I, s:I, s:O}", "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_WIRE_TRANSFER_TRACE_ERROR, "exchange_http_status", (json_int_t) hr->http_status, "exchange_code", (json_int_t) hr->ec, "exchange_reply", hr->reply)); return; } for (unsigned int i = 0; ipreflight (db->cls); qs = db->store_transfer_to_proof (db->cls, tctx->current_exchange, &tctx->current_wtid, tctx->current_execution_time, exchange_pub, hr->reply); if (GNUNET_DB_STATUS_SOFT_ERROR != qs) break; } if (0 > qs) { /* Not good, but not fatal either, log error and continue */ /* 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); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to store transfer-to-proof mapping in DB\n"); } for (struct TrackCoinContext *tcc = tctx->tcc_head; NULL != tcc; tcc = tcc->next) { if (GNUNET_YES == tcc->have_wtid) continue; for (unsigned int d = 0; dcoin_pub)) { tcc->wtid = tctx->current_wtid; tcc->execution_time = tctx->current_execution_time; tcc->have_wtid = GNUNET_YES; } for (unsigned int i = 0; ipreflight (db->cls); qs = db->store_coin_to_transfer (db->cls, &details[d].h_contract_terms, &details[d].coin_pub, &tctx->current_wtid); if (GNUNET_DB_STATUS_SOFT_ERROR != qs) break; } if (0 > qs) { /* Not good, but not fatal either, log error and continue */ /* 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); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to store coin-to-transfer mapping in DB\n"); } } } /* Continue tracing (will also handle case that we are done) */ trace_coins (tctx); } /** * Closure for #proof_cb(). */ struct ProofCheckContext { /** * Proof returned from #proof_cb. The reference counter was * increased for this reference and it must thus be freed. * NULL if we did not find any proof. The JSON should * match the `TrackTransferResponse` of the exchange API * (https://api.taler.net/api-exchange.html#tracktransferresponse) */ json_t *p_ret; }; /** * Function called with information about a wire transfer identifier. * We actually never expect this to be called. * * @param cls closure with a `struct ProofCheckContext` * @param proof proof from exchange about what the wire transfer was for */ static void proof_cb (void *cls, const json_t *proof) { struct ProofCheckContext *pcc = cls; GNUNET_break (NULL == pcc->p_ret); pcc->p_ret = json_incref ((json_t *) proof); } /** * This function takes the wtid from the coin being tracked * and _track_ it against the exchange. This way, we know * all the other coins which were aggregated together with * this one. This way we save further HTTP requests to track * the other coins. * * @param cls closure with a `struct TrackCoinContext` * @param hr HTTP response details * @param exchange_pub public key of the exchange used for signing @a json * @param wtid wire transfer identifier used by the exchange, NULL if exchange did not * yet execute the transaction * @param execution_time actual or planned execution time for the wire transfer * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL) */ static void wtid_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_WireTransferIdentifierRawP *wtid, struct GNUNET_TIME_Absolute execution_time, const struct TALER_Amount *coin_contribution) { struct TrackCoinContext *tcc = cls; struct TrackTransactionContext *tctx = tcc->tctx; struct ProofCheckContext pcc; enum GNUNET_DB_QueryStatus qs; tcc->dwh = NULL; if (MHD_HTTP_OK != hr->http_status) { if (MHD_HTTP_ACCEPTED == hr->http_status) { resume_track_transaction_with_response ( tcc->tctx, MHD_HTTP_ACCEPTED, /* Return verbatim what the exchange said. */ TALER_MHD_make_json (hr->reply)); return; } /* Transaction not resolved for one of the coins, report error! */ resume_track_transaction_with_response ( tcc->tctx, MHD_HTTP_FAILED_DEPENDENCY, TALER_MHD_make_json_pack ( "{s:I, s:I, s:I, s:O}", "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_COIN_TRACE_ERROR, "exchange_http_status", (json_int_t) hr->http_status, "exchange_code", (json_int_t) hr->ec, "exchange_reply", hr->reply)); return; } tctx->current_wtid = *wtid; tctx->current_execution_time = execution_time; pcc.p_ret = NULL; /* attempt to find this wtid's track from our database, Will make pcc.p_ret point to a "proof", if one exists. */ db->preflight (db->cls); qs = db->find_proof_by_wtid (db->cls, tctx->current_exchange, wtid, &proof_cb, &pcc); 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); resume_track_transaction_with_response ( tcc->tctx, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_MHD_make_error ( TALER_EC_TRACK_TRANSACTION_DB_FETCH_TRANSACTION_ERROR, "Fail to query database about proofs")); return; } if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { /* How come this wtid was already stored into the database and _not all_ of its coins were already tracked? Inconsistent state (! At least regarding what the exchange tells us) */ GNUNET_break_op (0); resume_track_transaction_with_response ( tcc->tctx, MHD_HTTP_FAILED_DEPENDENCY, TALER_MHD_make_json_pack ( "{s:I, s:s, s:O, s:o, s:o}", "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_CONFLICTING_REPORTS, "hint", "conflicting transfer data from exchange", "transaction_tracking_claim", hr->reply, "wtid_tracking_claim", pcc.p_ret, "coin_pub", GNUNET_JSON_from_data_auto (&tcc->coin_pub))); return; } tctx->wdh = TALER_EXCHANGE_transfers_get (tctx->eh, wtid, &wire_deposits_cb, tctx); } /** * We have obtained all WTIDs, now prepare the response * * @param tctx handle for the operation */ static void generate_response (struct TrackTransactionContext *tctx) { unsigned int num_wtid = 0; /* count how many disjoint wire transfer identifiers there are; note that there should only usually be one, so while this is worst-case O(n^2), in pracitce this is O(n) */ for (struct TrackCoinContext *tcc = tctx->tcc_head; NULL != tcc; tcc = tcc->next) { int found = GNUNET_NO; for (struct TrackCoinContext *tcc2 = tctx->tcc_head; tcc2 != tcc; tcc2 = tcc2->next) { if (0 == GNUNET_memcmp (&tcc->wtid, &tcc2->wtid)) { found = GNUNET_YES; break; } } if (GNUNET_NO == found) num_wtid++; } { /* on-stack allocation is fine, as the number of coins and the number of wire-transfers per-transaction is expected to be tiny. */ struct TransactionWireTransfer wts[num_wtid]; unsigned int wtid_off; wtid_off = 0; for (struct TrackCoinContext *tcc = tctx->tcc_head; NULL != tcc; tcc = tcc->next) { int found = GNUNET_NO; for (struct TrackCoinContext *tcc2 = tctx->tcc_head; tcc2 != tcc; tcc2 = tcc2->next) { if (0 == GNUNET_memcmp (&tcc->wtid, &tcc2->wtid)) { found = GNUNET_YES; break; } } if (GNUNET_NO == found) { unsigned int num_coins; struct TransactionWireTransfer *wt; wt = &wts[wtid_off++]; wt->wtid = tcc->wtid; wt->exchange_url = tcc->exchange_url; wt->execution_time = tcc->execution_time; /* count number of coins with this wtid */ num_coins = 0; for (struct TrackCoinContext *tcc2 = tctx->tcc_head; NULL != tcc2; tcc2 = tcc2->next) { if (0 == GNUNET_memcmp (&wt->wtid, &tcc2->wtid)) num_coins++; } /* initialize coins array */ wt->num_coins = num_coins; wt->coins = GNUNET_new_array (num_coins, struct TALER_MERCHANT_CoinWireTransfer); num_coins = 0; for (struct TrackCoinContext *tcc2 = tctx->tcc_head; NULL != tcc2; tcc2 = tcc2->next) { if (0 == GNUNET_memcmp (&wt->wtid, &tcc2->wtid)) { struct TALER_MERCHANT_CoinWireTransfer *coin = &wt->coins[num_coins++]; coin->coin_pub = tcc2->coin_pub; coin->amount_with_fee = tcc2->amount_with_fee; coin->deposit_fee = tcc2->deposit_fee; } } } /* GNUNET_NO == found */ } /* for all tcc */ GNUNET_assert (wtid_off == num_wtid); { struct MHD_Response *resp; resp = make_track_transaction_ok (num_wtid, wts); for (wtid_off = 0; wtid_off < num_wtid; wtid_off++) GNUNET_free (wts[wtid_off].coins); resume_track_transaction_with_response (tctx, MHD_HTTP_OK, resp); } } /* end of scope for 'wts' and 'resp' */ } /** * Find the exchange to trace the next coin(s). * * @param tctx operation context */ static void find_exchange (struct TrackTransactionContext *tctx); /** * This function is called to 'trace the wire transfers' * (true?) for all of the coins of the transaction of the @a tctx. * Once we have traced all coins, we build the response. * * @param tctx track context with established connection to exchange */ static void trace_coins (struct TrackTransactionContext *tctx) { struct TrackCoinContext *tcc; /* Make sure we are connected to the exchange. */ GNUNET_assert (NULL != tctx->eh); for (tcc = tctx->tcc_head; NULL != tcc; tcc = tcc->next) /* How come one doesn't have wtid? */ if (GNUNET_YES != tcc->have_wtid) break; if (NULL != tcc) { if (0 != strcmp (tcc->exchange_url, tctx->current_exchange)) { /* exchange changed, find matching one first! */ tctx->eh = NULL; tctx->current_exchange = NULL; find_exchange (tctx); return; } /* we are not done requesting WTIDs from the current exchange; do the next one */ tcc->dwh = TALER_EXCHANGE_deposits_get (tctx->eh, &tctx->mi->privkey, &tctx->h_wire, &tctx->h_contract_terms, &tcc->coin_pub, &wtid_cb, tcc); return; } tctx->current_exchange = NULL; tctx->eh = NULL; generate_response (tctx); } /** * Function called with the result of our exchange lookup. * Merely provide the execution context to the routine actually * tracking the coin. * * @param cls the `struct TrackTransactionContext` * @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_transaction_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 TrackTransactionContext *tctx = cls; tctx->fo = NULL; if (MHD_HTTP_OK != hr->http_status) { /* The request failed somehow */ GNUNET_break_op (0); resume_track_transaction_with_response ( tctx, 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_TRANSACTION_EXCHANGE_KEYS_FAILURE, "exchange_http_status", (json_int_t) hr->http_status, "exchange_code", (json_int_t) hr->ec, "exchange_reply", hr->reply)); return; } tctx->eh = eh; trace_coins (tctx); } /** * Handle a timeout for the processing of the track transaction request. * * @param cls closure */ static void handle_track_transaction_timeout (void *cls) { struct TrackTransactionContext *tctx = cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /track/transaction with error after timeout\n"); tctx->timeout_task = NULL; if (NULL != tctx->fo) { TMH_EXCHANGES_find_exchange_cancel (tctx->fo); tctx->fo = NULL; } resume_track_transaction_with_response (tctx, MHD_HTTP_SERVICE_UNAVAILABLE, TALER_MHD_make_error ( TALER_EC_PAY_EXCHANGE_TIMEOUT, "exchange not reachable")); } /** * Information about the wire transfer corresponding to * a deposit operation. Note that it is in theory possible * that we have a @a transaction_id and @a coin_pub in the * result that do not match a deposit that we know about, * for example because someone else deposited funds into * our account. * * @param cls closure * @param transaction_id ID of the contract * @param coin_pub public key of the coin * @param wtid identifier of the wire transfer in which the exchange * send us the money for the coin deposit * @param execution_time when was the wire transfer executed? * @param exchange_proof proof from exchange about what the deposit was for * NULL if we have not asked for this signature */ static void transfer_cb (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_WireTransferIdentifierRawP *wtid, struct GNUNET_TIME_Absolute execution_time, const json_t *exchange_proof) { struct TrackCoinContext *tcc = cls; if (0 != GNUNET_memcmp (coin_pub, &tcc->coin_pub)) return; tcc->wtid = *wtid; tcc->execution_time = execution_time; tcc->have_wtid = GNUNET_YES; } /** * Responsible to get the current coin wtid and store it into its state. * * @param cls closure * @param transaction_id of the contract * @param coin_pub public key of the coin * @param exchange_url URL of exchange that issued @a coin_pub * @param amount_with_fee amount the exchange will deposit 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 coin_cb (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 TrackTransactionContext *tctx = cls; struct TrackCoinContext *tcc; tcc = GNUNET_new (struct TrackCoinContext); tcc->tctx = tctx; tcc->coin_pub = *coin_pub; tcc->exchange_url = GNUNET_strdup (exchange_url); tcc->amount_with_fee = *amount_with_fee; tcc->deposit_fee = *deposit_fee; GNUNET_CONTAINER_DLL_insert (tctx->tcc_head, tctx->tcc_tail, tcc); /* find all those pairs associated to this contract term's hash code. The callback will then set the wtid for the "current coin" context. */ { enum GNUNET_DB_QueryStatus qs; qs = db->find_transfers_by_hash (db->cls, h_contract_terms, &transfer_cb, tcc); if (0 > qs) { GNUNET_break (0); tctx->qs = qs; } } } /** * Find the exchange to trace the next coin(s). * * @param tctx operation context */ static void find_exchange (struct TrackTransactionContext *tctx) { struct TrackCoinContext *tcc = tctx->tcc_head; while ( (NULL != tcc) && (GNUNET_YES == tcc->have_wtid) ) tcc = tcc->next; if (NULL != tcc) { tctx->current_exchange = tcc->exchange_url; tctx->fo = TMH_EXCHANGES_find_exchange ( tctx->current_exchange, NULL, GNUNET_NO, &process_track_transaction_with_exchange, tctx); } else { generate_response (tctx); } } /** * Handle a "/track/transaction" request. * * @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_transaction (struct TMH_RequestHandler *rh, struct MHD_Connection *connection, void **connection_cls, const char *upload_data, size_t *upload_data_size, struct MerchantInstance *mi) { struct TrackTransactionContext *tctx; const char *order_id; enum GNUNET_DB_QueryStatus qs; struct json_t *contract_terms; if (NULL == *connection_cls) { tctx = GNUNET_new (struct TrackTransactionContext); tctx->hc.cc = &track_transaction_cleanup; tctx->connection = connection; *connection_cls = tctx; } else { /* not first call, recover state */ tctx = *connection_cls; } if (0 != tctx->response_code) { MHD_RESULT ret; /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == tctx->response_code) { GNUNET_break (0); return MHD_NO; /* hard error */ } ret = MHD_queue_response (connection, tctx->response_code, tctx->response); if (NULL != tctx->response) { MHD_destroy_response (tctx->response); tctx->response = NULL; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Queueing response (%u) for /track/transaction (%s).\n", (unsigned int) tctx->response_code, ret ? "OK" : "FAILED"); return ret; } if ( (NULL != tctx->fo) || (NULL != tctx->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 */ } order_id = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "order_id"); if (NULL == order_id) return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_PARAMETER_MISSING, "order_id"); tctx->mi = mi; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Tracking on behalf of instance '%s'\n", mi->id); /* Map order id to contract terms; the objective is to get the contract term's hashcode so as to retrieve all the coins which have been deposited for it. */ db->preflight (db->cls); qs = db->find_contract_terms (db->cls, &contract_terms, order_id, &tctx->mi->pubkey); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_TRACK_TRANSACTION_DB_FETCH_TRANSACTION_ERROR, "Database error finding contract terms"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND, "Given order_id doesn't map to any proposal"); if (GNUNET_OK != TALER_JSON_contract_hash (contract_terms, &tctx->h_contract_terms)) { json_decref (contract_terms); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_INTERNAL_LOGIC_ERROR, "Failed to hash contract terms"); } { struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_absolute_time ("refund_deadline", &tctx->refund_deadline), TALER_JSON_spec_absolute_time ("timestamp", &tctx->timestamp), TALER_JSON_spec_amount ("amount", &tctx->total_amount), GNUNET_JSON_spec_fixed_auto ("h_wire", &tctx->h_wire), GNUNET_JSON_spec_end () }; if (GNUNET_YES != GNUNET_JSON_parse (contract_terms, spec, NULL, NULL)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); json_decref (contract_terms); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_INTERNAL_LOGIC_ERROR, "Failed to parse contract terms from DB"); } json_decref (contract_terms); } tctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; db->preflight (db->cls); /* Find coins which have been deposited for this contract, and retrieve the wtid for each one. */ qs = db->find_payments (db->cls, &tctx->h_contract_terms, &tctx->mi->pubkey, &coin_cb, tctx); if ( (0 > qs) || (0 > tctx->qs) ) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != tctx->qs); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_TRACK_TRANSACTION_DB_FETCH_PAYMENT_ERROR, "Database error: failed to find payment data"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_TRACK_TRANSACTION_DB_NO_DEPOSITS_ERROR, "deposit data not found"); } *connection_cls = tctx; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending /track/transaction handling while working with the exchange\n"); MHD_suspend_connection (connection); tctx->timeout_task = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT, &handle_track_transaction_timeout, tctx); find_exchange (tctx); return MHD_YES; } /* end of taler-merchant-httpd_track-transaction.c */