/*
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 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 */