Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_reserves_purse.c
18 : * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
19 : * verifies the coin signature before handing things off
20 : * to the database.
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <jansson.h>
27 : #include <microhttpd.h>
28 : #include <pthread.h>
29 : #include "taler_json_lib.h"
30 : #include "taler_kyclogic_lib.h"
31 : #include "taler_mhd_lib.h"
32 : #include "taler-exchange-httpd_reserves_purse.h"
33 : #include "taler-exchange-httpd_responses.h"
34 : #include "taler_exchangedb_lib.h"
35 : #include "taler-exchange-httpd_keys.h"
36 :
37 :
38 : /**
39 : * Closure for #purse_transaction.
40 : */
41 : struct ReservePurseContext
42 : {
43 :
44 : /**
45 : * Public key of the reserve we are creating a purse for.
46 : */
47 : const struct TALER_ReservePublicKeyP *reserve_pub;
48 :
49 : /**
50 : * Fees for the operation.
51 : */
52 : const struct TEH_GlobalFee *gf;
53 :
54 : /**
55 : * Signature of the reserve affirming the merge.
56 : */
57 : struct TALER_ReserveSignatureP reserve_sig;
58 :
59 : /**
60 : * Purse fee the client is willing to pay.
61 : */
62 : struct TALER_Amount purse_fee;
63 :
64 : /**
65 : * Total amount already put into the purse.
66 : */
67 : struct TALER_Amount deposit_total;
68 :
69 : /**
70 : * Merge time.
71 : */
72 : struct GNUNET_TIME_Timestamp merge_timestamp;
73 :
74 : /**
75 : * Our current time.
76 : */
77 : struct GNUNET_TIME_Timestamp exchange_timestamp;
78 :
79 : /**
80 : * Details about an encrypted contract, if any.
81 : */
82 : struct TALER_EncryptedContract econtract;
83 :
84 : /**
85 : * Merge key for the purse.
86 : */
87 : struct TALER_PurseMergePublicKeyP merge_pub;
88 :
89 : /**
90 : * Merge affirmation by the @e merge_pub.
91 : */
92 : struct TALER_PurseMergeSignatureP merge_sig;
93 :
94 : /**
95 : * Signature of the client affiming this request.
96 : */
97 : struct TALER_PurseContractSignatureP purse_sig;
98 :
99 : /**
100 : * Fundamental details about the purse.
101 : */
102 : struct TEH_PurseDetails pd;
103 :
104 : /**
105 : * Hash of the @e payto_uri.
106 : */
107 : struct TALER_PaytoHashP h_payto;
108 :
109 : /**
110 : * KYC status of the operation.
111 : */
112 : struct TALER_EXCHANGEDB_KycStatus kyc;
113 :
114 : /**
115 : * Minimum age for deposits into this purse.
116 : */
117 : uint32_t min_age;
118 :
119 : /**
120 : * Flags for the operation.
121 : */
122 : enum TALER_WalletAccountMergeFlags flags;
123 :
124 : /**
125 : * Do we lack an @e econtract?
126 : */
127 : bool no_econtract;
128 :
129 : };
130 :
131 :
132 : /**
133 : * Function called to iterate over KYC-relevant
134 : * transaction amounts for a particular time range.
135 : * Called within a database transaction, so must
136 : * not start a new one.
137 : *
138 : * @param cls a `struct ReservePurseContext`
139 : * @param limit maximum time-range for which events
140 : * should be fetched (timestamp in the past)
141 : * @param cb function to call on each event found,
142 : * events must be returned in reverse chronological
143 : * order
144 : * @param cb_cls closure for @a cb
145 : */
146 : static void
147 0 : amount_iterator (void *cls,
148 : struct GNUNET_TIME_Absolute limit,
149 : TALER_EXCHANGEDB_KycAmountCallback cb,
150 : void *cb_cls)
151 : {
152 0 : struct ReservePurseContext *rpc = cls;
153 : enum GNUNET_DB_QueryStatus qs;
154 :
155 0 : cb (cb_cls,
156 0 : &rpc->deposit_total,
157 : GNUNET_TIME_absolute_get ());
158 0 : qs = TEH_plugin->select_merge_amounts_for_kyc_check (
159 0 : TEH_plugin->cls,
160 0 : &rpc->h_payto,
161 : limit,
162 : cb,
163 : cb_cls);
164 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
165 : "Got %d additional transactions for this merge and limit %llu\n",
166 : qs,
167 : (unsigned long long) limit.abs_value_us);
168 0 : GNUNET_break (qs >= 0);
169 0 : }
170 :
171 :
172 : /**
173 : * Execute database transaction for /reserves/$PID/purse. Runs the transaction
174 : * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
175 : * a MHD response. IF it returns an hard error, the transaction logic MUST
176 : * queue a MHD response and set @a mhd_ret. IF it returns the soft error
177 : * code, the function MAY be called again to retry and MUST not queue a MHD
178 : * response.
179 : *
180 : * @param cls a `struct ReservePurseContext`
181 : * @param connection MHD request context
182 : * @param[out] mhd_ret set to MHD status on error
183 : * @return transaction status
184 : */
185 : static enum GNUNET_DB_QueryStatus
186 0 : purse_transaction (void *cls,
187 : struct MHD_Connection *connection,
188 : MHD_RESULT *mhd_ret)
189 : {
190 0 : struct ReservePurseContext *rpc = cls;
191 : enum GNUNET_DB_QueryStatus qs;
192 :
193 : const char *required;
194 :
195 0 : required = TALER_KYCLOGIC_kyc_test_required (
196 : TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
197 0 : &rpc->h_payto,
198 0 : TEH_plugin->select_satisfied_kyc_processes,
199 0 : TEH_plugin->cls,
200 : &amount_iterator,
201 : rpc);
202 0 : if (NULL != required)
203 : {
204 0 : rpc->kyc.ok = false;
205 0 : return TEH_plugin->insert_kyc_requirement_for_account (
206 0 : TEH_plugin->cls,
207 : required,
208 0 : &rpc->h_payto,
209 : &rpc->kyc.requirement_row);
210 : }
211 0 : rpc->kyc.ok = true;
212 :
213 : {
214 0 : bool in_conflict = true;
215 :
216 : /* 1) store purse */
217 0 : qs = TEH_plugin->insert_purse_request (
218 0 : TEH_plugin->cls,
219 0 : &rpc->pd.purse_pub,
220 0 : &rpc->merge_pub,
221 : rpc->pd.purse_expiration,
222 0 : &rpc->pd.h_contract_terms,
223 : rpc->min_age,
224 : rpc->flags,
225 0 : &rpc->purse_fee,
226 0 : &rpc->pd.target_amount,
227 0 : &rpc->purse_sig,
228 : &in_conflict);
229 0 : if (qs < 0)
230 : {
231 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
232 0 : return qs;
233 0 : TALER_LOG_WARNING (
234 : "Failed to store purse purse information in database\n");
235 0 : *mhd_ret =
236 0 : TALER_MHD_reply_with_error (connection,
237 : MHD_HTTP_INTERNAL_SERVER_ERROR,
238 : TALER_EC_GENERIC_DB_STORE_FAILED,
239 : "insert purse request");
240 0 : return GNUNET_DB_STATUS_HARD_ERROR;
241 : }
242 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
243 0 : return qs;
244 0 : if (in_conflict)
245 : {
246 : struct TALER_PurseMergePublicKeyP merge_pub;
247 : struct GNUNET_TIME_Timestamp purse_expiration;
248 : struct TALER_PrivateContractHashP h_contract_terms;
249 : struct TALER_Amount target_amount;
250 : struct TALER_Amount balance;
251 : struct TALER_PurseContractSignatureP purse_sig;
252 : uint32_t min_age;
253 :
254 0 : TEH_plugin->rollback (TEH_plugin->cls);
255 0 : qs = TEH_plugin->select_purse_request (
256 0 : TEH_plugin->cls,
257 0 : &rpc->pd.purse_pub,
258 : &merge_pub,
259 : &purse_expiration,
260 : &h_contract_terms,
261 : &min_age,
262 : &target_amount,
263 : &balance,
264 : &purse_sig);
265 0 : if (qs <= 0)
266 : {
267 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
268 0 : GNUNET_break (0 != qs);
269 0 : TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
270 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
271 : MHD_HTTP_INTERNAL_SERVER_ERROR,
272 : TALER_EC_GENERIC_DB_FETCH_FAILED,
273 : "select purse request");
274 0 : return GNUNET_DB_STATUS_HARD_ERROR;
275 : }
276 : *mhd_ret
277 0 : = TALER_MHD_REPLY_JSON_PACK (
278 : connection,
279 : MHD_HTTP_CONFLICT,
280 : TALER_JSON_pack_ec (
281 : TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
282 : TALER_JSON_pack_amount ("amount",
283 : &target_amount),
284 : GNUNET_JSON_pack_uint64 ("min_age",
285 : min_age),
286 : GNUNET_JSON_pack_timestamp ("purse_expiration",
287 : purse_expiration),
288 : GNUNET_JSON_pack_data_auto ("purse_sig",
289 : &purse_sig),
290 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
291 : &h_contract_terms),
292 : GNUNET_JSON_pack_data_auto ("merge_pub",
293 : &merge_pub));
294 0 : return GNUNET_DB_STATUS_HARD_ERROR;
295 : }
296 :
297 : }
298 :
299 : /* 2) create purse with reserve (and debit reserve for purse creation!) */
300 : {
301 0 : bool in_conflict = true;
302 0 : bool insufficient_funds = true;
303 0 : bool no_reserve = true;
304 :
305 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
306 : "Creating purse with flags %d\n",
307 : rpc->flags);
308 0 : qs = TEH_plugin->do_reserve_purse (
309 0 : TEH_plugin->cls,
310 0 : &rpc->pd.purse_pub,
311 0 : &rpc->merge_sig,
312 : rpc->merge_timestamp,
313 0 : &rpc->reserve_sig,
314 : (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
315 0 : == rpc->flags)
316 : ? NULL
317 0 : : &rpc->gf->fees.purse,
318 : rpc->reserve_pub,
319 : &in_conflict,
320 : &no_reserve,
321 : &insufficient_funds);
322 0 : if (qs < 0)
323 : {
324 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
325 0 : return qs;
326 0 : TALER_LOG_WARNING (
327 : "Failed to store purse merge information in database\n");
328 0 : *mhd_ret =
329 0 : TALER_MHD_reply_with_error (connection,
330 : MHD_HTTP_INTERNAL_SERVER_ERROR,
331 : TALER_EC_GENERIC_DB_STORE_FAILED,
332 : "do reserve purse");
333 0 : return GNUNET_DB_STATUS_HARD_ERROR;
334 : }
335 0 : if (in_conflict)
336 : {
337 : /* same purse already merged into a different reserve!? */
338 : struct TALER_PurseContractPublicKeyP purse_pub;
339 : struct TALER_PurseMergeSignatureP merge_sig;
340 : struct GNUNET_TIME_Timestamp merge_timestamp;
341 : char *partner_url;
342 : struct TALER_ReservePublicKeyP reserve_pub;
343 :
344 0 : TEH_plugin->rollback (TEH_plugin->cls);
345 0 : qs = TEH_plugin->select_purse_merge (
346 0 : TEH_plugin->cls,
347 : &purse_pub,
348 : &merge_sig,
349 : &merge_timestamp,
350 : &partner_url,
351 : &reserve_pub);
352 0 : if (qs <= 0)
353 : {
354 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
355 0 : GNUNET_break (0 != qs);
356 0 : TALER_LOG_WARNING (
357 : "Failed to fetch purse merge information from database\n");
358 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
359 : MHD_HTTP_INTERNAL_SERVER_ERROR,
360 : TALER_EC_GENERIC_DB_FETCH_FAILED,
361 : "select purse merge");
362 0 : return GNUNET_DB_STATUS_HARD_ERROR;
363 : }
364 : *mhd_ret
365 0 : = TALER_MHD_REPLY_JSON_PACK (
366 : connection,
367 : MHD_HTTP_CONFLICT,
368 : TALER_JSON_pack_ec (
369 : TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
370 : GNUNET_JSON_pack_string ("partner_url",
371 : NULL == partner_url
372 : ? TEH_base_url
373 : : partner_url),
374 : GNUNET_JSON_pack_timestamp ("merge_timestamp",
375 : merge_timestamp),
376 : GNUNET_JSON_pack_data_auto ("merge_sig",
377 : &merge_sig),
378 : GNUNET_JSON_pack_data_auto ("reserve_pub",
379 : &reserve_pub));
380 0 : GNUNET_free (partner_url);
381 0 : return GNUNET_DB_STATUS_HARD_ERROR;
382 : }
383 0 : if ( (no_reserve) &&
384 : ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
385 0 : == rpc->flags) ||
386 0 : (! TALER_amount_is_zero (&rpc->gf->fees.purse)) ) )
387 : {
388 : *mhd_ret
389 0 : = TALER_MHD_REPLY_JSON_PACK (
390 : connection,
391 : MHD_HTTP_NOT_FOUND,
392 : TALER_JSON_pack_ec (
393 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
394 0 : return GNUNET_DB_STATUS_HARD_ERROR;
395 : }
396 0 : if (insufficient_funds)
397 : {
398 : *mhd_ret
399 0 : = TALER_MHD_REPLY_JSON_PACK (
400 : connection,
401 : MHD_HTTP_CONFLICT,
402 : TALER_JSON_pack_ec (
403 : TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
404 0 : return GNUNET_DB_STATUS_HARD_ERROR;
405 : }
406 : }
407 : /* 3) if present, persist contract */
408 0 : if (! rpc->no_econtract)
409 : {
410 0 : bool in_conflict = true;
411 :
412 0 : qs = TEH_plugin->insert_contract (TEH_plugin->cls,
413 0 : &rpc->pd.purse_pub,
414 0 : &rpc->econtract,
415 : &in_conflict);
416 0 : if (qs < 0)
417 : {
418 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
419 0 : return qs;
420 0 : TALER_LOG_WARNING ("Failed to store purse information in database\n");
421 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
422 : MHD_HTTP_INTERNAL_SERVER_ERROR,
423 : TALER_EC_GENERIC_DB_STORE_FAILED,
424 : "purse purse contract");
425 0 : return GNUNET_DB_STATUS_HARD_ERROR;
426 : }
427 0 : if (in_conflict)
428 : {
429 : struct TALER_EncryptedContract econtract;
430 : struct GNUNET_HashCode h_econtract;
431 :
432 0 : qs = TEH_plugin->select_contract_by_purse (
433 0 : TEH_plugin->cls,
434 0 : &rpc->pd.purse_pub,
435 : &econtract);
436 0 : if (qs <= 0)
437 : {
438 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
439 0 : return qs;
440 0 : GNUNET_break (0 != qs);
441 0 : TALER_LOG_WARNING (
442 : "Failed to store fetch contract information from database\n");
443 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
444 : MHD_HTTP_INTERNAL_SERVER_ERROR,
445 : TALER_EC_GENERIC_DB_FETCH_FAILED,
446 : "select contract");
447 0 : return GNUNET_DB_STATUS_HARD_ERROR;
448 : }
449 0 : GNUNET_CRYPTO_hash (econtract.econtract,
450 : econtract.econtract_size,
451 : &h_econtract);
452 : *mhd_ret
453 0 : = TALER_MHD_REPLY_JSON_PACK (
454 : connection,
455 : MHD_HTTP_CONFLICT,
456 : TALER_JSON_pack_ec (
457 : TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
458 : GNUNET_JSON_pack_data_auto ("h_econtract",
459 : &h_econtract),
460 : GNUNET_JSON_pack_data_auto ("econtract_sig",
461 : &econtract.econtract_sig),
462 : GNUNET_JSON_pack_data_auto ("contract_pub",
463 : &econtract.contract_pub));
464 0 : GNUNET_free (econtract.econtract);
465 0 : return GNUNET_DB_STATUS_HARD_ERROR;
466 : }
467 : }
468 0 : return qs;
469 : }
470 :
471 :
472 : MHD_RESULT
473 0 : TEH_handler_reserves_purse (
474 : struct TEH_RequestContext *rc,
475 : const struct TALER_ReservePublicKeyP *reserve_pub,
476 : const json_t *root)
477 : {
478 0 : struct MHD_Connection *connection = rc->connection;
479 0 : struct ReservePurseContext rpc = {
480 : .reserve_pub = reserve_pub,
481 0 : .exchange_timestamp = GNUNET_TIME_timestamp_get ()
482 : };
483 0 : bool no_purse_fee = true;
484 : struct GNUNET_JSON_Specification spec[] = {
485 0 : TALER_JSON_spec_amount ("purse_value",
486 : TEH_currency,
487 : &rpc.pd.target_amount),
488 0 : GNUNET_JSON_spec_uint32 ("min_age",
489 : &rpc.min_age),
490 0 : GNUNET_JSON_spec_mark_optional (
491 : TALER_JSON_spec_amount ("purse_fee",
492 : TEH_currency,
493 : &rpc.purse_fee),
494 : &no_purse_fee),
495 0 : GNUNET_JSON_spec_mark_optional (
496 : TALER_JSON_spec_econtract ("econtract",
497 : &rpc.econtract),
498 : &rpc.no_econtract),
499 0 : GNUNET_JSON_spec_fixed_auto ("merge_pub",
500 : &rpc.merge_pub),
501 0 : GNUNET_JSON_spec_fixed_auto ("merge_sig",
502 : &rpc.merge_sig),
503 0 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
504 : &rpc.reserve_sig),
505 0 : GNUNET_JSON_spec_fixed_auto ("purse_pub",
506 : &rpc.pd.purse_pub),
507 0 : GNUNET_JSON_spec_fixed_auto ("purse_sig",
508 : &rpc.purse_sig),
509 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
510 : &rpc.pd.h_contract_terms),
511 0 : GNUNET_JSON_spec_timestamp ("merge_timestamp",
512 : &rpc.merge_timestamp),
513 0 : GNUNET_JSON_spec_timestamp ("purse_expiration",
514 : &rpc.pd.purse_expiration),
515 0 : GNUNET_JSON_spec_end ()
516 : };
517 :
518 : {
519 : enum GNUNET_GenericReturnValue res;
520 :
521 0 : res = TALER_MHD_parse_json_data (connection,
522 : root,
523 : spec);
524 0 : if (GNUNET_SYSERR == res)
525 : {
526 0 : GNUNET_break (0);
527 0 : return MHD_NO; /* hard failure */
528 : }
529 0 : if (GNUNET_NO == res)
530 : {
531 0 : GNUNET_break_op (0);
532 0 : return MHD_YES; /* failure */
533 : }
534 : }
535 : {
536 : char *payto_uri;
537 :
538 0 : payto_uri = TALER_reserve_make_payto (TEH_base_url,
539 : reserve_pub);
540 0 : TALER_payto_hash (payto_uri,
541 : &rpc.h_payto);
542 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
543 0 : if (GNUNET_OK !=
544 0 : TALER_wallet_purse_merge_verify (payto_uri,
545 : rpc.merge_timestamp,
546 : &rpc.pd.purse_pub,
547 : &rpc.merge_pub,
548 : &rpc.merge_sig))
549 : {
550 0 : GNUNET_break_op (0);
551 0 : GNUNET_JSON_parse_free (spec);
552 0 : GNUNET_free (payto_uri);
553 0 : return TALER_MHD_reply_with_error (
554 : connection,
555 : MHD_HTTP_FORBIDDEN,
556 : TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
557 : NULL);
558 : }
559 0 : GNUNET_free (payto_uri);
560 : }
561 0 : GNUNET_assert (GNUNET_OK ==
562 : TALER_amount_set_zero (TEH_currency,
563 : &rpc.deposit_total));
564 0 : if (GNUNET_TIME_timestamp_cmp (rpc.pd.purse_expiration,
565 : <,
566 : rpc.exchange_timestamp))
567 : {
568 0 : GNUNET_break_op (0);
569 0 : GNUNET_JSON_parse_free (spec);
570 0 : return TALER_MHD_reply_with_error (connection,
571 : MHD_HTTP_BAD_REQUEST,
572 : TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
573 : NULL);
574 : }
575 0 : if (GNUNET_TIME_absolute_is_never (rpc.pd.purse_expiration.abs_time))
576 : {
577 0 : GNUNET_break_op (0);
578 0 : GNUNET_JSON_parse_free (spec);
579 0 : return TALER_MHD_reply_with_error (connection,
580 : MHD_HTTP_BAD_REQUEST,
581 : TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
582 : NULL);
583 : }
584 : {
585 : struct TEH_KeyStateHandle *keys;
586 :
587 0 : keys = TEH_keys_get_state ();
588 0 : if (NULL == keys)
589 : {
590 0 : GNUNET_break (0);
591 0 : GNUNET_JSON_parse_free (spec);
592 0 : return TALER_MHD_reply_with_error (connection,
593 : MHD_HTTP_INTERNAL_SERVER_ERROR,
594 : TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
595 : NULL);
596 : }
597 0 : rpc.gf = TEH_keys_global_fee_by_time (keys,
598 : rpc.exchange_timestamp);
599 : }
600 0 : if (NULL == rpc.gf)
601 : {
602 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
603 : "Cannot purse purse: global fees not configured!\n");
604 0 : GNUNET_JSON_parse_free (spec);
605 0 : return TALER_MHD_reply_with_error (connection,
606 : MHD_HTTP_INTERNAL_SERVER_ERROR,
607 : TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
608 : NULL);
609 : }
610 0 : if (no_purse_fee)
611 : {
612 0 : rpc.flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
613 0 : TALER_amount_set_zero (TEH_currency,
614 : &rpc.purse_fee);
615 : }
616 : else
617 : {
618 0 : rpc.flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
619 0 : if (-1 ==
620 0 : TALER_amount_cmp (&rpc.purse_fee,
621 0 : &rpc.gf->fees.purse))
622 : {
623 : /* rpc.purse_fee is below gf.fees.purse! */
624 0 : GNUNET_break_op (0);
625 0 : GNUNET_JSON_parse_free (spec);
626 0 : return TALER_MHD_reply_with_error (connection,
627 : MHD_HTTP_BAD_REQUEST,
628 : TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
629 0 : TALER_amount2s (&rpc.gf->fees.purse));
630 : }
631 : }
632 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
633 0 : if (GNUNET_OK !=
634 0 : TALER_wallet_purse_create_verify (rpc.pd.purse_expiration,
635 : &rpc.pd.h_contract_terms,
636 : &rpc.merge_pub,
637 : rpc.min_age,
638 : &rpc.pd.target_amount,
639 : &rpc.pd.purse_pub,
640 : &rpc.purse_sig))
641 : {
642 0 : GNUNET_break_op (0);
643 0 : GNUNET_JSON_parse_free (spec);
644 0 : return TALER_MHD_reply_with_error (
645 : connection,
646 : MHD_HTTP_FORBIDDEN,
647 : TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
648 : NULL);
649 : }
650 0 : if (GNUNET_OK !=
651 0 : TALER_wallet_account_merge_verify (rpc.merge_timestamp,
652 : &rpc.pd.purse_pub,
653 : rpc.pd.purse_expiration,
654 : &rpc.pd.h_contract_terms,
655 : &rpc.pd.target_amount,
656 : &rpc.purse_fee,
657 : rpc.min_age,
658 : rpc.flags,
659 : rpc.reserve_pub,
660 : &rpc.reserve_sig))
661 : {
662 0 : GNUNET_break_op (0);
663 0 : GNUNET_JSON_parse_free (spec);
664 0 : return TALER_MHD_reply_with_error (
665 : connection,
666 : MHD_HTTP_FORBIDDEN,
667 : TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
668 : NULL);
669 : }
670 0 : if ( (! rpc.no_econtract) &&
671 : (GNUNET_OK !=
672 0 : TALER_wallet_econtract_upload_verify (rpc.econtract.econtract,
673 : rpc.econtract.econtract_size,
674 : &rpc.econtract.contract_pub,
675 : &rpc.pd.purse_pub,
676 : &rpc.econtract.econtract_sig)) )
677 : {
678 0 : TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
679 0 : GNUNET_JSON_parse_free (spec);
680 0 : return TALER_MHD_reply_with_error (connection,
681 : MHD_HTTP_FORBIDDEN,
682 : TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
683 : NULL);
684 : }
685 :
686 :
687 0 : if (GNUNET_SYSERR ==
688 0 : TEH_plugin->preflight (TEH_plugin->cls))
689 : {
690 0 : GNUNET_break (0);
691 0 : GNUNET_JSON_parse_free (spec);
692 0 : return TALER_MHD_reply_with_error (connection,
693 : MHD_HTTP_INTERNAL_SERVER_ERROR,
694 : TALER_EC_GENERIC_DB_START_FAILED,
695 : "preflight failure");
696 : }
697 :
698 : /* execute transaction */
699 : {
700 : MHD_RESULT mhd_ret;
701 :
702 0 : if (GNUNET_OK !=
703 0 : TEH_DB_run_transaction (connection,
704 : "execute purse purse",
705 : TEH_MT_REQUEST_RESERVE_PURSE,
706 : &mhd_ret,
707 : &purse_transaction,
708 : &rpc))
709 : {
710 0 : GNUNET_JSON_parse_free (spec);
711 0 : return mhd_ret;
712 : }
713 : }
714 :
715 0 : if (! rpc.kyc.ok)
716 0 : return TEH_RESPONSE_reply_kyc_required (connection,
717 : &rpc.h_payto,
718 : &rpc.kyc);
719 : /* generate regular response */
720 : {
721 : MHD_RESULT res;
722 :
723 0 : res = TEH_RESPONSE_reply_purse_created (connection,
724 : rpc.exchange_timestamp,
725 : &rpc.deposit_total,
726 : &rpc.pd);
727 0 : GNUNET_JSON_parse_free (spec);
728 0 : return res;
729 : }
730 : }
731 :
732 :
733 : /* end of taler-exchange-httpd_reserves_purse.c */
|