summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c1250
1 files changed, 1250 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c b/src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c
new file mode 100644
index 00000000..39f8ce9e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c
@@ -0,0 +1,1250 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#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; i<num_transfers; i++)
+ {
+ const struct TransactionWireTransfer *transfer = &transfers[i];
+ struct TALER_Amount sum;
+
+ sum = transfer->coins[0].amount_with_fee;
+ for (unsigned int j = 1; j<transfer->num_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; i<MAX_RETRIES; i++)
+ {
+ db->preflight (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; d<details_length; d++)
+ {
+ if (0 == GNUNET_memcmp (&details[d].coin_pub,
+ &tcc->coin_pub))
+ {
+ tcc->wtid = tctx->current_wtid;
+ tcc->execution_time = tctx->current_execution_time;
+ tcc->have_wtid = GNUNET_YES;
+ }
+
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ db->preflight (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 <coin, wtid> 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_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[] = {
+ GNUNET_JSON_spec_absolute_time ("refund_deadline",
+ &tctx->refund_deadline),
+ GNUNET_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 */