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 3 : TMH_force_tip_pickup_resume ()
254 : {
255 : struct PickupContext *nxt;
256 :
257 3 : 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 3 : }
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 : if (NULL == blind_sig)
294 : {
295 0 : GNUNET_free (po);
296 0 : stop_operations (pc);
297 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
298 0 : pc->response =
299 0 : TALER_MHD_MAKE_JSON_PACK (
300 : TALER_JSON_pack_ec (TALER_EC_MERCHANT_TIP_PICKUP_EXCHANGE_ERROR),
301 : TMH_pack_exchange_reply (hr));
302 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
303 : pc_tail,
304 : pc);
305 0 : MHD_resume_connection (pc->connection);
306 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
307 0 : return;
308 : }
309 0 : qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
310 0 : &pc->pickup_id,
311 : po->offset,
312 : blind_sig);
313 0 : GNUNET_free (po);
314 0 : if (qs < 0)
315 : {
316 0 : stop_operations (pc);
317 0 : pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
318 0 : pc->response = TALER_MHD_make_error (
319 : TALER_EC_GENERIC_DB_STORE_FAILED,
320 : "blind signature");
321 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
322 : pc_tail,
323 : pc);
324 0 : MHD_resume_connection (pc->connection);
325 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
326 0 : return;
327 : }
328 0 : if (NULL == pc->po_head)
329 : {
330 0 : stop_operations (pc); /* stops timeout job */
331 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
332 : pc_tail,
333 : pc);
334 0 : MHD_resume_connection (pc->connection);
335 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
336 : }
337 : }
338 :
339 :
340 : /**
341 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
342 : * operation as part of a withdraw objective. If the exchange is ready,
343 : * withdraws the planchet from the exchange.
344 : *
345 : * @param cls closure, with our `struct PlanchetOperation *`
346 : * @param hr HTTP response details
347 : * @param eh handle to the exchange context
348 : * @param payto_uri payto://-URI of the exchange
349 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
350 : * @param exchange_trusted true if this exchange is trusted by config
351 : */
352 : static void
353 0 : do_withdraw (void *cls,
354 : const struct TALER_EXCHANGE_HttpResponse *hr,
355 : struct TALER_EXCHANGE_Handle *eh,
356 : const char *payto_uri,
357 : const struct TALER_Amount *wire_fee,
358 : bool exchange_trusted)
359 : {
360 0 : struct PlanchetOperation *po = cls;
361 0 : struct PickupContext *pc = po->pc;
362 :
363 0 : po->fo = NULL;
364 0 : if (NULL == hr)
365 : {
366 0 : stop_operations (pc);
367 0 : GNUNET_CONTAINER_DLL_remove (pc->po_head,
368 : pc->po_tail,
369 : po);
370 0 : GNUNET_free (po);
371 0 : pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
372 0 : pc->response = TALER_MHD_MAKE_JSON_PACK (
373 : TALER_JSON_pack_ec (
374 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
375 0 : MHD_resume_connection (pc->connection);
376 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
377 0 : return;
378 : }
379 0 : if (NULL == eh)
380 : {
381 0 : stop_operations (pc);
382 0 : GNUNET_CONTAINER_DLL_remove (pc->po_head,
383 : pc->po_tail,
384 : po);
385 0 : GNUNET_free (po);
386 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
387 0 : pc->response =
388 0 : TALER_MHD_MAKE_JSON_PACK (
389 : TALER_JSON_pack_ec (
390 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
391 : TMH_pack_exchange_reply (hr));
392 0 : MHD_resume_connection (pc->connection);
393 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
394 0 : return;
395 : }
396 0 : po->w2h = TALER_EXCHANGE_withdraw2 (eh,
397 0 : &po->pd,
398 0 : &pc->reserve_priv,
399 : &withdraw_cb,
400 : po);
401 : }
402 :
403 :
404 : /**
405 : * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
406 : * @a offset. Sets up the respective operation and adds it @a pc's operation
407 : * list. Once the operation is complete, the resulting blind signature is
408 : * committed to the merchant's database. If all planchet operations are
409 : * completed, the HTTP processing is resumed.
410 : *
411 : * @param[in,out] pc a pending pickup operation that includes @a planchet
412 : * @param exchange_url identifies an exchange to do the pickup from
413 : * @param planchet details about the coin to pick up
414 : * @param offset offset of @a planchet in the list, needed to process the reply
415 : */
416 : static void
417 0 : try_withdraw (struct PickupContext *pc,
418 : const char *exchange_url,
419 : const struct TALER_PlanchetDetail *planchet,
420 : unsigned int offset)
421 : {
422 : struct PlanchetOperation *po;
423 :
424 0 : po = GNUNET_new (struct PlanchetOperation);
425 0 : po->pc = pc;
426 0 : po->pd = *planchet;
427 0 : po->offset = offset;
428 0 : po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
429 : NULL,
430 : GNUNET_NO,
431 : &do_withdraw,
432 : po);
433 0 : GNUNET_assert (NULL != po->fo);
434 0 : GNUNET_CONTAINER_DLL_insert (pc->po_head,
435 : pc->po_tail,
436 : po);
437 0 : }
438 :
439 :
440 : /**
441 : * Handle timeout for pickup.
442 : *
443 : * @param cls a `struct PickupContext *`
444 : */
445 : static void
446 0 : do_timeout (void *cls)
447 : {
448 0 : struct PickupContext *pc = cls;
449 :
450 0 : pc->tt = NULL;
451 0 : stop_operations (pc);
452 0 : pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
453 0 : pc->response = TALER_MHD_make_error (
454 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
455 : NULL);
456 0 : GNUNET_CONTAINER_DLL_remove (pc_head,
457 : pc_tail,
458 : pc);
459 0 : MHD_resume_connection (pc->connection);
460 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
461 0 : }
462 :
463 :
464 : /**
465 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
466 : * operation as part of a withdraw objective. Here, we initialize
467 : * the "total_requested" amount by adding up the cost of the planchets
468 : * provided by the client.
469 : *
470 : * @param cls closure, with our `struct PickupContext *`
471 : * @param hr HTTP response details
472 : * @param eh handle to the exchange context
473 : * @param payto_uri payto://-URI of the exchange
474 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
475 : * @param exchange_trusted true if this exchange is trusted by config
476 : */
477 : static void
478 0 : compute_total_requested (void *cls,
479 : const struct TALER_EXCHANGE_HttpResponse *hr,
480 : struct TALER_EXCHANGE_Handle *eh,
481 : const char *payto_uri,
482 : const struct TALER_Amount *wire_fee,
483 : bool exchange_trusted)
484 : {
485 0 : struct PickupContext *pc = cls;
486 : const struct TALER_EXCHANGE_Keys *keys;
487 :
488 0 : pc->fo = NULL;
489 0 : stop_operations (pc); /* stops timeout job */
490 0 : if (NULL == hr)
491 : {
492 0 : pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
493 0 : pc->response = TALER_MHD_MAKE_JSON_PACK (
494 : TALER_JSON_pack_ec (
495 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
496 0 : MHD_resume_connection (pc->connection);
497 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
498 0 : return;
499 : }
500 0 : if (NULL == eh)
501 : {
502 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
503 0 : pc->response =
504 0 : TALER_MHD_MAKE_JSON_PACK (
505 : TALER_JSON_pack_ec (
506 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
507 : TMH_pack_exchange_reply (hr));
508 0 : MHD_resume_connection (pc->connection);
509 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
510 0 : return;
511 : }
512 0 : if (NULL == (keys = TALER_EXCHANGE_get_keys (eh)))
513 : {
514 0 : pc->http_status = MHD_HTTP_BAD_GATEWAY;
515 0 : pc->response =
516 0 : TALER_MHD_MAKE_JSON_PACK (
517 : TALER_JSON_pack_ec (
518 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE),
519 : TMH_pack_exchange_reply (hr));
520 0 : MHD_resume_connection (pc->connection);
521 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
522 0 : return;
523 : }
524 0 : GNUNET_assert (GNUNET_OK ==
525 : TALER_amount_set_zero (TMH_currency,
526 : &pc->total_requested));
527 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
528 : {
529 0 : struct TALER_PlanchetDetail *pd = &pc->planchets[i];
530 : const struct TALER_EXCHANGE_DenomPublicKey *dpk;
531 :
532 0 : dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
533 0 : &pd->denom_pub_hash);
534 0 : if (NULL == dpk)
535 : {
536 0 : pc->http_status = MHD_HTTP_CONFLICT;
537 0 : pc->response =
538 0 : TALER_MHD_MAKE_JSON_PACK (
539 : TALER_JSON_pack_ec (
540 : TALER_EC_MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN),
541 : TMH_pack_exchange_reply (hr));
542 0 : MHD_resume_connection (pc->connection);
543 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
544 0 : return;
545 : }
546 :
547 0 : if ( (GNUNET_YES !=
548 0 : TALER_amount_cmp_currency (&pc->total_requested,
549 0 : &dpk->value)) ||
550 : (0 >
551 0 : TALER_amount_add (&pc->total_requested,
552 0 : &pc->total_requested,
553 : &dpk->value)) )
554 : {
555 0 : pc->http_status = MHD_HTTP_BAD_REQUEST;
556 0 : pc->response =
557 0 : TALER_MHD_make_error (TALER_EC_MERCHANT_TIP_PICKUP_SUMMATION_FAILED,
558 : "Could not add up values to compute pickup total");
559 0 : MHD_resume_connection (pc->connection);
560 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
561 0 : return;
562 : }
563 : }
564 0 : pc->tr_initialized = true;
565 0 : MHD_resume_connection (pc->connection);
566 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
567 : }
568 :
569 :
570 : /**
571 : * The tip lookup operation failed. Generate an error response based on the @a qs.
572 : *
573 : * @param connection connection to generate error for
574 : * @param qs DB status to base error creation on
575 : * @return MHD result code
576 : */
577 : static MHD_RESULT
578 0 : reply_lookup_tip_failed (struct MHD_Connection *connection,
579 : enum GNUNET_DB_QueryStatus qs)
580 : {
581 : unsigned int response_code;
582 : enum TALER_ErrorCode ec;
583 :
584 0 : TMH_db->rollback (TMH_db->cls);
585 0 : switch (qs)
586 : {
587 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
588 0 : ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
589 0 : response_code = MHD_HTTP_NOT_FOUND;
590 0 : break;
591 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
592 0 : ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
593 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
594 0 : break;
595 0 : case GNUNET_DB_STATUS_HARD_ERROR:
596 0 : ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
597 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
598 0 : break;
599 0 : default:
600 0 : GNUNET_break (0);
601 0 : ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
602 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
603 0 : break;
604 : }
605 0 : return TALER_MHD_reply_with_error (connection,
606 : response_code,
607 : ec,
608 : NULL);
609 : }
610 :
611 :
612 : MHD_RESULT
613 0 : TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
614 : struct MHD_Connection *connection,
615 : struct TMH_HandlerContext *hc)
616 : {
617 0 : struct PickupContext *pc = hc->ctx;
618 : char *exchange_url;
619 : struct TALER_Amount total_authorized;
620 : struct TALER_Amount total_picked_up;
621 : struct TALER_Amount total_remaining;
622 : struct GNUNET_TIME_Timestamp expiration;
623 : enum GNUNET_DB_QueryStatus qs;
624 : unsigned int num_retries;
625 :
626 0 : if (NULL == pc)
627 : {
628 : json_t *planchets;
629 : json_t *planchet;
630 : size_t index;
631 :
632 0 : pc = GNUNET_new (struct PickupContext);
633 0 : hc->ctx = pc;
634 0 : hc->cc = &pick_context_cleanup;
635 :
636 0 : GNUNET_assert (NULL != hc->infix);
637 0 : if (GNUNET_OK !=
638 0 : GNUNET_CRYPTO_hash_from_string (hc->infix,
639 : &pc->tip_id.hash))
640 : {
641 : /* tip_id has wrong encoding */
642 0 : GNUNET_break_op (0);
643 0 : return TALER_MHD_reply_with_error (connection,
644 : MHD_HTTP_BAD_REQUEST,
645 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
646 0 : hc->infix);
647 : }
648 :
649 : {
650 : struct GNUNET_JSON_Specification spec[] = {
651 0 : GNUNET_JSON_spec_json ("planchets",
652 : &planchets),
653 0 : GNUNET_JSON_spec_end ()
654 : };
655 : {
656 : enum GNUNET_GenericReturnValue res;
657 :
658 0 : res = TALER_MHD_parse_json_data (connection,
659 0 : hc->request_body,
660 : spec);
661 0 : if (GNUNET_OK != res)
662 : return (GNUNET_NO == res)
663 : ? MHD_YES
664 0 : : MHD_NO;
665 : }
666 : }
667 0 : if (! json_is_array (planchets))
668 : {
669 0 : GNUNET_break_op (0);
670 0 : json_decref (planchets);
671 0 : return TALER_MHD_reply_with_error (connection,
672 : MHD_HTTP_BAD_REQUEST,
673 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
674 : "planchets");
675 : }
676 :
677 0 : GNUNET_array_grow (pc->planchets,
678 : pc->planchets_length,
679 : json_array_size (planchets));
680 0 : json_array_foreach (planchets, index, planchet) {
681 0 : struct TALER_PlanchetDetail *pd = &pc->planchets[index];
682 : struct GNUNET_JSON_Specification spec[] = {
683 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
684 : &pd->denom_pub_hash),
685 0 : TALER_JSON_spec_blinded_planchet ("coin_ev",
686 : &pd->blinded_planchet),
687 0 : GNUNET_JSON_spec_end ()
688 : };
689 : {
690 : enum GNUNET_GenericReturnValue res;
691 :
692 0 : res = TALER_MHD_parse_json_data (connection,
693 : planchet,
694 : spec);
695 0 : if (GNUNET_OK != res)
696 : {
697 0 : json_decref (planchets);
698 : return (GNUNET_NO == res)
699 : ? MHD_YES
700 0 : : MHD_NO;
701 : }
702 : }
703 : }
704 0 : json_decref (planchets);
705 : {
706 : struct GNUNET_HashContext *hc;
707 :
708 0 : hc = GNUNET_CRYPTO_hash_context_start ();
709 0 : GNUNET_CRYPTO_hash_context_read (hc,
710 0 : &pc->tip_id,
711 : sizeof (pc->tip_id));
712 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
713 : {
714 0 : struct TALER_PlanchetDetail *pd = &pc->planchets[i];
715 :
716 0 : GNUNET_CRYPTO_hash_context_read (hc,
717 0 : &pd->denom_pub_hash,
718 : sizeof (pd->denom_pub_hash));
719 0 : TALER_blinded_planchet_hash_ (&pd->blinded_planchet,
720 : hc);
721 : }
722 0 : GNUNET_CRYPTO_hash_context_finish (hc,
723 : &pc->pickup_id.hash);
724 : }
725 : }
726 :
727 0 : if (NULL != pc->response)
728 : {
729 : MHD_RESULT ret;
730 :
731 0 : ret = MHD_queue_response (connection,
732 : pc->http_status,
733 : pc->response);
734 0 : pc->response = NULL;
735 0 : return ret;
736 : }
737 :
738 0 : if (! pc->tr_initialized)
739 : {
740 0 : qs = TMH_db->lookup_tip (TMH_db->cls,
741 0 : hc->instance->settings.id,
742 0 : &pc->tip_id,
743 : &total_authorized,
744 : &total_picked_up,
745 : &expiration,
746 : &exchange_url,
747 : &pc->reserve_priv);
748 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
749 0 : return reply_lookup_tip_failed (connection,
750 : qs);
751 0 : MHD_suspend_connection (connection);
752 0 : pc->connection = connection;
753 0 : pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
754 : &do_timeout,
755 : pc);
756 0 : pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
757 : NULL,
758 : GNUNET_NO,
759 : &compute_total_requested,
760 : pc);
761 0 : GNUNET_free (exchange_url);
762 0 : return MHD_YES;
763 : }
764 :
765 :
766 0 : TMH_db->preflight (TMH_db->cls);
767 0 : num_retries = 0;
768 0 : RETRY:
769 0 : num_retries++;
770 0 : if (num_retries > MAX_RETRIES)
771 : {
772 0 : GNUNET_break (0);
773 0 : return TALER_MHD_reply_with_error (connection,
774 : MHD_HTTP_INTERNAL_SERVER_ERROR,
775 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
776 : NULL);
777 : }
778 0 : if (GNUNET_OK !=
779 0 : TMH_db->start (TMH_db->cls,
780 : "pickup tip"))
781 : {
782 0 : GNUNET_break (0);
783 0 : return TALER_MHD_reply_with_error (connection,
784 : MHD_HTTP_INTERNAL_SERVER_ERROR,
785 : TALER_EC_GENERIC_DB_START_FAILED,
786 : NULL);
787 : }
788 0 : {
789 0 : struct TALER_BlindedDenominationSignature sigs[
790 0 : GNUNET_NZL (pc->planchets_length)];
791 :
792 0 : memset (sigs,
793 : 0,
794 : sizeof (sigs));
795 0 : qs = TMH_db->lookup_pickup (TMH_db->cls,
796 0 : hc->instance->settings.id,
797 0 : &pc->tip_id,
798 0 : &pc->pickup_id,
799 : &exchange_url,
800 : &pc->reserve_priv,
801 : pc->planchets_length,
802 : sigs);
803 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
804 : "Lookup pickup `%s' resulted in %d\n",
805 : GNUNET_h2s (&pc->pickup_id.hash),
806 : qs);
807 0 : if (qs > GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)
808 0 : qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
809 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
810 : {
811 0 : bool rollback = false;
812 :
813 0 : for (unsigned int i = 0; i< pc->planchets_length; i++)
814 : {
815 0 : if (TALER_DENOMINATION_INVALID != sigs[i].cipher)
816 0 : continue;
817 0 : if (! rollback)
818 : {
819 0 : TMH_db->rollback (TMH_db->cls);
820 0 : MHD_suspend_connection (connection);
821 0 : GNUNET_CONTAINER_DLL_insert (pc_head,
822 : pc_tail,
823 : pc);
824 0 : pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
825 : &do_timeout,
826 : pc);
827 0 : rollback = true;
828 : }
829 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
830 : "Lookup pickup `%s' initiated withdraw #%u\n",
831 : GNUNET_h2s (&pc->pickup_id.hash),
832 : i);
833 0 : try_withdraw (pc,
834 : exchange_url,
835 0 : &pc->planchets[i],
836 : i);
837 : }
838 0 : GNUNET_free (exchange_url);
839 0 : if (rollback)
840 0 : return MHD_YES;
841 : /* we got _all_ signatures, can continue! */
842 : }
843 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
844 : {
845 : unsigned int response_code;
846 : enum TALER_ErrorCode ec;
847 :
848 0 : TMH_db->rollback (TMH_db->cls);
849 0 : switch (qs)
850 : {
851 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
852 : {
853 : json_t *blind_sigs;
854 :
855 0 : blind_sigs = json_array ();
856 0 : GNUNET_assert (NULL != blind_sigs);
857 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
858 : {
859 0 : GNUNET_assert (0 ==
860 : json_array_append_new (
861 : blind_sigs,
862 : GNUNET_JSON_PACK (
863 : TALER_JSON_pack_blinded_denom_sig ("blind_sig",
864 : &sigs[i]))));
865 0 : TALER_blinded_denom_sig_free (&sigs[i]);
866 : }
867 0 : return TALER_MHD_REPLY_JSON_PACK (
868 : connection,
869 : MHD_HTTP_OK,
870 : GNUNET_JSON_pack_array_steal ("blind_sigs",
871 : blind_sigs));
872 : }
873 : break;
874 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
875 0 : goto RETRY;
876 0 : case GNUNET_DB_STATUS_HARD_ERROR:
877 0 : ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
878 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
879 0 : break;
880 0 : default:
881 0 : GNUNET_break (0);
882 0 : ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
883 0 : response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
884 0 : break;
885 : }
886 0 : return TALER_MHD_reply_with_error (connection,
887 : response_code,
888 : ec,
889 : NULL);
890 : }
891 : }
892 :
893 0 : qs = TMH_db->lookup_tip (TMH_db->cls,
894 0 : hc->instance->settings.id,
895 0 : &pc->tip_id,
896 : &total_authorized,
897 : &total_picked_up,
898 : &expiration,
899 : &exchange_url,
900 : &pc->reserve_priv);
901 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
902 : {
903 0 : TMH_db->rollback (TMH_db->cls);
904 0 : goto RETRY;
905 : }
906 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
907 : {
908 0 : TMH_db->rollback (TMH_db->cls);
909 0 : return reply_lookup_tip_failed (connection,
910 : qs);
911 : }
912 0 : if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
913 : {
914 0 : GNUNET_free (exchange_url);
915 0 : TMH_db->rollback (TMH_db->cls);
916 0 : return TALER_MHD_reply_with_error (connection,
917 : MHD_HTTP_GONE,
918 : TALER_EC_MERCHANT_TIP_PICKUP_HAS_EXPIRED,
919 0 : hc->infix);
920 : }
921 0 : if (0 >
922 0 : TALER_amount_subtract (&total_remaining,
923 : &total_authorized,
924 : &total_picked_up))
925 : {
926 0 : GNUNET_free (exchange_url);
927 0 : GNUNET_break_op (0);
928 0 : TMH_db->rollback (TMH_db->cls);
929 0 : return TALER_MHD_reply_with_error (connection,
930 : MHD_HTTP_INTERNAL_SERVER_ERROR,
931 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
932 : "picked up amount exceeds authorized amount");
933 : }
934 :
935 0 : if (0 >
936 0 : TALER_amount_cmp (&total_remaining,
937 0 : &pc->total_requested))
938 : {
939 : /* total_remaining < pc->total_requested */
940 0 : GNUNET_free (exchange_url);
941 0 : GNUNET_break_op (0);
942 0 : TMH_db->rollback (TMH_db->cls);
943 0 : return TALER_MHD_reply_with_error (connection,
944 : MHD_HTTP_BAD_REQUEST,
945 : TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
946 0 : hc->infix);
947 : }
948 :
949 0 : GNUNET_assert (0 <
950 : TALER_amount_add (&total_picked_up,
951 : &total_picked_up,
952 : &pc->total_requested));
953 0 : qs = TMH_db->insert_pickup (TMH_db->cls,
954 0 : hc->instance->settings.id,
955 0 : &pc->tip_id,
956 : &total_picked_up,
957 0 : &pc->pickup_id,
958 0 : &pc->total_requested);
959 0 : if (qs < 0)
960 : {
961 0 : TMH_db->rollback (TMH_db->cls);
962 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
963 0 : goto RETRY;
964 0 : GNUNET_free (exchange_url);
965 0 : return TALER_MHD_reply_with_error (connection,
966 : MHD_HTTP_INTERNAL_SERVER_ERROR,
967 : TALER_EC_GENERIC_DB_STORE_FAILED,
968 : "pickup");
969 : }
970 0 : qs = TMH_db->commit (TMH_db->cls);
971 0 : if (qs < 0)
972 : {
973 0 : TMH_db->rollback (TMH_db->cls);
974 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
975 0 : goto RETRY;
976 0 : GNUNET_free (exchange_url);
977 0 : return TALER_MHD_reply_with_error (connection,
978 : MHD_HTTP_INTERNAL_SERVER_ERROR,
979 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
980 : NULL);
981 : }
982 0 : MHD_suspend_connection (connection);
983 0 : GNUNET_CONTAINER_DLL_insert (pc_head,
984 : pc_tail,
985 : pc);
986 0 : pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
987 : &do_timeout,
988 : pc);
989 0 : for (unsigned int i = 0; i<pc->planchets_length; i++)
990 : {
991 0 : try_withdraw (pc,
992 : exchange_url,
993 0 : &pc->planchets[i],
994 : i);
995 : }
996 0 : GNUNET_free (exchange_url);
997 0 : return MHD_YES;
998 : }
|