Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2026 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
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_post-batch-deposit.c
19 : * @brief Implementation of the /batch-deposit request of the exchange's HTTP API
20 : * @author Sree Harsha Totakura <sreeharsha@totakura.in>
21 : * @author Christian Grothoff
22 : */
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP status codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler/taler_json_lib.h"
29 : #include "taler/taler_auditor_service.h"
30 : #include "exchange_api_common.h"
31 : #include "exchange_api_handle.h"
32 : #include "taler/taler_signatures.h"
33 : #include "exchange_api_curl_defaults.h"
34 :
35 : #define DEBUG 0
36 :
37 : /**
38 : * 1:#AUDITOR_CHANCE is the probability that we report deposits
39 : * to the auditor.
40 : *
41 : * 20==5% of going to auditor. This is possibly still too high, but set
42 : * deliberately this high for testing
43 : */
44 : #define AUDITOR_CHANCE 20
45 :
46 :
47 : /**
48 : * Entry in list of ongoing interactions with an auditor.
49 : */
50 : struct TEAH_AuditorInteractionEntry
51 : {
52 : /**
53 : * DLL entry.
54 : */
55 : struct TEAH_AuditorInteractionEntry *next;
56 :
57 : /**
58 : * DLL entry.
59 : */
60 : struct TEAH_AuditorInteractionEntry *prev;
61 :
62 : /**
63 : * URL of our auditor. For logging.
64 : */
65 : const char *auditor_url;
66 :
67 : /**
68 : * Interaction state.
69 : */
70 : struct TALER_AUDITOR_DepositConfirmationHandle *dch;
71 :
72 : /**
73 : * Batch deposit this is for.
74 : */
75 : struct TALER_EXCHANGE_PostBatchDepositHandle *dh;
76 : };
77 :
78 :
79 : /**
80 : * @brief A Batch Deposit Handle
81 : */
82 : struct TALER_EXCHANGE_PostBatchDepositHandle
83 : {
84 :
85 : /**
86 : * The keys of the exchange.
87 : */
88 : struct TALER_EXCHANGE_Keys *keys;
89 :
90 : /**
91 : * Context for our curl request(s).
92 : */
93 : struct GNUNET_CURL_Context *ctx;
94 :
95 : /**
96 : * The base URL of the exchange (used to build the endpoint URL in _start).
97 : */
98 : char *base_url;
99 :
100 : /**
101 : * The full endpoint URL for this request (built in _start).
102 : */
103 : char *url;
104 :
105 : /**
106 : * Context for #TEH_curl_easy_post(). Keeps the data that must
107 : * persist for Curl to make the upload.
108 : */
109 : struct TALER_CURL_PostContext post_ctx;
110 :
111 : /**
112 : * Handle for the request.
113 : */
114 : struct GNUNET_CURL_Job *job;
115 :
116 : /**
117 : * Function to call with the result.
118 : */
119 : TALER_EXCHANGE_PostBatchDepositCallback cb;
120 :
121 : /**
122 : * Closure for @a cb.
123 : */
124 : void *cb_cls;
125 :
126 : /**
127 : * Details about the contract.
128 : */
129 : struct TALER_EXCHANGE_DepositContractDetail dcd;
130 :
131 : /**
132 : * Array with details about the coins.
133 : */
134 : struct TALER_EXCHANGE_CoinDepositDetail *cdds;
135 :
136 : /**
137 : * Hash of the merchant's wire details.
138 : */
139 : struct TALER_MerchantWireHashP h_wire;
140 :
141 : /**
142 : * Hash over "policy extensions" which were removed as a feature.
143 : * For now always all zeros!
144 : */
145 : struct TALER_ExtensionPolicyHashP h_policy;
146 :
147 : /**
148 : * Time when this confirmation was generated / when the exchange received
149 : * the deposit request.
150 : */
151 : struct GNUNET_TIME_Timestamp exchange_timestamp;
152 :
153 : /**
154 : * Exchange signature, set for #auditor_cb.
155 : */
156 : struct TALER_ExchangeSignatureP exchange_sig;
157 :
158 : /**
159 : * Head of DLL of interactions with this auditor.
160 : */
161 : struct TEAH_AuditorInteractionEntry *ai_head;
162 :
163 : /**
164 : * Tail of DLL of interactions with this auditor.
165 : */
166 : struct TEAH_AuditorInteractionEntry *ai_tail;
167 :
168 : /**
169 : * Result to return to the application once @e ai_head is empty.
170 : */
171 : struct TALER_EXCHANGE_PostBatchDepositResponse dr;
172 :
173 : /**
174 : * Exchange signing public key, set for #auditor_cb.
175 : */
176 : struct TALER_ExchangePublicKeyP exchange_pub;
177 :
178 : /**
179 : * Total amount deposited without fees as calculated by us.
180 : */
181 : struct TALER_Amount total_without_fee;
182 :
183 : /**
184 : * Response object to free at the end.
185 : */
186 : json_t *response;
187 :
188 : /**
189 : * The JSON body to POST (built during _create, used during _start).
190 : */
191 : json_t *deposit_obj;
192 :
193 : /**
194 : * Chance that we will inform the auditor about the deposit
195 : * is 1:n, where the value of this field is "n".
196 : */
197 : unsigned int auditor_chance;
198 :
199 : /**
200 : * Length of the @e cdds array.
201 : */
202 : unsigned int num_cdds;
203 :
204 : /**
205 : * Whether to verify the merchant signature on the contract terms.
206 : */
207 : bool verify_merchant_sig;
208 :
209 : };
210 :
211 :
212 : /**
213 : * Finish batch deposit operation by calling the callback.
214 : *
215 : * @param[in] dh handle to finished batch deposit operation
216 : */
217 : static void
218 98 : finish_dh (struct TALER_EXCHANGE_PostBatchDepositHandle *dh)
219 : {
220 98 : dh->cb (dh->cb_cls,
221 98 : &dh->dr);
222 98 : TALER_EXCHANGE_post_batch_deposit_cancel (dh);
223 98 : }
224 :
225 :
226 : /**
227 : * Function called with the result from our call to the
228 : * auditor's /deposit-confirmation handler.
229 : *
230 : * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
231 : * @param dcr response
232 : */
233 : static void
234 0 : acc_confirmation_cb (
235 : void *cls,
236 : const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
237 : {
238 0 : struct TEAH_AuditorInteractionEntry *aie = cls;
239 0 : struct TALER_EXCHANGE_PostBatchDepositHandle *dh = aie->dh;
240 :
241 0 : if (MHD_HTTP_OK != dcr->hr.http_status)
242 : {
243 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
244 : "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
245 : aie->auditor_url,
246 : dcr->hr.http_status,
247 : dcr->hr.ec);
248 : }
249 0 : GNUNET_CONTAINER_DLL_remove (dh->ai_head,
250 : dh->ai_tail,
251 : aie);
252 0 : GNUNET_free (aie);
253 0 : if (NULL == dh->ai_head)
254 0 : finish_dh (dh);
255 0 : }
256 :
257 :
258 : /**
259 : * Function called for each auditor to give us a chance to possibly
260 : * launch a deposit confirmation interaction.
261 : *
262 : * @param cls closure
263 : * @param auditor_url base URL of the auditor
264 : * @param auditor_pub public key of the auditor
265 : */
266 : static void
267 32 : auditor_cb (void *cls,
268 : const char *auditor_url,
269 : const struct TALER_AuditorPublicKeyP *auditor_pub)
270 32 : {
271 32 : struct TALER_EXCHANGE_PostBatchDepositHandle *dh = cls;
272 : const struct TALER_EXCHANGE_SigningPublicKey *spk;
273 : struct TEAH_AuditorInteractionEntry *aie;
274 32 : const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (
275 : dh->num_cdds)];
276 32 : const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL (
277 : dh->num_cdds)];
278 :
279 64 : for (unsigned int i = 0; i<dh->num_cdds; i++)
280 : {
281 32 : const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i];
282 :
283 32 : csigs[i] = &cdd->coin_sig;
284 32 : cpubs[i] = &cdd->coin_pub;
285 : }
286 :
287 32 : if (0 !=
288 32 : GNUNET_CRYPTO_random_u32 (dh->auditor_chance))
289 : {
290 32 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
291 : "Not providing deposit confirmation to auditor\n");
292 32 : return;
293 : }
294 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
295 : "Will provide deposit confirmation to auditor `%s'\n",
296 : TALER_B2S (auditor_pub));
297 0 : spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
298 0 : &dh->exchange_pub);
299 0 : if (NULL == spk)
300 : {
301 0 : GNUNET_break_op (0);
302 0 : return;
303 : }
304 0 : aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
305 0 : aie->dh = dh;
306 0 : aie->auditor_url = auditor_url;
307 0 : aie->dch = TALER_AUDITOR_deposit_confirmation (
308 : dh->ctx,
309 : auditor_url,
310 0 : &dh->h_wire,
311 0 : &dh->h_policy,
312 0 : &dh->dcd.h_contract_terms,
313 : dh->exchange_timestamp,
314 : dh->dcd.wire_deadline,
315 : dh->dcd.refund_deadline,
316 0 : &dh->total_without_fee,
317 : dh->num_cdds,
318 : cpubs,
319 : csigs,
320 0 : &dh->dcd.merchant_pub,
321 0 : &dh->exchange_pub,
322 0 : &dh->exchange_sig,
323 0 : &dh->keys->master_pub,
324 : spk->valid_from,
325 : spk->valid_until,
326 : spk->valid_legal,
327 : &spk->master_sig,
328 : &acc_confirmation_cb,
329 : aie);
330 0 : if (NULL == aie->dch)
331 : {
332 0 : GNUNET_break (0);
333 0 : GNUNET_free (aie);
334 0 : return;
335 : }
336 0 : GNUNET_CONTAINER_DLL_insert (dh->ai_head,
337 : dh->ai_tail,
338 : aie);
339 : }
340 :
341 :
342 : /**
343 : * Function called when we're done processing the
344 : * HTTP /batch-deposit request.
345 : *
346 : * @param cls the `struct TALER_EXCHANGE_PostBatchDepositHandle`
347 : * @param response_code HTTP response code, 0 on error
348 : * @param response parsed JSON result, NULL on error
349 : */
350 : static void
351 98 : handle_deposit_finished (void *cls,
352 : long response_code,
353 : const void *response)
354 : {
355 98 : struct TALER_EXCHANGE_PostBatchDepositHandle *dh = cls;
356 98 : const json_t *j = response;
357 98 : struct TALER_EXCHANGE_PostBatchDepositResponse *dr = &dh->dr;
358 :
359 98 : dh->job = NULL;
360 98 : dh->response = json_incref ((json_t*) j);
361 98 : dr->hr.reply = dh->response;
362 98 : dr->hr.http_status = (unsigned int) response_code;
363 98 : switch (response_code)
364 : {
365 0 : case 0:
366 0 : dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
367 0 : break;
368 84 : case MHD_HTTP_OK:
369 : {
370 : bool prev33;
371 : struct GNUNET_JSON_Specification spec[] = {
372 84 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
373 : &dh->exchange_sig),
374 84 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
375 : &dh->exchange_pub),
376 84 : GNUNET_JSON_spec_mark_optional (
377 : TALER_JSON_spec_amount (
378 : "accumulated_total_without_fee",
379 84 : dh->total_without_fee.currency,
380 : &dr->details.ok.accumulated_total_without_fee),
381 : &prev33),
382 84 : GNUNET_JSON_spec_mark_optional (
383 : TALER_JSON_spec_web_url ("transaction_base_url",
384 : &dr->details.ok.transaction_base_url),
385 : NULL),
386 84 : GNUNET_JSON_spec_timestamp ("exchange_timestamp",
387 : &dh->exchange_timestamp),
388 84 : GNUNET_JSON_spec_end ()
389 : };
390 :
391 84 : if (GNUNET_OK !=
392 84 : GNUNET_JSON_parse (j,
393 : spec,
394 : NULL, NULL))
395 : {
396 0 : GNUNET_break_op (0);
397 0 : dr->hr.http_status = 0;
398 0 : dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
399 0 : break;
400 : }
401 84 : if (GNUNET_OK !=
402 84 : TALER_EXCHANGE_test_signing_key (dh->keys,
403 84 : &dh->exchange_pub))
404 : {
405 0 : GNUNET_break_op (0);
406 0 : dr->hr.http_status = 0;
407 0 : dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
408 0 : break;
409 : }
410 84 : if (prev33)
411 : dr->details.ok.accumulated_total_without_fee
412 0 : = dh->total_without_fee;
413 84 : if (1 ==
414 84 : TALER_amount_cmp (&dh->total_without_fee,
415 84 : &dr->details.ok.accumulated_total_without_fee))
416 : {
417 : /* Amount signed by exchange is SMALLER than what we deposited */
418 0 : GNUNET_break_op (0);
419 0 : dr->hr.http_status = 0;
420 0 : dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
421 0 : break;
422 : }
423 84 : {
424 84 : const struct TALER_CoinSpendSignatureP *csigs[
425 84 : GNUNET_NZL (dh->num_cdds)];
426 :
427 170 : for (unsigned int i = 0; i<dh->num_cdds; i++)
428 86 : csigs[i] = &dh->cdds[i].coin_sig;
429 84 : if (GNUNET_OK !=
430 84 : TALER_exchange_online_deposit_confirmation_verify (
431 84 : &dh->dcd.h_contract_terms,
432 84 : &dh->h_wire,
433 84 : &dh->h_policy,
434 : dh->exchange_timestamp,
435 : dh->dcd.wire_deadline,
436 : dh->dcd.refund_deadline,
437 84 : &dr->details.ok.accumulated_total_without_fee,
438 : dh->num_cdds,
439 : csigs,
440 84 : &dh->dcd.merchant_pub,
441 84 : &dh->exchange_pub,
442 84 : &dh->exchange_sig))
443 : {
444 0 : GNUNET_break_op (0);
445 0 : dr->hr.http_status = 0;
446 0 : dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
447 0 : break;
448 : }
449 : }
450 84 : dh->total_without_fee = dr->details.ok.accumulated_total_without_fee;
451 84 : TALER_EXCHANGE_get_auditors_for_dc_ (dh->keys,
452 : &auditor_cb,
453 : dh);
454 : }
455 84 : dr->details.ok.exchange_sig = &dh->exchange_sig;
456 84 : dr->details.ok.exchange_pub = &dh->exchange_pub;
457 84 : dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
458 84 : break;
459 0 : case MHD_HTTP_BAD_REQUEST:
460 : /* This should never happen, either us or the exchange is buggy
461 : (or API version conflict); just pass JSON reply to the application */
462 0 : dr->hr.ec = TALER_JSON_get_error_code (j);
463 0 : dr->hr.hint = TALER_JSON_get_error_hint (j);
464 0 : break;
465 0 : case MHD_HTTP_FORBIDDEN:
466 0 : dr->hr.ec = TALER_JSON_get_error_code (j);
467 0 : dr->hr.hint = TALER_JSON_get_error_hint (j);
468 : /* Nothing really to verify, exchange says one of the signatures is
469 : invalid; as we checked them, this should never happen, we
470 : should pass the JSON reply to the application */
471 0 : break;
472 0 : case MHD_HTTP_NOT_FOUND:
473 0 : dr->hr.ec = TALER_JSON_get_error_code (j);
474 0 : dr->hr.hint = TALER_JSON_get_error_hint (j);
475 : /* Nothing really to verify, this should never
476 : happen, we should pass the JSON reply to the application */
477 0 : break;
478 9 : case MHD_HTTP_CONFLICT:
479 : {
480 9 : dr->hr.ec = TALER_JSON_get_error_code (j);
481 9 : dr->hr.hint = TALER_JSON_get_error_hint (j);
482 9 : switch (dr->hr.ec)
483 : {
484 4 : case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
485 : {
486 : struct GNUNET_JSON_Specification spec[] = {
487 4 : GNUNET_JSON_spec_fixed_auto (
488 : "coin_pub",
489 : &dr->details.conflict.details.insufficient_funds.coin_pub),
490 4 : GNUNET_JSON_spec_fixed_auto (
491 : "h_denom_pub",
492 : &dr->details.conflict.details.insufficient_funds.h_denom_pub),
493 4 : GNUNET_JSON_spec_end ()
494 : };
495 :
496 4 : if (GNUNET_OK !=
497 4 : GNUNET_JSON_parse (j,
498 : spec,
499 : NULL, NULL))
500 : {
501 0 : GNUNET_break_op (0);
502 0 : dr->hr.http_status = 0;
503 0 : dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
504 0 : break;
505 : }
506 : }
507 4 : break;
508 0 : case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
509 : {
510 : struct GNUNET_JSON_Specification spec[] = {
511 0 : GNUNET_JSON_spec_fixed_auto (
512 : "coin_pub",
513 : &dr->details.conflict.details
514 : .coin_conflicting_age_hash.coin_pub),
515 0 : GNUNET_JSON_spec_fixed_auto (
516 : "h_denom_pub",
517 : &dr->details.conflict.details
518 : .coin_conflicting_age_hash.h_denom_pub),
519 0 : GNUNET_JSON_spec_end ()
520 : };
521 :
522 0 : if (GNUNET_OK !=
523 0 : GNUNET_JSON_parse (j,
524 : spec,
525 : NULL, NULL))
526 : {
527 0 : GNUNET_break_op (0);
528 0 : dr->hr.http_status = 0;
529 0 : dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
530 0 : break;
531 : }
532 : }
533 0 : break;
534 1 : case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
535 : {
536 : struct GNUNET_JSON_Specification spec[] = {
537 1 : GNUNET_JSON_spec_fixed_auto (
538 : "coin_pub",
539 : &dr->details.conflict.details
540 : .coin_conflicting_denomination_key.coin_pub),
541 1 : GNUNET_JSON_spec_end ()
542 : };
543 :
544 1 : if (GNUNET_OK !=
545 1 : GNUNET_JSON_parse (j,
546 : spec,
547 : NULL, NULL))
548 : {
549 0 : GNUNET_break_op (0);
550 0 : dr->hr.http_status = 0;
551 0 : dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
552 0 : break;
553 : }
554 : }
555 1 : break;
556 4 : case TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT:
557 4 : break;
558 0 : default:
559 0 : GNUNET_break_op (0);
560 0 : break;
561 : }
562 : }
563 9 : break;
564 0 : case MHD_HTTP_GONE:
565 : /* could happen if denomination was revoked */
566 : /* Note: one might want to check /keys for revocation
567 : signature here, alas tricky in case our /keys
568 : is outdated => left to clients */
569 0 : dr->hr.ec = TALER_JSON_get_error_code (j);
570 0 : dr->hr.hint = TALER_JSON_get_error_hint (j);
571 0 : break;
572 5 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
573 5 : dr->hr.ec = TALER_JSON_get_error_code (j);
574 5 : dr->hr.hint = TALER_JSON_get_error_hint (j);
575 5 : if (GNUNET_OK !=
576 5 : TALER_EXCHANGE_parse_451 (&dr->details.unavailable_for_legal_reasons,
577 : j))
578 : {
579 0 : GNUNET_break_op (0);
580 0 : dr->hr.http_status = 0;
581 0 : dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
582 0 : break;
583 : }
584 5 : break;
585 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
586 0 : dr->hr.ec = TALER_JSON_get_error_code (j);
587 0 : dr->hr.hint = TALER_JSON_get_error_hint (j);
588 : /* Server had an internal issue; we should retry, but this API
589 : leaves this to the application */
590 0 : break;
591 0 : default:
592 : /* unexpected response code */
593 0 : dr->hr.ec = TALER_JSON_get_error_code (j);
594 0 : dr->hr.hint = TALER_JSON_get_error_hint (j);
595 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
596 : "Unexpected response code %u/%d for exchange deposit\n",
597 : (unsigned int) response_code,
598 : dr->hr.ec);
599 0 : GNUNET_break_op (0);
600 0 : break;
601 : }
602 98 : if (NULL != dh->ai_head)
603 0 : return;
604 98 : finish_dh (dh);
605 : }
606 :
607 :
608 : struct TALER_EXCHANGE_PostBatchDepositHandle *
609 98 : TALER_EXCHANGE_post_batch_deposit_create (
610 : struct GNUNET_CURL_Context *ctx,
611 : const char *url,
612 : struct TALER_EXCHANGE_Keys *keys,
613 : const struct TALER_EXCHANGE_DepositContractDetail *dcd,
614 : unsigned int num_cdds,
615 : const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
616 : enum TALER_ErrorCode *ec)
617 98 : {
618 : struct TALER_EXCHANGE_PostBatchDepositHandle *dh;
619 : json_t *deposits;
620 : const struct GNUNET_HashCode *wallet_data_hashp;
621 :
622 98 : if (0 == num_cdds)
623 : {
624 0 : GNUNET_break (0);
625 0 : *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
626 0 : return NULL;
627 : }
628 98 : if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
629 : >,
630 : dcd->wire_deadline))
631 : {
632 0 : GNUNET_break_op (0);
633 0 : *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
634 0 : return NULL;
635 : }
636 98 : dh = GNUNET_new (struct TALER_EXCHANGE_PostBatchDepositHandle);
637 98 : dh->auditor_chance = AUDITOR_CHANCE;
638 98 : dh->ctx = ctx;
639 98 : dh->base_url = GNUNET_strdup (url);
640 98 : dh->cdds = GNUNET_memdup (cdds,
641 : num_cdds * sizeof (*cdds));
642 98 : dh->num_cdds = num_cdds;
643 98 : dh->dcd = *dcd;
644 98 : TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
645 : &dcd->wire_salt,
646 : &dh->h_wire);
647 98 : deposits = json_array ();
648 98 : GNUNET_assert (NULL != deposits);
649 98 : GNUNET_assert (GNUNET_OK ==
650 : TALER_amount_set_zero (cdds[0].amount.currency,
651 : &dh->total_without_fee));
652 198 : for (unsigned int i = 0; i<num_cdds; i++)
653 : {
654 100 : const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
655 : const struct TALER_EXCHANGE_DenomPublicKey *dki;
656 : const struct TALER_AgeCommitmentHashP *h_age_commitmentp;
657 : struct TALER_Amount amount_without_fee;
658 :
659 100 : dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
660 : &cdd->h_denom_pub);
661 100 : if (NULL == dki)
662 : {
663 0 : *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
664 0 : GNUNET_break_op (0);
665 0 : json_decref (deposits);
666 0 : GNUNET_free (dh->base_url);
667 0 : GNUNET_free (dh->cdds);
668 0 : GNUNET_free (dh);
669 0 : return NULL;
670 : }
671 100 : if (0 >
672 100 : TALER_amount_subtract (&amount_without_fee,
673 : &cdd->amount,
674 : &dki->fees.deposit))
675 : {
676 0 : *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
677 0 : GNUNET_break_op (0);
678 0 : json_decref (deposits);
679 0 : GNUNET_free (dh->base_url);
680 0 : GNUNET_free (dh->cdds);
681 0 : GNUNET_free (dh);
682 0 : return NULL;
683 : }
684 100 : GNUNET_assert (0 <=
685 : TALER_amount_add (&dh->total_without_fee,
686 : &dh->total_without_fee,
687 : &amount_without_fee));
688 100 : if (GNUNET_OK !=
689 100 : TALER_EXCHANGE_verify_deposit_signature_ (dcd,
690 100 : &dh->h_policy,
691 100 : &dh->h_wire,
692 : cdd,
693 : dki))
694 : {
695 0 : *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
696 0 : GNUNET_break_op (0);
697 0 : json_decref (deposits);
698 0 : GNUNET_free (dh->base_url);
699 0 : GNUNET_free (dh->cdds);
700 0 : GNUNET_free (dh);
701 0 : return NULL;
702 : }
703 100 : if (GNUNET_is_zero (&cdd->h_age_commitment))
704 74 : h_age_commitmentp = NULL;
705 : else
706 26 : h_age_commitmentp = &cdd->h_age_commitment;
707 100 : GNUNET_assert (
708 : 0 ==
709 : json_array_append_new (
710 : deposits,
711 : GNUNET_JSON_PACK (
712 : TALER_JSON_pack_amount ("contribution",
713 : &cdd->amount),
714 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
715 : &cdd->h_denom_pub),
716 : TALER_JSON_pack_denom_sig ("ub_sig",
717 : &cdd->denom_sig),
718 : GNUNET_JSON_pack_data_auto ("coin_pub",
719 : &cdd->coin_pub),
720 : GNUNET_JSON_pack_allow_null (
721 : GNUNET_JSON_pack_data_auto ("h_age_commitment",
722 : h_age_commitmentp)),
723 : GNUNET_JSON_pack_data_auto ("coin_sig",
724 : &cdd->coin_sig)
725 : )));
726 : }
727 :
728 98 : if (GNUNET_is_zero (&dcd->wallet_data_hash))
729 98 : wallet_data_hashp = NULL;
730 : else
731 0 : wallet_data_hashp = &dcd->wallet_data_hash;
732 98 : dh->deposit_obj = GNUNET_JSON_PACK (
733 : TALER_JSON_pack_full_payto ("merchant_payto_uri",
734 : dcd->merchant_payto_uri),
735 : GNUNET_JSON_pack_allow_null (
736 : GNUNET_JSON_pack_string ("extra_wire_subject_metadata",
737 : dcd->extra_wire_subject_metadata)),
738 : GNUNET_JSON_pack_data_auto ("wire_salt",
739 : &dcd->wire_salt),
740 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
741 : &dcd->h_contract_terms),
742 : GNUNET_JSON_pack_array_steal ("coins",
743 : deposits),
744 : GNUNET_JSON_pack_allow_null (
745 : GNUNET_JSON_pack_data_auto ("wallet_data_hash",
746 : wallet_data_hashp)),
747 : GNUNET_JSON_pack_timestamp ("timestamp",
748 : dcd->wallet_timestamp),
749 : GNUNET_JSON_pack_data_auto ("merchant_pub",
750 : &dcd->merchant_pub),
751 : GNUNET_JSON_pack_data_auto ("merchant_sig",
752 : &dcd->merchant_sig),
753 : GNUNET_JSON_pack_allow_null (
754 : GNUNET_JSON_pack_timestamp ("refund_deadline",
755 : dcd->refund_deadline)),
756 : GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
757 : dcd->wire_deadline));
758 98 : if (NULL == dh->deposit_obj)
759 : {
760 0 : GNUNET_break (0);
761 0 : *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
762 0 : GNUNET_free (dh->base_url);
763 0 : GNUNET_free (dh->cdds);
764 0 : GNUNET_free (dh);
765 0 : return NULL;
766 : }
767 98 : dh->keys = TALER_EXCHANGE_keys_incref (keys);
768 98 : *ec = TALER_EC_NONE;
769 98 : return dh;
770 : }
771 :
772 :
773 : enum GNUNET_GenericReturnValue
774 0 : TALER_EXCHANGE_post_batch_deposit_set_options_ (
775 : struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh,
776 : unsigned int num_options,
777 : const struct TALER_EXCHANGE_PostBatchDepositOptionValue options[])
778 : {
779 0 : for (unsigned int i = 0; i < num_options; i++)
780 : {
781 0 : const struct TALER_EXCHANGE_PostBatchDepositOptionValue *opt = &options[i];
782 0 : switch (opt->option)
783 : {
784 0 : case TALER_EXCHANGE_POST_BATCH_DEPOSIT_OPTION_END:
785 0 : return GNUNET_OK;
786 0 : case TALER_EXCHANGE_POST_BATCH_DEPOSIT_OPTION_FORCE_DC:
787 0 : pbdh->auditor_chance = 1;
788 0 : break;
789 0 : case TALER_EXCHANGE_POST_BATCH_DEPOSIT_OPTION_VERIFY_MERCHANT_SIG:
790 0 : pbdh->verify_merchant_sig = true;
791 0 : break;
792 : }
793 : }
794 0 : return GNUNET_OK;
795 : }
796 :
797 :
798 : enum TALER_ErrorCode
799 98 : TALER_EXCHANGE_post_batch_deposit_start (
800 : struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh,
801 : TALER_EXCHANGE_PostBatchDepositCallback cb,
802 : TALER_EXCHANGE_POST_BATCH_DEPOSIT_RESULT_CLOSURE *cb_cls)
803 : {
804 : CURL *eh;
805 :
806 98 : if (pbdh->verify_merchant_sig &&
807 : (GNUNET_OK !=
808 0 : TALER_merchant_contract_verify (
809 0 : &pbdh->dcd.h_contract_terms,
810 0 : &pbdh->dcd.merchant_pub,
811 : &pbdh->dcd.merchant_sig)) )
812 : {
813 0 : GNUNET_break_op (0);
814 0 : return TALER_EC_GENERIC_PARAMETER_MALFORMED;
815 : }
816 98 : pbdh->cb = cb;
817 98 : pbdh->cb_cls = cb_cls;
818 98 : pbdh->url = TALER_url_join (pbdh->base_url,
819 : "batch-deposit",
820 : NULL);
821 98 : if (NULL == pbdh->url)
822 : {
823 0 : GNUNET_break (0);
824 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
825 : }
826 98 : eh = TALER_EXCHANGE_curl_easy_get_ (pbdh->url);
827 196 : if ( (NULL == eh) ||
828 : (GNUNET_OK !=
829 98 : TALER_curl_easy_post (&pbdh->post_ctx,
830 : eh,
831 98 : pbdh->deposit_obj)) )
832 : {
833 0 : GNUNET_break (0);
834 0 : if (NULL != eh)
835 0 : curl_easy_cleanup (eh);
836 0 : return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
837 : }
838 : /* FIXME: Help debug #11305 */
839 : #if DEBUG
840 : GNUNET_assert (CURLE_OK ==
841 : curl_easy_setopt (eh,
842 : CURLOPT_VERBOSE,
843 : 1));
844 : #endif
845 98 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
846 : "URL for batch-deposit: `%s'\n",
847 : pbdh->url);
848 196 : pbdh->job = GNUNET_CURL_job_add2 (pbdh->ctx,
849 : eh,
850 98 : pbdh->post_ctx.headers,
851 : &handle_deposit_finished,
852 : pbdh);
853 98 : if (NULL == pbdh->job)
854 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
855 98 : return TALER_EC_NONE;
856 : }
857 :
858 :
859 : void
860 98 : TALER_EXCHANGE_post_batch_deposit_cancel (
861 : struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh)
862 : {
863 : struct TEAH_AuditorInteractionEntry *aie;
864 :
865 98 : while (NULL != (aie = pbdh->ai_head))
866 : {
867 0 : GNUNET_assert (aie->dh == pbdh);
868 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
869 : "Not sending deposit confirmation to auditor `%s' due to cancellation\n",
870 : aie->auditor_url);
871 0 : TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
872 0 : GNUNET_CONTAINER_DLL_remove (pbdh->ai_head,
873 : pbdh->ai_tail,
874 : aie);
875 0 : GNUNET_free (aie);
876 : }
877 98 : if (NULL != pbdh->job)
878 : {
879 0 : GNUNET_CURL_job_cancel (pbdh->job);
880 0 : pbdh->job = NULL;
881 : }
882 98 : TALER_EXCHANGE_keys_decref (pbdh->keys);
883 98 : GNUNET_free (pbdh->base_url);
884 98 : GNUNET_free (pbdh->url);
885 98 : GNUNET_free (pbdh->cdds);
886 98 : TALER_curl_easy_post_finished (&pbdh->post_ctx);
887 98 : json_decref (pbdh->deposit_obj);
888 98 : json_decref (pbdh->response);
889 98 : GNUNET_free (pbdh);
890 98 : }
891 :
892 :
893 : /* end of exchange_api_post-batch-deposit.c */
|