Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-2023 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_purse_create_with_deposit.c
19 : * @brief Implementation of the client to create a purse with
20 : * an initial set of deposits (and a contract)
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_json_lib.h>
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_json_lib.h"
30 : #include "taler_exchange_service.h"
31 : #include "exchange_api_handle.h"
32 : #include "exchange_api_common.h"
33 : #include "taler_signatures.h"
34 : #include "exchange_api_curl_defaults.h"
35 :
36 :
37 : /**
38 : * Information we track per deposited coin.
39 : */
40 : struct Deposit
41 : {
42 : /**
43 : * Coin's public key.
44 : */
45 : struct TALER_CoinSpendPublicKeyP coin_pub;
46 :
47 : /**
48 : * Signature made with the coin.
49 : */
50 : struct TALER_CoinSpendSignatureP coin_sig;
51 :
52 : /**
53 : * Coin's denomination.
54 : */
55 : struct TALER_DenominationHashP h_denom_pub;
56 :
57 : /**
58 : * Age restriction hash for the coin.
59 : */
60 : struct TALER_AgeCommitmentHash ahac;
61 :
62 : /**
63 : * How much did we say the coin contributed.
64 : */
65 : struct TALER_Amount contribution;
66 : };
67 :
68 :
69 : /**
70 : * @brief A purse create with deposit handle
71 : */
72 : struct TALER_EXCHANGE_PurseCreateDepositHandle
73 : {
74 :
75 : /**
76 : * The keys of the exchange this request handle will use
77 : */
78 : struct TALER_EXCHANGE_Keys *keys;
79 :
80 : /**
81 : * The url for this request.
82 : */
83 : char *url;
84 :
85 : /**
86 : * The base URL of the exchange.
87 : */
88 : char *exchange_url;
89 :
90 : /**
91 : * Context for #TEH_curl_easy_post(). Keeps the data that must
92 : * persist for Curl to make the upload.
93 : */
94 : struct TALER_CURL_PostContext ctx;
95 :
96 : /**
97 : * Handle for the request.
98 : */
99 : struct GNUNET_CURL_Job *job;
100 :
101 : /**
102 : * Function to call with the result.
103 : */
104 : TALER_EXCHANGE_PurseCreateDepositCallback cb;
105 :
106 : /**
107 : * Closure for @a cb.
108 : */
109 : void *cb_cls;
110 :
111 : /**
112 : * Expected value in the purse after fees.
113 : */
114 : struct TALER_Amount purse_value_after_fees;
115 :
116 : /**
117 : * Our encrypted contract (if we had any).
118 : */
119 : struct TALER_EncryptedContract econtract;
120 :
121 : /**
122 : * Public key of the merge capability.
123 : */
124 : struct TALER_PurseMergePublicKeyP merge_pub;
125 :
126 : /**
127 : * Public key of the purse.
128 : */
129 : struct TALER_PurseContractPublicKeyP purse_pub;
130 :
131 : /**
132 : * Signature with the purse key on the request.
133 : */
134 : struct TALER_PurseContractSignatureP purse_sig;
135 :
136 : /**
137 : * Hash over the purse's contract terms.
138 : */
139 : struct TALER_PrivateContractHashP h_contract_terms;
140 :
141 : /**
142 : * When does the purse expire.
143 : */
144 : struct GNUNET_TIME_Timestamp purse_expiration;
145 :
146 : /**
147 : * Array of @e num_deposit deposits.
148 : */
149 : struct Deposit *deposits;
150 :
151 : /**
152 : * How many deposits did we make?
153 : */
154 : unsigned int num_deposits;
155 :
156 : };
157 :
158 :
159 : /**
160 : * Function called when we're done processing the
161 : * HTTP /deposit request.
162 : *
163 : * @param cls the `struct TALER_EXCHANGE_DepositHandle`
164 : * @param response_code HTTP response code, 0 on error
165 : * @param response parsed JSON result, NULL on error
166 : */
167 : static void
168 11 : handle_purse_create_deposit_finished (void *cls,
169 : long response_code,
170 : const void *response)
171 : {
172 11 : struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls;
173 11 : const json_t *j = response;
174 11 : struct TALER_EXCHANGE_PurseCreateDepositResponse dr = {
175 : .hr.reply = j,
176 11 : .hr.http_status = (unsigned int) response_code
177 : };
178 11 : const struct TALER_EXCHANGE_Keys *keys = pch->keys;
179 :
180 11 : pch->job = NULL;
181 11 : switch (response_code)
182 : {
183 0 : case 0:
184 0 : dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
185 0 : break;
186 9 : case MHD_HTTP_OK:
187 : {
188 : struct GNUNET_TIME_Timestamp etime;
189 : struct TALER_Amount total_deposited;
190 : struct TALER_ExchangeSignatureP exchange_sig;
191 : struct TALER_ExchangePublicKeyP exchange_pub;
192 : struct GNUNET_JSON_Specification spec[] = {
193 9 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
194 : &exchange_sig),
195 9 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
196 : &exchange_pub),
197 9 : GNUNET_JSON_spec_timestamp ("exchange_timestamp",
198 : &etime),
199 9 : TALER_JSON_spec_amount ("total_deposited",
200 9 : pch->purse_value_after_fees.currency,
201 : &total_deposited),
202 9 : GNUNET_JSON_spec_end ()
203 : };
204 :
205 9 : if (GNUNET_OK !=
206 9 : GNUNET_JSON_parse (j,
207 : spec,
208 : NULL, NULL))
209 : {
210 0 : GNUNET_break_op (0);
211 0 : dr.hr.http_status = 0;
212 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
213 0 : break;
214 : }
215 9 : if (GNUNET_OK !=
216 9 : TALER_EXCHANGE_test_signing_key (keys,
217 : &exchange_pub))
218 : {
219 0 : GNUNET_break_op (0);
220 0 : dr.hr.http_status = 0;
221 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
222 0 : break;
223 : }
224 9 : if (GNUNET_OK !=
225 9 : TALER_exchange_online_purse_created_verify (
226 : etime,
227 : pch->purse_expiration,
228 9 : &pch->purse_value_after_fees,
229 : &total_deposited,
230 9 : &pch->purse_pub,
231 9 : &pch->h_contract_terms,
232 : &exchange_pub,
233 : &exchange_sig))
234 : {
235 0 : GNUNET_break_op (0);
236 0 : dr.hr.http_status = 0;
237 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
238 0 : break;
239 : }
240 : }
241 9 : break;
242 0 : case MHD_HTTP_BAD_REQUEST:
243 : /* This should never happen, either us or the exchange is buggy
244 : (or API version conflict); just pass JSON reply to the application */
245 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
246 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
247 0 : break;
248 0 : case MHD_HTTP_FORBIDDEN:
249 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
250 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
251 : /* Nothing really to verify, exchange says one of the signatures is
252 : invalid; as we checked them, this should never happen, we
253 : should pass the JSON reply to the application */
254 0 : break;
255 0 : case MHD_HTTP_NOT_FOUND:
256 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
257 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
258 : /* Nothing really to verify, this should never
259 : happen, we should pass the JSON reply to the application */
260 0 : break;
261 2 : case MHD_HTTP_CONFLICT:
262 : {
263 2 : dr.hr.ec = TALER_JSON_get_error_code (j);
264 2 : switch (dr.hr.ec)
265 : {
266 0 : case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
267 0 : if (GNUNET_OK !=
268 0 : TALER_EXCHANGE_check_purse_create_conflict_ (
269 0 : &pch->purse_sig,
270 0 : &pch->purse_pub,
271 : j))
272 : {
273 0 : GNUNET_break_op (0);
274 0 : dr.hr.http_status = 0;
275 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
276 0 : break;
277 : }
278 0 : break;
279 2 : case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
280 : /* Nothing to check anymore here, proof needs to be
281 : checked in the GET /coins/$COIN_PUB handler */
282 2 : break;
283 0 : case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
284 : // FIXME #7267: write check (add to exchange_api_common! */
285 0 : break;
286 0 : case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
287 : {
288 : struct TALER_CoinSpendPublicKeyP coin_pub;
289 : struct TALER_CoinSpendSignatureP coin_sig;
290 : struct TALER_DenominationHashP h_denom_pub;
291 : struct TALER_AgeCommitmentHash phac;
292 0 : bool found = false;
293 :
294 0 : if (GNUNET_OK !=
295 0 : TALER_EXCHANGE_check_purse_coin_conflict_ (
296 0 : &pch->purse_pub,
297 0 : pch->exchange_url,
298 : j,
299 : &h_denom_pub,
300 : &phac,
301 : &coin_pub,
302 : &coin_sig))
303 : {
304 0 : GNUNET_break_op (0);
305 0 : dr.hr.http_status = 0;
306 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
307 0 : break;
308 : }
309 0 : for (unsigned int i = 0; i<pch->num_deposits; i++)
310 : {
311 0 : struct Deposit *deposit = &pch->deposits[i];
312 :
313 0 : if (0 !=
314 0 : GNUNET_memcmp (&coin_pub,
315 : &deposit->coin_pub))
316 0 : continue;
317 0 : if (0 !=
318 0 : GNUNET_memcmp (&deposit->h_denom_pub,
319 : &h_denom_pub))
320 : {
321 0 : found = true;
322 0 : break;
323 : }
324 0 : if (0 !=
325 0 : GNUNET_memcmp (&deposit->ahac,
326 : &phac))
327 : {
328 0 : found = true;
329 0 : break;
330 : }
331 0 : if (0 ==
332 0 : GNUNET_memcmp (&coin_sig,
333 : &deposit->coin_sig))
334 : {
335 0 : GNUNET_break_op (0);
336 0 : continue;
337 : }
338 0 : found = true;
339 0 : break;
340 : }
341 0 : if (! found)
342 : {
343 : /* conflict is for a different coin! */
344 0 : GNUNET_break_op (0);
345 0 : dr.hr.http_status = 0;
346 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
347 0 : break;
348 : }
349 : }
350 : case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
351 0 : if (GNUNET_OK !=
352 0 : TALER_EXCHANGE_check_purse_econtract_conflict_ (
353 0 : &pch->econtract.econtract_sig,
354 0 : &pch->purse_pub,
355 : j))
356 : {
357 0 : GNUNET_break_op (0);
358 0 : dr.hr.http_status = 0;
359 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
360 0 : break;
361 : }
362 0 : break;
363 0 : default:
364 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
365 : "Unexpected error code %d for conflcting deposit\n",
366 : dr.hr.ec);
367 0 : GNUNET_break_op (0);
368 0 : dr.hr.http_status = 0;
369 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
370 : }
371 : }
372 2 : break;
373 0 : case MHD_HTTP_GONE:
374 : /* could happen if denomination was revoked */
375 : /* Note: one might want to check /keys for revocation
376 : signature here, alas tricky in case our /keys
377 : is outdated => left to clients */
378 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
379 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
380 0 : break;
381 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
382 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
383 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
384 : /* Server had an internal issue; we should retry, but this API
385 : leaves this to the application */
386 0 : break;
387 0 : default:
388 : /* unexpected response code */
389 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
390 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
391 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
392 : "Unexpected response code %u/%d for exchange deposit\n",
393 : (unsigned int) response_code,
394 : dr.hr.ec);
395 0 : GNUNET_break_op (0);
396 0 : break;
397 : }
398 11 : pch->cb (pch->cb_cls,
399 : &dr);
400 11 : TALER_EXCHANGE_purse_create_with_deposit_cancel (pch);
401 11 : }
402 :
403 :
404 : struct TALER_EXCHANGE_PurseCreateDepositHandle *
405 11 : TALER_EXCHANGE_purse_create_with_deposit (
406 : struct GNUNET_CURL_Context *ctx,
407 : const char *url,
408 : struct TALER_EXCHANGE_Keys *keys,
409 : const struct TALER_PurseContractPrivateKeyP *purse_priv,
410 : const struct TALER_PurseMergePrivateKeyP *merge_priv,
411 : const struct TALER_ContractDiffiePrivateP *contract_priv,
412 : const json_t *contract_terms,
413 : unsigned int num_deposits,
414 : const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
415 : bool upload_contract,
416 : TALER_EXCHANGE_PurseCreateDepositCallback cb,
417 : void *cb_cls)
418 11 : {
419 : struct TALER_EXCHANGE_PurseCreateDepositHandle *pch;
420 : json_t *create_obj;
421 : json_t *deposit_arr;
422 : CURL *eh;
423 : char arg_str[sizeof (pch->purse_pub) * 2 + 32];
424 11 : uint32_t min_age = 0;
425 :
426 11 : pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle);
427 11 : pch->cb = cb;
428 11 : pch->cb_cls = cb_cls;
429 : {
430 : struct GNUNET_JSON_Specification spec[] = {
431 11 : GNUNET_JSON_spec_timestamp ("pay_deadline",
432 : &pch->purse_expiration),
433 11 : TALER_JSON_spec_amount_any ("amount",
434 : &pch->purse_value_after_fees),
435 11 : GNUNET_JSON_spec_mark_optional (
436 : GNUNET_JSON_spec_uint32 ("minimum_age",
437 : &min_age),
438 : NULL),
439 11 : GNUNET_JSON_spec_end ()
440 : };
441 :
442 11 : if (GNUNET_OK !=
443 11 : GNUNET_JSON_parse (contract_terms,
444 : spec,
445 : NULL, NULL))
446 : {
447 0 : GNUNET_break (0);
448 0 : return NULL;
449 : }
450 : }
451 11 : if (GNUNET_OK !=
452 11 : TALER_JSON_contract_hash (contract_terms,
453 : &pch->h_contract_terms))
454 : {
455 0 : GNUNET_break (0);
456 0 : return NULL;
457 : }
458 11 : GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
459 : &pch->purse_pub.eddsa_pub);
460 : {
461 : char pub_str[sizeof (pch->purse_pub) * 2];
462 : char *end;
463 :
464 11 : end = GNUNET_STRINGS_data_to_string (
465 11 : &pch->purse_pub,
466 : sizeof (pch->purse_pub),
467 : pub_str,
468 : sizeof (pub_str));
469 11 : *end = '\0';
470 11 : GNUNET_snprintf (arg_str,
471 : sizeof (arg_str),
472 : "purses/%s/create",
473 : pub_str);
474 : }
475 11 : GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
476 : &pch->merge_pub.eddsa_pub);
477 11 : pch->url = TALER_url_join (url,
478 : arg_str,
479 : NULL);
480 11 : if (NULL == pch->url)
481 : {
482 0 : GNUNET_break (0);
483 0 : GNUNET_free (pch);
484 0 : return NULL;
485 : }
486 11 : pch->num_deposits = num_deposits;
487 11 : pch->deposits = GNUNET_new_array (num_deposits,
488 : struct Deposit);
489 11 : deposit_arr = json_array ();
490 11 : GNUNET_assert (NULL != deposit_arr);
491 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
492 : "Signing with URL `%s'\n",
493 : url);
494 22 : for (unsigned int i = 0; i<num_deposits; i++)
495 : {
496 11 : const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
497 11 : const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
498 11 : struct Deposit *d = &pch->deposits[i];
499 : json_t *jdeposit;
500 11 : struct TALER_AgeCommitmentHash *aghp = NULL;
501 : struct TALER_AgeAttestation attest;
502 11 : struct TALER_AgeAttestation *attestp = NULL;
503 :
504 11 : if (NULL != acp)
505 : {
506 0 : TALER_age_commitment_hash (&acp->commitment,
507 : &d->ahac);
508 0 : aghp = &d->ahac;
509 0 : if (GNUNET_OK !=
510 0 : TALER_age_commitment_attest (acp,
511 : min_age,
512 : &attest))
513 : {
514 0 : GNUNET_break (0);
515 0 : GNUNET_array_grow (pch->deposits,
516 : pch->num_deposits,
517 : 0);
518 0 : GNUNET_free (pch->url);
519 0 : json_decref (deposit_arr);
520 0 : GNUNET_free (pch);
521 0 : return NULL;
522 : }
523 : }
524 11 : d->contribution = deposit->amount;
525 11 : d->h_denom_pub = deposit->h_denom_pub;
526 11 : GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
527 : &d->coin_pub.eddsa_pub);
528 11 : TALER_wallet_purse_deposit_sign (
529 : url,
530 11 : &pch->purse_pub,
531 : &deposit->amount,
532 11 : &d->h_denom_pub,
533 11 : &d->ahac,
534 : &deposit->coin_priv,
535 : &d->coin_sig);
536 11 : jdeposit = GNUNET_JSON_PACK (
537 : GNUNET_JSON_pack_allow_null (
538 : GNUNET_JSON_pack_data_auto ("h_age_commitment",
539 : aghp)),
540 : GNUNET_JSON_pack_allow_null (
541 : GNUNET_JSON_pack_data_auto ("age_attestation",
542 : attestp)),
543 : TALER_JSON_pack_amount ("amount",
544 : &deposit->amount),
545 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
546 : &deposit->h_denom_pub),
547 : TALER_JSON_pack_denom_sig ("ub_sig",
548 : &deposit->denom_sig),
549 : GNUNET_JSON_pack_data_auto ("coin_sig",
550 : &d->coin_sig),
551 : GNUNET_JSON_pack_data_auto ("coin_pub",
552 : &d->coin_pub));
553 11 : GNUNET_assert (0 ==
554 : json_array_append_new (deposit_arr,
555 : jdeposit));
556 : }
557 11 : TALER_wallet_purse_create_sign (pch->purse_expiration,
558 11 : &pch->h_contract_terms,
559 11 : &pch->merge_pub,
560 : min_age,
561 11 : &pch->purse_value_after_fees,
562 : purse_priv,
563 : &pch->purse_sig);
564 11 : if (upload_contract)
565 : {
566 11 : TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub,
567 : contract_priv,
568 : merge_priv,
569 : contract_terms,
570 : &pch->econtract.econtract,
571 : &pch->econtract.econtract_size);
572 11 : GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
573 : &pch->econtract.contract_pub.ecdhe_pub);
574 11 : TALER_wallet_econtract_upload_sign (pch->econtract.econtract,
575 : pch->econtract.econtract_size,
576 11 : &pch->econtract.contract_pub,
577 : purse_priv,
578 : &pch->econtract.econtract_sig);
579 : }
580 11 : create_obj = GNUNET_JSON_PACK (
581 : TALER_JSON_pack_amount ("amount",
582 : &pch->purse_value_after_fees),
583 : GNUNET_JSON_pack_uint64 ("min_age",
584 : min_age),
585 : GNUNET_JSON_pack_allow_null (
586 : TALER_JSON_pack_econtract ("econtract",
587 : upload_contract
588 : ? &pch->econtract
589 : : NULL)),
590 : GNUNET_JSON_pack_data_auto ("purse_sig",
591 : &pch->purse_sig),
592 : GNUNET_JSON_pack_data_auto ("merge_pub",
593 : &pch->merge_pub),
594 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
595 : &pch->h_contract_terms),
596 : GNUNET_JSON_pack_timestamp ("purse_expiration",
597 : pch->purse_expiration),
598 : GNUNET_JSON_pack_array_steal ("deposits",
599 : deposit_arr));
600 11 : GNUNET_assert (NULL != create_obj);
601 11 : eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
602 22 : if ( (NULL == eh) ||
603 : (GNUNET_OK !=
604 11 : TALER_curl_easy_post (&pch->ctx,
605 : eh,
606 : create_obj)) )
607 : {
608 0 : GNUNET_break (0);
609 0 : if (NULL != eh)
610 0 : curl_easy_cleanup (eh);
611 0 : json_decref (create_obj);
612 0 : GNUNET_free (pch->econtract.econtract);
613 0 : GNUNET_array_grow (pch->deposits,
614 : pch->num_deposits,
615 : 0);
616 0 : GNUNET_free (pch->url);
617 0 : GNUNET_free (pch);
618 0 : return NULL;
619 : }
620 11 : json_decref (create_obj);
621 11 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
622 : "URL for purse create with deposit: `%s'\n",
623 : pch->url);
624 11 : pch->keys = TALER_EXCHANGE_keys_incref (keys);
625 11 : pch->exchange_url = GNUNET_strdup (url);
626 22 : pch->job = GNUNET_CURL_job_add2 (ctx,
627 : eh,
628 11 : pch->ctx.headers,
629 : &handle_purse_create_deposit_finished,
630 : pch);
631 11 : return pch;
632 : }
633 :
634 :
635 : void
636 11 : TALER_EXCHANGE_purse_create_with_deposit_cancel (
637 : struct TALER_EXCHANGE_PurseCreateDepositHandle *pch)
638 : {
639 11 : if (NULL != pch->job)
640 : {
641 0 : GNUNET_CURL_job_cancel (pch->job);
642 0 : pch->job = NULL;
643 : }
644 11 : GNUNET_free (pch->econtract.econtract);
645 11 : GNUNET_free (pch->exchange_url);
646 11 : GNUNET_free (pch->url);
647 11 : GNUNET_array_grow (pch->deposits,
648 : pch->num_deposits,
649 : 0);
650 11 : TALER_EXCHANGE_keys_decref (pch->keys);
651 11 : TALER_curl_easy_post_finished (&pch->ctx);
652 11 : GNUNET_free (pch);
653 11 : }
654 :
655 :
656 : /* end of exchange_api_purse_create_with_deposit.c */
|