From fe19d9ada3f6dfdf3014b9610090b8750de32db8 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 21 May 2020 23:06:59 +0200 Subject: towards POST tips pickup impl --- .../taler-merchant-httpd_post-tips-ID-pickup.c | 829 +++++++++++++++++++-- .../taler-merchant-httpd_private-get-tips-ID.c | 94 ++- src/include/taler_merchantdb_plugin.h | 149 ++++ 3 files changed, 997 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c index 44258343..c24c2c05 100644 --- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c +++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c @@ -29,13 +29,468 @@ #include "taler-merchant-httpd_tip-pickup.h" +/** + * How often do we retry on serialization errors? + */ +#define MAX_RETRIES 3 + +/** + * How long do we give the exchange operation to complete withdrawing + * all of the planchets? + */ +#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_SECONDS, 45) + + +/** + * Active pickup operations. + */ +struct PickupContext; + + +/** + * Handle for an individual planchet we are processing for a tip. + */ +struct PlanchetOperation +{ + /** + * Active pickup operation this planchet belongs with. + */ + struct PickupContext *pc; + + /** + * Find operation (while active), later NULL. + */ + struct TMH_EXCHANGES_FindOperation *fo; + + /** + * Withdraw handle (NULL while @e fo is active). + */ + struct TALER_EXCHANGE_Withdraw2Handle *w2h; + + /** + * Details about the planchet for withdrawing. + */ + struct TALER_PlanchetDetail pd; + + /** + * Offset of this planchet in the original request. + */ + unsigned int offset; +}; + + +/** + * Active pickup operations. + */ +struct PickupContext +{ + /** + * Kept in a DLL. + */ + struct PickupContext *next; + + /** + * Kept in a DLL. + */ + struct PickupContext *prev; + + /** + * The connection. + */ + struct MHD_Connection *connection; + + /** + * Timeout task. + */ + struct GNUNET_SCHEDULER_Task *tt; + + /** + * Head of DLL of exchange operations on planchets. + */ + struct PlanchetOperation *po_head; + + /** + * Tail of DLL of exchange operations on planchets. + */ + struct PlanchetOperation *po_tail; + + /** + * HTTP response to return (set on errors). + */ + struct MHD_Response *response; + + /** + * Find operation (while active), later NULL. + */ + struct TMH_EXCHANGES_FindOperation *fo; + + /** + * Which reserve are we draining? + */ + struct TALER_ReservePrivateKey reserve_priv; + + /** + * Which tip is being picked up? + */ + struct GNUNET_HashCode tip_id; + + /** + * Array of our planchets. + */ + struct TALER_PlanchetDetail *planchets; + + /** + * Length of the @e planchets array. + */ + unsigned int planchets_length; + + /** + * HTTP status to use (set on errors). + */ + unsigned int http_status; + + /** + * Total amount requested in the pick up operation. Computed by + * totaling up the amounts of all the @e planchets. + */ + struct TALER_Amount total_requested; + + /** + * True if @e total_requested has been initialized. + */ + bool tr_initialized; +}; + + +/** + * Head of DLL. + */ +static struct PickupContext *pc_head; + +/** + * Tail of DLL. + */ +static struct PickupContext *pc_tail; + + +/** + * Stop all ongoing operations associated with @a pc. + */ +static void +stop_operations (struct PickupContext *pc) +{ + struct PlanchetOperation *po; + + if (NULL != pc->tt) + { + GNUNET_SCHEDULER_cancel (pc->tt); + pc->tt = NULL; + } + if (NULL != pc->fo) + { + TMH_EXCHANGES_find_exchange_cancel (pc->fo); + pc->fo = NULL; + } + while (NULL != (po = pc->po_head)) + { + if (NULL != po->fo) + { + TMH_EXCHANGES_find_exchange_cancel (po->fo); + po->fo = NULL; + } + if (NULL != po->w2h) + { + TALER_EXCHANGE_withdraw2_cancel (po->w2h); + po->w2h = NULL; + } + GNUNET_CONTAINER_DLL_remove (pc->po_head, + pc->po_tail, + po); + } +} + + +/** + * Function called to clean up. + * + * @param cls a `struct PickupContext *` to clean up + */ +static void +pick_context_cleanup (void *cls) +{ + struct PickupContext *pc = cls; + + stop_operations (pc); /* should not be any... */ + for (unsigned int i = 0; iplanchets_length; i++) + GNUNET_free (pc->planchets[i].coin_ev); + GNUNET_array_grow (pc->planchets, + pc->planchets_length, + 0); + GNUNET_free (pc); +} + + /** * We are shutting down, force resuming all suspended pickup operations. */ void TMH_force_tip_pickup_resume () { - // FIXME! + for (struct PickupContext *pc = pc_head; + NULL != pc; + pc = pc->next) + { + stop_operations (pc); + MHD_resume_connection (pc->connection); + } +} + + +/** + * Callbacks of this type are used to serve the result of submitting a + * withdraw request to a exchange without the (un)blinding factor. + * We persist the result in the database and, if we were the last + * planchet operation, resume HTTP processing. + * + * @param cls closure with a `struct PlanchetOperation *` + * @param hr HTTP response data + * @param blind_sig blind signature over the coin, NULL on error + */ +static void +withdraw_cb (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + const struct GNUNET_CRYPTO_RsaSignature *blind_sig) +{ + struct PlanchetOperation *po = cls; + struct PickupContext *pc = po->pc; + + GNUNET_CONTAINER_DLL_remove (pc->po_head, + pc->po_tail, + po); + if (NULL == blind_sig) + { + stop_operations (pc); + pc->http_status = MHD_HTTP_FAILED_DEPENDENCY; + pc->response = + TALER_MHD_make_json_pack ( + "{s:I, s:I, s:I, s:O}", + "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_ERROR, + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_reply", hr->reply); + MHD_resume_connection (pc->connection); + return; + } + qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls, + &pc->pickup_id, + po->offset, + blind_sig); + if (qs < 0) + { + stop_operations (pc); + pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + pc->response = TALER_MHD_make_error ( + TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR, + "Could not store blind signature in DB"); + MHD_resume_connection (pc->connection); + return; + } + if (NULL == pc->po_head) + { + stop_operations (pc); /* stops timeout job */ + MHD_resume_connection (pc->connection); + } +} + + +/** + * Function called with the result of a #TMH_EXCHANGES_find_exchange() + * operation as part of a withdraw objective. If the exchange is ready, + * withdraws the planchet from the exchange. + * + * @param cls closure, with our `struct PlanchetOperation *` + * @param hr HTTP response details + * @param eh handle to the exchange context + * @param payto_uri payto://-URI of the exchange + * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available + * @param exchange_trusted true if this exchange is trusted by config + */ +static void +do_withdraw (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + struct TALER_EXCHANGE_Handle *eh, + const char *payto_uri, + const struct TALER_Amount *wire_fee, + bool exchange_trusted) +{ + struct PlanchetOperation *po = cls; + struct PickupContext *pc = po->pc; + + po->fo = NULL; + if (NULL == eh) + { + stop_operations (pc); + GNUNET_CONTAINER_DLL_remove (pc->po_head, + pc->po_tail, + po); + pc->http_status = MHD_HTTP_FAILED_DEPENDENCY; + pc->response = + TALER_MHD_make_json_pack ( + "{s:I, s:I, s:I, s:O}", + "code", (json_int_t) TALER_EC_TIP_PICKUP_CONTACT_EXCHANGE_ERROR, + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_reply", hr->reply); + MHD_resume_connection (pc->connection); + return; + } + po->w2h = TALER_EXCHANGE_withdraw2 (eh, + &po->pd, + &pc->reserve_priv, + &withdraw_cb, + po); +} + + +/** + * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet + * @a offset. Sets up the respective operation and adds it @a pc's operation + * list. Once the operation is complete, the resulting blind signature is + * committed to the merchant's database. If all planchet operations are + * completed, the HTTP processing is resumed. + * + * @param[in,out] pc a pending pickup operation that includes @a planchet + * @param exchange_url identifies an exchange to do the pickup from + * @param planchet details about the coin to pick up + * @param offset offset of @a planchet in the list, needed to process the reply + */ +static void +try_withdraw (struct PickupContext *pc, + const char *exchange_url, + const struct TALER_PlanchetDetail *planchet, + unsigned int offset) +{ + struct PlanchetOperation *po; + + po = GNUNET_new (struct PlanchetOperation); + po->pc = pc; + po->pd = *planchet; + po->offset = offset; + po->fo = TMH_EXCHANGES_find_exchange (exchange_url, + NULL, + GNUNET_NO, + &do_withdraw, + po); + GNUNET_assert (NULL != po->fo); + GNUNET_CONTAINER_DLL_insert (pc->fo_head, + pc->fo_tail, + fo); +} + + +/** + * Handle timeout for pickup. + * + * @param cls a `struct PickupContext *` + */ +static void +do_timeout (void *cls) +{ + struct PickupContext *pc = cls; + + pc->tt = NULL; + stop_operations (pc); + pc->http_status = MHD_HTTP_REQUEST_TIMEOUT; + pc->response = TALER_MHD_make_error ( + TALER_EC_TIP_PICKUP_EXCHANGE_TIMEOUT, + "Timeout trying to withdraw from exchange (try again later)"); + MHD_resume_connection (pc->connection); +} + + +/** + * Function called with the result of a #TMH_EXCHANGES_find_exchange() + * operation as part of a withdraw objective. Here, we initialize + * the "total_requested" amount by adding up the cost of the planchets + * provided by the client. + * + * @param cls closure, with our `struct PickupContext *` + * @param hr HTTP response details + * @param eh handle to the exchange context + * @param payto_uri payto://-URI of the exchange + * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available + * @param exchange_trusted true if this exchange is trusted by config + */ +static void +compute_total_requested (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + struct TALER_EXCHANGE_Handle *eh, + const char *payto_uri, + const struct TALER_Amount *wire_fee, + bool exchange_trusted) +{ + struct PickupContext *pc = cls; + const struct TALER_EXCHANGE_Keys *keys; + + pc->fo = NULL; + stop_operations (pc); /* stops timeout job */ + if ( (NULL == eh) || + (NULL == (keys = TALER_EXCHANGE_get_keys (eh))) ) + { + pc->http_status = MHD_HTTP_FAILED_DEPENDENCY; + pc->response = + TALER_MHD_make_json_pack ( + "{s:I, s:I, s:I, s:O}", + "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_KEYS_ERROR, + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_reply", hr->reply); + MHD_resume_connection (pc->connection); + return; + } + TALER_amount_get_zero (TMH_currency, + &pc->total_requested); + for (unsigned int i = 0; iplanchets_length; i++) + { + struct TALER_PlanchetDetail *pd = &pc->planchets[i]; + const struct TALER_EXCHANGE_DenomPublicKey *dpk; + + dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys, + &pd->denom_pub_hash); + if (NULL == dpk) + { + pc->http_status = MHD_HTTP_BAD_REQUEST; + pc->response = + TALER_MHD_make_json_pack ( + "{s:I, s:I, s:I, s:O}", + "code", (json_int_t) TALER_EC_TIP_PICKUP_DENOMINATION_UNKNOWN, + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_reply", hr->reply); + MHD_resume_connection (pc->connection); + return; + } + + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&pc->total_requested, + &dpk->value)) || + (0 > + TALER_amount_add (&pc->total_requested, + &pc->total_requested, + &dpk->value)) ) + { + pc->http_status = MHD_HTTP_BAD_REQUEST; + pc->response = + TALER_MHD_make_error (TALER_EC_TIP_PICKUP_SUMMATION_FAILED, + "Could not add up values to compute pickup total"); + MHD_resume_connection (pc->connection); + return; + } + } + pc->tr_initialized = true; + + return; } @@ -53,43 +508,271 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { + struct PickupContext *pc = hc->ctx; + + char *justification; char *exchange_url; - struct GNUNET_HashCode tip_id; - struct TALER_Amount tip_amount; - struct TALER_Amount tip_amount_left; - struct GNUNET_TIME_Absolute timestamp; + + struct TALER_Amount total_authorized; + struct TALER_Amount total_picked_up; + struct TALER_Amount total_remaining; + struct GNUNET_TIME_Absolute expiration; struct GNUNET_TIME_Absolute timestamp_expire; + struct TALER_ReservePrivateKey reserve_priv; MHD_RESULT ret; enum GNUNET_DB_QueryStatus qs; + unsigned int num_coins; + unsigned int num_retries; + + if (NULL == pc) + { + json_t *planchets; + json_t *planchet; + size_t index; + + pc = GNUNET_new (struct PickupContext); + hc->ctx = pc; + hc->cc = &pick_context_cleanup; + + GNUNET_assert (NULL != hc->infix); + if (GNUNET_OK != + GNUNET_CRYPTO_hash_from_string (hc->infix, + &pc->tip_id)) + { + /* tip_id has wrong encoding */ + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "tip_id malformed"); + } + + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("planchets", + &planchets), + GNUNET_JSON_spec_end () + }; + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + if (GNUNET_OK != res) + return (GNUNET_NO == res) + ? MHD_YES + : MHD_NO; + } + } + if (! json_is_array (planchets)) + { + GNUNET_break_op (0); + json_decref (planchets); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS, + "Invalid bank account information"); + } + + GNUNET_array_grow (pc->planchets, + pc->planchets_length, + json_array_size (planchets)); + json_array_foreach (planchets, index, planchet) { + struct TALER_PlanchetDetail *pd = &pc->planchets[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", + &pd->denom_pub_hash), + GNUNET_JSON_spec_varsize ("coin_ev", + (void **) &pd->coin_ev, + &pd->coin_ev_size), + GNUNET_JSON_spec_end () + }; + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + planchet, + spec); + if (GNUNET_OK != res) + { + json_decref (planchets); + return (GNUNET_NO == res) + ? MHD_YES + : MHD_NO; + } + } + } + json_decref (planchets); + { + struct GNUNET_HashContext *hc; + + hc = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hc, + &pc->tip_id, + sizeof (pc->tip_id)); + for (unsigned int i = 0; iplanchets_length; i++) + { + GNUNET_CRYPTO_hash_context_read (hc, + &pd->denom_pub_hash, + sizeof (pd->denom_pub_hash)); + GNUNET_CRYPTO_hash_context_read (hc, + pd->coin_ev, + pd->coin_ev_size); + } + GNUNET_CRYPTO_hash_context_finish (hc, + &pc->pickup_id); + } + } + + if (NULL != pc->response) + { + MDH_RESULT ret; + + ret = MHD_queue_response (connection, + pc->http_status, + pc->response); + pc->response = NULL; + return ret; + } + + if (! pc->tr_initialized) + { + MHD_suspend_connection (connection); + pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT, + &do_timeout, + pc); + pc->fo = TMH_EXCHANGES_find_exchange (exchange_url, + NULL, + GNUNET_NO, + &compute_total_requested, + pc); + return MHD_YES; + } + - GNUNET_assert (NULL != hc->infix); + TMH_db->preflight (TMH_db->cls); + num_retries = 0; +RETRY: + num_retries++; + if (num_retries > MAX_RETRIES) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_TIP_PICKUP_DB_ERROR_SOFT, + "Too many DB serialization failures"); + } if (GNUNET_OK != - GNUNET_CRYPTO_hash_from_string (hc->infix, - &tip_id)) + TMH_db->start (TMH_db->cls, + "pickup tip")) { - /* tip_id has wrong encoding */ - GNUNET_break_op (0); + GNUNET_break (0); return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MALFORMED, - "tip_id malformed"); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_TIP_PICKUP_DB_ERROR_HARD, + "Could not begin transaction"); } + { + struct GNUNET_CRYPTO_RsaSignature *sigs[num_coins]; + + qs = TMH_db->lookup_pickup (TMH_db->cls, + hc->instance->settings.id, + &tip_id, + &pickup_id, + &exchange_url, + &pc->reserve_priv; + num_coins, + sigs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + for (unsigned int i = 0; itt = GNUNET_SCHEDULER_add_delayed (TIMEOUT, + &do_timeout, + pc); + TMH_db->rollback (TMH_db->cls); + return MHD_YES; + } + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs) + { + unsigned int response_code; + enum TALER_ErrorCode ec; - db->preflight (db->cls); - // FIXME: logic here is completely bonkers! - qs = db->lookup_tip_by_id (db->cls, - &tip_id, - &exchange_url, - &extra, - &tip_amount, - &tip_amount_left, - ×tamp); + TMH_db->rollback (TMH_db->cls); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + { + json_t *blind_sigs; + + blind_sigs = json_array (); + GNUNET_assert (NULL != blind_sigs); + for (unsigned int i = 0; ilookup_tip (TMH_db->cls, + hc->instance->settings.id, + &tip_id, + &total_authorized, + &total_picked_up, + &expiration, + &exchange_url, + &pc->reserve_priv); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { unsigned int response_code; enum TALER_ErrorCode ec; + TMH_db->rollback (TMH_db->cls); switch (qs) { case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: @@ -97,9 +780,7 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh, response_code = MHD_HTTP_NOT_FOUND; break; case GNUNET_DB_STATUS_SOFT_ERROR: - ec = TALER_EC_TIP_PICKUP_DB_ERROR_SOFT; - response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - break; + goto RETRY; case GNUNET_DB_STATUS_HARD_ERROR: ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD; response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; @@ -113,24 +794,82 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh, return TALER_MHD_reply_with_error (connection, response_code, ec, - "Could not determine exchange URL for the given tip id"); - } - - timestamp_expire = GNUNET_TIME_absolute_add (timestamp, - GNUNET_TIME_UNIT_DAYS); - - ret = TALER_MHD_reply_json_pack ( - connection, - MHD_HTTP_OK, - "{s:s, s:o, s:o, s:o, s:o, s:o}", - "exchange_url", exchange_url, - "amount", TALER_JSON_from_amount (&tip_amount), - "amount_left", TALER_JSON_from_amount (&tip_amount_left), - "stamp_created", GNUNET_JSON_from_time_abs (timestamp), - "stamp_expire", GNUNET_JSON_from_time_abs (timestamp_expire), - "extra", extra); - - GNUNET_free (exchange_url); - json_decref (extra); - return ret; + "Could not process pickup"); + } + if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us) + { + TMH_db->rollback (TMH_db->cls); + return TALER_MHD_reply_with_error (connection, + response_code, + ec, + "Could not process pickup"); + } + if (0 > + TALER_amount_subtract (&total_left, + &total_authorized, + &total_picked_up)) + { + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_LOGIC_ERROR, + "tip already overdrawn"); + } + + if (0 > + TALER_amount_cmp (&total_left, + &total_requested)) + { + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING, + "requested amount exceeds amount left in tip"); + } + + GNUNET_assert (0 > + TALER_amount_add (&total_picked_up, + &total_picked_up, + &total_requested)); + qs = TMH_db->insert_pickup (TMH_db->cls, + hc->instance->settings.id, + &tip_id, + &total_picked_up, + &pickup_id, + &total_requested); + if (qs < 0) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR, + "Could not store pickup ID in database"); + } + qs = TMH_db->commit (TMH_db->cls); + if (qs < 0) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR, + "Could not commit transaction to database"); + } + MHD_suspend_connection (connection); + pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT, + &do_timeout, + pc); + for (unsigned int i = 0; iinfix); if (GNUNET_OK != @@ -63,17 +66,27 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh, TALER_EC_PARAMETER_MALFORMED, "tip_id malformed"); } + { + const char *pstr; + pstr = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "pickups"); + fpu = (NULL != pstr) ? 0 == strcasecmp (pstr, "yes") : + false; + } db->preflight (db->cls); - // FIXME: logic here is completely bonkers! - qs = db->lookup_tip_by_id (db->cls, - &tip_id, - &exchange_url, - &extra, - &tip_amount, - &tip_amount_left, - ×tamp); - + qs = db->lookup_tip_details (db->cls, + hc->instance->settings.id, + &tip_id, + fpu, + &total_authorized, + &total_picked_up, + &reason, + &expiration, + &reserve_pub, + &pickups_length, + &pickups); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { unsigned int response_code; @@ -104,21 +117,42 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh, ec, "Could not determine exchange URL for the given tip id"); } + if (fpu) + { + pickups_json = json_array (); + GNUNET_assert (NULL != pickups_json); + for (unsigned int i = 0; i