Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty
12 : of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 : See the GNU Affero General Public License for more details.
14 :
15 : You should have received a copy of the GNU Affero General
16 : Public License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file taler-exchange-httpd_batch-withdraw.c
21 : * @brief Handle /reserves/$RESERVE_PUB/batch-withdraw requests
22 : * @author Florian Dold
23 : * @author Benedikt Mueller
24 : * @author Christian Grothoff
25 : */
26 : #include "platform.h"
27 : #include <gnunet/gnunet_util_lib.h>
28 : #include <jansson.h>
29 : #include "taler_json_lib.h"
30 : #include "taler_kyclogic_lib.h"
31 : #include "taler_mhd_lib.h"
32 : #include "taler-exchange-httpd_batch-withdraw.h"
33 : #include "taler-exchange-httpd_responses.h"
34 : #include "taler-exchange-httpd_keys.h"
35 :
36 :
37 : /**
38 : * Information per planchet in the batch.
39 : */
40 : struct PlanchetContext
41 : {
42 :
43 : /**
44 : * Hash of the (blinded) message to be signed by the Exchange.
45 : */
46 : struct TALER_BlindedCoinHashP h_coin_envelope;
47 :
48 : /**
49 : * Value of the coin being exchanged (matching the denomination key)
50 : * plus the transaction fee. We include this in what is being
51 : * signed so that we can verify a reserve's remaining total balance
52 : * without needing to access the respective denomination key
53 : * information each time.
54 : */
55 : struct TALER_Amount amount_with_fee;
56 :
57 : /**
58 : * Blinded planchet.
59 : */
60 : struct TALER_BlindedPlanchet blinded_planchet;
61 :
62 : /**
63 : * Set to the resulting signed coin data to be returned to the client.
64 : */
65 : struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
66 :
67 : };
68 :
69 : /**
70 : * Context for #batch_withdraw_transaction.
71 : */
72 : struct BatchWithdrawContext
73 : {
74 :
75 : /**
76 : * Public key of the reserv.
77 : */
78 : const struct TALER_ReservePublicKeyP *reserve_pub;
79 :
80 : /**
81 : * KYC status of the reserve used for the operation.
82 : */
83 : struct TALER_EXCHANGEDB_KycStatus kyc;
84 :
85 : /**
86 : * Array of @e planchets_length planchets we are processing.
87 : */
88 : struct PlanchetContext *planchets;
89 :
90 : /**
91 : * Hash of the payto-URI representing the reserve
92 : * from which we are withdrawing.
93 : */
94 : struct TALER_PaytoHashP h_payto;
95 :
96 : /**
97 : * Current time for the DB transaction.
98 : */
99 : struct GNUNET_TIME_Timestamp now;
100 :
101 : /**
102 : * Total amount from all coins with fees.
103 : */
104 : struct TALER_Amount batch_total;
105 :
106 : /**
107 : * Length of the @e planchets array.
108 : */
109 : unsigned int planchets_length;
110 :
111 : };
112 :
113 :
114 : /**
115 : * Function called to iterate over KYC-relevant
116 : * transaction amounts for a particular time range.
117 : * Called within a database transaction, so must
118 : * not start a new one.
119 : *
120 : * @param cls closure, identifies the event type and
121 : * account to iterate over events for
122 : * @param limit maximum time-range for which events
123 : * should be fetched (timestamp in the past)
124 : * @param cb function to call on each event found,
125 : * events must be returned in reverse chronological
126 : * order
127 : * @param cb_cls closure for @a cb
128 : */
129 : static void
130 0 : batch_withdraw_amount_cb (void *cls,
131 : struct GNUNET_TIME_Absolute limit,
132 : TALER_EXCHANGEDB_KycAmountCallback cb,
133 : void *cb_cls)
134 : {
135 0 : struct BatchWithdrawContext *wc = cls;
136 : enum GNUNET_DB_QueryStatus qs;
137 :
138 0 : if (GNUNET_OK !=
139 0 : cb (cb_cls,
140 0 : &wc->batch_total,
141 : wc->now.abs_time))
142 0 : return;
143 0 : qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
144 0 : TEH_plugin->cls,
145 0 : &wc->h_payto,
146 : limit,
147 : cb,
148 : cb_cls);
149 0 : GNUNET_break (qs >= 0);
150 : }
151 :
152 :
153 : /**
154 : * Function implementing withdraw transaction. Runs the
155 : * transaction logic; IF it returns a non-error code, the transaction
156 : * logic MUST NOT queue a MHD response. IF it returns an hard error,
157 : * the transaction logic MUST queue a MHD response and set @a mhd_ret.
158 : * IF it returns the soft error code, the function MAY be called again
159 : * to retry and MUST not queue a MHD response.
160 : *
161 : * Note that "wc->collectable.sig" is set before entering this function as we
162 : * signed before entering the transaction.
163 : *
164 : * @param cls a `struct BatchWithdrawContext *`
165 : * @param connection MHD request which triggered the transaction
166 : * @param[out] mhd_ret set to MHD response status for @a connection,
167 : * if transaction failed (!)
168 : * @return transaction status
169 : */
170 : static enum GNUNET_DB_QueryStatus
171 0 : batch_withdraw_transaction (void *cls,
172 : struct MHD_Connection *connection,
173 : MHD_RESULT *mhd_ret)
174 : {
175 0 : struct BatchWithdrawContext *wc = cls;
176 : uint64_t ruuid;
177 : enum GNUNET_DB_QueryStatus qs;
178 0 : bool balance_ok = false;
179 0 : bool found = false;
180 : const char *kyc_required;
181 :
182 0 : wc->now = GNUNET_TIME_timestamp_get ();
183 0 : qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
184 : wc->reserve_pub,
185 : &wc->h_payto);
186 0 : if (qs < 0)
187 0 : return qs;
188 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
189 : {
190 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
191 : MHD_HTTP_NOT_FOUND,
192 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
193 : NULL);
194 0 : return GNUNET_DB_STATUS_HARD_ERROR;
195 : }
196 0 : kyc_required = TALER_KYCLOGIC_kyc_test_required (
197 : TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
198 0 : &wc->h_payto,
199 0 : TEH_plugin->select_satisfied_kyc_processes,
200 0 : TEH_plugin->cls,
201 : &batch_withdraw_amount_cb,
202 : wc);
203 0 : if (NULL != kyc_required)
204 : {
205 : /* insert KYC requirement into DB! */
206 0 : wc->kyc.ok = false;
207 0 : return TEH_plugin->insert_kyc_requirement_for_account (
208 0 : TEH_plugin->cls,
209 : kyc_required,
210 0 : &wc->h_payto,
211 : &wc->kyc.requirement_row);
212 : }
213 0 : wc->kyc.ok = true;
214 0 : qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls,
215 : wc->now,
216 : wc->reserve_pub,
217 0 : &wc->batch_total,
218 : &found,
219 : &balance_ok,
220 : &ruuid);
221 0 : if (0 > qs)
222 : {
223 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
224 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
225 : MHD_HTTP_INTERNAL_SERVER_ERROR,
226 : TALER_EC_GENERIC_DB_FETCH_FAILED,
227 : "update_reserve_batch_withdraw");
228 0 : return qs;
229 : }
230 0 : if (! found)
231 : {
232 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
233 : MHD_HTTP_NOT_FOUND,
234 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
235 : NULL);
236 0 : return GNUNET_DB_STATUS_HARD_ERROR;
237 : }
238 0 : if (! balance_ok)
239 : {
240 0 : TEH_plugin->rollback (TEH_plugin->cls);
241 0 : *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
242 : connection,
243 0 : &wc->batch_total,
244 : wc->reserve_pub);
245 0 : return GNUNET_DB_STATUS_HARD_ERROR;
246 : }
247 :
248 : /* Add information about each planchet in the batch */
249 0 : for (unsigned int i = 0; i<wc->planchets_length; i++)
250 : {
251 0 : struct PlanchetContext *pc = &wc->planchets[i];
252 0 : const struct TALER_BlindedPlanchet *bp = &pc->blinded_planchet;
253 : const struct TALER_CsNonce *nonce;
254 0 : bool denom_unknown = true;
255 0 : bool conflict = true;
256 0 : bool nonce_reuse = true;
257 :
258 0 : nonce = (TALER_DENOMINATION_CS == bp->cipher)
259 : ? &bp->details.cs_blinded_planchet.nonce
260 0 : : NULL;
261 0 : qs = TEH_plugin->do_batch_withdraw_insert (TEH_plugin->cls,
262 : nonce,
263 0 : &pc->collectable,
264 : wc->now,
265 : ruuid,
266 : &denom_unknown,
267 : &conflict,
268 : &nonce_reuse);
269 0 : if (0 > qs)
270 : {
271 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
272 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
273 : MHD_HTTP_INTERNAL_SERVER_ERROR,
274 : TALER_EC_GENERIC_DB_FETCH_FAILED,
275 : "do_withdraw");
276 0 : return qs;
277 : }
278 0 : if (denom_unknown)
279 : {
280 0 : GNUNET_break (0);
281 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
282 : MHD_HTTP_INTERNAL_SERVER_ERROR,
283 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
284 : NULL);
285 0 : return GNUNET_DB_STATUS_HARD_ERROR;
286 : }
287 0 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
288 : (conflict) )
289 : {
290 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
291 : "Idempotent coin in batch, not allowed. Aborting.\n");
292 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
293 : MHD_HTTP_CONFLICT,
294 : TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
295 : NULL);
296 0 : return GNUNET_DB_STATUS_HARD_ERROR;
297 : }
298 0 : if (nonce_reuse)
299 : {
300 0 : GNUNET_break_op (0);
301 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
302 : MHD_HTTP_BAD_REQUEST,
303 : TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
304 : NULL);
305 0 : return GNUNET_DB_STATUS_HARD_ERROR;
306 : }
307 : }
308 0 : TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW]++;
309 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
310 : }
311 :
312 :
313 : /**
314 : * Generates our final (successful) response.
315 : *
316 : * @param rc request context
317 : * @param wc operation context
318 : * @return MHD queue status
319 : */
320 : static MHD_RESULT
321 0 : generate_reply_success (const struct TEH_RequestContext *rc,
322 : const struct BatchWithdrawContext *wc)
323 : {
324 : json_t *sigs;
325 :
326 0 : if (! wc->kyc.ok)
327 : {
328 : /* KYC required */
329 0 : return TEH_RESPONSE_reply_kyc_required (rc->connection,
330 : &wc->h_payto,
331 : &wc->kyc);
332 : }
333 :
334 0 : sigs = json_array ();
335 0 : GNUNET_assert (NULL != sigs);
336 0 : for (unsigned int i = 0; i<wc->planchets_length; i++)
337 : {
338 0 : struct PlanchetContext *pc = &wc->planchets[i];
339 :
340 0 : GNUNET_assert (
341 : 0 ==
342 : json_array_append_new (
343 : sigs,
344 : GNUNET_JSON_PACK (
345 : TALER_JSON_pack_blinded_denom_sig (
346 : "ev_sig",
347 : &pc->collectable.sig))));
348 : }
349 0 : TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length;
350 0 : return TALER_MHD_REPLY_JSON_PACK (
351 : rc->connection,
352 : MHD_HTTP_OK,
353 : GNUNET_JSON_pack_array_steal ("ev_sigs",
354 : sigs));
355 : }
356 :
357 :
358 : /**
359 : * Check if the @a rc is replayed and we already have an
360 : * answer. If so, replay the existing answer and return the
361 : * HTTP response.
362 : *
363 : * @param rc request context
364 : * @param wc parsed request data
365 : * @param[out] mret HTTP status, set if we return true
366 : * @return true if the request is idempotent with an existing request
367 : * false if we did not find the request in the DB and did not set @a mret
368 : */
369 : static bool
370 0 : check_request_idempotent (const struct TEH_RequestContext *rc,
371 : const struct BatchWithdrawContext *wc,
372 : MHD_RESULT *mret)
373 : {
374 0 : for (unsigned int i = 0; i<wc->planchets_length; i++)
375 : {
376 0 : struct PlanchetContext *pc = &wc->planchets[i];
377 : enum GNUNET_DB_QueryStatus qs;
378 :
379 0 : qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
380 0 : &pc->h_coin_envelope,
381 : &pc->collectable);
382 0 : if (0 > qs)
383 : {
384 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
385 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
386 0 : *mret = TALER_MHD_reply_with_error (rc->connection,
387 : MHD_HTTP_INTERNAL_SERVER_ERROR,
388 : TALER_EC_GENERIC_DB_FETCH_FAILED,
389 : "get_withdraw_info");
390 0 : return true; /* well, kind-of */
391 : }
392 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
393 0 : return false;
394 : }
395 : /* generate idempotent reply */
396 0 : TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
397 0 : *mret = generate_reply_success (rc,
398 : wc);
399 0 : return true;
400 : }
401 :
402 :
403 : /**
404 : * The request was parsed successfully. Prepare
405 : * our side for the main DB transaction.
406 : *
407 : * @param rc request details
408 : * @param wc storage for request processing
409 : * @return MHD result for the @a rc
410 : */
411 : static MHD_RESULT
412 0 : prepare_transaction (const struct TEH_RequestContext *rc,
413 : struct BatchWithdrawContext *wc)
414 : {
415 : /* Note: We could check the reserve balance here,
416 : just to be reasonably sure that the reserve has
417 : a sufficient balance before doing the "expensive"
418 : signatures... */
419 : /* Sign before transaction! */
420 0 : for (unsigned int i = 0; i<wc->planchets_length; i++)
421 : {
422 0 : struct PlanchetContext *pc = &wc->planchets[i];
423 : enum TALER_ErrorCode ec;
424 :
425 0 : ec = TEH_keys_denomination_sign_withdraw (
426 0 : &pc->collectable.denom_pub_hash,
427 0 : &pc->blinded_planchet,
428 : &pc->collectable.sig);
429 0 : if (TALER_EC_NONE != ec)
430 : {
431 0 : GNUNET_break (0);
432 0 : return TALER_MHD_reply_with_ec (rc->connection,
433 : ec,
434 : NULL);
435 : }
436 : }
437 :
438 : /* run transaction */
439 : {
440 : MHD_RESULT mhd_ret;
441 :
442 0 : if (GNUNET_OK !=
443 0 : TEH_DB_run_transaction (rc->connection,
444 : "run batch withdraw",
445 : TEH_MT_REQUEST_WITHDRAW,
446 : &mhd_ret,
447 : &batch_withdraw_transaction,
448 : wc))
449 : {
450 0 : return mhd_ret;
451 : }
452 : }
453 : /* return final positive response */
454 0 : return generate_reply_success (rc,
455 : wc);
456 : }
457 :
458 :
459 : /**
460 : * Continue processing the request @a rc by parsing the
461 : * @a planchets and then running the transaction.
462 : *
463 : * @param rc request details
464 : * @param wc storage for request processing
465 : * @param planchets array of planchets to parse
466 : * @return MHD result for the @a rc
467 : */
468 : static MHD_RESULT
469 0 : parse_planchets (const struct TEH_RequestContext *rc,
470 : struct BatchWithdrawContext *wc,
471 : const json_t *planchets)
472 : {
473 : struct TEH_KeyStateHandle *ksh;
474 : MHD_RESULT mret;
475 :
476 0 : for (unsigned int i = 0; i<wc->planchets_length; i++)
477 : {
478 0 : struct PlanchetContext *pc = &wc->planchets[i];
479 : struct GNUNET_JSON_Specification ispec[] = {
480 0 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
481 : &pc->collectable.reserve_sig),
482 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
483 : &pc->collectable.denom_pub_hash),
484 0 : TALER_JSON_spec_blinded_planchet ("coin_ev",
485 : &pc->blinded_planchet),
486 0 : GNUNET_JSON_spec_end ()
487 : };
488 :
489 : {
490 : enum GNUNET_GenericReturnValue res;
491 :
492 0 : res = TALER_MHD_parse_json_data (rc->connection,
493 0 : json_array_get (planchets,
494 : i),
495 : ispec);
496 0 : if (GNUNET_OK != res)
497 0 : return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
498 : }
499 0 : pc->collectable.reserve_pub = *wc->reserve_pub;
500 0 : for (unsigned int k = 0; k<i; k++)
501 : {
502 0 : const struct PlanchetContext *kpc = &wc->planchets[k];
503 :
504 0 : if (0 ==
505 0 : TALER_blinded_planchet_cmp (&kpc->blinded_planchet,
506 0 : &pc->blinded_planchet))
507 : {
508 0 : GNUNET_break_op (0);
509 0 : return TALER_MHD_reply_with_error (rc->connection,
510 : MHD_HTTP_BAD_REQUEST,
511 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
512 : "duplicate planchet");
513 : }
514 : }
515 : }
516 :
517 0 : ksh = TEH_keys_get_state ();
518 0 : if (NULL == ksh)
519 : {
520 0 : if (! check_request_idempotent (rc,
521 : wc,
522 : &mret))
523 : {
524 0 : return TALER_MHD_reply_with_error (rc->connection,
525 : MHD_HTTP_INTERNAL_SERVER_ERROR,
526 : TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
527 : NULL);
528 : }
529 0 : return mret;
530 : }
531 0 : for (unsigned int i = 0; i<wc->planchets_length; i++)
532 : {
533 0 : struct PlanchetContext *pc = &wc->planchets[i];
534 : struct TEH_DenominationKey *dk;
535 :
536 0 : dk = TEH_keys_denomination_by_hash2 (ksh,
537 0 : &pc->collectable.denom_pub_hash,
538 : NULL,
539 : NULL);
540 0 : if (NULL == dk)
541 : {
542 0 : if (! check_request_idempotent (rc,
543 : wc,
544 : &mret))
545 : {
546 0 : return TEH_RESPONSE_reply_unknown_denom_pub_hash (
547 : rc->connection,
548 0 : &pc->collectable.denom_pub_hash);
549 : }
550 0 : return mret;
551 : }
552 0 : if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
553 : {
554 : /* This denomination is past the expiration time for withdraws */
555 0 : if (! check_request_idempotent (rc,
556 : wc,
557 : &mret))
558 : {
559 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
560 : rc->connection,
561 0 : &pc->collectable.denom_pub_hash,
562 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
563 : "WITHDRAW");
564 : }
565 0 : return mret;
566 : }
567 0 : if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
568 : {
569 : /* This denomination is not yet valid, no need to check
570 : for idempotency! */
571 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
572 : rc->connection,
573 0 : &pc->collectable.denom_pub_hash,
574 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
575 : "WITHDRAW");
576 : }
577 0 : if (dk->recoup_possible)
578 : {
579 : /* This denomination has been revoked */
580 0 : if (! check_request_idempotent (rc,
581 : wc,
582 : &mret))
583 : {
584 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
585 : rc->connection,
586 0 : &pc->collectable.denom_pub_hash,
587 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
588 : "WITHDRAW");
589 : }
590 0 : return mret;
591 : }
592 0 : if (dk->denom_pub.cipher != pc->blinded_planchet.cipher)
593 : {
594 : /* denomination cipher and blinded planchet cipher not the same */
595 0 : GNUNET_break_op (0);
596 0 : return TALER_MHD_reply_with_error (rc->connection,
597 : MHD_HTTP_BAD_REQUEST,
598 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
599 : NULL);
600 : }
601 0 : if (0 >
602 0 : TALER_amount_add (&pc->collectable.amount_with_fee,
603 0 : &dk->meta.value,
604 0 : &dk->meta.fees.withdraw))
605 : {
606 0 : GNUNET_break (0);
607 0 : return TALER_MHD_reply_with_error (rc->connection,
608 : MHD_HTTP_INTERNAL_SERVER_ERROR,
609 : TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
610 : NULL);
611 : }
612 0 : if (0 >
613 0 : TALER_amount_add (&wc->batch_total,
614 0 : &wc->batch_total,
615 0 : &pc->collectable.amount_with_fee))
616 : {
617 0 : GNUNET_break (0);
618 0 : return TALER_MHD_reply_with_error (rc->connection,
619 : MHD_HTTP_INTERNAL_SERVER_ERROR,
620 : TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
621 : NULL);
622 : }
623 :
624 0 : if (GNUNET_OK !=
625 0 : TALER_coin_ev_hash (&pc->blinded_planchet,
626 0 : &pc->collectable.denom_pub_hash,
627 : &pc->collectable.h_coin_envelope))
628 : {
629 0 : GNUNET_break (0);
630 0 : return TALER_MHD_reply_with_error (rc->connection,
631 : MHD_HTTP_INTERNAL_SERVER_ERROR,
632 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
633 : NULL);
634 : }
635 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
636 0 : if (GNUNET_OK !=
637 0 : TALER_wallet_withdraw_verify (&pc->collectable.denom_pub_hash,
638 0 : &pc->collectable.amount_with_fee,
639 0 : &pc->collectable.h_coin_envelope,
640 0 : &pc->collectable.reserve_pub,
641 0 : &pc->collectable.reserve_sig))
642 : {
643 0 : GNUNET_break_op (0);
644 0 : return TALER_MHD_reply_with_error (rc->connection,
645 : MHD_HTTP_FORBIDDEN,
646 : TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
647 : NULL);
648 : }
649 : }
650 : /* everything parsed */
651 0 : return prepare_transaction (rc,
652 : wc);
653 : }
654 :
655 :
656 : MHD_RESULT
657 0 : TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
658 : const struct TALER_ReservePublicKeyP *reserve_pub,
659 : const json_t *root)
660 : {
661 : struct BatchWithdrawContext wc;
662 : json_t *planchets;
663 : struct GNUNET_JSON_Specification spec[] = {
664 0 : GNUNET_JSON_spec_json ("planchets",
665 : &planchets),
666 0 : GNUNET_JSON_spec_end ()
667 : };
668 :
669 0 : memset (&wc,
670 : 0,
671 : sizeof (wc));
672 0 : GNUNET_assert (GNUNET_OK ==
673 : TALER_amount_set_zero (TEH_currency,
674 : &wc.batch_total));
675 0 : wc.reserve_pub = reserve_pub;
676 : {
677 : enum GNUNET_GenericReturnValue res;
678 :
679 0 : res = TALER_MHD_parse_json_data (rc->connection,
680 : root,
681 : spec);
682 0 : if (GNUNET_OK != res)
683 0 : return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
684 : }
685 0 : if ( (! json_is_array (planchets)) ||
686 0 : (0 == json_array_size (planchets)) )
687 : {
688 0 : GNUNET_JSON_parse_free (spec);
689 0 : GNUNET_break_op (0);
690 0 : return TALER_MHD_reply_with_error (rc->connection,
691 : MHD_HTTP_BAD_REQUEST,
692 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
693 : "planchets");
694 : }
695 0 : wc.planchets_length = json_array_size (planchets);
696 0 : if (wc.planchets_length > TALER_MAX_FRESH_COINS)
697 : {
698 0 : GNUNET_JSON_parse_free (spec);
699 0 : GNUNET_break_op (0);
700 0 : return TALER_MHD_reply_with_error (rc->connection,
701 : MHD_HTTP_BAD_REQUEST,
702 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
703 : "too many planchets");
704 : }
705 0 : {
706 0 : struct PlanchetContext splanchets[wc.planchets_length];
707 : MHD_RESULT ret;
708 :
709 0 : memset (splanchets,
710 : 0,
711 : sizeof (splanchets));
712 0 : wc.planchets = splanchets;
713 0 : ret = parse_planchets (rc,
714 : &wc,
715 : planchets);
716 : /* Clean up */
717 0 : for (unsigned int i = 0; i<wc.planchets_length; i++)
718 : {
719 0 : struct PlanchetContext *pc = &wc.planchets[i];
720 :
721 0 : TALER_blinded_planchet_free (&pc->blinded_planchet);
722 0 : TALER_blinded_denom_sig_free (&pc->collectable.sig);
723 : }
724 0 : GNUNET_JSON_parse_free (spec);
725 0 : return ret;
726 : }
727 : }
728 :
729 :
730 : /* end of taler-exchange-httpd_batch-withdraw.c */
|