taler-exchange-httpd_purses_create.c (23679B)
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_purses_create.c 18 * @brief Handle /purses/$PID/create 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 "taler/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 "taler/taler_json_lib.h" 29 #include "taler/taler_mhd_lib.h" 30 #include "taler-exchange-httpd_common_deposit.h" 31 #include "taler-exchange-httpd_purses_create.h" 32 #include "taler-exchange-httpd_responses.h" 33 #include "taler/taler_exchangedb_lib.h" 34 #include "taler-exchange-httpd_keys.h" 35 36 37 /** 38 * Closure for #create_transaction. 39 */ 40 struct PurseCreateContext 41 { 42 43 /** 44 * Total actually deposited by all the coins. 45 */ 46 struct TALER_Amount deposit_total; 47 48 /** 49 * Our current time. 50 */ 51 struct GNUNET_TIME_Timestamp exchange_timestamp; 52 53 /** 54 * Merge key for the purse. 55 */ 56 struct TALER_PurseMergePublicKeyP merge_pub; 57 58 /** 59 * Encrypted contract of for the purse. 60 */ 61 struct TALER_EncryptedContract econtract; 62 63 /** 64 * Signature of the client affiming this request. 65 */ 66 struct TALER_PurseContractSignatureP purse_sig; 67 68 /** 69 * Fundamental details about the purse. 70 */ 71 struct TEH_PurseDetails pd; 72 73 /** 74 * Array of coins being deposited. 75 */ 76 struct TEH_PurseDepositedCoin *coins; 77 78 /** 79 * Length of the @e coins array. 80 */ 81 unsigned int num_coins; 82 83 /** 84 * Minimum age for deposits into this purse. 85 */ 86 uint32_t min_age; 87 88 /** 89 * Do we have an @e econtract? 90 */ 91 bool no_econtract; 92 93 }; 94 95 96 /** 97 * Execute database transaction for /purses/$PID/create. Runs the transaction 98 * logic; IF it returns a non-error code, the transaction logic MUST NOT queue 99 * a MHD response. IF it returns an hard error, the transaction logic MUST 100 * queue a MHD response and set @a mhd_ret. IF it returns the soft error 101 * code, the function MAY be called again to retry and MUST not queue a MHD 102 * response. 103 * 104 * @param cls a `struct PurseCreateContext` 105 * @param connection MHD request context 106 * @param[out] mhd_ret set to MHD status on error 107 * @return transaction status 108 */ 109 static enum GNUNET_DB_QueryStatus 110 create_transaction (void *cls, 111 struct MHD_Connection *connection, 112 MHD_RESULT *mhd_ret) 113 { 114 struct PurseCreateContext *pcc = cls; 115 enum GNUNET_DB_QueryStatus qs; 116 struct TALER_Amount purse_fee; 117 bool in_conflict = true; 118 119 GNUNET_assert (GNUNET_OK == 120 TALER_amount_set_zero (TEH_currency, 121 &purse_fee)); 122 /* 1) create purse */ 123 qs = TEH_plugin->insert_purse_request ( 124 TEH_plugin->cls, 125 &pcc->pd.purse_pub, 126 &pcc->merge_pub, 127 pcc->pd.purse_expiration, 128 &pcc->pd.h_contract_terms, 129 pcc->min_age, 130 TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE, 131 &purse_fee, 132 &pcc->pd.target_amount, 133 &pcc->purse_sig, 134 &in_conflict); 135 if (qs < 0) 136 { 137 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 138 return qs; 139 TALER_LOG_WARNING ( 140 "Failed to store create purse information in database\n"); 141 *mhd_ret = 142 TALER_MHD_reply_with_error (connection, 143 MHD_HTTP_INTERNAL_SERVER_ERROR, 144 TALER_EC_GENERIC_DB_STORE_FAILED, 145 "purse create"); 146 return GNUNET_DB_STATUS_HARD_ERROR; 147 } 148 if (in_conflict) 149 { 150 struct TALER_PurseMergePublicKeyP merge_pub; 151 struct GNUNET_TIME_Timestamp purse_expiration; 152 struct TALER_PrivateContractHashP h_contract_terms; 153 struct TALER_Amount target_amount; 154 struct TALER_Amount balance; 155 struct TALER_PurseContractSignatureP purse_sig; 156 uint32_t min_age; 157 158 TEH_plugin->rollback (TEH_plugin->cls); 159 qs = TEH_plugin->get_purse_request (TEH_plugin->cls, 160 &pcc->pd.purse_pub, 161 &merge_pub, 162 &purse_expiration, 163 &h_contract_terms, 164 &min_age, 165 &target_amount, 166 &balance, 167 &purse_sig); 168 if (qs < 0) 169 { 170 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 171 TALER_LOG_WARNING ("Failed to fetch purse information from database\n"); 172 *mhd_ret = TALER_MHD_reply_with_error (connection, 173 MHD_HTTP_INTERNAL_SERVER_ERROR, 174 TALER_EC_GENERIC_DB_FETCH_FAILED, 175 "select purse request"); 176 return GNUNET_DB_STATUS_HARD_ERROR; 177 } 178 *mhd_ret 179 = TALER_MHD_REPLY_JSON_PACK ( 180 connection, 181 MHD_HTTP_CONFLICT, 182 TALER_JSON_pack_ec ( 183 TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA), 184 TALER_JSON_pack_amount ("amount", 185 &target_amount), 186 GNUNET_JSON_pack_uint64 ("min_age", 187 min_age), 188 GNUNET_JSON_pack_timestamp ("purse_expiration", 189 purse_expiration), 190 GNUNET_JSON_pack_data_auto ("purse_sig", 191 &purse_sig), 192 GNUNET_JSON_pack_data_auto ("h_contract_terms", 193 &h_contract_terms), 194 GNUNET_JSON_pack_data_auto ("merge_pub", 195 &merge_pub)); 196 return GNUNET_DB_STATUS_HARD_ERROR; 197 } 198 /* 2) deposit all coins */ 199 for (unsigned int i = 0; i<pcc->num_coins; i++) 200 { 201 struct TEH_PurseDepositedCoin *coin = &pcc->coins[i]; 202 bool balance_ok = false; 203 bool conflict = true; 204 bool too_late = true; 205 206 qs = TEH_make_coin_known (&coin->cpi, 207 connection, 208 &coin->known_coin_id, 209 mhd_ret); 210 if (qs < 0) 211 return qs; 212 qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls, 213 &pcc->pd.purse_pub, 214 &coin->cpi.coin_pub, 215 &coin->amount, 216 &coin->coin_sig, 217 &coin->amount_minus_fee, 218 &balance_ok, 219 &too_late, 220 &conflict); 221 if (qs <= 0) 222 { 223 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 224 return qs; 225 GNUNET_break (0 != qs); 226 TALER_LOG_WARNING ( 227 "Failed to store purse deposit information in database\n"); 228 *mhd_ret = TALER_MHD_reply_with_error (connection, 229 MHD_HTTP_INTERNAL_SERVER_ERROR, 230 TALER_EC_GENERIC_DB_STORE_FAILED, 231 "purse create deposit"); 232 return GNUNET_DB_STATUS_HARD_ERROR; 233 } 234 if (! balance_ok) 235 { 236 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 237 "Coin %s has insufficient balance for purse deposit of amount %s\n", 238 TALER_B2S (&coin->cpi.coin_pub), 239 TALER_amount2s (&coin->amount)); 240 *mhd_ret 241 = TEH_RESPONSE_reply_coin_insufficient_funds ( 242 connection, 243 TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, 244 &coin->cpi.denom_pub_hash, 245 &coin->cpi.coin_pub); 246 return GNUNET_DB_STATUS_HARD_ERROR; 247 } 248 if (too_late) 249 { 250 *mhd_ret 251 = TALER_MHD_reply_with_ec ( 252 connection, 253 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 254 "too late to deposit on purse creation"); 255 return GNUNET_DB_STATUS_HARD_ERROR; 256 } 257 if (conflict) 258 { 259 struct TALER_Amount amount; 260 struct TALER_CoinSpendPublicKeyP coin_pub; 261 struct TALER_CoinSpendSignatureP coin_sig; 262 struct TALER_DenominationHashP h_denom_pub; 263 struct TALER_AgeCommitmentHashP phac; 264 char *partner_url = NULL; 265 266 TEH_plugin->rollback (TEH_plugin->cls); 267 qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls, 268 &pcc->pd.purse_pub, 269 &coin->cpi.coin_pub, 270 &amount, 271 &h_denom_pub, 272 &phac, 273 &coin_sig, 274 &partner_url); 275 if (qs < 0) 276 { 277 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 278 TALER_LOG_WARNING ( 279 "Failed to fetch purse deposit information from database\n"); 280 *mhd_ret = TALER_MHD_reply_with_error (connection, 281 MHD_HTTP_INTERNAL_SERVER_ERROR, 282 TALER_EC_GENERIC_DB_FETCH_FAILED, 283 "get purse deposit"); 284 return GNUNET_DB_STATUS_HARD_ERROR; 285 } 286 287 *mhd_ret 288 = TALER_MHD_REPLY_JSON_PACK ( 289 connection, 290 MHD_HTTP_CONFLICT, 291 TALER_JSON_pack_ec ( 292 TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA), 293 GNUNET_JSON_pack_data_auto ("coin_pub", 294 &coin_pub), 295 GNUNET_JSON_pack_data_auto ("coin_sig", 296 &coin_sig), 297 GNUNET_JSON_pack_data_auto ("h_denom_pub", 298 &h_denom_pub), 299 GNUNET_JSON_pack_data_auto ("h_age_restrictions", 300 &phac), 301 GNUNET_JSON_pack_allow_null ( 302 GNUNET_JSON_pack_string ("partner_url", 303 partner_url)), 304 TALER_JSON_pack_amount ("amount", 305 &amount)); 306 GNUNET_free (partner_url); 307 return GNUNET_DB_STATUS_HARD_ERROR; 308 } 309 } 310 /* 3) if present, persist contract */ 311 if (! pcc->no_econtract) 312 { 313 in_conflict = true; 314 qs = TEH_plugin->insert_contract (TEH_plugin->cls, 315 &pcc->pd.purse_pub, 316 &pcc->econtract, 317 &in_conflict); 318 if (qs < 0) 319 { 320 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 321 return qs; 322 TALER_LOG_WARNING ("Failed to store purse information in database\n"); 323 *mhd_ret = TALER_MHD_reply_with_error (connection, 324 MHD_HTTP_INTERNAL_SERVER_ERROR, 325 TALER_EC_GENERIC_DB_STORE_FAILED, 326 "purse create contract"); 327 return GNUNET_DB_STATUS_HARD_ERROR; 328 } 329 if (in_conflict) 330 { 331 struct TALER_EncryptedContract econtract; 332 struct GNUNET_HashCode h_econtract; 333 334 qs = TEH_plugin->select_contract_by_purse ( 335 TEH_plugin->cls, 336 &pcc->pd.purse_pub, 337 &econtract); 338 if (qs <= 0) 339 { 340 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 341 return qs; 342 GNUNET_break (0 != qs); 343 TALER_LOG_WARNING ( 344 "Failed to store fetch contract information from database\n"); 345 *mhd_ret = TALER_MHD_reply_with_error (connection, 346 MHD_HTTP_INTERNAL_SERVER_ERROR, 347 TALER_EC_GENERIC_DB_FETCH_FAILED, 348 "select contract"); 349 return GNUNET_DB_STATUS_HARD_ERROR; 350 } 351 GNUNET_CRYPTO_hash (econtract.econtract, 352 econtract.econtract_size, 353 &h_econtract); 354 *mhd_ret 355 = TALER_MHD_REPLY_JSON_PACK ( 356 connection, 357 MHD_HTTP_CONFLICT, 358 TALER_JSON_pack_ec ( 359 TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA), 360 GNUNET_JSON_pack_data_auto ("h_econtract", 361 &h_econtract), 362 GNUNET_JSON_pack_data_auto ("econtract_sig", 363 &econtract.econtract_sig), 364 GNUNET_JSON_pack_data_auto ("contract_pub", 365 &econtract.contract_pub)); 366 GNUNET_free (econtract.econtract); 367 return GNUNET_DB_STATUS_HARD_ERROR; 368 } 369 } 370 return qs; 371 } 372 373 374 /** 375 * Parse a coin and check signature of the coin and the denomination 376 * signature over the coin. 377 * 378 * @param[in,out] connection our HTTP connection 379 * @param[in,out] pcc request context 380 * @param[out] coin coin to initialize 381 * @param jcoin coin to parse 382 * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned, 383 * #GNUNET_SYSERR on failure and no error could be returned 384 */ 385 static enum GNUNET_GenericReturnValue 386 parse_coin (struct MHD_Connection *connection, 387 struct PurseCreateContext *pcc, 388 struct TEH_PurseDepositedCoin *coin, 389 const json_t *jcoin) 390 { 391 enum GNUNET_GenericReturnValue iret; 392 393 if (GNUNET_OK != 394 (iret = TEH_common_purse_deposit_parse_coin (connection, 395 coin, 396 jcoin))) 397 return iret; 398 if (GNUNET_OK != 399 (iret = TEH_common_deposit_check_purse_deposit ( 400 connection, 401 coin, 402 &pcc->pd.purse_pub, 403 pcc->min_age))) 404 return iret; 405 if (0 > 406 TALER_amount_add (&pcc->deposit_total, 407 &pcc->deposit_total, 408 &coin->amount_minus_fee)) 409 { 410 GNUNET_break (0); 411 return (MHD_YES == 412 TALER_MHD_reply_with_error (connection, 413 MHD_HTTP_INTERNAL_SERVER_ERROR, 414 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 415 "total deposit contribution")) 416 ? GNUNET_NO 417 : GNUNET_SYSERR; 418 } 419 return GNUNET_OK; 420 } 421 422 423 MHD_RESULT 424 TEH_handler_purses_create ( 425 struct TEH_RequestContext *rc, 426 const struct TALER_PurseContractPublicKeyP *purse_pub, 427 const json_t *root) 428 { 429 struct MHD_Connection *connection = rc->connection; 430 struct PurseCreateContext pcc = { 431 .pd.purse_pub = *purse_pub, 432 .exchange_timestamp = GNUNET_TIME_timestamp_get () 433 }; 434 const json_t *deposits; 435 json_t *deposit; 436 unsigned int idx; 437 struct GNUNET_JSON_Specification spec[] = { 438 TALER_JSON_spec_amount ("amount", 439 TEH_currency, 440 &pcc.pd.target_amount), 441 GNUNET_JSON_spec_uint32 ("min_age", 442 &pcc.min_age), 443 GNUNET_JSON_spec_mark_optional ( 444 TALER_JSON_spec_econtract ("econtract", 445 &pcc.econtract), 446 &pcc.no_econtract), 447 GNUNET_JSON_spec_fixed_auto ("merge_pub", 448 &pcc.merge_pub), 449 GNUNET_JSON_spec_fixed_auto ("purse_sig", 450 &pcc.purse_sig), 451 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 452 &pcc.pd.h_contract_terms), 453 GNUNET_JSON_spec_array_const ("deposits", 454 &deposits), 455 GNUNET_JSON_spec_timestamp ("purse_expiration", 456 &pcc.pd.purse_expiration), 457 GNUNET_JSON_spec_end () 458 }; 459 const struct TEH_GlobalFee *gf; 460 461 { 462 enum GNUNET_GenericReturnValue res; 463 464 res = TALER_MHD_parse_json_data (connection, 465 root, 466 spec); 467 if (GNUNET_SYSERR == res) 468 { 469 GNUNET_break (0); 470 return MHD_NO; /* hard failure */ 471 } 472 if (GNUNET_NO == res) 473 { 474 GNUNET_break_op (0); 475 return MHD_YES; /* failure */ 476 } 477 } 478 GNUNET_assert (GNUNET_OK == 479 TALER_amount_set_zero (TEH_currency, 480 &pcc.deposit_total)); 481 if (GNUNET_TIME_timestamp_cmp (pcc.pd.purse_expiration, 482 <, 483 pcc.exchange_timestamp)) 484 { 485 GNUNET_break_op (0); 486 GNUNET_JSON_parse_free (spec); 487 return TALER_MHD_reply_with_error (connection, 488 MHD_HTTP_BAD_REQUEST, 489 TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW, 490 NULL); 491 } 492 if (GNUNET_TIME_absolute_is_never (pcc.pd.purse_expiration.abs_time)) 493 { 494 GNUNET_break_op (0); 495 GNUNET_JSON_parse_free (spec); 496 return TALER_MHD_reply_with_error (connection, 497 MHD_HTTP_BAD_REQUEST, 498 TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER, 499 NULL); 500 } 501 pcc.num_coins = json_array_size (deposits); 502 if ( (0 == pcc.num_coins) || 503 (pcc.num_coins > TALER_MAX_COINS) ) 504 { 505 GNUNET_break_op (0); 506 GNUNET_JSON_parse_free (spec); 507 return TALER_MHD_reply_with_error (connection, 508 MHD_HTTP_BAD_REQUEST, 509 TALER_EC_GENERIC_PARAMETER_MALFORMED, 510 "deposits"); 511 } 512 { 513 struct TEH_KeyStateHandle *keys; 514 515 keys = TEH_keys_get_state (); 516 if (NULL == keys) 517 { 518 GNUNET_break (0); 519 GNUNET_JSON_parse_free (spec); 520 return TALER_MHD_reply_with_error (connection, 521 MHD_HTTP_INTERNAL_SERVER_ERROR, 522 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 523 NULL); 524 } 525 gf = TEH_keys_global_fee_by_time (keys, 526 pcc.exchange_timestamp); 527 } 528 if (NULL == gf) 529 { 530 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 531 "Cannot create purse: global fees not configured!\n"); 532 GNUNET_JSON_parse_free (spec); 533 return TALER_MHD_reply_with_error (connection, 534 MHD_HTTP_INTERNAL_SERVER_ERROR, 535 TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING, 536 NULL); 537 } 538 /* parse deposits */ 539 pcc.coins = GNUNET_new_array (pcc.num_coins, 540 struct TEH_PurseDepositedCoin); 541 json_array_foreach (deposits, idx, deposit) 542 { 543 enum GNUNET_GenericReturnValue res; 544 struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx]; 545 546 res = parse_coin (connection, 547 &pcc, 548 coin, 549 deposit); 550 if (GNUNET_OK != res) 551 { 552 for (unsigned int i = 0; i<idx; i++) 553 TEH_common_purse_deposit_free_coin (&pcc.coins[i]); 554 GNUNET_free (pcc.coins); 555 GNUNET_JSON_parse_free (spec); 556 return (GNUNET_NO == res) ? MHD_YES : MHD_NO; 557 } 558 } 559 560 if (0 < TALER_amount_cmp (&gf->fees.purse, 561 &pcc.deposit_total)) 562 { 563 GNUNET_break_op (0); 564 GNUNET_free (pcc.coins); 565 GNUNET_JSON_parse_free (spec); 566 return TALER_MHD_reply_with_error (connection, 567 MHD_HTTP_BAD_REQUEST, 568 TALER_EC_EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE, 569 NULL); 570 } 571 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 572 573 if (GNUNET_OK != 574 TALER_wallet_purse_create_verify ( 575 pcc.pd.purse_expiration, 576 &pcc.pd.h_contract_terms, 577 &pcc.merge_pub, 578 pcc.min_age, 579 &pcc.pd.target_amount, 580 &pcc.pd.purse_pub, 581 &pcc.purse_sig)) 582 { 583 TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n"); 584 for (unsigned int i = 0; i<pcc.num_coins; i++) 585 TEH_common_purse_deposit_free_coin (&pcc.coins[i]); 586 GNUNET_free (pcc.coins); 587 GNUNET_JSON_parse_free (spec); 588 return TALER_MHD_reply_with_error (connection, 589 MHD_HTTP_FORBIDDEN, 590 TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID, 591 NULL); 592 } 593 if ( (! pcc.no_econtract) && 594 (GNUNET_OK != 595 TALER_wallet_econtract_upload_verify (pcc.econtract.econtract, 596 pcc.econtract.econtract_size, 597 &pcc.econtract.contract_pub, 598 purse_pub, 599 &pcc.econtract.econtract_sig)) ) 600 { 601 TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n"); 602 for (unsigned int i = 0; i<pcc.num_coins; i++) 603 TEH_common_purse_deposit_free_coin (&pcc.coins[i]); 604 GNUNET_free (pcc.coins); 605 GNUNET_JSON_parse_free (spec); 606 return TALER_MHD_reply_with_error (connection, 607 MHD_HTTP_FORBIDDEN, 608 TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID, 609 NULL); 610 } 611 612 613 if (GNUNET_SYSERR == 614 TEH_plugin->preflight (TEH_plugin->cls)) 615 { 616 GNUNET_break (0); 617 for (unsigned int i = 0; i<pcc.num_coins; i++) 618 TEH_common_purse_deposit_free_coin (&pcc.coins[i]); 619 GNUNET_free (pcc.coins); 620 GNUNET_JSON_parse_free (spec); 621 return TALER_MHD_reply_with_error (connection, 622 MHD_HTTP_INTERNAL_SERVER_ERROR, 623 TALER_EC_GENERIC_DB_START_FAILED, 624 "preflight failure"); 625 } 626 627 /* execute transaction */ 628 { 629 MHD_RESULT mhd_ret; 630 631 if (GNUNET_OK != 632 TEH_DB_run_transaction (connection, 633 "execute purse create", 634 TEH_MT_REQUEST_PURSE_CREATE, 635 &mhd_ret, 636 &create_transaction, 637 &pcc)) 638 { 639 for (unsigned int i = 0; i<pcc.num_coins; i++) 640 TEH_common_purse_deposit_free_coin (&pcc.coins[i]); 641 GNUNET_free (pcc.coins); 642 GNUNET_JSON_parse_free (spec); 643 return mhd_ret; 644 } 645 } 646 647 /* generate regular response */ 648 { 649 MHD_RESULT res; 650 651 res = TEH_RESPONSE_reply_purse_created (connection, 652 pcc.exchange_timestamp, 653 &pcc.deposit_total, 654 &pcc.pd); 655 for (unsigned int i = 0; i<pcc.num_coins; i++) 656 TEH_common_purse_deposit_free_coin (&pcc.coins[i]); 657 GNUNET_free (pcc.coins); 658 GNUNET_JSON_parse_free (spec); 659 return res; 660 } 661 } 662 663 664 /* end of taler-exchange-httpd_purses_create.c */