Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2017-2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-httpd_post-tips-ID-pickup.c
18 : * @brief implementation of a POST /tips/ID/pickup handler
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <microhttpd.h>
23 : #include <jansson.h>
24 : #include <taler/taler_json_lib.h>
25 : #include <taler/taler_signatures.h>
26 : #include "taler-merchant-httpd.h"
27 : #include "taler-merchant-httpd_mhd.h"
28 : #include "taler-merchant-httpd_helper.h"
29 : #include "taler-merchant-httpd_exchanges.h"
30 : #include "taler-merchant-httpd_post-tips-ID-pickup.h"
31 :
32 :
33 : /**
34 : * How often do we retry on serialization errors?
35 : */
36 : #define MAX_RETRIES 3
37 :
38 : /**
39 : * How long do we give the exchange operation to complete withdrawing
40 : * all of the planchets?
41 : */
42 : #define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
43 : GNUNET_TIME_UNIT_SECONDS, 45)
44 :
45 :
46 : /**
47 : * Active pickup operations.
48 : */
49 : struct PickupContext;
50 :
51 :
52 : /**
53 : * Handle for an individual planchet we are processing for a tip.
54 : */
55 : struct PlanchetOperation
56 : {
57 : /**
58 : * Active pickup operation this planchet belongs with.
59 : */
60 : struct PickupContext *pc;
61 :
62 : /**
63 : * Kept in a DLL.
64 : */
65 : struct PlanchetOperation *prev;
66 :
67 : /**
68 : * Kept in a DLL.
69 : */
70 : struct PlanchetOperation *next;
71 :
72 : /**
73 : * Find operation (while active), later NULL.
74 : */
75 : struct TMH_EXCHANGES_FindOperation *fo;
76 :
77 : /**
78 : * Withdraw handle (NULL while @e fo is active).
79 : */
80 : struct TALER_EXCHANGE_Withdraw2Handle *w2h;
81 :
82 : /**
83 : * Details about the planchet for withdrawing.
84 : */
85 : struct TALER_PlanchetDetail pd;
86 :
87 : /**
88 : * Offset of this planchet in the original request.
89 : */
90 : unsigned int offset;
91 : };
92 :
93 :
94 : /**
95 : * Active pickup operations.
96 : */
97 : struct PickupContext
98 : {
99 : /**
100 : * Kept in a DLL.
101 : */
102 : struct PickupContext *next;
103 :
104 : /**
105 : * Kept in a DLL.
106 : */
107 : struct PickupContext *prev;
108 :
109 : /**
110 : * The connection.
111 : */
112 : struct MHD_Connection *connection;
113 :
114 : /**
115 : * Timeout task.
116 : */
117 : struct GNUNET_SCHEDULER_Task *tt;
118 :
119 : /**
120 : * Head of DLL of exchange operations on planchets.
121 : */
122 : struct PlanchetOperation *po_head;
123 :
124 : /**
125 : * Tail of DLL of exchange operations on planchets.
126 : */
127 : struct PlanchetOperation *po_tail;
128 :
129 : /**
130 : * HTTP response to return (set on errors).
131 : */
132 : struct MHD_Response *response;
133 :
134 : /**
135 : * Find operation (while active), later NULL.
136 : */
137 : struct TMH_EXCHANGES_FindOperation *fo;
138 :
139 : /**
140 : * Which reserve are we draining?
141 : */
142 : struct TALER_ReservePrivateKeyP reserve_priv;
143 :
144 : /**
145 : * Which tip is being picked up?
146 : */
147 : struct TALER_TipIdentifierP tip_id;
148 :
149 : /**
150 : * What is the ID of the pickup operation? (Basically a
151 : * hash over the key inputs).
152 : */
153 : struct TALER_PickupIdentifierP pickup_id;
154 :
155 : /**
156 : * Array of our planchets.
157 : */
158 : struct TALER_PlanchetDetail *planchets;
159 :
160 : /**
161 : * Length of the @e planchets array.
162 : */
163 : unsigned int planchets_length;
164 :
165 : /**
166 : * HTTP status to use (set on errors).
167 : */
168 : unsigned int http_status;
169 :
170 : /**
171 : * Total amount requested in the pick up operation. Computed by
172 : * totaling up the amounts of all the @e planchets.
173 : */
174 : struct TALER_Amount total_requested;
175 :
176 : /**
177 : * True if @e total_requested has been initialized.
178 : */
179 : bool tr_initialized;
180 : };
181 :
182 :
183 : /**
184 : * Head of DLL.
185 : */
186 : static struct PickupContext *pc_head;
187 :
188 : /**
189 : * Tail of DLL.
190 : */
191 : static struct PickupContext *pc_tail;
192 :
193 :
194 : /**
195 : * Stop all ongoing operations associated with @a pc.
196 : */
197 : static void
198 0 : stop_operations (struct PickupContext *pc)
199 : {
200 : struct PlanchetOperation *po;
201 :
202 0 : if (NULL != pc->tt)
203 : {
204 0 : GNUNET_SCHEDULER_cancel (pc->tt);
205 0 : pc->tt = NULL;
206 : }
207 0 : if (NULL != pc->fo)
208 : {
209 0 : TMH_EXCHANGES_find_exchange_cancel (pc->fo);
210 0 : pc->fo = NULL;
211 : }
212 0 : while (NULL != (po = pc->po_head))
213 : {
214 0 : if (NULL != po->fo)
215 : {
216 0 : TMH_EXCHANGES_find_exchange_cancel (po->fo);
217 0 : po->fo = NULL;
218 : }
219 0 : if (NULL != po->w2h)
220 : {
221 0 : TALER_EXCHANGE_withdraw2_cancel (po->w2h);
222 0 : po->w2h = NULL;
223 : }
224 0 : GNUNET_CONTAINER_DLL_remove (pc->po_head,
225 : pc->po_tail,
226 : po);
227 0 : GNUNET_free (po);
228 : }
229 0 : }
230 :
231 :
232 : /**
233 : * Function called to clean up.
234 : *
235 : * @param cls a `struct PickupContext *` to clean up
236 : */
237 : static void
238 0 : pick_context_cleanup (void *cls)
239 : {
240 0 : struct PickupContext *pc = cls;
241 :
242 0 : stop_operations (pc); /* should not be any... */
243 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
244 0 : TALER_planchet_detail_free (&pc->planchets[i]);
245 0 : GNUNET_array_grow (pc->planchets,
246 : pc->planchets_length,
247 : 0);
248 0 : GNUNET_free (pc);
249 0 : }
250 :
251 :
252 : void
253 0 : TMH_force_tip_pickup_resume ()
254 : {
255 : struct PickupContext *nxt;
256 :
257 0 : for (struct PickupContext *pc = pc_head;
258 : NULL != pc;
259 0 : pc = nxt)
260 : {
261 0 : nxt = pc->next;
262 0 : stop_operations (pc);
263 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
264 : pc_tail,
265 : pc);
266 0 : MHD_resume_connection (pc->connection);
267 : }
268 0 : }
269 :
270 :
271 : /**
272 : * Callbacks of this type are used to serve the result of submitting a
273 : * withdraw request to a exchange without the (un)blinding factor.
274 : * We persist the result in the database and, if we were the last
275 : * planchet operation, resume HTTP processing.
276 : *
277 : * @param cls closure with a `struct PlanchetOperation *`
278 : * @param hr HTTP response data
279 : * @param blind_sig blind signature over the coin, NULL on error
280 : */
281 : static void
282 0 : withdraw_cb (void *cls,
283 : const struct TALER_EXCHANGE_HttpResponse *hr,
284 : const struct TALER_BlindedDenominationSignature *blind_sig)
285 : {
286 0 : struct PlanchetOperation *po = cls;
287 0 : struct PickupContext *pc = po->pc;
288 : enum GNUNET_DB_QueryStatus qs;
289 :
290 0 : GNUNET_CONTAINER_DLL_remove (pc->po_head,
291 : pc->po_tail,
292 : po);
293 0 : TMH_db->preflight (TMH_db->cls);
294 0 : if (NULL == blind_sig)
295 : {
296 0 : GNUNET_free (po);
297 0 : stop_operations (pc);
298 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
299 0 : pc->response =
300 0 : TALER_MHD_MAKE_JSON_PACK (
301 : TALER_JSON_pack_ec (TALER_EC_MERCHANT_TIP_PICKUP_EXCHANGE_ERROR),
302 : TMH_pack_exchange_reply (hr));
303 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
304 : pc_tail,
305 : pc);
306 0 : MHD_resume_connection (pc->connection);
307 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
308 0 : return;
309 : }
310 0 : qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
311 0 : &pc->pickup_id,
312 : po->offset,
313 : blind_sig);
314 0 : GNUNET_free (po);
315 0 : if (qs < 0)
316 : {
317 0 : stop_operations (pc);
318 0 : pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
319 0 : pc->response = TALER_MHD_make_error (
320 : TALER_EC_GENERIC_DB_STORE_FAILED,
321 : "blind signature");
322 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
323 : pc_tail,
324 : pc);
325 0 : MHD_resume_connection (pc->connection);
326 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
327 0 : return;
328 : }
329 0 : if (NULL == pc->po_head)
330 : {
331 0 : stop_operations (pc); /* stops timeout job */
332 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
333 : pc_tail,
334 : pc);
335 0 : MHD_resume_connection (pc->connection);
336 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
337 : }
338 : }
339 :
340 :
341 : /**
342 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
343 : * operation as part of a withdraw objective. If the exchange is ready,
344 : * withdraws the planchet from the exchange.
345 : *
346 : * @param cls closure, with our `struct PlanchetOperation *`
347 : * @param hr HTTP response details
348 : * @param eh handle to the exchange context
349 : * @param payto_uri payto://-URI of the exchange
350 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
351 : * @param exchange_trusted true if this exchange is trusted by config
352 : */
353 : static void
354 0 : do_withdraw (void *cls,
355 : const struct TALER_EXCHANGE_HttpResponse *hr,
356 : struct TALER_EXCHANGE_Handle *eh,
357 : const char *payto_uri,
358 : const struct TALER_Amount *wire_fee,
359 : bool exchange_trusted)
360 : {
361 0 : struct PlanchetOperation *po = cls;
362 0 : struct PickupContext *pc = po->pc;
363 :
364 0 : po->fo = NULL;
365 0 : TMH_db->preflight (TMH_db->cls);
366 0 : if (NULL == hr)
367 : {
368 0 : stop_operations (pc);
369 0 : GNUNET_CONTAINER_DLL_remove (pc->po_head,
370 : pc->po_tail,
371 : po);
372 0 : GNUNET_free (po);
373 0 : pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
374 0 : pc->response = TALER_MHD_MAKE_JSON_PACK (
375 : TALER_JSON_pack_ec (
376 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
377 0 : MHD_resume_connection (pc->connection);
378 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
379 0 : return;
380 : }
381 0 : if (NULL == eh)
382 : {
383 0 : stop_operations (pc);
384 0 : GNUNET_CONTAINER_DLL_remove (pc->po_head,
385 : pc->po_tail,
386 : po);
387 0 : GNUNET_free (po);
388 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
389 0 : pc->response =
390 0 : TALER_MHD_MAKE_JSON_PACK (
391 : TALER_JSON_pack_ec (
392 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
393 : TMH_pack_exchange_reply (hr));
394 0 : MHD_resume_connection (pc->connection);
395 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
396 0 : return;
397 : }
398 0 : po->w2h = TALER_EXCHANGE_withdraw2 (eh,
399 0 : &po->pd,
400 0 : &pc->reserve_priv,
401 : &withdraw_cb,
402 : po);
403 : }
404 :
405 :
406 : /**
407 : * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
408 : * @a offset. Sets up the respective operation and adds it @a pc's operation
409 : * list. Once the operation is complete, the resulting blind signature is
410 : * committed to the merchant's database. If all planchet operations are
411 : * completed, the HTTP processing is resumed.
412 : *
413 : * @param[in,out] pc a pending pickup operation that includes @a planchet
414 : * @param exchange_url identifies an exchange to do the pickup from
415 : * @param planchet details about the coin to pick up
416 : * @param offset offset of @a planchet in the list, needed to process the reply
417 : */
418 : static void
419 0 : try_withdraw (struct PickupContext *pc,
420 : const char *exchange_url,
421 : const struct TALER_PlanchetDetail *planchet,
422 : unsigned int offset)
423 : {
424 : struct PlanchetOperation *po;
425 :
426 0 : TMH_db->preflight (TMH_db->cls);
427 0 : po = GNUNET_new (struct PlanchetOperation);
428 0 : po->pc = pc;
429 0 : po->pd = *planchet;
430 0 : po->offset = offset;
431 0 : po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
432 : NULL,
433 : GNUNET_NO,
434 : &do_withdraw,
435 : po);
436 0 : GNUNET_assert (NULL != po->fo);
437 0 : GNUNET_CONTAINER_DLL_insert (pc->po_head,
438 : pc->po_tail,
439 : po);
440 0 : }
441 :
442 :
443 : /**
444 : * Handle timeout for pickup.
445 : *
446 : * @param cls a `struct PickupContext *`
447 : */
448 : static void
449 0 : do_timeout (void *cls)
450 : {
451 0 : struct PickupContext *pc = cls;
452 :
453 0 : pc->tt = NULL;
454 0 : stop_operations (pc);
455 0 : pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
456 0 : pc->response = TALER_MHD_make_error (
457 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
458 : NULL);
459 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
460 : pc_tail,
461 : pc);
462 0 : MHD_resume_connection (pc->connection);
463 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
464 0 : }
465 :
466 :
467 : /**
468 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
469 : * operation as part of a withdraw objective. Here, we initialize
470 : * the "total_requested" amount by adding up the cost of the planchets
471 : * provided by the client.
472 : *
473 : * @param cls closure, with our `struct PickupContext *`
474 : * @param hr HTTP response details
475 : * @param eh handle to the exchange context
476 : * @param payto_uri payto://-URI of the exchange
477 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
478 : * @param exchange_trusted true if this exchange is trusted by config
479 : */
480 : static void
481 0 : compute_total_requested (void *cls,
482 : const struct TALER_EXCHANGE_HttpResponse *hr,
483 : struct TALER_EXCHANGE_Handle *eh,
484 : const char *payto_uri,
485 : const struct TALER_Amount *wire_fee,
486 : bool exchange_trusted)
487 : {
488 0 : struct PickupContext *pc = cls;
489 : const struct TALER_EXCHANGE_Keys *keys;
490 :
491 0 : pc->fo = NULL;
492 0 : stop_operations (pc); /* stops timeout job */
493 0 : if (NULL == hr)
494 : {
495 0 : pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
496 0 : pc->response = TALER_MHD_MAKE_JSON_PACK (
497 : TALER_JSON_pack_ec (
498 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
499 0 : MHD_resume_connection (pc->connection);
500 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
501 0 : return;
502 : }
503 0 : if (NULL == eh)
504 : {
505 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
506 0 : pc->response =
507 0 : TALER_MHD_MAKE_JSON_PACK (
508 : TALER_JSON_pack_ec (
509 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
510 : TMH_pack_exchange_reply (hr));
511 0 : MHD_resume_connection (pc->connection);
512 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
513 0 : return;
514 : }
515 0 : if (NULL == (keys = TALER_EXCHANGE_get_keys (eh)))
516 : {
517 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
518 0 : pc->response =
519 0 : TALER_MHD_MAKE_JSON_PACK (
520 : TALER_JSON_pack_ec (
521 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE),
522 : TMH_pack_exchange_reply (hr));
523 0 : MHD_resume_connection (pc->connection);
524 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
525 0 : return;
526 : }
527 0 : GNUNET_assert (GNUNET_OK ==
528 : TALER_amount_set_zero (TMH_currency,
529 : &pc->total_requested));
530 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
531 : {
532 0 : struct TALER_PlanchetDetail *pd = &pc->planchets[i];
533 : const struct TALER_EXCHANGE_DenomPublicKey *dpk;
534 :
535 0 : dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
536 0 : &pd->denom_pub_hash);
537 0 : if (NULL == dpk)
538 : {
539 0 : pc->http_status = MHD_HTTP_CONFLICT;
540 0 : pc->response =
541 0 : TALER_MHD_MAKE_JSON_PACK (
542 : TALER_JSON_pack_ec (
543 : TALER_EC_MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN),
544 : TMH_pack_exchange_reply (hr));
545 0 : MHD_resume_connection (pc->connection);
546 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
547 0 : return;
548 : }
549 :
550 0 : if ( (GNUNET_YES !=
551 0 : TALER_amount_cmp_currency (&pc->total_requested,
552 0 : &dpk->value)) ||
553 : (0 >
554 0 : TALER_amount_add (&pc->total_requested,
555 0 : &pc->total_requested,
556 : &dpk->value)) )
557 : {
558 0 : pc->http_status = MHD_HTTP_BAD_REQUEST;
559 0 : pc->response =
560 0 : TALER_MHD_make_error (TALER_EC_MERCHANT_TIP_PICKUP_SUMMATION_FAILED,
561 : "Could not add up values to compute pickup total");
562 0 : MHD_resume_connection (pc->connection);
563 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
564 0 : return;
565 : }
566 : }
567 0 : pc->tr_initialized = true;
568 0 : MHD_resume_connection (pc->connection);
569 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
570 : }
571 :
572 :
573 : /**
574 : * The tip lookup operation failed. Generate an error response based on the @a qs.
575 : *
576 : * @param connection connection to generate error for
577 : * @param qs DB status to base error creation on
578 : * @return MHD result code
579 : */
580 : static MHD_RESULT
581 0 : reply_lookup_tip_failed (struct MHD_Connection *connection,
582 : enum GNUNET_DB_QueryStatus qs)
583 : {
584 : unsigned int response_code;
585 : enum TALER_ErrorCode ec;
586 :
587 0 : TMH_db->rollback (TMH_db->cls);
588 0 : switch (qs)
589 : {
590 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
591 0 : ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
592 0 : response_code = MHD_HTTP_NOT_FOUND;
593 0 : break;
594 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
595 0 : ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
596 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
597 0 : break;
598 0 : case GNUNET_DB_STATUS_HARD_ERROR:
599 0 : ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
600 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
601 0 : break;
602 0 : default:
603 0 : GNUNET_break (0);
604 0 : ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
605 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
606 0 : break;
607 : }
608 0 : return TALER_MHD_reply_with_error (connection,
609 : response_code,
610 : ec,
611 : NULL);
612 : }
613 :
614 :
615 : MHD_RESULT
616 0 : TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
617 : struct MHD_Connection *connection,
618 : struct TMH_HandlerContext *hc)
619 : {
620 0 : struct PickupContext *pc = hc->ctx;
621 : char *exchange_url;
622 : struct TALER_Amount total_authorized;
623 : struct TALER_Amount total_picked_up;
624 : struct TALER_Amount total_remaining;
625 : struct GNUNET_TIME_Timestamp expiration;
626 : enum GNUNET_DB_QueryStatus qs;
627 : unsigned int num_retries;
628 :
629 0 : if (NULL == pc)
630 : {
631 : json_t *planchets;
632 : json_t *planchet;
633 : size_t index;
634 :
635 0 : pc = GNUNET_new (struct PickupContext);
636 0 : hc->ctx = pc;
637 0 : hc->cc = &pick_context_cleanup;
638 :
639 0 : GNUNET_assert (NULL != hc->infix);
640 0 : if (GNUNET_OK !=
641 0 : GNUNET_CRYPTO_hash_from_string (hc->infix,
642 : &pc->tip_id.hash))
643 : {
644 : /* tip_id has wrong encoding */
645 0 : GNUNET_break_op (0);
646 0 : return TALER_MHD_reply_with_error (connection,
647 : MHD_HTTP_BAD_REQUEST,
648 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
649 0 : hc->infix);
650 : }
651 :
652 : {
653 : struct GNUNET_JSON_Specification spec[] = {
654 0 : GNUNET_JSON_spec_json ("planchets",
655 : &planchets),
656 0 : GNUNET_JSON_spec_end ()
657 : };
658 : {
659 : enum GNUNET_GenericReturnValue res;
660 :
661 0 : res = TALER_MHD_parse_json_data (connection,
662 0 : hc->request_body,
663 : spec);
664 0 : if (GNUNET_OK != res)
665 : return (GNUNET_NO == res)
666 : ? MHD_YES
667 0 : : MHD_NO;
668 : }
669 : }
670 0 : if (! json_is_array (planchets))
671 : {
672 0 : GNUNET_break_op (0);
673 0 : json_decref (planchets);
674 0 : return TALER_MHD_reply_with_error (connection,
675 : MHD_HTTP_BAD_REQUEST,
676 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
677 : "planchets");
678 : }
679 :
680 0 : GNUNET_array_grow (pc->planchets,
681 : pc->planchets_length,
682 : json_array_size (planchets));
683 0 : json_array_foreach (planchets, index, planchet) {
684 0 : struct TALER_PlanchetDetail *pd = &pc->planchets[index];
685 : struct GNUNET_JSON_Specification spec[] = {
686 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
687 : &pd->denom_pub_hash),
688 0 : TALER_JSON_spec_blinded_planchet ("coin_ev",
689 : &pd->blinded_planchet),
690 0 : GNUNET_JSON_spec_end ()
691 : };
692 : {
693 : enum GNUNET_GenericReturnValue res;
694 :
695 0 : res = TALER_MHD_parse_json_data (connection,
696 : planchet,
697 : spec);
698 0 : if (GNUNET_OK != res)
699 : {
700 0 : json_decref (planchets);
701 : return (GNUNET_NO == res)
702 : ? MHD_YES
703 0 : : MHD_NO;
704 : }
705 : }
706 : }
707 0 : json_decref (planchets);
708 : {
709 : struct GNUNET_HashContext *hc;
710 :
711 0 : hc = GNUNET_CRYPTO_hash_context_start ();
712 0 : GNUNET_CRYPTO_hash_context_read (hc,
713 0 : &pc->tip_id,
714 : sizeof (pc->tip_id));
715 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
716 : {
717 0 : struct TALER_PlanchetDetail *pd = &pc->planchets[i];
718 :
719 0 : GNUNET_CRYPTO_hash_context_read (hc,
720 0 : &pd->denom_pub_hash,
721 : sizeof (pd->denom_pub_hash));
722 0 : TALER_blinded_planchet_hash_ (&pd->blinded_planchet,
723 : hc);
724 : }
725 0 : GNUNET_CRYPTO_hash_context_finish (hc,
726 : &pc->pickup_id.hash);
727 : }
728 : }
729 :
730 0 : if (NULL != pc->response)
731 : {
732 : MHD_RESULT ret;
733 :
734 0 : ret = MHD_queue_response (connection,
735 : pc->http_status,
736 : pc->response);
737 0 : pc->response = NULL;
738 0 : return ret;
739 : }
740 :
741 0 : if (! pc->tr_initialized)
742 : {
743 0 : qs = TMH_db->lookup_tip (TMH_db->cls,
744 0 : hc->instance->settings.id,
745 0 : &pc->tip_id,
746 : &total_authorized,
747 : &total_picked_up,
748 : &expiration,
749 : &exchange_url,
750 : &pc->reserve_priv);
751 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
752 0 : return reply_lookup_tip_failed (connection,
753 : qs);
754 0 : MHD_suspend_connection (connection);
755 0 : pc->connection = connection;
756 0 : pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
757 : &do_timeout,
758 : pc);
759 0 : pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
760 : NULL,
761 : GNUNET_NO,
762 : &compute_total_requested,
763 : pc);
764 0 : GNUNET_free (exchange_url);
765 0 : return MHD_YES;
766 : }
767 :
768 :
769 0 : TMH_db->preflight (TMH_db->cls);
770 0 : num_retries = 0;
771 0 : RETRY:
772 0 : num_retries++;
773 0 : if (num_retries > MAX_RETRIES)
774 : {
775 0 : GNUNET_break (0);
776 0 : return TALER_MHD_reply_with_error (connection,
777 : MHD_HTTP_INTERNAL_SERVER_ERROR,
778 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
779 : NULL);
780 : }
781 0 : if (GNUNET_OK !=
782 0 : TMH_db->start (TMH_db->cls,
783 : "pickup tip"))
784 : {
785 0 : GNUNET_break (0);
786 0 : return TALER_MHD_reply_with_error (connection,
787 : MHD_HTTP_INTERNAL_SERVER_ERROR,
788 : TALER_EC_GENERIC_DB_START_FAILED,
789 : NULL);
790 : }
791 0 : {
792 0 : struct TALER_BlindedDenominationSignature sigs[
793 0 : GNUNET_NZL (pc->planchets_length)];
794 :
795 0 : memset (sigs,
796 : 0,
797 : sizeof (sigs));
798 0 : qs = TMH_db->lookup_pickup (TMH_db->cls,
799 0 : hc->instance->settings.id,
800 0 : &pc->tip_id,
801 0 : &pc->pickup_id,
802 : &exchange_url,
803 : &pc->reserve_priv,
804 : pc->planchets_length,
805 : sigs);
806 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
807 : "Lookup pickup `%s' resulted in %d\n",
808 : GNUNET_h2s (&pc->pickup_id.hash),
809 : qs);
810 0 : if (qs > GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)
811 0 : qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
812 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
813 : {
814 0 : bool rollback = false;
815 :
816 0 : for (unsigned int i = 0; i< pc->planchets_length; i++)
817 : {
818 0 : if (TALER_DENOMINATION_INVALID != sigs[i].cipher)
819 0 : continue;
820 0 : if (! rollback)
821 : {
822 0 : TMH_db->rollback (TMH_db->cls);
823 0 : MHD_suspend_connection (connection);
824 0 : GNUNET_CONTAINER_DLL_insert (pc_head,
825 : pc_tail,
826 : pc);
827 0 : pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
828 : &do_timeout,
829 : pc);
830 0 : rollback = true;
831 : }
832 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
833 : "Lookup pickup `%s' initiated withdraw #%u\n",
834 : GNUNET_h2s (&pc->pickup_id.hash),
835 : i);
836 0 : try_withdraw (pc,
837 : exchange_url,
838 0 : &pc->planchets[i],
839 : i);
840 : }
841 0 : GNUNET_free (exchange_url);
842 0 : if (rollback)
843 0 : return MHD_YES;
844 : /* we got _all_ signatures, can continue! */
845 : }
846 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
847 : {
848 : unsigned int response_code;
849 : enum TALER_ErrorCode ec;
850 :
851 0 : TMH_db->rollback (TMH_db->cls);
852 0 : switch (qs)
853 : {
854 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
855 : {
856 : json_t *blind_sigs;
857 :
858 0 : blind_sigs = json_array ();
859 0 : GNUNET_assert (NULL != blind_sigs);
860 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
861 : {
862 0 : GNUNET_assert (0 ==
863 : json_array_append_new (
864 : blind_sigs,
865 : GNUNET_JSON_PACK (
866 : TALER_JSON_pack_blinded_denom_sig ("blind_sig",
867 : &sigs[i]))));
868 0 : TALER_blinded_denom_sig_free (&sigs[i]);
869 : }
870 0 : return TALER_MHD_REPLY_JSON_PACK (
871 : connection,
872 : MHD_HTTP_OK,
873 : GNUNET_JSON_pack_array_steal ("blind_sigs",
874 : blind_sigs));
875 : }
876 : break;
877 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
878 0 : goto RETRY;
879 0 : case GNUNET_DB_STATUS_HARD_ERROR:
880 0 : ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
881 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
882 0 : break;
883 0 : default:
884 0 : GNUNET_break (0);
885 0 : ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
886 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
887 0 : break;
888 : }
889 0 : return TALER_MHD_reply_with_error (connection,
890 : response_code,
891 : ec,
892 : NULL);
893 : }
894 : }
895 :
896 0 : qs = TMH_db->lookup_tip (TMH_db->cls,
897 0 : hc->instance->settings.id,
898 0 : &pc->tip_id,
899 : &total_authorized,
900 : &total_picked_up,
901 : &expiration,
902 : &exchange_url,
903 : &pc->reserve_priv);
904 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
905 : {
906 0 : TMH_db->rollback (TMH_db->cls);
907 0 : goto RETRY;
908 : }
909 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
910 : {
911 0 : TMH_db->rollback (TMH_db->cls);
912 0 : return reply_lookup_tip_failed (connection,
913 : qs);
914 : }
915 0 : if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
916 : {
917 0 : GNUNET_free (exchange_url);
918 0 : TMH_db->rollback (TMH_db->cls);
919 0 : return TALER_MHD_reply_with_error (connection,
920 : MHD_HTTP_GONE,
921 : TALER_EC_MERCHANT_TIP_PICKUP_HAS_EXPIRED,
922 0 : hc->infix);
923 : }
924 0 : if (0 >
925 0 : TALER_amount_subtract (&total_remaining,
926 : &total_authorized,
927 : &total_picked_up))
928 : {
929 0 : GNUNET_free (exchange_url);
930 0 : GNUNET_break_op (0);
931 0 : TMH_db->rollback (TMH_db->cls);
932 0 : return TALER_MHD_reply_with_error (connection,
933 : MHD_HTTP_INTERNAL_SERVER_ERROR,
934 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
935 : "picked up amount exceeds authorized amount");
936 : }
937 :
938 0 : if (0 >
939 0 : TALER_amount_cmp (&total_remaining,
940 0 : &pc->total_requested))
941 : {
942 : /* total_remaining < pc->total_requested */
943 0 : GNUNET_free (exchange_url);
944 0 : GNUNET_break_op (0);
945 0 : TMH_db->rollback (TMH_db->cls);
946 0 : return TALER_MHD_reply_with_error (connection,
947 : MHD_HTTP_BAD_REQUEST,
948 : TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
949 0 : hc->infix);
950 : }
951 :
952 0 : GNUNET_assert (0 <
953 : TALER_amount_add (&total_picked_up,
954 : &total_picked_up,
955 : &pc->total_requested));
956 0 : qs = TMH_db->insert_pickup (TMH_db->cls,
957 0 : hc->instance->settings.id,
958 0 : &pc->tip_id,
959 : &total_picked_up,
960 0 : &pc->pickup_id,
961 0 : &pc->total_requested);
962 0 : if (qs < 0)
963 : {
964 0 : TMH_db->rollback (TMH_db->cls);
965 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
966 0 : goto RETRY;
967 0 : GNUNET_free (exchange_url);
968 0 : return TALER_MHD_reply_with_error (connection,
969 : MHD_HTTP_INTERNAL_SERVER_ERROR,
970 : TALER_EC_GENERIC_DB_STORE_FAILED,
971 : "pickup");
972 : }
973 0 : qs = TMH_db->commit (TMH_db->cls);
974 0 : if (qs < 0)
975 : {
976 0 : TMH_db->rollback (TMH_db->cls);
977 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
978 0 : goto RETRY;
979 0 : GNUNET_free (exchange_url);
980 0 : return TALER_MHD_reply_with_error (connection,
981 : MHD_HTTP_INTERNAL_SERVER_ERROR,
982 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
983 : NULL);
984 : }
985 0 : MHD_suspend_connection (connection);
986 0 : GNUNET_CONTAINER_DLL_insert (pc_head,
987 : pc_tail,
988 : pc);
989 0 : pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
990 : &do_timeout,
991 : pc);
992 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
993 : {
994 0 : try_withdraw (pc,
995 : exchange_url,
996 0 : &pc->planchets[i],
997 : i);
998 : }
999 0 : GNUNET_free (exchange_url);
1000 0 : return MHD_YES;
1001 : }
|